Refactored RCTLog and added facility to prepend extra data to the log message

This commit is contained in:
Nick Lockwood 2015-04-07 07:36:26 -07:00
parent 5b3e935332
commit 80cd687e95
15 changed files with 411 additions and 179 deletions

View File

@ -1,5 +1,9 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",

View File

@ -71,7 +71,7 @@
UIView *view = viewRegistry[reactTag];
if (!view) {
RCTLogWarn(@"React tag %@ is not registered with the view registry", reactTag);
RCTLogWarn(@"React tag #%@ is not registered with the view registry", reactTag);
return;
}

View File

@ -119,7 +119,9 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
[_jsQueue addOperationWithBlock:^{
if (!self.valid) {
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{NSLocalizedDescriptionKey:@"socket closed"}];
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
NSLocalizedDescriptionKey: @"socket closed"
}];
callback(error, nil);
return;
}

View File

@ -28,6 +28,11 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
extern NSString *const RCTReloadBridge;
/**
* This function returns the module name for a given class.
*/
extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
/**
* Async batched bridge used to communicate with the JavaScript application.
*/
@ -81,14 +86,6 @@ extern NSString *const RCTReloadBridge;
*/
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
/**
* Global logging function that will print to both xcode and JS debugger consoles.
*
* NOTE: Use via RCTLog* macros defined in RCTLog.h
* TODO (#5906496): should log function be exposed here, or could it be a module?
*/
+ (void)log:(NSArray *)objects level:(NSString *)level;
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;

View File

@ -22,6 +22,7 @@
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@ -41,10 +42,7 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
NSString *const RCTReloadBridge = @"RCTReloadBridge";
/**
* This function returns the module name for a given class.
*/
static NSString *RCTModuleNameForClass(Class cls)
NSString *RCTBridgeModuleNameForClass(Class cls)
{
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
}
@ -92,7 +90,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
[(NSMutableArray *)modules addObject:cls];
// Add module name
NSString *moduleName = RCTModuleNameForClass(cls);
NSString *moduleName = RCTBridgeModuleNameForClass(cls);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
}
});
@ -187,7 +185,7 @@ static Class _globalExecutorClass;
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
@ -203,7 +201,7 @@ static Class _globalExecutorClass;
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
@ -231,7 +229,7 @@ static Class _globalExecutorClass;
RCT_ARG_BLOCK(
if (json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTModuleNameForClass(_moduleClass), _JSMethodName);
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
@ -268,7 +266,7 @@ static Class _globalExecutorClass;
// Safety check
if (arguments.count != _argumentBlocks.count) {
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
RCTModuleNameForClass(_moduleClass), _JSMethodName,
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
arguments.count, _argumentBlocks.count);
return;
}
@ -544,7 +542,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
}
// Instantiate modules
@ -895,27 +893,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
}
+ (void)log:(NSArray *)objects level:(NSString *)level
+ (void)logMessage:(NSString *)message level:(NSString *)level
{
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
RCTLogError(@"ERROR: No valid JS executor to log %@.", objects);
RCTLogError(@"ERROR: No valid JS executor to log '%@'.", message);
return;
}
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
// TODO (#5906496): Find out and document why we skip the first object
for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) {
if ([NSJSONSerialization isValidJSONObject:@[ob]]) {
[args addObject:ob];
} else {
[args addObject:[ob description]];
}
}
// Note: the js executor could get invalidated while we're trying to call this...need to watch out for that.
// Note: the js executor could get invalidated while we're trying to call
// this...need to watch out for that.
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:args
arguments:@[level, message]
callback:^(id json, NSError *error) {}];
}

View File

@ -145,7 +145,7 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
json, [json class], #type); \
json, [json classForCoder], #type); \
json = nil; \
return code; \
} \
@ -181,7 +181,8 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
return default; \
} \
if (![json isKindOfClass:[NSString class]]) { \
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", \
#type, [json classForCoder], json); \
} \
id value = mapping[json]; \
if(!value && [json description].length > 0) { \

View File

@ -45,7 +45,7 @@ RCT_CONVERTER(NSString *, NSString, description)
}
return number;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
}
return nil;
}
@ -53,7 +53,7 @@ RCT_CONVERTER(NSString *, NSString, description)
+ (NSURL *)NSURL:(id)json
{
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json class], json);
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
return nil;
}
@ -98,7 +98,7 @@ RCT_CONVERTER(NSString *, NSString, description)
}
return date;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json class]);
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json classForCoder]);
}
return nil;
}
@ -234,7 +234,8 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", \
#type, [json classForCoder], json); \
} \
return result; \
} \
@ -511,8 +512,8 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
} else if (json && ![json isKindOfClass:[NSNull class]]) {
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, \
received %@: %@", [json class], json);
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@",
[json classForCoder], json);
}
// Default color
@ -538,7 +539,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
// image itself) so as to reduce overhead on subsequent checks of the same input
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json class], json);
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json classForCoder], json);
return nil;
}

