FBControlCore/Utility/FBControlCoreLogger.m (251 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBControlCoreLogger.h" #import "FBDataConsumer.h" #import "FBFileWriter.h" #import "FBControlCoreLogger+OSLog.h" @interface FBControlCoreLogger_NSLog : NSObject <FBControlCoreLogger> @end @implementation FBControlCoreLogger_NSLog @synthesize name = _name; @synthesize level = _level; - (instancetype)initWithname:(NSString *)name level:(FBControlCoreLogLevel)level { self = [super init]; if (!self) { return nil; } _name = name; _level = level; return self; } - (id<FBControlCoreLogger>)log:(NSString *)message { NSString *string = self.name ? [NSString stringWithFormat:@"[%@] %@", self.name, message] : message; NSLog(@"%@", string); return self; } - (id<FBControlCoreLogger>)logFormat:(NSString *)format, ... { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); return [self log:string]; } - (id<FBControlCoreLogger>)info { return self; } - (id<FBControlCoreLogger>)debug { return self; } - (id<FBControlCoreLogger>)error { return self; } - (id<FBControlCoreLogger>)withName:(NSString *)name { return [[self.class alloc] initWithname:name level:self.level]; } - (id<FBControlCoreLogger>)withDateFormatEnabled:(BOOL)dateFormat { return self; } @end @implementation FBCompositeLogger - (instancetype)initWithLoggers:(NSArray<id<FBControlCoreLogger>> *)loggers { self = [super init]; if (!self) { return nil; } _loggers = loggers; return self; } - (id<FBControlCoreLogger>)log:(NSString *)message { message = [FBControlCoreLoggerFactory loggableStringLine:message]; if (!message) { return self; } for (id<FBControlCoreLogger> logger in self.loggers) { [logger log:message]; } return self; } - (id<FBControlCoreLogger>)logFormat:(NSString *)format, ... { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); return [self log:string]; } - (id<FBControlCoreLogger>)info { return [self loggerByApplyingSelector:_cmd]; } - (id<FBControlCoreLogger>)debug { return [self loggerByApplyingSelector:_cmd]; } - (id<FBControlCoreLogger>)error { return [self loggerByApplyingSelector:_cmd]; } - (id<FBControlCoreLogger>)withName:(NSString *)name { return [self loggerByApplyingSelector:_cmd object:name]; } - (id<FBControlCoreLogger>)withDateFormatEnabled:(BOOL)dateFormat { return [self loggerByApplyingSelector:_cmd object:@(dateFormat)]; } - (NSString *)name { return nil; } - (FBControlCoreLogLevel)level { return FBControlCoreLogLevelMultiple; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" - (id<FBControlCoreLogger>)loggerByApplyingSelector:(SEL)selector { NSMutableArray<id<FBControlCoreLogger>> *loggers = [NSMutableArray arrayWithCapacity:self.loggers.count]; for (id<FBControlCoreLogger> logger in self.loggers) { [loggers addObject:[logger performSelector:selector]]; } return [[self.class alloc] initWithLoggers:loggers]; } - (id<FBControlCoreLogger>)loggerByApplyingSelector:(SEL)selector object:(id)object { NSMutableArray<id<FBControlCoreLogger>> *loggers = [NSMutableArray arrayWithCapacity:self.loggers.count]; for (id<FBControlCoreLogger> logger in self.loggers) { [loggers addObject:[logger performSelector:selector withObject:object]]; } return [[self.class alloc] initWithLoggers:loggers]; } #pragma clang diagnostic pop @end @interface FBControlCoreLogger_Consumer : NSObject <FBControlCoreLogger> @property (nonatomic, strong, readonly) id<FBDataConsumer> consumer; @property (nonatomic, strong, readonly, nullable) NSDateFormatter *dateFormatter; @end @implementation FBControlCoreLogger_Consumer @synthesize name = _name; @synthesize level = _level; - (instancetype)initWithConsumer:(id<FBDataConsumer>)consumer name:(NSString *)name dateFormatter:(NSDateFormatter *)dateFormatter { self = [super init]; if (!self) { return nil; } _consumer = consumer; _name = name; _dateFormatter = dateFormatter; return self; } #pragma mark Protocol Implementation - (id<FBControlCoreLogger>)log:(NSString *)message { message = [FBControlCoreLoggerFactory loggableStringLine:message]; if (!message) { return self; } NSMutableString *string = [NSMutableString string]; if (self.dateFormatter) { [string appendFormat:@"%@ ", [self.dateFormatter stringFromDate:NSDate.date]]; } if (self.name) { [string appendFormat:@"[%@] ", self.name]; } [string appendString:message]; [string appendString:@"\n"]; NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; @synchronized(self.consumer) { [self.consumer consumeData:data]; } return self; } - (id<FBControlCoreLogger>)logFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); return [self log:string]; } - (id<FBControlCoreLogger>)info { return [[self.class alloc] initWithConsumer:self.consumer name:self.name dateFormatter:self.dateFormatter]; } - (id<FBControlCoreLogger>)debug { return [[self.class alloc] initWithConsumer:self.consumer name:self.name dateFormatter:self.dateFormatter]; } - (id<FBControlCoreLogger>)error { return [[self.class alloc] initWithConsumer:self.consumer name:self.name dateFormatter:self.dateFormatter]; } - (id<FBControlCoreLogger>)withName:(NSString *)name { return [[self.class alloc] initWithConsumer:self.consumer name:name dateFormatter:self.dateFormatter]; } - (id<FBControlCoreLogger>)withDateFormatEnabled:(BOOL)enabled __attribute__((no_sanitize("bool"))) { NSDateFormatter *dateFormatter = nil; if (enabled) { dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSSZZZ"]; } return [[self.class alloc] initWithConsumer:self.consumer name:self.name dateFormatter:dateFormatter]; } @end @implementation FBControlCoreLoggerFactory #pragma mark Public #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" + (id<FBControlCoreLogger>)systemLoggerWritingToStderr:(BOOL)writeToStdErr withDebugLogging:(BOOL)debugLogging; { // Use the appropriate logger. FBControlCoreLogLevel level = debugLogging ? FBControlCoreLogLevelDebug : FBControlCoreLogLevelInfo; id<FBControlCoreLogger> systemLogger = [self osLoggerWithLevel:level] ?: [FBControlCoreLogger_NSLog new]; // If we don't care about stderr, just return the system logger. if (!writeToStdErr) { return systemLogger; } // If the system logger will log to stderr in the current build environment or runtime // don't bother adding a logger that will additionally log to stderr. if (FBControlCoreLoggerFactory.systemLoggerWillLogToStdErr) { return systemLogger; } // In contexts where we run without mirroring enabled. return [self compositeLoggerWithLoggers:@[ systemLogger, [self loggerToFileDescriptor:STDERR_FILENO closeOnEndOfFile:NO], ]]; } #pragma clang diagnostic pop + (FBCompositeLogger *)compositeLoggerWithLoggers:(NSArray<id<FBControlCoreLogger>> *)loggers { return [[FBCompositeLogger alloc] initWithLoggers:loggers]; } + (id<FBControlCoreLogger>)loggerToConsumer:(id<FBDataConsumer>)consumer { return [[FBControlCoreLogger_Consumer alloc] initWithConsumer:consumer name:nil dateFormatter:nil]; } + (id<FBControlCoreLogger>)loggerToFileDescriptor:(int)fileDescriptor closeOnEndOfFile:(BOOL)closeOnEndOfFile { id<FBDataConsumer> consumer = [FBFileWriter syncWriterWithFileDescriptor:fileDescriptor closeOnEndOfFile:closeOnEndOfFile]; return [[FBControlCoreLogger_Consumer alloc] initWithConsumer:consumer name:nil dateFormatter:nil]; } + (NSString *)loggableStringLine:(NSString *)string { if (!string) { return nil; } string = [string stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; if (string.length == 0) { return nil; } return string; } @end