Files
react-native/React/Base/RCTLog.m
Spencer Ahrens 781b17fd0f [ReactNative] include stack in native redboxes
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}
2015-06-12 15:48:20 -08:00

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
}
}