View File

@ -7,58 +7,138 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTAssert.h"
#import "RCTRedBox.h"
#define RCTLOG_INFO 1
#define RCTLOG_WARN 2
#define RCTLOG_ERROR 3
#define RCTLOG_MUSTFIX 4
// If set to e.g. `RCTLOG_ERROR`, will assert after logging the first error.
#if DEBUG
#define RCTLOG_FATAL_LEVEL RCTLOG_MUSTFIX
#define RCTLOG_REDBOX_LEVEL RCTLOG_ERROR
#else
#define RCTLOG_FATAL_LEVEL (RCTLOG_MUSTFIX + 1)
#define RCTLOG_REDBOX_LEVEL (RCTLOG_MUSTFIX + 1)
#endif
// If defined, only log messages that match this regex will fatal
#define RCTLOG_FATAL_REGEX nil
extern __unsafe_unretained NSString *RCTLogLevels[];
#define _RCTLog(_level, ...) do { \
NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \
NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \
if (_level >= RCTLOG_FATAL_LEVEL) { \
BOOL __RCTLog__fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
} \
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
} \
if (_level >= RCTLOG_REDBOX_LEVEL) { \
[[RCTRedBox sharedInstance] showErrorMessage:__RCTLog__msg]; \
} \
} while (0)
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
#define RCTLogInfo(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
#define RCTLogWarn(...) _RCTLog(RCTLOG_WARN, __VA_ARGS__)
#define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__)
#define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__)
#ifdef __cplusplus
extern "C" {
#endif
NSString *RCTLogObjects(NSArray *objects, NSString *level);
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
/**
* Thresholds for logs to raise an assertion, or display redbox, respectively.
* You can override these values when debugging in order to tweak the default
* logging behavior.
*/
#define RCTLOG_FATAL_LEVEL RCTLogLevelMustFix
#define RCTLOG_REDBOX_LEVEL RCTLogLevelError
void RCTInjectLogFunction(void (^logFunction)(NSString *msg));
/**
* A regular expression that can be used to selectively limit the throwing of
* a exception to specific log contents.
*/
#define RCTLOG_FATAL_REGEX nil
/**
* An enum representing the severity of the log message.
*/
typedef NS_ENUM(NSInteger, RCTLogLevel) {
RCTLogLevelInfo = 1,
RCTLogLevelWarning = 2,
RCTLogLevelError = 3,
RCTLogLevelMustFix = 4
};
/**
* A block signature to be used for custom logging functions. In most cases you
* will want to pass these arguments to the RCTFormatLog function in order to
* generate a string, or use the RCTSimpleLogFunction() constructor to register
* a simple function that does not use all of the arguments.
*/
typedef void (^RCTLogFunction)(
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message
);
/**
* A method to generate a string from a collection of log data. To omit any
* particular data from the log, just pass nil or zero for the argument.
*/
NSString *RCTFormatLog(
NSDate *timestamp,
NSThread *thread,
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message
);
/**
* A method to generate a log function from a block with a much simpler
* template. The message passed to the simpler block is equivalent to the
* output of the RCTFormatLog() function.
*/
RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message));
/**
* The default logging function used by RCTLogXX.
*/
extern RCTLogFunction RCTDefaultLogFunction;
/**
* These methods get and set the current logging threshold. This is the level
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
* RCTLogLevelError for production.
*/
void RCTSetLogThreshold(RCTLogLevel threshold);
RCTLogLevel RCTGetLogThreshold(void);
/**
* These methods get and set the current logging function called by the RCTLogXX
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
void RCTSetLogFunction(RCTLogFunction logFunction);
RCTLogFunction RCTGetLogFunction(void);
/**
* This appends additional code to the existing log function, without replacing
* the existing functionality. Useful if you just want to forward logs to an
* extra service without changing the default behavior.
*/
void RCTAddLogFunction(RCTLogFunction logFunction);
/**
* This method adds a conditional prefix to any messages logged within the scope
* of the passed block. This is useful for adding additional context to log
* messages. The block will be performed synchronously on the current thread.
*/
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
/**
* Private logging functions - ignore these.
*/
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
#define _RCTLog(lvl, ...) do { \
NSString *msg = [NSString stringWithFormat:__VA_ARGS__]; \
if (lvl >= RCTLOG_FATAL_LEVEL) { \
BOOL fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
if ([msg rangeOfString:RCTLOG_FATAL_REGEX options:NSRegularExpressionSearch].length) { \
fail = NO; \
} \
} \
RCTCAssert(!fail, @"FATAL ERROR: %@", msg); \
}\
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
} while (0)
/**
* Legacy injection function - don't use this.
*/
void RCTInjectLogFunction(void (^)(NSString *msg));
/**
* Logging macros. Use these to log information, warnings and errors in your
* own code.
*/
#define RCTLog(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
#define RCTLogInfo(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
#define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__)
#define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__)
#define RCTLogMustFix(...) _RCTLog(RCTLogLevelMustFix, __VA_ARGS__)
#ifdef __cplusplus
}

