38

日志类应该在每次写入日志文件时打开/关闭日志文件流,还是应该在应用程序的整个生命周期中保持日志文件流打开,直到所有日志记录完成?

我在桌面应用程序的上下文中询问。我看到人们用两种方式都这样做,并且想知道哪种方法可以为记录器产生最佳的全面结果。

4

13 回答 13

19

如果您有频繁的读/写,则使用单个 open/close使文件在整个生命周期内保持打开状态会更有效。

您可能希望定期或在每次写入后刷新,但万一您的应用程序崩溃,您可能没有将所有数据写入文件。在基于 Unix 的系统上使用 fflush,在 Windows 上使用 FlushFileBuffers。

如果您也在 Windows 上运行,则可以使用带有 FILE_FLAG_NO_BUFFERING 的 CreateFile API 在每次写入时直接转到文件。

最好在整个生命周期内保持文件打开因为每次打开/关闭文件时,如果文件正在使用中,您可能会失败。例如,您可能有一个备份应用程序,它在备份文件时运行并打开/关闭您的文件。这可能会导致您的程序无法访问您自己的文件。理想情况下,您希望始终保持文件打开并在 Windows (FILE_SHARE_READ) 上指定共享标志。在基于 Unix 的系统上,共享将是默认设置。

于 2008-10-02T19:16:00.667 回答
17

一般来说,正如其他人所说,保持文件打开以提高性能(打开是一个相对较慢的操作)。但是,您需要考虑如果您保持文件打开并且人们删除日志文件或截断它会发生什么。这取决于开放时间使用的标志。(我正在解决 Unix - 类似的考虑可能适用于 Windows,但我会接受比我更有知识的人的更正)。

如果有人看到日志文件增长到 1 MiB,然后将其删除,那么应用程序将不会更明智,并且 Unix 将保持日志数据的安全,直到日志被应用程序关闭。更重要的是,用户会感到困惑,因为他们可能创建了一个与旧日志文件同名的新日志文件,并且对应用程序“停止记录”的原因感到困惑。当然,它没有;它只是记录到其他人无法访问的旧文件。

如果有人注意到日志文件已经增长到 1 MiB,然后将其截断,那么应用程序也不会更明智。但是,根据打开日志文件的方式,您可能会得到奇怪的结果。如果文件不是用 O_APPEND (POSIX-speak) 打开的,那么程序将继续在日志文件中的当前偏移量处写入,文件的前 1 MiB 将显示为零字节流——这很恰当混淆查看文件的程序。

如何避免这些问题?

  • 使用 O_APPEND 打开日志文件。
  • 定期fstat()在文件描述符上使用并检查是否st_nlink为零。

如果链接计数变为零,则有人删除了您的日志文件。是时候关闭它,然后重新打开一个新的。与stat()or相比open()fstat()应该很快;它基本上是直接从内存中的东西中复制信息,不需要名称查找。因此,您可能应该在每次要写作时都这样做。

建议:

  • 确保有一种机制告诉程序切换日志。
  • 确保在消息中记录完整的日期和时间。

我的应用程序会显示时间而不是日期。今天早些时候,我有一个消息文件,其中包含 8 月 17 日的一些条目(其中一条消息不小心在时间之后的消息中包含了日期),然后是今天的一些条目,但我只能说是因为我创建了它们。如果我在一周内查看日志文件,我无法确定它们是在哪一天创建的(尽管我会知道它们的创建时间)。这种事情很烦人。

你也可以看看像 Apache 这样的系统是做什么的——它们有处理日志文件的机制,还有处理日志轮换的工具。注意:如果应用程序确实保持单个文件打开,不使用附加模式,并且不计划日志轮换或大小限制,那么对于日志文件增长或在开始时有大块零,您无能为力 - 其他而不是定期重新启动应用程序。

您应该确保尽快完成对日志的所有写入。如果使用文件描述符,则只有内核缓冲;这可能是可以接受的,但请考虑O_SYNCO_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(和更改)。反社会,但可能。

于 2008-10-02T21:52:19.527 回答
4

为了性能,保持打开。为安全起见,请经常冲洗

这意味着运行时库在它有大量数据之前不会尝试缓冲写入——在写入之前你可能会崩溃!

于 2008-10-02T19:23:05.530 回答
2

我倾向于让它们保持打开状态——但使用设置为允许其他读者的文件共享权限打开它们,并确保在每条消息中刷新日志输出。

我讨厌那些甚至不让你在运行时查看日志文件的程序,或者日志文件没有被刷新并且落后于正在发生的事情的程序。

于 2008-10-02T19:18:41.610 回答
2

通常最好让它们保持打开状态。

如果您担心能够从另一个进程读取它们,则需要确保用于打开/创建它们的共享模式允许其他人读取它们(但显然不能写入它们)。

如果您担心在崩溃时丢失数据,您应该定期刷新/提交它们的缓冲区。

于 2008-10-02T19:20:19.110 回答
2

这是一个权衡。每次打开和关闭文件都更有可能在程序崩溃时在磁盘上更新文件。另一方面,在打开文件、寻找结尾以及向其附加数据时会产生一些开销。

在 Windows 上,您将无法在文件打开时移动/重命名/删除文件,因此打开/写入/关闭可能有助于长时间运行的进程,您可能偶尔希望存档旧日志内容而不中断作家。

在我完成这种日志记录的大多数情况下,我保持文件打开,并使用 fflush() 使程序崩溃时文件更有可能是最新的。

于 2008-10-02T19:22:37.717 回答
1

打开和关闭。如果系统崩溃,可以将您从损坏的文件中拯救出来。

于 2008-10-02T19:17:04.533 回答
1

我看不出有任何理由关闭它。

另一方面,关闭和重新打开需要一些额外的时间。

于 2008-10-02T19:17:05.797 回答
1

我可以想到几个您不想打开文件的原因:

  • 如果在多个不同的应用程序、用户或应用程序实例之间共享一个日志文件,您可能会遇到锁定问题。
  • 如果您没有正确清除流缓冲区,则当该应用程序崩溃并且您最需要它们时,您可能会丢失最后几个条目。

另一方面,打开文件可能会很慢,即使在附加模式下也是如此。最后,这取决于您的应用程序在做什么。

于 2008-10-02T19:17:42.503 回答
0

每次关闭文件的好处是操作系统会保证将新消息写入磁盘。如果您让文件保持打开状态并且您的程序崩溃,那么整个内容可能不会被写入。您也可以通过执行 fflush() 或您使用的语言中的任何等效项来完成相同的事情。

于 2008-10-02T19:18:26.510 回答
0

作为您的应用程序的用户,我希望它不要打开文件,除非它是应用程序的真正要求。在系统崩溃等情况下可能会出错的另一件事。

于 2008-10-02T19:21:58.793 回答
0

我会在每次写入(或一批写入)时打开和关闭。如果这样做会导致桌面应用程序出现性能问题,那么您可能过于频繁地写入日志文件(尽管我确信大量写入可能有正当理由)。

于 2008-10-02T19:28:01.340 回答
0

对于大型密集型应用程序,我通常会在应用程序期间保持日志文件打开,并有一个单独的线程定期将内存中的日志内容刷新到 HDD。文件的打开和关闭操作需要系统调用,如果你深入到较低的级别,这是很多工作。

于 2008-10-02T20:33:18.157 回答