级别: ★☆☆☆☆
标签:「iOS」「Log」「CocoaLumberjack」
作者: dac_1033
审校: QiShare团队
在开发过程中打印日志功能是必不可少的,日志的处理主要分为以下几种:
- Debug/Release模式下日志的处理;
- 将日志记录在本地文件,并上传服务器;
- 由业务App控制是否开启日志。
1. Debug/Release模式下日志的处理
第一点在开发业务App时最常使用,一行语句就搞定:
#ifdef DEBUG
#define DLog(fmt, ...) {NSLog((@"%s [Line %d]\n " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);}
#else
#define DLog(...)
#endif
如果图方便,可以将以上代码写到prefix.pch文件中,在需要输出log的地方调用DLog(...),即可在Debug/Release模式切换时,自动开启/关闭打印log。
2. 将日志记录在本地文件
将日志记录在本地文件常用三方库CocoaLumberjack来处理,我们通过这个库可以将日志输出至控制台,也可以写在本地文件中,我们还可以设置输出日志等级。具体CocoaLumberjack的使用方式可以参考iOS开发之日志框架CocoaLumberjack的配置与使用详解
3. 兼容CocoaLumberjack的同时,控制日志的开关
这个场景经常出现在framework开发过程中,我们开发一个framework,其中需要自己输出日志到本地文件,另外宿主App联调时,也常常需要主动控制log的开和关。我们定义了一个LogManager类,LogManager.h代码如下:
@import Foundation;
//#define LUMBERJACK @"LUMBERJACK"
#ifdef LUMBERJACK
#define LOG_LEVEL_DEF ddLogLevelf
#import <CocoaLumberjack/CocoaLumberjack.h>
#else /* LUMBERJACK */
typedef NS_OPTIONS(NSUInteger, DDLogFlag){
// 0...00001 DDLogFlagError
DDLogFlagError = (1 << 0),
// 0...00010 DDLogFlagWarning
DDLogFlagWarning = (1 << 1),
// 0...00100 DDLogFlagInfo
DDLogFlagInfo = (1 << 2),
// 0...01000 DDLogFlagDebug
DDLogFlagDebug = (1 << 3),
// 0...10000 DDLogFlagVerbose
DDLogFlagVerbose = (1 << 4)
};
typedef NS_ENUM(NSUInteger, DDLogLevel){
DDLogLevelOff = 0,
// Error logs only
DDLogLevelError = (DDLogFlagError),
// Error and warning logs
DDLogLevelWarning = (DDLogLevelError | DDLogFlagWarning),
// Error, warning and info logs
DDLogLevelInfo = (DDLogLevelWarning | DDLogFlagInfo),
// Error, warning, info and debug logs
DDLogLevelDebug = (DDLogLevelInfo | DDLogFlagDebug),
// Error, warning, info, debug and verbose logs
DDLogLevelVerbose = (DDLogLevelDebug | DDLogFlagVerbose),
// All logs (1...11111)
DDLogLevelAll = NSUIntegerMax
};
#define CustomLog(format,...) [MQTTLog customLogWithFormatString:[NSString stringWithFormat:format, ##__VA_ARGS__]]
#define DDLogVerbose if (ddLogLevel & DDLogFlagVerbose) CustomLog
#define DDLogDebug if (ddLogLevel & DDLogFlagDebug) CustomLog
#define DDLogWarn if (ddLogLevel & DDLogFlagWarning) CustomLog
#define DDLogInfo if (ddLogLevel & DDLogFlagInfo) CustomLog
#define DDLogError if (ddLogLevel & DDLogFlagError) CustomLog
#endif /* LUMBERJACK */
extern DDLogLevel ddLogLevel;
extern BOOL logEnable;
/** MQTTLog lets you define the log level for MQTTClient
* independently of using CocoaLumberjack
*/
@interface MQTTLog: NSObject
// default one is DDLogLevelVerbose
+ (void)setLogLevel:(DDLogLevel)logLevel;
+ (void)setLogEnable:(BOOL)logEnable;
+ (void)customLogWithFormatString:(NSString *)formatString;
@end
LogManager.m代码如下:
#import "MQTTLog.h"
#ifdef LUMBERJACK
// 自定义日志文件里 log的格式
@interface DDLogFileFormatter : NSObject <DDLogFormatter>
{
NSDateFormatter *dateFormatter;
}
- (id)init;
- (instancetype)initWithDateFormatter:(NSDateFormatter *)dateFormatter;
@end
@implementation DDLogFileFormatter
- (id)init {
return [self initWithDateFormatter:nil];
}
- (instancetype)initWithDateFormatter:(NSDateFormatter *)aDateFormatter {
if ((self = [super init])) {
if (aDateFormatter) {
dateFormatter = aDateFormatter;
} else {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
[dateFormatter setDateFormat:@"yyyyMMdd-HHmmss.SSS"];
}
}
return self;
}
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
NSString *dateAndTime = [dateFormatter stringFromDate:(logMessage.timestamp)];
NSString* level;
switch(logMessage.flag) {
case DDLogFlagError:
level=@"E";
break;
case DDLogFlagWarning:
level=@"W";
break;
case DDLogFlagInfo:
level=@"I";
break;
case DDLogFlagDebug:
level=@"D";
break;
case DDLogFlagVerbose:
level=@"V";
break;
default:
level=@"U";
break;
}
NSString *formatStr = [NSString stringWithFormat:@"%@ %@ %d.%@[%@:%ld:%@] %@", level, dateAndTime, getpid(),logMessage.threadID, logMessage.fileName, (unsigned long)logMessage.line, logMessage.function, logMessage.message];
return formatStr;
}
@end
#endif
BOOL mqttLogEnable = NO;
@implementation MQTTLog
DDLogLevel ddLogLevel = DDLogLevelVerbose;
+ (void)setLogLevel:(DDLogLevel)logLevel {
ddLogLevel = logLevel;
}
+ (void)setLogEnable:(BOOL)logEnable {
mqttLogEnable = logEnable;
if (logEnable) {
[MQTTLog startDDLogger];
}
}
+ (void)startDDLogger {
#ifdef LUMBERJACK
#ifdef DEBUG
//[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
[DDLog addLogger:[DDOSLogger sharedInstance]]; // ASL = Apple System Logs
#endif
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
[dateFormatter setDateFormat:@"yyyyMMdd-HHmmss.SSS"];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *folderPath = [NSString stringWithFormat:@"%@/QiTestSDK/QiTestSDKLogs", path[0]];
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
}
DDLogFileManagerDefault *fileManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:folderPath];
DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:fileManager];
fileLogger.rollingFrequency = 60 * 60 * 24 * 3; // 3 days rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 5;
fileLogger.logFileManager.logFilesDiskQuota = 50 * 1024 * 1024; // 50M
fileLogger.maximumFileSize = 10 * 1024 * 1024;
fileLogger.logFormatter = [[DDLogFileFormatter alloc] init];
[DDLog addLogger:fileLogger];
#endif
}
+ (void)customLogWithFormatString:(NSString *)formatString {
if (mqttLogEnable) {// 开启了Log
NSLog(@"%@", formatString);
}
}
@end
这样通过宏定义,既能兼容CocoaLumberjack,也能实现log的开关。如果想美观一点实现CocoaLumberjack的“挂载”,请参考iOS App中实现可拆卸一个framework的两种方式。
推荐文章:
iOS App中可拆卸一个framework的两种方式
自定义WKWebView显示内容(一)
Swift 5.1 (6) - 函数
Swift 5.1 (5) - 控制流
Xcode11 新建工程中的SceneDelegate
iOS App启动优化(二)—— 使用“Time Profiler”工具监控App的启动耗时
iOS App启动优化(一)—— 了解App的启动流程
iOS WKWebView的基本使用
Swift 5.1 (4) - 集合类型