View File

@ -9,85 +9,218 @@
#import "RCTLog.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTRedBox.h"
__unsafe_unretained NSString *RCTLogLevels[] = {
@"info",
@"warn",
@"error",
@"mustfix"
@interface RCTBridge (Logging)
+ (void)logMessage:(NSString *)message level:(NSString *)level;
@end
static NSString *const RCTLogPrefixStack = @"RCTLogPrefixStack";
const char *RCTLogLevels[] = {
"info",
"warn",
"error",
"mustfix"
};
static void (^RCTInjectedLogFunction)(NSString *msg);
static RCTLogFunction RCTCurrentLogFunction;
static RCTLogLevel RCTCurrentLogThreshold;
void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) {
RCTInjectedLogFunction = logFunction;
}
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName)
void RCTLogSetup(void) __attribute__((constructor));
void RCTLogSetup()
{
NSString *threadName = [[NSThread currentThread] name];
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
if (!threadName || threadName.length <= 0) {
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
}
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
}
RCTCurrentLogFunction = RCTDefaultLogFunction;
// TODO (#5906496): Does this need to be tied to RCTBridge?
NSString *RCTLogObjects(NSArray *objects, NSString *level)
{
NSString *str = objects[0];
#if TARGET_IPHONE_SIMULATOR
if ([RCTBridge hasValidJSExecutor]) {
fprintf(stderr, "%s\n", [str UTF8String]); // don't print timestamps and other junk
[RCTBridge log:objects level:level];
} else
#if DEBUG
RCTCurrentLogThreshold = RCTLogLevelInfo - 1;
#else
RCTCurrentLogThreshold = RCTLogLevelError;
#endif
{
// Print normal errors with timestamps when not in simulator.
// Non errors are already compiled out above, so log as error here.
if (RCTInjectedLogFunction) {
RCTInjectedLogFunction(str);
} else {
NSLog(@">\n %@", str);
}
}
return str;
}
// Returns array of objects. First arg is a simple string to print, remaining args
// are objects to pass through to the debugger so they are inspectable in the console.
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
RCTLogFunction RCTDefaultLogFunction = ^(
RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message
)
{
va_list args;
va_start(args, format);
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
NSString *log = RCTFormatLog(
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
);
fprintf(stderr, "%s\n", log.UTF8String);
};
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
BOOL valid = YES;
for (int i = 0; i < formatParts.count; i++) {
if (i == 0) { // first part is always a string
[objects addObject:formatParts[i]];
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,
NSThread *thread,
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]];
}
[log appendString:@"[react]"];
if (level) {
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
}
if (thread) {
NSString *threadName = thread.name;
if (threadName.length == 0) {
#if DEBUG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
#pragma clang diagnostic pop
#else
threadName = [NSString stringWithFormat:@"%p", thread];
#endif
}
[log appendFormat:@"[tid:%@]", threadName];
}
if (fileName) {
fileName = [fileName lastPathComponent];
if (lineNumber) {
[log appendFormat:@"[%@:%@]", fileName, lineNumber];
} else {
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
id obj = va_arg(args, id);
[objects addObject:obj ?: @"null"];
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
} else {
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
valid = NO;
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
}
[log appendFormat:@"[%@]", fileName];
}
}
va_end(args);
va_start(args, format);
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
va_end(args);
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
[objectsOut addObjectsFromArray:objects];
return objectsOut;
if (message) {
[log appendString:@" "];
[log appendString:message];
}
return log;
}
RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message))
{
return ^(RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message) {
logFunction(level, RCTFormatLog(
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
));
};
}
void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
{
if (RCTCurrentLogFunction && level >= RCTCurrentLogThreshold) {
// Get message
va_list args;
va_start(args, format);
__block 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 DEBUG
// Log to red box
if (level >= RCTLOG_REDBOX_LEVEL) {
[[RCTRedBox sharedInstance] showErrorMessage:message];
}
// Log to JS executor
if ([RCTBridge hasValidJSExecutor]) {
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
}
#endif
}
}
#pragma mark - Deprecated
void RCTInjectLogFunction(void (^logFunction)(NSString *msg))
{
RCTSetLogFunction(^(RCTLogLevel level,
NSString *fileName,
NSNumber *lineNumber,
NSString *message) {
if (level > RCTLogLevelError) {
// Use custom log function
NSString *loc = fileName ? [NSString stringWithFormat:@"[%@:%@] ", fileName, lineNumber] : @"";
logFunction([loc stringByAppendingString:message]);
} else if (RCTDefaultLogFunction && level >= RCTCurrentLogThreshold) {
// Use default logger
RCTDefaultLogFunction(level, fileName, lineNumber, message);
}
});
}

