Mac开发-日志保存过滤以及上传
程序员文章站
2023-12-28 10:43:28
...
对于mac开发者来说,进行日志收集和上传尤为重要,没有日志就无法进行分析问题。通常来说我们会将日志文件.log存在
~/library/logs/YourAppName
的目录下,大部分app都是这样放置的,然后缓存文件存在~/library/Caches/YourAppName
目录下。
简单的日志存储
使用stdout重定向来将控制台的日志写入日志文件.log中,从而达到日志存储的目的
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
通常来说,日志文件名称需要加上日志以及app名等相关信息,以便于筛选。
- (void)redirectNSlogToDocumentFolder{
//这里判断是否连接xcode,如果连接xcode我们更希望是输出到控制台,这样我们能更能直接进行调试
#if DEBUG
#else
if (isatty(STDOUT_FILENO)){
NSLog(@"Appdelegate redirectNSlogToDocumentFolder failed : connected xcode mode");
return;
}
#endif
NSString *genseePath = [self mylogPath];
NSFileManager* manager = [NSFileManager defaultManager];
[manager createDirectoryAtPath:genseePath withIntermediateDirectories:NO attributes:NULL error:NULL];
NSString* bundleName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
NSString* bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
//这里使用的日期格式为 yyyyMMdd-HHmmss
[dateFormatter setDateFormat:@"yyyyMMdd-HHmmss"];
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@_%@_%@.log",bundleName,bundleID,dateString];
_logPath = fileName;
NSString *logFilePath = [genseePath stringByAppendingPathComponent:fileName];
NSLog(@"[Appdelegate] redirectNSlogToDocumentFolder : %@",logFilePath);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
//删除过期的日志信息
[self clearUnusedlog];
}
- (NSString *)mylogPath {
NSArray *array = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *logsPath = [[array objectAtIndex:0] stringByAppendingPathComponent:@"Logs"];
//这里webcast为app名称
NSString *genseePath = [logsPath stringByAppendingPathComponent:@"Webcast"];
return genseePath;
}
这里重定向的不好之处就是无法同时进行调试和日志记录,也无法选择只输出部分日志信息,还需要进行对NSLog宏定义。
其实有很多日志框架就解决了这些问题。 CocoaLumberjack 算是比较突出的一个
日志删除
日志文件没有上限限制,所以每次我们需要删除很久远的日志,可以是3天前,或者30天前的日志。避免不必要的空间浪费。
- (void)clearUnusedlog {
NSString *genseePath = [self mylogPath];
NSArray *tempArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:genseePath error:nil];
for (NSString* filename in tempArray) {
NSString *filetype = filename.pathExtension;
if ([filetype isEqualToString:@"log"]){
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[0-9]{8}-[0-9]{6}" options:NSRegularExpressionCaseInsensitive error:&error];
NSArray* matches = [regex matchesInString:filename options:0
range:NSMakeRange(0, [filename length])];
if (matches.count <= 0) continue;
for (NSInteger i = matches.count - 1; i >= 0; i--) {
NSTextCheckingResult *match = matches[i];
if (match.numberOfRanges > 0) {
NSRange strRange = [match rangeAtIndex:0];
NSString *dataStr = [filename substringWithRange:strRange];
if ([self isBeforeDays:30 dataStr:dataStr]) {
NSError *error;
BOOL result = [[NSFileManager defaultManager] removeItemAtPath:[genseePath stringByAppendingPathComponent:filename] error:&error];
if (result) {
NSLog(@"[Appdelegate] clearUnusedlog : %@",filename);
}else{
NSLog(@"[Appdelegate] clearUnusedlog Error : %@",error.description);
}
}
}
}
}
}
}
/**
判断是否是30天以前的文件
*/
- (BOOL)isBeforeDays:(int)nday dataStr:(NSString*)dataStr
{
NSDate *datenow = [NSDate date];
long nowTimeInterval=(long)[datenow timeIntervalSince1970];
NSTimeInterval secondsPernDay = 24 * 60 * 60*nday;
if (nday == 0) {
secondsPernDay = 60*5; //for debug test
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyyMMdd-HHmmss"];
NSDate* data=[dateFormatter dateFromString:dataStr];
long beforeNdayTimeInterval=(long)[data timeIntervalSince1970];
BOOL isNdayBefore= nowTimeInterval-beforeNdayTimeInterval>secondsPernDay;
return isNdayBefore;
}
关于日志删选
这里使用正则表达式进行匹配日志形式,也可以进行截取字符串指定后几位来进行。
或者我们也可以通过文件创建日期来进行筛选,使用NSCalendar
和NSDateComponents
来计算创建日期和现在的时间间隔。
NSDictionary* fileAttribs = [[NSFileManager defaultManager]attributesOfItemAtPath:filePath error:nil];
NSDate* date = [fileAttribs objectForKey:NSFileCreationDate];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
//这里的components决定了需要计算哪个单位的数值,若只需要天,则传入NSCalendarUnitDay即可
NSDateComponents *components = [calendar components:NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond
fromDate:date
toDate:[NSDate date]
options:0];
if(components.day <= 3){
[_zipArchive addFileToZip:filePath newname:str];
}
日志上传
日志上传我们通常要经过3个步骤
- 过滤,选择仅3天内的日志
- 压缩,打包zip文件
- 上传到服务器
如果只需要压缩一个文件夹,我们可以使用终端命令来操作,如何在代码中进行终端操作?
如果我们需要在一个包含所有杂乱的文件夹中选择文件进行压缩,则需要一个一个加入到zip中,使用终端固然可以达到效果。这里推荐使用ZipArchive三方库来实现更加简单。
对于创建的zip文件,发送之后通常需要删除,我们可以放在/tmp/
目录下,这样下次重启就会自动删除,还可以满足用户自己去查看自己发送的日志的case。
-(void)getSendData{
NSString *targetZipPath = @"/tmp/webcast_log.zip";
[_zipArchive CreateZipFile2:targetZipPath];
[self getTraingLogs];
[self postDataToService]; //这里即使用https协议将zip文件传输到服务器,使用系统的NSUrlSession即可
}
- (void)getTraingLogs{
NSArray *array1 = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *logsPath = [[array1 objectAtIndex:0] stringByAppendingPathComponent:@"Logs"];
NSString *path = [logsPath stringByAppendingPathComponent:@"Webcast"];
// NSString* path = @"/tmp/Training";
NSArray* array = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:path error:nil];
[self getNearThreeDaysLogs:array path:path];
}
- (void)getNearThreeDaysLogs:(NSArray*)array path:(NSString*)path{
for(NSString* str in array){
NSString* filePath = [path stringByAppendingPathComponent:str];
NSString* fileNmae = [filePath lastPathComponent];
if([fileNmae hasPrefix:@"Webcast"]){
//这里可以获取到文件创建的日期属性,并不是以命名来判断,如果是命名,使用NSDate+NSDataFormatter来处理即可
NSDictionary* fileAttribs = [[NSFileManager defaultManager]attributesOfItemAtPath:filePath error:nil];
NSDate* date = [fileAttribs objectForKey:NSFileCreationDate];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond
fromDate:date
toDate:[NSDate date]
options:0];
if(components.day <= 3){
[_zipArchive addFileToZip:filePath newname:str];
}
}
}
}
崩溃信息
一般来说,我们会在崩溃后,将崩溃信息附加到此次的日志信息最后,这样方便定位,不用匹配日志文件和崩溃文件。
关于崩溃信息如何收集,可以去看看 使用PLCrashReporter来保存崩溃日志信息
这样子简单的日志系统就算成立了,方便日后分析和处理问题
推荐阅读