日志类应该在每次写入日志文件时打开/关闭日志文件流,还是应该在应用程序的整个生命周期中保持日志文件流打开,直到所有日志记录完成?
我是在桌面应用程序的上下文中询问的。我见过人们同时采用这两种方法,我想知道哪种方法能为记录器带来最好的全面结果。
如果您有频繁的读/写操作,那么通过单个打开/关闭在整个生命周期内保持文件打开更有效。
您可能希望定期刷新,或者在每次写入之后刷新。如果您的应用程序崩溃,您可能无法将所有数据写入文件。在基于Unix的系统上使用fflush,在Windows上使用FlushFileBuffers。
如果您也在Windows上运行,则可以使用带有FILE_FLAG_NO_BUFFERING的CreateFile API在每次写入时直接转到文件。
最好在整个生命周期内保持文件打开,因为每次打开/关闭文件时,如果文件正在使用,可能会出现故障。例如,您可能有备份应用程序,该应用程序在备份文件时运行并打开/关闭文件。这可能会导致您的程序无法访问您自己的文件。理想情况下,您希望始终打开文件,并在Windows上指定共享标志(file_SHARE_READ)。在基于Unix的系统上,共享将是默认设置。
通常,正如其他人所说,保持文件打开以提高性能(打开是一个相对较慢的操作)。然而,你需要考虑如果你保持文件打开,而人们要么删除日志文件,要么截断它,会发生什么。这取决于打开时使用的标志。(我说的是Unix——类似的考虑可能适用于Windows,但我会接受那些比我更有知识的人的更正)。
如果有人看到日志文件增长到1MIB,然后将其删除,应用程序将不会知道,Unix将保持日志数据的安全,直到应用程序关闭日志为止。更重要的是,用户会感到困惑,因为他们可能创建了一个与旧日志文件同名的新日志文件,并对应用程序为什么"停止日志记录"感到困惑。当然,事实并非如此;它只是登录到其他人无法访问的旧文件。
如果有人注意到日志文件已经增长到1 MiB,然后截断它,那么应用程序也不会知道。不过,根据日志文件的打开方式,您可能会得到奇怪的结果。如果文件不是用O_APPEND(POSIX说话)打开的,那么程序将继续在日志文件中以其当前偏移量进行写入,并且文件的前1MIB将显示为零字节流——这很容易混淆查看文件的程序。
如何避免这些问题?
- 使用O_APPEND打开日志文件
- 定期在文件描述符上使用
fstat()
,并检查st_nlink
是否为零
如果链接数为零,则有人删除了您的日志文件。是时候关闭它,重新打开一个新的。与stat()
或open()
相比,fstat()
应快速;它基本上是直接从内存中复制信息,不需要名称查找。所以,你可能应该在每次写作时都这样做。
建议:
- 确保有一个机制告诉程序切换日志
- 请确保在邮件中记录完整的日期和时间
我的申请表上写的是时间而不是日期。今天早些时候,我有一个消息文件,其中有一些来自8月17日的条目(其中一条消息意外地在消息中包含了时间之后的日期),还有一些来自今天的条目,但我只能说出来,因为我创建了它们。如果我在几周后查看日志文件,我无法判断它们是在哪一天创建的(尽管我知道它们是在什么时候创建的)。那种事很烦人。
您还可以了解Apache等系统的功能——它们有处理日志文件的机制,还有处理日志轮换的工具。注意:如果应用程序确实打开了一个文件,没有使用附加模式,也没有计划日志轮换或大小限制,那么除了定期重新启动应用程序之外,对于日志文件的增长或在开始时有大块的零,你就无能为力了。
您应该确保尽快完成对日志的所有写入。如果使用文件描述符,则只有内核缓冲;这可能是可以接受的,但是考虑O_SYNC
或O_DSYNC
选项到open()
。如果使用文件流I/O,请确保每次写入后都跟有fflush()
。如果您有一个多线程应用程序,请确保每个write()
都包含一条完整的消息;不要试图分别编写消息的各个部分。对于文件流I/O,您可能需要使用flockfile()
和亲属来将操作分组在一起。使用文件描述符I/O,您可以使用dprintf()
对文件描述符进行格式化I/O(尽管不完全清楚dprintf()
是否对write()
进行了一次调用),或者使用writev()
在一次操作中写入单独的数据段。
顺便说一句,"包含"零的磁盘块实际上并没有在磁盘上分配。通过创建每个文件只有几个GiB,但除了最后一个磁盘块之外,所有文件都只包含零,你真的可以破坏人们的备份策略。基本上(为了简洁起见,省略了错误检查和文件名生成):
int fd = open("/some/file", O_WRITE|O_CREATE|O_TRUNC, 0444);
lseek(fd, 1024L * 1024L * 1024L, 0);
write(fd, "hi", 2);
close(fd);
这占用了磁盘上的一个磁盘块,但在(未压缩)备份时占用1 GiB(和更改),在恢复时占用1 GB(和更改。反社会,但有可能。
为了提高性能,保持打开。为了安全起见,经常冲洗。
这意味着运行时库在拥有大量数据之前不会尝试缓冲写入——您可能会在写入之前崩溃!
我倾向于让它们保持打开状态,但打开它们时要设置文件共享权限,以允许其他阅读器,并确保用每条消息刷新日志输出。
我讨厌那些在运行时甚至不让你查看日志文件的程序,或者那些日志文件没有刷新并且滞后于正在发生的事情的程序。
通常最好保持它们的打开状态。
如果你担心能够从另一个进程中读取它们,你需要确保你用来打开/创建它们的共享模式允许其他人读取它们(但显然不能写入它们)。
如果你担心在崩溃时丢失数据,你应该定期刷新/提交它们的缓冲区。
这是一种权衡。每次打开和关闭文件都会使文件更有可能在程序崩溃时在磁盘上更新。另一方面,打开文件、查找文件末尾并将数据附加到文件中会产生一些开销
在Windows上,当文件打开时,您将无法移动/重命名/删除该文件,因此打开/写入/关闭可能有助于长期运行的进程,在该进程中,您可能偶尔希望在不中断编写器的情况下归档旧日志内容。
在我做过这种日志记录的大多数情况下,我都会保持文件打开,并使用fflush()在程序崩溃时使文件更有可能是最新的。
打开和关闭。在系统崩溃的情况下,可以将您从损坏的文件中拯救出来。
我看不出有任何理由关闭它。
另一方面,关闭和重新开放需要一点额外的时间。
我能想到几个你不想打开文件的原因:
- 如果日志文件在多个不同的应用程序、用户或应用程序实例之间共享,则可能存在锁定问题
- 如果你没有正确清除流缓冲区,当应用程序崩溃时,你可能会丢失最后几个条目,而你最需要它们
另一方面,打开文件可能很慢,即使在附加模式下也是如此。归根结底,这取决于你的应用程序在做什么。
每次关闭文件的好处是操作系统将保证新消息写入磁盘。如果你打开文件,程序崩溃,那么整个程序可能不会被编写。您也可以通过执行fflush()或使用的语言中的等效操作来完成同样的事情。
作为应用程序的用户,我希望它不要打开文件,除非这是应用程序的真正需求。在系统崩溃等情况下,还有一件事可能会出错。
我会在每次写入(或一批写入)时打开和关闭。如果这样做会导致桌面应用程序出现性能问题,那么您可能过于频繁地写入日志文件(尽管我确信大量写入可能是有正当理由的)。
对于大型密集型应用程序,我通常会在应用程序期间打开日志文件,并有一个单独的线程定期将内存中的日志内容刷新到HDD。文件打开和关闭操作需要系统调用,如果您查看较低级别,这将是一项艰巨的工作。