View File

@ -17,6 +17,9 @@ extern NSString *const RCTReloadViewsNotification;
@interface RCTRootView : UIView <RCTInvalidating>
/**
* - Designated initializer -
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER;
@ -39,6 +42,10 @@ extern NSString *const RCTReloadViewsNotification;
*/
@property (nonatomic, copy, readonly) NSString *moduleName;
/**
* The bridge used by the root view. Bridges can be shared between multiple
* root views, so you can use this property to initialize another RCTRootView.
*/
@property (nonatomic, strong, readonly) RCTBridge *bridge;
/**

View File

@ -17,7 +17,6 @@
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTSourceCode.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"

View File

@ -38,23 +38,18 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
if (!string) {
return JSValueMakeUndefined(context);
}
NSString *str = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
NSError *error = nil;
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
JSStringRelease(string);
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSString *modifiedString = [regex stringByReplacingMatchesInString:str options:0 range:NSMakeRange(0, [str length]) withTemplate:@"[$4$5] \t$2"];
error:NULL];
message = [regex stringByReplacingMatchesInString:message
options:0
range:(NSRange){0, message.length}
withTemplate:@"[$4$5] \t$2"];
modifiedString = [@"RCTJSLog> " stringByAppendingString:modifiedString];
#if TARGET_IPHONE_SIMULATOR
fprintf(stderr, "%s\n", [modifiedString UTF8String]); // don't print timestamps and other junk
#else
// Print normal errors with timestamps to files when not in simulator.
RCTLogObjects(@[modifiedString], @"log");
#endif
JSStringRelease(string);
_RCTLogFormat(0, NULL, -1, @"%@", message);
}
return JSValueMakeUndefined(context);

View File

@ -9,6 +9,7 @@
#import "RCTAlertManager.h"
#import "RCTAssert.h"
#import "RCTLog.h"
@interface RCTAlertManager() <UIAlertViewDelegate>

View File

@ -197,6 +197,11 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
@synthesize bridge = _bridge;
/**
* Declared in RCTBridge.
*/
extern NSString *RCTBridgeModuleNameForClass(Class cls);
/**
* This function derives the view name automatically
* from the module name.
@ -334,7 +339,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
dispatch_async(_bridge.shadowQueue, ^{
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag %@", reactTag);
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
rootShadowView.frame = frame;
[rootShadowView updateLayout];
@ -672,7 +677,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
}
}
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
static BOOL RCTCallPropertySetter(NSString *key, SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
{
// TODO: cache respondsToSelector tests
if ([manager respondsToSelector:setter]) {
@ -681,7 +686,25 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView,
value = nil;
}
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
void (^block)() = ^{
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
};
#if DEBUG
NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([manager class]));
NSString *logPrefix = [NSString stringWithFormat:
@"Error setting property '%@' of %@ with tag #%@: ",
key, viewName, [view reactTag]];
RCTPerformBlockWithLogPrefix(block, logPrefix);
#else
block();
#endif
return YES;
}
return NO;
@ -693,7 +716,7 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view,
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
RCTCallPropertySetter(setter, obj, view, defaultView, manager);
RCTCallPropertySetter(key, setter, obj, view, defaultView, manager);
}];
}
@ -704,7 +727,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
RCTCallPropertySetter(setter, obj, shadowView, defaultView, manager);
RCTCallPropertySetter(key, setter, obj, shadowView, defaultView, manager);
}];
@ -875,7 +898,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view) {
RCTLogError(@"measure cannot find view with tag %@", reactTag);
RCTLogError(@"measure cannot find view with tag #%@", reactTag);
return;
}
CGRect frame = view.frame;
@ -1039,7 +1062,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
} else {
RCTCAssert(NO, @"Tag %@ does not conform to RCTScrollableProtocol", reactTag);
RCTCAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
}
} else {
uiManager.mainScrollView = nil;
@ -1056,7 +1079,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:YES];
} else {
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
}
}];
}
@ -1070,7 +1093,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:NO];
} else {
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
}
}];
}
@ -1084,7 +1107,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES];
} else {
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
}
}];
}

View File

@ -72,7 +72,7 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle)
UIView *view = viewRegistry[reactTag];
if (!view) {
RCTLogError(@"Cannot find view with tag %@", reactTag);
RCTLogError(@"Cannot find view with tag #%@", reactTag);
return;
}