/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #import "RCTLog.h" #include #include #import "RCTAssert.h" #import "RCTBridge.h" #import "RCTBridge+Private.h" #import "RCTDefines.h" #import "RCTRedBox.h" #import "RCTUtils.h" static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack"; const char *RCTLogLevels[] = { "trace", "info", "warn", "error", "fatal", }; #if RCT_DEBUG static const RCTLogLevel RCTDefaultLogThreshold = (RCTLogLevel)(RCTLogLevelInfo - 1); #else static const RCTLogLevel RCTDefaultLogThreshold = RCTLogLevelError; #endif static RCTLogFunction RCTCurrentLogFunction; static RCTLogLevel RCTCurrentLogThreshold = RCTDefaultLogThreshold; RCTLogLevel RCTGetLogThreshold() { return RCTCurrentLogThreshold; } void RCTSetLogThreshold(RCTLogLevel threshold) { RCTCurrentLogThreshold = threshold; } RCTLogFunction RCTDefaultLogFunction = ^( RCTLogLevel level, __unused RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message ) { NSString *log = RCTFormatLog([NSDate date], level, fileName, lineNumber, message); fprintf(stderr, "%s\n", log.UTF8String); fflush(stderr); int aslLevel; switch(level) { case RCTLogLevelTrace: aslLevel = ASL_LEVEL_DEBUG; break; case RCTLogLevelInfo: aslLevel = ASL_LEVEL_NOTICE; break; case RCTLogLevelWarning: aslLevel = ASL_LEVEL_WARNING; break; case RCTLogLevelError: aslLevel = ASL_LEVEL_ERR; break; case RCTLogLevelFatal: aslLevel = ASL_LEVEL_CRIT; break; } asl_log(NULL, NULL, aslLevel, "%s", message.UTF8String); }; void RCTSetLogFunction(RCTLogFunction logFunction) { RCTCurrentLogFunction = logFunction; } RCTLogFunction RCTGetLogFunction() { if (!RCTCurrentLogFunction) { RCTCurrentLogFunction = RCTDefaultLogFunction; } return RCTCurrentLogFunction; } void RCTAddLogFunction(RCTLogFunction logFunction) { RCTLogFunction existing = RCTGetLogFunction(); if (existing) { RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { existing(level, source, fileName, lineNumber, message); logFunction(level, source, fileName, lineNumber, message); }); } else { RCTSetLogFunction(logFunction); } } /** * returns the topmost stacked log function for the current thread, which * may not be the same as the current value of RCTCurrentLogFunction. */ static RCTLogFunction RCTGetLocalLogFunction() { NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; NSArray *functionStack = threadDictionary[RCTLogFunctionStack]; RCTLogFunction logFunction = functionStack.lastObject; if (logFunction) { return logFunction; } return RCTGetLogFunction(); } void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction) { NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; NSMutableArray *functionStack = threadDictionary[RCTLogFunctionStack]; if (!functionStack) { functionStack = [NSMutableArray new]; threadDictionary[RCTLogFunctionStack] = functionStack; } [functionStack addObject:logFunction]; block(); [functionStack removeLastObject]; } void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix) { RCTLogFunction logFunction = RCTGetLocalLogFunction(); if (logFunction) { RCTPerformBlockWithLogFunction(block, ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { logFunction(level, source, fileName, lineNumber, [prefix stringByAppendingString:message]); }); } } NSString *RCTFormatLog( NSDate *timestamp, RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message ) { NSMutableString *log = [NSMutableString new]; if (timestamp) { static NSDateFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [NSDateFormatter new]; formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS "; }); [log appendString:[formatter stringFromDate:timestamp]]; } if (level) { [log appendFormat:@"[%s]", RCTLogLevels[level]]; } [log appendFormat:@"[tid:%@]", RCTCurrentThreadName()]; if (fileName) { fileName = fileName.lastPathComponent; if (lineNumber) { [log appendFormat:@"[%@:%@]", fileName, lineNumber]; } else { [log appendFormat:@"[%@]", fileName]; } } if (message) { [log appendString:@" "]; [log appendString:message]; } return log; } static NSRegularExpression *nativeStackFrameRegex() { static dispatch_once_t onceToken; static NSRegularExpression *_regex; dispatch_once(&onceToken, ^{ NSError *regexError; _regex = [NSRegularExpression regularExpressionWithPattern:@"0x[0-9a-f]+ (.*) \\+ (\\d+)$" options:0 error:®exError]; if (regexError) { RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]); } }); return _regex; } void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...) { RCTLogFunction logFunction = RCTGetLocalLogFunction(); BOOL log = RCT_DEBUG || (logFunction != nil); if (log && level >= RCTGetLogThreshold()) { // Get message va_list args; va_start(args, format); NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); // Call log function if (logFunction) { logFunction(level, RCTLogSourceNative, fileName ? @(fileName) : nil, lineNumber > 0 ? @(lineNumber) : nil, message); } #if RCT_DEBUG // Log to red box in debug mode. if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { NSArray *stackSymbols = [NSThread callStackSymbols]; NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)]; [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) { if (idx == 0) { // don't include the current frame return; } NSRange range = NSMakeRange(0, frameSymbols.length); NSTextCheckingResult *match = [nativeStackFrameRegex() firstMatchInString:frameSymbols options:0 range:range]; if (!match) { return; } NSString *methodName = [frameSymbols substringWithRange:[match rangeAtIndex:1]]; char *demangledName = abi::__cxa_demangle([methodName UTF8String], NULL, NULL, NULL); if (demangledName) { methodName = @(demangledName); free(demangledName); } if (idx == 1 && fileName) { NSString *file = [@(fileName) componentsSeparatedByString:@"/"].lastObject; [stack addObject:@{@"methodName": methodName, @"file": file, @"lineNumber": @(lineNumber)}]; } else { [stack addObject:@{@"methodName": methodName}]; } }]; dispatch_async(dispatch_get_main_queue(), ^{ // red box is thread safe, but by deferring to main queue we avoid a startup // race condition that causes the module to be accessed before it has loaded [[RCTBridge currentBridge].redBox showErrorMessage:message withStack:stack]; }); } if (!RCTRunningInTestEnvironment()) { // Log to JS executor [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"]; } #endif } } void _RCTLogJavaScriptInternal(RCTLogLevel level, NSString *message) { RCTLogFunction logFunction = RCTGetLocalLogFunction(); BOOL log = RCT_DEBUG || (logFunction != nil); if (log && level >= RCTGetLogThreshold()) { if (logFunction) { logFunction(level, RCTLogSourceJavaScript, nil, nil, message); } } }