Files
Hippy/modules/ios/base/HippyLog.mm

209 lines
7.6 KiB
Plaintext

/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed 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 "HippyLog.h"
#include "footstone/logging.h"
#pragma mark NativeLog Methods
static NSString *const HippyLogFunctionStack = @"HippyLogFunctionStack";
const char *HippyLogLevels[] = {
"trace",
"info",
"warn",
"error",
"fatal",
};
#if HIPPY_DEBUG
HippyLogLevel HPDefaultLogThreshold = HippyLogLevelTrace;
#else
HippyLogLevel HPDefaultLogThreshold = HippyLogLevelInfo;
#endif
#if HIPPY_DEBUG
static HippyRedBoxFunction hippyCurrentRedBoxFunction;
void HippySetRedBoxFunction(HippyRedBoxFunction redBoxFunction) {
hippyCurrentRedBoxFunction = redBoxFunction;
}
#endif /* HIPPY_DEBUG */
static HippyLogFunction HPCurrentLogFunction;
static HippyLogLevel HPCurrentLogThreshold = HPDefaultLogThreshold;
HippyLogLevel HippyGetLogThreshold() {
return HPCurrentLogThreshold;
}
void HippySetLogThreshold(HippyLogLevel threshold) {
HPCurrentLogThreshold = threshold;
}
HippyLogFunction HippyDefaultLogFunction = ^(HippyLogLevel level, __unused HippyLogSource source,
NSString *fileName, NSNumber *lineNumber, NSString *message) {
NSString *log = HippyFormatLog([NSDate date], level, fileName, lineNumber, message);
fprintf(stderr, "%s\n", log.UTF8String);
fflush(stderr);
};
void HippySetLogFunction(HippyLogFunction logFunction) {
HPCurrentLogFunction = logFunction;
}
HippyLogFunction HippyGetLogFunction() {
if (!HPCurrentLogFunction) {
HPCurrentLogFunction = HippyDefaultLogFunction;
}
return HPCurrentLogFunction;
}
void HippyAddLogFunction(HippyLogFunction logFunction) {
HippyLogFunction existing = HippyGetLogFunction();
if (existing) {
HippySetLogFunction(^(HippyLogLevel level, HippyLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
existing(level, source, fileName, lineNumber, message);
logFunction(level, source, fileName, lineNumber, message);
});
} else {
HippySetLogFunction(logFunction);
}
}
/**
* returns the topmost stacked log function for the current thread, which
* may not be the same as the current value of HippyCurrentLogFunction.
*/
static HippyLogFunction HippyGetLocalLogFunction() {
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSArray<HippyLogFunction> *functionStack = threadDictionary[HippyLogFunctionStack];
HippyLogFunction logFunction = functionStack.lastObject;
if (logFunction) {
return logFunction;
}
return HippyGetLogFunction();
}
void HippyPerformBlockWithLogFunction(void (^block)(void), HippyLogFunction logFunction) {
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray<HippyLogFunction> *functionStack = threadDictionary[HippyLogFunctionStack];
if (!functionStack) {
functionStack = [NSMutableArray new];
threadDictionary[HippyLogFunctionStack] = functionStack;
}
[functionStack addObject:logFunction];
block();
[functionStack removeLastObject];
}
void HippyPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix) {
HippyLogFunction logFunction = HippyGetLocalLogFunction();
if (logFunction) {
HippyPerformBlockWithLogFunction(block,
^(HippyLogLevel level, HippyLogSource source,
NSString *fileName, NSNumber *lineNumber, NSString *message) {
logFunction(level, source, fileName, lineNumber, [prefix stringByAppendingString:message]);
});
}
}
static NSString *currentThreadName(void) {
NSThread *thread = [NSThread currentThread];
NSString *threadName = [NSThread isMainThread] ? @"main" : thread.name;
if (threadName.length == 0) {
const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
if (label && strlen(label) > 0) {
threadName = @(label);
} else {
threadName = [NSString stringWithFormat:@"%p", thread];
}
}
return threadName;
}
NSString *HippyFormatLog(NSDate *timestamp, HippyLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
NSMutableString *log = [NSMutableString new];
if (timestamp) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS ";
});
[log appendString:[formatter stringFromDate:timestamp]];
}
[log appendFormat:@"[%s]", HippyLogLevels[level]];
[log appendFormat:@"[%@]", currentThreadName()];
if (fileName) {
fileName = fileName.lastPathComponent;
[log appendFormat:@"[%@:%@]", fileName, lineNumber];
}
if (message) {
[log appendString:@" "];
[log appendString:message];
}
return log;
}
void HippyLogNativeInternal(HippyLogLevel level, const char *fileName, int lineNumber, NSString *format, ...) {
HippyLogFunction logFunction = HippyGetLocalLogFunction();
BOOL log = HIPPY_DEBUG || (logFunction != nil);
if (log && level >= HippyGetLogThreshold()) {
// Get message
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Call log function
if (logFunction) {
logFunction(level, HippyLogSourceNative, fileName ? @(fileName) : nil, @(lineNumber), message);
}
#if HIPPY_DEBUG
if (level >= HIPPYLOG_REDBOX_LEVEL) {
NSArray<NSString *> *stackSymbols = [NSThread callStackSymbols];
NSMutableArray<NSDictionary *> *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)];
[stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused 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 && fileName) {
NSString *file = [@(fileName) componentsSeparatedByString:@"/"].lastObject;
[stack addObject:@{@"methodName": methodName, @"file": file, @"lineNumber": @(lineNumber)}];
} else {
[stack addObject:@ { @"methodName": methodName }];
}
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
if (hippyCurrentRedBoxFunction) {
hippyCurrentRedBoxFunction(message, stack);
}
});
}
#endif
}
}