1 // Copyright 2018 Google
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #import "Private/GULLogger.h"
19 #import <GoogleUtilities/GULAppEnvironmentUtil.h>
20 #import "Public/GULLoggerLevel.h"
22 /// ASL client facility name used by GULLogger.
23 const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger";
25 static dispatch_once_t sGULLoggerOnceToken;
27 static aslclient sGULLoggerClient;
29 static dispatch_queue_t sGULClientQueue;
31 static BOOL sGULLoggerDebugMode;
33 static GULLoggerLevel sGULLoggerMaximumLevel;
35 // Allow clients to register a version to include in the log.
36 static const char *sVersion = "";
38 static GULLoggerService kGULLoggerLogger = @"[GULLogger]";
41 /// The regex pattern for the message code.
42 static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$";
43 static NSRegularExpression *sMessageCodeRegex;
46 void GULLoggerInitializeASL(void) {
47 dispatch_once(&sGULLoggerOnceToken, ^{
48 NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue];
49 uint32_t aslOptions = ASL_OPT_STDERR;
50 #if TARGET_OS_SIMULATOR
51 // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag.
52 if (majorOSVersion >= 11) {
56 // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag.
57 if (majorOSVersion >= 10) {
60 #endif // TARGET_OS_SIMULATOR
62 #pragma clang diagnostic push
63 #pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated
64 // Initialize the ASL client handle.
65 sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions);
66 sGULLoggerMaximumLevel = GULLoggerLevelNotice;
68 // Set the filter used by system/device log. Initialize in default mode.
69 asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE));
71 sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
72 dispatch_set_target_queue(sGULClientQueue,
73 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
76 [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL];
81 void GULLoggerEnableSTDERR(void) {
82 asl_add_log_file(sGULLoggerClient, STDERR_FILENO);
85 void GULLoggerForceDebug(void) {
86 // We should enable debug mode if we're not running from App Store.
87 if (![GULAppEnvironmentUtil isFromAppStore]) {
88 sGULLoggerDebugMode = YES;
89 GULSetLoggerLevel(GULLoggerLevelDebug);
93 void GULSetLoggerLevel(GULLoggerLevel loggerLevel) {
94 if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) {
95 GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld",
99 GULLoggerInitializeASL();
100 // We should not raise the logger level if we are running from App Store.
101 if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) {
105 sGULLoggerMaximumLevel = loggerLevel;
106 dispatch_async(sGULClientQueue, ^{
107 asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel));
112 * Check if the level is high enough to be loggable.
114 __attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) {
115 GULLoggerInitializeASL();
116 if (sGULLoggerDebugMode) {
119 return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel);
123 void GULResetLogger() {
124 sGULLoggerOnceToken = 0;
127 aslclient getGULLoggerClient() {
128 return sGULLoggerClient;
131 dispatch_queue_t getGULClientQueue() {
132 return sGULClientQueue;
135 BOOL getGULLoggerDebugMode() {
136 return sGULLoggerDebugMode;
140 void GULLoggerRegisterVersion(const char *version) {
144 void GULLogBasic(GULLoggerLevel level,
145 GULLoggerService service,
147 NSString *messageCode,
150 GULLoggerInitializeASL();
151 if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) {
156 NSCAssert(messageCode.length == 11, @"Incorrect message code length.");
157 NSRange messageCodeRange = NSMakeRange(0, messageCode.length);
158 NSUInteger numberOfMatches =
159 [sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange];
160 NSCAssert(numberOfMatches == 1, @"Incorrect message code format.");
162 NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
163 logMsg = [NSString stringWithFormat:@"%s - %@[%@] %@", sVersion, service, messageCode, logMsg];
164 dispatch_async(sGULClientQueue, ^{
165 asl_log(sGULLoggerClient, NULL, level, "%s", logMsg.UTF8String);
168 #pragma clang diagnostic pop
171 * Generates the logging functions using macros.
173 * Calling GULLogError(kGULLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows:
174 * yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [{service}][I-COR000001] Configure blah failed.
175 * Calling GULLogDebug(kGULLoggerCore, @"I-COR000001", @"Configure succeed.") shows:
176 * yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{service}][I-COR000001] Configure succeed.
178 #define GUL_LOGGING_FUNCTION(level) \
179 void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \
180 NSString *message, ...) { \
182 va_start(args_ptr, message); \
183 GULLogBasic(GULLoggerLevel##level, service, force, messageCode, message, args_ptr); \
187 GUL_LOGGING_FUNCTION(Error)
188 GUL_LOGGING_FUNCTION(Warning)
189 GUL_LOGGING_FUNCTION(Notice)
190 GUL_LOGGING_FUNCTION(Info)
191 GUL_LOGGING_FUNCTION(Debug)
193 #undef GUL_MAKE_LOGGER
195 #pragma mark - GULLoggerWrapper
197 @implementation GULLoggerWrapper
199 + (void)logWithLevel:(GULLoggerLevel)level
200 withService:(GULLoggerService)service
201 withCode:(NSString *)messageCode
202 withMessage:(NSString *)message
203 withArgs:(va_list)args {
204 GULLogBasic(level, service, NO, messageCode, message, args);