FBControlCore/Utility/FBLoggingWrapper.m (224 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 "FBLoggingWrapper.h" @interface FBLoggingWrapper () @property (nonatomic, strong, readonly) id wrappedObject; @property (nonatomic, strong, readonly) id<FBEventReporter> eventReporter; @property (nonatomic, strong, readonly) id<FBControlCoreLogger> logger; @property (nonatomic, strong, readonly) dispatch_queue_t queue; @property (nonatomic, assign, readonly) BOOL simplifiedNaming; @end @implementation FBLoggingWrapper #pragma mark Initializers + (instancetype)wrap:(id)wrappedObject simplifiedNaming:(BOOL)simplifiedNaming eventReporter:(nullable id<FBEventReporter>)eventReporter logger:(nullable id<FBControlCoreLogger>)logger { return [[self alloc] initWithWrappedObject:wrappedObject simplifiedNaming:simplifiedNaming eventReporter:eventReporter logger:logger]; } - (instancetype)initWithWrappedObject:(id)wrappedObject simplifiedNaming:(BOOL)simplifiedNaming eventReporter:(nullable id<FBEventReporter>)eventReporter logger:(nullable id<FBControlCoreLogger>)logger { self = [super init]; if (!self) { return nil; } _wrappedObject = wrappedObject; _simplifiedNaming = simplifiedNaming; _queue = dispatch_queue_create("com.facebook.fbcontrolcore.logging_wrapper", DISPATCH_QUEUE_SERIAL); _eventReporter = eventReporter; _logger = logger; return self; } #pragma mark Forwarding - (BOOL)respondsToSelector:(SEL)selector { return [super respondsToSelector:selector] || [self.wrappedObject respondsToSelector:selector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { return [super methodSignatureForSelector:selector] ?: [self.wrappedObject methodSignatureForSelector:selector]; } - (void)forwardInvocation:(NSInvocation *)invocation { if ([self.wrappedObject respondsToSelector:invocation.selector]) { [self runInvocation:invocation]; } else { [super forwardInvocation:invocation]; } } - (void)runInvocation:(NSInvocation *)invocation { // Extract information about the start of the call. NSDate *startDate = NSDate.date; NSString *methodName = [self.class methodName:invocation simplifiedNaming:self.simplifiedNaming]; NSArray<NSString *> *descriptionOfArguments = [self.class descriptionOfArguments:invocation]; FBEventReporterSubject *beforeSubject = [self.class subjectForBeforeInvocation:methodName descriptionOfArguments:descriptionOfArguments logger:self.logger]; [self.eventReporter report:beforeSubject]; // Extract the first method argument and retain it, if it exists id firstMethodArgument = nil; if (invocation.methodSignature.numberOfArguments > 2 && strcmp([invocation.methodSignature getArgumentTypeAtIndex:2], "@") == 0) { __unsafe_unretained id argument = nil; [invocation getArgument:&argument atIndex:2]; firstMethodArgument = argument; } // Invoke on the Future Handler on the appropriate queue. [invocation invokeWithTarget:self.wrappedObject]; void *returnValue = NULL; [invocation getReturnValue:&returnValue]; FBFuture *future = (__bridge FBFuture *)(returnValue); // Log the end of the call when the future resolves. if ([future isKindOfClass:FBFuture.class]) { [future onQueue:self.queue notifyOfCompletion:^(FBFuture *completedFuture) { FBEventReporterSubject *afterSubject = [self.class subjectAfterCompletion:completedFuture methodName:methodName descriptionOfArguments:descriptionOfArguments startDate:startDate firstMethodArgument:firstMethodArgument logger:self.logger]; [self.eventReporter report:afterSubject]; }]; } } #pragma mark - Subjects + (FBEventReporterSubject *)subjectForBeforeInvocation:(NSString *)methodName descriptionOfArguments:(NSArray<NSString *> *)descriptionOfArguments logger:(id<FBControlCoreLogger>)logger { [logger.info logFormat:@"%@ called with: %@", methodName, [FBCollectionInformation oneLineDescriptionFromArray:descriptionOfArguments]]; return [FBEventReporterSubject subjectForStartedCall:methodName arguments:descriptionOfArguments]; } + (FBEventReporterSubject *)subjectAfterCompletion:(FBFuture *)future methodName:(NSString *)methodName descriptionOfArguments:(NSArray<NSString *> *)descriptionOfArguments startDate:(NSDate *)startDate firstMethodArgument:(id)firstMethodArgument logger:(id<FBControlCoreLogger>)logger { NSTimeInterval duration = [NSDate.date timeIntervalSinceDate:startDate]; NSError *error = future.error; NSNumber *size = nil; if ([firstMethodArgument respondsToSelector:@selector(bytesTransferred)]) { size = @([firstMethodArgument bytesTransferred]); } if (error) { NSString *message = error.localizedDescription; [logger.debug logFormat:@"%@ failed with: %@", methodName, message]; return [FBEventReporterSubject subjectForFailingCall:methodName duration:duration message:message size:size arguments:descriptionOfArguments]; } else { [logger.debug logFormat:@"%@ succeeded", methodName]; return [FBEventReporterSubject subjectForSuccessfulCall:methodName duration:duration size:size arguments:descriptionOfArguments]; } } #pragma mark - NSInvocation inspection + (NSString *)methodName:(NSInvocation *)invocation simplifiedNaming:(BOOL)simplifiedNaming { // This will log the first argument in the method name, so method names should be unique relative to the first component within the selector if (simplifiedNaming) { return [NSStringFromSelector(invocation.selector) componentsSeparatedByString:@":"][0]; } // Otherwise log the entire selector return NSStringFromSelector(invocation.selector); } + (NSArray<NSString *> *)descriptionOfArguments:(NSInvocation *)invocation { NSMutableArray<NSString *> *descriptions = NSMutableArray.array; for (int index = 2; index < (int) invocation.methodSignature.numberOfArguments; index++) { NSString *description = [self descriptionForAgumentAtIndex:index inInvoation:invocation]; if (description.length > 100) { description = [NSString stringWithFormat:@"%@...", [description substringToIndex:100]]; } [descriptions addObject:description]; } return descriptions; } + (NSString *)descriptionForAgumentAtIndex:(int)index inInvoation:(NSInvocation *)invocation { NSString *type = [NSString stringWithUTF8String:[invocation.methodSignature getArgumentTypeAtIndex:(NSUInteger)index]]; if ([type isEqualToString:@"c"]) { char argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%c", argument]; } if ([type isEqualToString:@"i"]) { int argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%d", argument]; } if ([type isEqualToString:@"s"]) { short argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%d", argument]; } if ([type isEqualToString:@"l"]) { long argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%ld", argument]; } if ([type isEqualToString:@"q"]) { long long argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%lld", argument]; } if ([type isEqualToString:@"C"]) { unsigned char argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%c", argument]; } if ([type isEqualToString:@"I"]) { unsigned int argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%d", argument]; } if ([type isEqualToString:@"S"]) { unsigned short argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%d", argument]; } if ([type isEqualToString:@"L"]) { unsigned long argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%ld", argument]; } if ([type isEqualToString:@"Q"]) { unsigned long long argument = 0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%lld", argument]; } if ([type isEqualToString:@"f"]) { float argument = 0.0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%f", argument]; } if ([type isEqualToString:@"d"]) { double argument = 0.0; [invocation getArgument:&argument atIndex:index]; return [NSString stringWithFormat:@"%f", argument]; } if ([type isEqualToString:@"v"]) { return @"void"; } if ([type isEqualToString:@"@"]) { __unsafe_unretained id argument = nil; [invocation getArgument:&argument atIndex:index]; return [self descriptionForObject:argument]; } return @"Unrecognised type"; } + (NSString *)descriptionForObject:(NSObject *)object { if ([object isKindOfClass:NSString.class]) { return [(NSString *)object description]; } if ([object isKindOfClass:NSData.class]) { NSData *data = (NSData *)object; return [NSString stringWithFormat:@"NSData of length %lu", (unsigned long)data.length]; } if (object == nil || [object isKindOfClass:NSNull.class]) { return @"null"; } if ([object isKindOfClass:NSArray.class]) { return [self implodeDescription:(NSSet<id> *)object prefix:@"NSArray"]; } if ([object isKindOfClass:NSSet.class]) { return [self implodeDescription:(NSSet<id> *)object prefix:@"NSSet"]; } if ([object isKindOfClass:NSDictionary.class]) { NSDictionary<id, id> *dict = (NSDictionary<id, id> *)object; NSMutableString *description = NSMutableString.string; [description appendString:@"NSDictionary{"]; for (NSObject *key in dict) { [description appendFormat:@"%@: %@", [self descriptionForObject:key], [self descriptionForObject:dict[key]]]; [description appendString:@", "]; } [description appendString:@"}"]; return description; } return object.description; } + (NSString *)implodeDescription:(id<NSFastEnumeration>)container prefix:(NSString *)prefix { NSMutableArray<NSString *> *descriptions = NSMutableArray.array; for (id inner in container) { [descriptions addObject:[self descriptionForObject:inner]]; } return [NSString stringWithFormat:@"%@[%@]", prefix, [descriptions componentsJoinedByString:@", "]]; } @end