使用 GCD 在 HTML 文件中写入日志无法按预期工作
Write logs in HTML file using GCD doesn't work as expected
我为 NSLog
创建了一个重载方法,以便将所有日志写入 HTML
文件。
所有日志都使用 GCD
.
写入 HTML
文件
问题是线路有时会 运行...
这是我的代码:
写入日志文件功能:
+(void)writeInLogFile:(NSString *)strLog inFolder:(NSString *)folder fileName:(NSString *)fileName extension:(NSString *)extension{
//Create Directory
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:folder];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) //Does directory already exists?
{
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error])
{
NSLog(@"%d||Create log directory error: %@",LOG_SEVERITY_HIGH, error);
}
}
NSString* filePath = [NSString stringWithFormat:@"%@/%@.%@",path,fileName,extension];
int nbOfLogFiles = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil] count];
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
[HandleString createLogFile:filePath andStrLog:strLog];
}
else{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
}
}
这就是结果:
调用(每次执行 NSLog
时调用):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
这就是结果:
如您所见,最后一行是 t运行cated...
这贯穿整个文件。
我尝试 运行 主线程上的进程,它运行良好,没有问题。
另一个有趣的事情是,当我更改 QOS 时结果不一样,例如优先级高,我有更多 t运行 线路。
编辑:写入文件的代码:
+(void)createLogFile:(NSString*)filePath andStrLog:(NSString*)strLog{
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
考虑到短语 "I tried to run the process on the main thread and it works well without problems." 我会说您的截断问题来自缓冲区在完全清空之前被关闭。这并不能解释您在使用高优先级时可能观察到的差异(请记住,额外的行可能是巧合,除非另有证明......)。
现在,我建议您在结束线程作用域之前尝试一个小 sleep...
我找到了一个有点棘手的解决方案:
我删除了 dispatch_asynch :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
在我写入日志文件的函数中,我做了:
static dispatch_queue_t asyncQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
asyncQueue = dispatch_queue_create("asyncQueue", NULL);
});
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
dispatch_async(asyncQueue, ^{
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
else{
dispatch_async(asyncQueue, ^{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
可能会对某人有所帮助。
但我想知道您认为这是不是一个好的解决方案?
约翰
问题是您同时从多个线程写入。考虑以下事件序列:
- 线程 A 寻找文件末尾
- 线程 B 寻找文件末尾
- 线程 B 写入数据
- 线程A写入数据
线程 A 的数据将覆盖线程 B 的数据。如果Thread A的数据更长,那么Thread B的数据就不会留下任何痕迹。如果线程A的数据较短,则线程B的数据超出该长度的部分将保留。
调换最后两步的顺序也有类似的问题。当然,还有更复杂的线程更多的场景。
一种解决方案是序列化对文件的所有访问,就像您在自我回答中所做的那样。另一种是以仅追加模式打开文件。在这种模式下,所有写入都在文件末尾完成。这是由 OS 强制执行的。两个同时写入无法相互覆盖。文件句柄的当前文件位置是无关紧要的,所以不需要寻找。
NSFileHandle
不直接支持以仅追加模式打开文件。您必须使用 open()
打开一个文件描述符,然后将该文件描述符交给 NSFileHandle
。 (你也可以只使用 write()
和 close()
,但它有点混乱。)例如:
int fd = open(filePath.fileSystemRepresentation, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
// handle error
}
NSFileHandle* fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
[fileHandle release]; // Only needed if not using ARC
请注意,在这种情况下您不必显式创建日志文件。由于 O_CREAT
标志,如果它不存在,open()
调用将创建它。
我为 NSLog
创建了一个重载方法,以便将所有日志写入 HTML
文件。
所有日志都使用 GCD
.
HTML
文件
问题是线路有时会 运行...
这是我的代码:
写入日志文件功能:
+(void)writeInLogFile:(NSString *)strLog inFolder:(NSString *)folder fileName:(NSString *)fileName extension:(NSString *)extension{
//Create Directory
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:folder];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) //Does directory already exists?
{
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error])
{
NSLog(@"%d||Create log directory error: %@",LOG_SEVERITY_HIGH, error);
}
}
NSString* filePath = [NSString stringWithFormat:@"%@/%@.%@",path,fileName,extension];
int nbOfLogFiles = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil] count];
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
[HandleString createLogFile:filePath andStrLog:strLog];
}
else{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
}
}
这就是结果:
调用(每次执行 NSLog
时调用):
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
这就是结果:
如您所见,最后一行是 t运行cated...
这贯穿整个文件。
我尝试 运行 主线程上的进程,它运行良好,没有问题。
另一个有趣的事情是,当我更改 QOS 时结果不一样,例如优先级高,我有更多 t运行 线路。
编辑:写入文件的代码:
+(void)createLogFile:(NSString*)filePath andStrLog:(NSString*)strLog{
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
考虑到短语 "I tried to run the process on the main thread and it works well without problems." 我会说您的截断问题来自缓冲区在完全清空之前被关闭。这并不能解释您在使用高优先级时可能观察到的差异(请记住,额外的行可能是巧合,除非另有证明......)。
现在,我建议您在结束线程作用域之前尝试一个小 sleep...
我找到了一个有点棘手的解决方案:
我删除了 dispatch_asynch :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[HandleString writeInLogFile:message];
});
在我写入日志文件的函数中,我做了:
static dispatch_queue_t asyncQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
asyncQueue = dispatch_queue_create("asyncQueue", NULL);
});
if(nbOfLogFiles <= NB_OF_LOG_FILES_BEFORE_PURGE +1){
dispatch_async(asyncQueue, ^{
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
else{
dispatch_async(asyncQueue, ^{
[HandleString purgeLogDirectory:path];
[HandleString createLogFile:filePath andStrLog:strLog];
});
}
可能会对某人有所帮助。
但我想知道您认为这是不是一个好的解决方案?
约翰
问题是您同时从多个线程写入。考虑以下事件序列:
- 线程 A 寻找文件末尾
- 线程 B 寻找文件末尾
- 线程 B 写入数据
- 线程A写入数据
线程 A 的数据将覆盖线程 B 的数据。如果Thread A的数据更长,那么Thread B的数据就不会留下任何痕迹。如果线程A的数据较短,则线程B的数据超出该长度的部分将保留。
调换最后两步的顺序也有类似的问题。当然,还有更复杂的线程更多的场景。
一种解决方案是序列化对文件的所有访问,就像您在自我回答中所做的那样。另一种是以仅追加模式打开文件。在这种模式下,所有写入都在文件末尾完成。这是由 OS 强制执行的。两个同时写入无法相互覆盖。文件句柄的当前文件位置是无关紧要的,所以不需要寻找。
NSFileHandle
不直接支持以仅追加模式打开文件。您必须使用 open()
打开一个文件描述符,然后将该文件描述符交给 NSFileHandle
。 (你也可以只使用 write()
和 close()
,但它有点混乱。)例如:
int fd = open(filePath.fileSystemRepresentation, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
// handle error
}
NSFileHandle* fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
[fileHandle writeData:[strLog dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
[fileHandle release]; // Only needed if not using ARC
请注意,在这种情况下您不必显式创建日志文件。由于 O_CREAT
标志,如果它不存在,open()
调用将创建它。