blob: e782f221678f79e7c28dc7a07cc1c4c3a26e0840 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXLog.h"
#import "WXUtility.h"
#import "WXAssert.h"
#import "WXSDKManager.h"
// Xcode does NOT natively support colors in the Xcode debugging console.
// You'll need to install the XcodeColors plugin to see colors in the Xcode console.
// https://github.com/robbiehanson/XcodeColors
//
// The following is documentation from the XcodeColors project:
//
//
// How to apply color formatting to your log statements:
//
// To set the foreground color:
// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255.
//
// To set the background color:
// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36.
//
// To reset the foreground color (to default value):
// Insert the ESCAPE_SEQ into your string, followed by "fg;"
//
// To reset the background color (to default value):
// Insert the ESCAPE_SEQ into your string, followed by "bg;"
//
// To reset the foreground and background color (to default values) in one operation:
// Insert the ESCAPE_SEQ into your string, followed by ";"
#ifdef DEBUG
static const WXLogLevel defaultLogLevel = WXLogLevelLog;
#else
static const WXLogLevel defaultLogLevel = WXLogLevelWarning;
#endif
static id<WXLogProtocol> _externalLog;
static BOOL _logToWebSocket = NO;
@interface WXSafeLog : NSObject
/**
* @abstract This is a safer but slower log to resolve DEBUG log crash when there is a retain cycle in the object
*/
+ (NSString *)getLogMessage:(NSString *)format arguments:(va_list)args;
@end
@implementation WXLog
{
WXLogLevel _logLevel;
}
+ (instancetype)sharedInstance
{
static WXLog *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
if (NSClassFromString(@"WXDebugLoggerBridge")) {
_logToWebSocket = YES;
}
_sharedInstance = [[self alloc] init];
_sharedInstance->_logLevel = defaultLogLevel;
});
return _sharedInstance;
}
+ (void)setLogLevel:(WXLogLevel)level
{
((WXLog*)[self sharedInstance])->_logLevel = level;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
Class propertyClass = NSClassFromString(@"WXTracingViewControllerManager");
SEL sel = NSSelectorFromString(@"loadTracingView");
if(propertyClass && [propertyClass respondsToSelector:sel]){
[propertyClass performSelector:sel];
}
#pragma clang diagnostic pop
}
+ (WXLogLevel)logLevel
{
return ((WXLog*)[self sharedInstance])->_logLevel;
}
+ (NSString *)logLevelString
{
NSDictionary *logLevelEnumToString =
@{
@(WXLogLevelOff) : @"off",
@(WXLogLevelError) : @"error",
@(WXLogLevelWarning) : @"warn",
@(WXLogLevelInfo) : @"info",
@(WXLogLevelLog) : @"log",
@(WXLogLevelDebug) : @"debug",
@(WXLogLevelAll) : @"debug"
};
return [logLevelEnumToString objectForKey:@([self logLevel])];
}
+ (void)setLogLevelString:(NSString *)levelString
{
NSDictionary *logLevelStringToEnum =
@{
@"all" : @(WXLogLevelAll),
@"error" : @(WXLogLevelError),
@"warn" : @(WXLogLevelWarning),
@"info" : @(WXLogLevelInfo),
@"debug" : @(WXLogLevelDebug),
@"log" : @(WXLogLevelLog)
};
[self setLogLevel:[logLevelStringToEnum[levelString] unsignedIntegerValue]];
}
+ (void)log:(WXLogFlag)flag file:(const char *)fileName line:(NSUInteger)line message:(NSString *)message
{
NSString *flagString;
switch (flag) {
case WXLogFlagError:
flagString = @"error";
break;
case WXLogFlagWarning:
flagString = @"warn";
break;
case WXLogFlagDebug:
flagString = @"debug";
break;
case WXLogFlagLog:
flagString = @"log";
break;
default:
flagString = @"info";
break;
}
NSString *logMessage = [NSString stringWithFormat:@"<Weex>[%@]%s:%lu, %@", flagString, fileName, (unsigned long)line, message];
if ([_externalLog logLevel] & flag) {
[_externalLog log:flag message:logMessage];
}
if (_logToWebSocket) {
[[WXSDKManager bridgeMgr] logToWebSocket:flagString message:message];
}
if ([WXLog logLevel] & flag) {
NSLog(@"%@", logMessage);
}
}
+ (void)devLog:(WXLogFlag)flag file:(const char *)fileName line:(NSUInteger)line format:(NSString *)format, ... {
if ([WXLog logLevel] & flag || [_externalLog logLevel] & flag) {
if (!format) {
return;
}
NSString *flagString = @"log";
switch (flag) {
case WXLogFlagError:
flagString = @"error";
break;
case WXLogFlagWarning:
flagString = @"warn";
break;
case WXLogFlagDebug:
flagString = @"debug";
break;
case WXLogFlagLog:
flagString = @"log";
break;
default:
flagString = @"info";
break;
}
va_list args;
va_start(args, format);
NSString *message = @"";
if (flag == WXLogFlagDebug) {
message = [WXSafeLog getLogMessage:format arguments:args];
} else {
message = [[NSString alloc] initWithFormat:format arguments:args];
}
va_end(args);
Class WXLogClass = NSClassFromString(@"WXDebugger");
if (WXLogClass) {
NSArray *messageAry = [NSArray arrayWithObjects:message, nil];
SEL selector = NSSelectorFromString(@"coutLogWithLevel:arguments:");
NSMethodSignature *methodSignature = [WXLogClass instanceMethodSignatureForSelector:selector];
if (methodSignature == nil) {
NSString *info = [NSString stringWithFormat:@"%@ not found", NSStringFromSelector(selector)];
[NSException raise:@"Method invocation appears abnormal" format:info, nil];
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:[WXLogClass alloc]];
[invocation setSelector:selector];
[invocation setArgument:&flagString atIndex:2];
[invocation setArgument:&messageAry atIndex:3];
[invocation invoke];
}
[self log:flag file:fileName line:line message:message];
}
}
#pragma mark - External Log
+ (void)registerExternalLog:(id<WXLogProtocol>)externalLog
{
_externalLog = externalLog;
}
+ (id<WXLogProtocol>)getCurrentExternalLog
{
return _externalLog;
}
@end
#pragma mark - WXSafeLog
static void dealWithValue(NSMutableString *result, id value, NSMutableSet *outSet);
static NSUInteger getParamCount(NSString *format, NSMutableDictionary *outDict)
{
static NSMutableSet *possibleTwoSet = nil;
static NSMutableSet *possibleThreeSet = nil;
static NSMutableSet *possibleTwo = nil;
static NSMutableSet *possibleThree = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *longArray = @[@"ld"];
NSArray *longLongArray = @[@"lld"];
NSDictionary *coolDict = @{@"long":longArray, @"long long":longLongArray};
possibleTwoSet = [NSMutableSet set];
possibleThreeSet = [NSMutableSet set];
possibleTwo = [NSMutableSet set];
possibleThree = [NSMutableSet set];
for (NSString * key in coolDict.allKeys) {
NSArray *array = coolDict[key];
for (NSString * value in array) {
if (value.length == 2) {
[possibleTwoSet addObject:value];
NSString *sub = [value substringWithRange:NSMakeRange(0,1)];
if (![possibleTwo containsObject:sub]) {
[possibleTwo addObject:sub];
}
} else if (value.length == 3) {
[possibleThreeSet addObject:value];
NSString *sub = [value substringWithRange:NSMakeRange(0,2)];
if (![possibleThree containsObject:sub]) {
[possibleThree addObject:sub];
}
}
}
}
});
NSUInteger paramCount = 0;
NSUInteger formatLength = [format length];
NSRange searchRange = NSMakeRange(0, formatLength);
NSRange paramRange = [format rangeOfString:@"%" options:0 range:searchRange];
while (paramRange.location != NSNotFound)
{
NSString *subString = @" ";
NSUInteger location = paramRange.location;
do {
location ++;
subString = [format substringWithRange:NSMakeRange(location, 1)];
} while (([subString compare:@"0"] != NSOrderedAscending && [subString compare:@"9"] != NSOrderedDescending) || [subString compare:@"."] == NSOrderedSame);
if ([possibleTwo containsObject:subString]) {
NSString *subString3 = [format substringWithRange:NSMakeRange(location, 2)];
if ([possibleThree containsObject:subString3]) {
NSString *subString4 = [format substringWithRange:NSMakeRange(location, 3)];
if ([possibleThreeSet containsObject:subString4]) {
subString = subString4;
}
} else {
NSString *subString2 = [format substringWithRange:NSMakeRange(location, 2)];
if ([possibleTwoSet containsObject:subString2]) {
subString = subString2;
}
}
}
[outDict setObject:subString forKey:@(paramCount)];
paramCount++;
searchRange.location = paramRange.location + 1;
searchRange.length = formatLength - searchRange.location;
paramRange = [format rangeOfString:@"%" options:0 range:searchRange];
}
return paramCount;
}
static NSString *dealWithDictionary(NSDictionary *dict, NSMutableSet *outSet)
{
NSMutableString *result = [NSMutableString new];
[result appendString:@" { "];
int i = 0;
for (id key in dict.allKeys) {
if ([key isKindOfClass:[NSDictionary class]] || [key isKindOfClass:[NSArray class]] || [key isKindOfClass:[NSSet class]]) {
[result appendString:@"(invalid Dictionary key) : ( )"];
} else {
[result appendString:[NSString stringWithFormat:@"%@ : ", key]];
id value = [dict objectForKey:key];
NSNumber *pointerValue = [NSNumber numberWithLong:(long)value];
if ([outSet containsObject:pointerValue]) {
[result appendString:@"(Found Retain Cycle!!!)"];
} else {
dealWithValue(result, value, outSet);
}
}
i++;
if (i < dict.allKeys.count) {
[result appendString:@" , "];
}
}
[result appendString:@" } "];
return result;
}
static NSString *dealWithArray(NSArray *array, NSMutableSet *outSet)
{
NSMutableString *result = [NSMutableString new];
[result appendString:@" [ "];
int i = 0;
for (id value in array) {
NSNumber *pointerValue = [NSNumber numberWithLong:(long)value];
if ([outSet containsObject:pointerValue]) {
[result appendString:@"(Found Retain Cycle!!!)"];
} else {
dealWithValue(result, value, outSet);
}
i++;
if (i < array.count) {
[result appendString:@" , "];
}
}
[result appendString:@" ] "];
return result;
}
static NSString *dealWithSet(NSSet *set, NSMutableSet *outSet)
{
NSMutableString *result = [NSMutableString new];
[result appendString:@" ( "];
int i = 0;
for (id value in set) {
NSNumber *pointerValue = [NSNumber numberWithLong:(long)value];
if ([outSet containsObject:pointerValue]) {
[result appendString:@"(Found Retain Cycle!!!)"];
} else {
dealWithValue(result, value, outSet);
}
i++;
if (i < set.count) {
[result appendString:@" , "];
}
}
[result appendString:@" ) "];
return result;
}
static void dealWithValue(NSMutableString *result, id value, NSMutableSet *outSet)
{
NSNumber *pointerValue = [NSNumber numberWithLong:(long)value];
if (pointerValue.longValue > 100000) {
[outSet addObject:pointerValue];
}
if ([value isKindOfClass:[NSArray class]]) {
NSString *tempString = dealWithArray((NSArray *)value, outSet);
[result appendString:tempString];
} else if ([value isKindOfClass:[NSDictionary class]]) {
[result appendString:dealWithDictionary((NSDictionary *)value, outSet)];
} else if ([value isKindOfClass:[NSSet class]]) {
[result appendString:dealWithSet((NSSet *)value, outSet)];
} else {
[result appendString:[NSString stringWithFormat:@"%@", value]];
}
}
@implementation WXSafeLog
+ (NSString *)getLogMessage:(NSString *)format arguments:(va_list)args
{
NSMutableString *mutableFormat = [NSMutableString stringWithString:format];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSUInteger count = getParamCount(format, dict);
NSMutableArray<NSString *> *replacedDict = [NSMutableArray array];
for (NSUInteger i = 0; i < count; i++) {
if ([dict[@(i)] isEqualToString:@"@"]) {
NSMutableSet *outSet = [NSMutableSet set];
__unsafe_unretained id obj = va_arg(args, id);
NSString *output = @"";
if (obj) {
if ([obj isKindOfClass:[NSDictionary class]]) {
output = dealWithDictionary((NSDictionary *)obj, outSet);
} else if ([obj isKindOfClass:[NSArray class]]) {
output = dealWithArray((NSArray *)obj, outSet);
} else if ([obj isKindOfClass:[NSSet class]]) {
output = dealWithSet((NSSet *)obj, outSet);
} else {
output = [NSString stringWithFormat:@"%@", obj];
}
}
[replacedDict addObject:output];
} else {
NSString *logFormat = dict[@(i)];
if (logFormat.length == 1) {
#define LEN 2
char tempChar[LEN];
memset(tempChar, 0, LEN);
[logFormat getCString:tempChar maxLength:LEN encoding:NSASCIIStringEncoding];
switch(tempChar[0]) {
case '%':
case 'c':
//Suppress "Second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int'" warning
// va_arg(args, char);
va_arg(args, int);
break;
case 'd':
case 'D':
va_arg(args, int);
break;
case 'f':
case 'F':
va_arg(args, double);
break;
case 'C':
// va_arg(args, unichar);
va_arg(args, int);
break;
case 's':
va_arg(args, char *);
break;
case 'S':
va_arg(args, unichar *);
break;
case 'p':
va_arg(args, void *);
break;
default:
va_arg(args, void *);
break;
}
} else {
static NSDictionary *map;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = @{
@"ld":@"long", \
@"lld":@"long long", \
};
});
NSString *type = map[dict[@(i)]];
if ([type isEqualToString:@"long"]) {
va_arg(args, long);
} else if ([type isEqualToString:@"long long"]) {
va_arg(args, long long);
}
}
}
}
NSString *prefix = @"#$~";
NSString *suffix = @"~$#";
NSString *replace = [NSString stringWithFormat:@"%@%%p%@", prefix, suffix];
[mutableFormat replaceOccurrencesOfString:@"%@" withString:replace options:NSLiteralSearch range:NSMakeRange(0, mutableFormat.length)];
NSMutableString *result = [[NSMutableString alloc] initWithFormat:mutableFormat arguments:args];
int j = 0;
NSRange range, range1, range2;
range1 = [result rangeOfString:prefix options:NSLiteralSearch];
range2 = [result rangeOfString:suffix options:NSLiteralSearch];
range = NSMakeRange(range1.location, range2.location+range2.length - range1.location);
while(range1.length>0 && range2.length>0) {
[result replaceCharactersInRange:range withString:replacedDict[j++]];
range1 = [result rangeOfString:prefix options:NSLiteralSearch];
range2 = [result rangeOfString:suffix options:NSLiteralSearch];
range = NSMakeRange(range1.location, range2.location+range2.length - range1.location);
}
return result;
}
@end