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