mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-11 22:32:38 +08:00
Summary:
@public
Include stack traces in native redboxes (e.g. from RCTLogError). It's not trivial to get the file names and line numbers for every frame of the stack, but we can get the first one which is nice.
Test Plan: {F22548418}
199 lines
5.4 KiB
Objective-C
199 lines
5.4 KiB
Objective-C
/**
|
|
* 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"
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTDefines.h"
|
|
#import "RCTRedBox.h"
|
|
|
|
@interface RCTBridge (Logging)
|
|
|
|
+ (void)logMessage:(NSString *)message level:(NSString *)level;
|
|
|
|
@end
|
|
|
|
static NSString *const RCTLogPrefixStack = @"RCTLogPrefixStack";
|
|
|
|
const char *RCTLogLevels[] = {
|
|
"info",
|
|
"warn",
|
|
"error",
|
|
"mustfix"
|
|
};
|
|
|
|
static RCTLogFunction RCTCurrentLogFunction;
|
|
static RCTLogLevel RCTCurrentLogThreshold;
|
|
|
|
__attribute__((constructor))
|
|
static void RCTLogSetup()
|
|
{
|
|
RCTCurrentLogFunction = RCTDefaultLogFunction;
|
|
|
|
#if RCT_DEBUG
|
|
RCTCurrentLogThreshold = RCTLogLevelInfo - 1;
|
|
#else
|
|
RCTCurrentLogThreshold = RCTLogLevelError;
|
|
#endif
|
|
|
|
}
|
|
|
|
RCTLogFunction RCTDefaultLogFunction = ^(
|
|
RCTLogLevel level,
|
|
NSString *fileName,
|
|
NSNumber *lineNumber,
|
|
NSString *message
|
|
)
|
|
{
|
|
NSString *log = RCTFormatLog(
|
|
[NSDate date], level, fileName, lineNumber, message
|
|
);
|
|
fprintf(stderr, "%s\n", log.UTF8String);
|
|
fflush(stderr);
|
|
};
|
|
|
|
void RCTSetLogFunction(RCTLogFunction logFunction)
|
|
{
|
|
RCTCurrentLogFunction = logFunction;
|
|
}
|
|
|
|
RCTLogFunction RCTGetLogFunction()
|
|
{
|
|
return RCTCurrentLogFunction;
|
|
}
|
|
|
|
void RCTAddLogFunction(RCTLogFunction logFunction)
|
|
{
|
|
RCTLogFunction existing = RCTCurrentLogFunction;
|
|
if (existing) {
|
|
RCTCurrentLogFunction = ^(RCTLogLevel level,
|
|
NSString *fileName,
|
|
NSNumber *lineNumber,
|
|
NSString *message) {
|
|
|
|
existing(level, fileName, lineNumber, message);
|
|
logFunction(level, fileName, lineNumber, message);
|
|
};
|
|
} else {
|
|
RCTCurrentLogFunction = logFunction;
|
|
}
|
|
}
|
|
|
|
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
|
|
{
|
|
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
|
NSMutableArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
|
if (!prefixStack) {
|
|
prefixStack = [[NSMutableArray alloc] init];
|
|
threadDictionary[RCTLogPrefixStack] = prefixStack;
|
|
}
|
|
[prefixStack addObject:prefix];
|
|
block();
|
|
[prefixStack removeLastObject];
|
|
}
|
|
|
|
NSString *RCTFormatLog(
|
|
NSDate *timestamp,
|
|
RCTLogLevel level,
|
|
NSString *fileName,
|
|
NSNumber *lineNumber,
|
|
NSString *message
|
|
)
|
|
{
|
|
NSMutableString *log = [[NSMutableString alloc] init];
|
|
if (timestamp) {
|
|
static NSDateFormatter *formatter;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
formatter = [[NSDateFormatter alloc] init];
|
|
formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS ";
|
|
});
|
|
[log appendString:[formatter stringFromDate:timestamp]];
|
|
}
|
|
if (level) {
|
|
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
|
|
}
|
|
|
|
[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;
|
|
}
|
|
|
|
void _RCTLogFormat(
|
|
RCTLogLevel level,
|
|
const char *fileName,
|
|
int lineNumber,
|
|
NSString *format, ...)
|
|
{
|
|
|
|
BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil);
|
|
if (log && level >= RCTCurrentLogThreshold) {
|
|
|
|
// Get message
|
|
va_list args;
|
|
va_start(args, format);
|
|
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
|
|
va_end(args);
|
|
|
|
// Add prefix
|
|
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
|
NSArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
|
NSString *prefix = [prefixStack lastObject];
|
|
if (prefix) {
|
|
message = [prefix stringByAppendingString:message];
|
|
}
|
|
|
|
// Call log function
|
|
RCTCurrentLogFunction(
|
|
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
|
|
);
|
|
|
|
#if RCT_DEBUG // Red box is only available in debug mode
|
|
|
|
// Log to red box
|
|
if (level >= RCTLOG_REDBOX_LEVEL) {
|
|
NSArray *stackSymbols = [NSThread callStackSymbols];
|
|
NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)];
|
|
[stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, BOOL *stop) {
|
|
if (idx != 0) { // don't include the current frame
|
|
NSString *address = [[frameSymbols componentsSeparatedByString:@"0x"][1] componentsSeparatedByString:@" "][0];
|
|
NSRange addressRange = [frameSymbols rangeOfString:address];
|
|
NSString *methodName = [frameSymbols substringFromIndex:(addressRange.location + addressRange.length + 1)];
|
|
if (idx == 1) {
|
|
NSString *file = [[@(fileName) componentsSeparatedByString:@"/"] lastObject];
|
|
stack[0] = @{@"methodName": methodName, @"file": file, @"lineNumber": @(lineNumber)};
|
|
} else {
|
|
stack[idx - 1] = @{@"methodName": methodName};
|
|
}
|
|
}
|
|
}];
|
|
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
|
}
|
|
|
|
// Log to JS executor
|
|
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
|
|
|
|
#endif
|
|
|
|
}
|
|
}
|