RCTLog.mm 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTLog.h"
  8. #include <cxxabi.h>
  9. #import <objc/message.h>
  10. #import <os/log.h>
  11. #import "RCTAssert.h"
  12. #import "RCTBridge+Private.h"
  13. #import "RCTBridge.h"
  14. #import "RCTDefines.h"
  15. #import "RCTRedBoxSetEnabled.h"
  16. #import "RCTUtils.h"
  17. static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
  18. const char *RCTLogLevels[] = {
  19. "trace",
  20. "info",
  21. "warn",
  22. "error",
  23. "fatal",
  24. };
  25. /* os log will discard debug and info messages if they are not needed */
  26. static const RCTLogLevel RCTDefaultLogThreshold = (RCTLogLevel)(RCTLogLevelInfo - 1);
  27. static RCTLogFunction RCTCurrentLogFunction;
  28. static RCTLogLevel RCTCurrentLogThreshold = RCTDefaultLogThreshold;
  29. RCTLogLevel RCTGetLogThreshold()
  30. {
  31. return RCTCurrentLogThreshold;
  32. }
  33. void RCTSetLogThreshold(RCTLogLevel threshold)
  34. {
  35. RCTCurrentLogThreshold = threshold;
  36. }
  37. static os_log_type_t RCTLogTypeForLogLevel(RCTLogLevel logLevel)
  38. {
  39. if (logLevel < RCTLogLevelInfo) {
  40. return OS_LOG_TYPE_DEBUG;
  41. } else if (logLevel <= RCTLogLevelWarning) {
  42. return OS_LOG_TYPE_INFO;
  43. } else {
  44. return OS_LOG_TYPE_ERROR;
  45. }
  46. }
  47. static os_log_t RCTLogForLogSource(RCTLogSource source)
  48. {
  49. switch (source) {
  50. case RCTLogSourceNative: {
  51. static os_log_t nativeLog;
  52. static dispatch_once_t onceToken;
  53. dispatch_once(&onceToken, ^{
  54. nativeLog = os_log_create("com.facebook.react.log", "native");
  55. });
  56. return nativeLog;
  57. }
  58. case RCTLogSourceJavaScript: {
  59. static os_log_t javaScriptLog;
  60. static dispatch_once_t onceToken;
  61. dispatch_once(&onceToken, ^{
  62. javaScriptLog = os_log_create("com.facebook.react.log", "javascript");
  63. });
  64. return javaScriptLog;
  65. }
  66. }
  67. }
  68. RCTLogFunction RCTDefaultLogFunction =
  69. ^(RCTLogLevel level,
  70. RCTLogSource source,
  71. __unused NSString *fileName,
  72. __unused NSNumber *lineNumber,
  73. NSString *message) {
  74. os_log_with_type(RCTLogForLogSource(source), RCTLogTypeForLogLevel(level), "%{public}s", message.UTF8String);
  75. };
  76. void RCTSetLogFunction(RCTLogFunction logFunction)
  77. {
  78. RCTCurrentLogFunction = logFunction;
  79. }
  80. RCTLogFunction RCTGetLogFunction()
  81. {
  82. if (!RCTCurrentLogFunction) {
  83. RCTCurrentLogFunction = RCTDefaultLogFunction;
  84. }
  85. return RCTCurrentLogFunction;
  86. }
  87. void RCTAddLogFunction(RCTLogFunction logFunction)
  88. {
  89. RCTLogFunction existing = RCTGetLogFunction();
  90. if (existing) {
  91. RCTSetLogFunction(
  92. ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
  93. existing(level, source, fileName, lineNumber, message);
  94. logFunction(level, source, fileName, lineNumber, message);
  95. });
  96. } else {
  97. RCTSetLogFunction(logFunction);
  98. }
  99. }
  100. /**
  101. * returns the topmost stacked log function for the current thread, which
  102. * may not be the same as the current value of RCTCurrentLogFunction.
  103. */
  104. static RCTLogFunction RCTGetLocalLogFunction()
  105. {
  106. NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
  107. NSArray<RCTLogFunction> *functionStack = threadDictionary[RCTLogFunctionStack];
  108. RCTLogFunction logFunction = functionStack.lastObject;
  109. if (logFunction) {
  110. return logFunction;
  111. }
  112. return RCTGetLogFunction();
  113. }
  114. void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction)
  115. {
  116. NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
  117. NSMutableArray<RCTLogFunction> *functionStack = threadDictionary[RCTLogFunctionStack];
  118. if (!functionStack) {
  119. functionStack = [NSMutableArray new];
  120. threadDictionary[RCTLogFunctionStack] = functionStack;
  121. }
  122. [functionStack addObject:logFunction];
  123. block();
  124. [functionStack removeLastObject];
  125. }
  126. void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
  127. {
  128. RCTLogFunction logFunction = RCTGetLocalLogFunction();
  129. if (logFunction) {
  130. RCTPerformBlockWithLogFunction(
  131. block, ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
  132. logFunction(level, source, fileName, lineNumber, [prefix stringByAppendingString:message]);
  133. });
  134. }
  135. }
  136. NSString *
  137. RCTFormatLog(NSDate *timestamp, RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message)
  138. {
  139. NSMutableString *log = [NSMutableString new];
  140. if (timestamp) {
  141. static NSDateFormatter *formatter;
  142. static dispatch_once_t onceToken;
  143. dispatch_once(&onceToken, ^{
  144. formatter = [NSDateFormatter new];
  145. formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS ";
  146. });
  147. [log appendString:[formatter stringFromDate:timestamp]];
  148. }
  149. if (level) {
  150. [log appendFormat:@"[%s]", RCTLogLevels[level]];
  151. }
  152. [log appendFormat:@"[tid:%@]", RCTCurrentThreadName()];
  153. if (fileName) {
  154. fileName = fileName.lastPathComponent;
  155. if (lineNumber) {
  156. [log appendFormat:@"[%@:%@]", fileName, lineNumber];
  157. } else {
  158. [log appendFormat:@"[%@]", fileName];
  159. }
  160. }
  161. if (message) {
  162. [log appendString:@" "];
  163. [log appendString:message];
  164. }
  165. return log;
  166. }
  167. NSString *RCTFormatLogLevel(RCTLogLevel level)
  168. {
  169. NSDictionary *levelsToString = @{
  170. @(RCTLogLevelTrace) : @"trace",
  171. @(RCTLogLevelInfo) : @"info",
  172. @(RCTLogLevelWarning) : @"warning",
  173. @(RCTLogLevelFatal) : @"fatal",
  174. @(RCTLogLevelError) : @"error"
  175. };
  176. return levelsToString[@(level)];
  177. }
  178. NSString *RCTFormatLogSource(RCTLogSource source)
  179. {
  180. NSDictionary *sourcesToString = @{@(RCTLogSourceNative) : @"native", @(RCTLogSourceJavaScript) : @"js"};
  181. return sourcesToString[@(source)];
  182. }
  183. static NSRegularExpression *nativeStackFrameRegex()
  184. {
  185. static dispatch_once_t onceToken;
  186. static NSRegularExpression *_regex;
  187. dispatch_once(&onceToken, ^{
  188. NSError *regexError;
  189. _regex = [NSRegularExpression regularExpressionWithPattern:@"0x[0-9a-f]+ (.*) \\+ (\\d+)$"
  190. options:0
  191. error:&regexError];
  192. if (regexError) {
  193. RCTLogError(@"Failed to build regex: %@", [regexError localizedDescription]);
  194. }
  195. });
  196. return _regex;
  197. }
  198. void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
  199. {
  200. RCTLogFunction logFunction = RCTGetLocalLogFunction();
  201. BOOL log = RCT_DEBUG || (logFunction != nil);
  202. if (log && level >= RCTGetLogThreshold()) {
  203. // Get message
  204. va_list args;
  205. va_start(args, format);
  206. NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
  207. va_end(args);
  208. // Call log function
  209. if (logFunction) {
  210. logFunction(
  211. level, RCTLogSourceNative, fileName ? @(fileName) : nil, lineNumber > 0 ? @(lineNumber) : nil, message);
  212. }
  213. // Log to red box if one is configured.
  214. if (RCTSharedApplication() && RCTRedBoxGetEnabled() && level >= RCTLOG_REDBOX_LEVEL) {
  215. NSArray<NSString *> *stackSymbols = [NSThread callStackSymbols];
  216. NSMutableArray<NSDictionary *> *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)];
  217. [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) {
  218. if (idx == 0) {
  219. // don't include the current frame
  220. return;
  221. }
  222. NSRange range = NSMakeRange(0, frameSymbols.length);
  223. NSTextCheckingResult *match = [nativeStackFrameRegex() firstMatchInString:frameSymbols options:0 range:range];
  224. if (!match) {
  225. return;
  226. }
  227. NSString *methodName = [frameSymbols substringWithRange:[match rangeAtIndex:1]];
  228. char *demangledName = abi::__cxa_demangle([methodName UTF8String], NULL, NULL, NULL);
  229. if (demangledName) {
  230. methodName = @(demangledName);
  231. free(demangledName);
  232. }
  233. if (idx == 1 && fileName) {
  234. NSString *file = [@(fileName) componentsSeparatedByString:@"/"].lastObject;
  235. [stack addObject:@{@"methodName" : methodName, @"file" : file, @"lineNumber" : @(lineNumber)}];
  236. } else {
  237. [stack addObject:@{@"methodName" : methodName}];
  238. }
  239. }];
  240. dispatch_async(dispatch_get_main_queue(), ^{
  241. // red box is thread safe, but by deferring to main queue we avoid a startup
  242. // race condition that causes the module to be accessed before it has loaded
  243. id redbox = [[RCTBridge currentBridge] moduleForName:@"RedBox" lazilyLoadIfNecessary:YES];
  244. if (redbox) {
  245. void (*showErrorMessage)(id, SEL, NSString *, NSMutableArray<NSDictionary *> *) =
  246. (__typeof__(showErrorMessage))objc_msgSend;
  247. SEL showErrorMessageSEL = NSSelectorFromString(@"showErrorMessage:withStack:");
  248. if ([redbox respondsToSelector:showErrorMessageSEL]) {
  249. showErrorMessage(redbox, showErrorMessageSEL, message, stack);
  250. }
  251. }
  252. });
  253. }
  254. #if RCT_DEBUG
  255. if (!RCTRunningInTestEnvironment()) {
  256. // Log to JS executor
  257. [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
  258. }
  259. #endif
  260. }
  261. }
  262. void _RCTLogJavaScriptInternal(RCTLogLevel level, NSString *message)
  263. {
  264. RCTLogFunction logFunction = RCTGetLocalLogFunction();
  265. BOOL log = RCT_DEBUG || (logFunction != nil);
  266. if (log && level >= RCTGetLogThreshold()) {
  267. if (logFunction) {
  268. logFunction(level, RCTLogSourceJavaScript, nil, nil, message);
  269. }
  270. }
  271. }