| /* |
| * 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 |