在桌面应用程序的生存期内,应该在每次写入时打开/关闭日志文件流还是保持打开状态?

时间:2020-03-06 15:02:40  来源:igfitidea点击:

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

我问一个桌面应用程序的上下文中。我已经看到人们同时使用两种方法,并且想知道哪种方法可以为记录器带来最佳的全方位结果。

解决方案

如果我们经常进行读/写操作,则通过一次打开/关闭操作就可以在整个生命周期内保持文件打开状态,这种方式效率更高。

我们可能需要定期刷新或者在每次写入后刷新,以防万一应用程序崩溃,我们可能并没有将所有数据写入文件。在基于Unix的系统上使用fflush,在Windows上使用FlushFileBuffers。

如果我们也在Windows上运行,则可以将CreateFile API与FILE_FLAG_NO_BUFFERING一起使用,以在每次写入时直接进入文件。

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

打开和关闭。在系统崩溃的情况下可以将我们从损坏的文件中拯救出来。

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

另一方面,关闭和重新打开会花费一些额外的时间。

我可以想到几个我们不想让文件保持打开状态的原因:

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

另一方面,即使在追加模式下,打开文件也可能很慢。最后,归结为应用程序在做什么。

每次关闭文件的好处是,操作系统将保证将新消息写入磁盘。如果我们让文件保持打开状态并且程序崩溃,则可能不会写整件事。我们也可以通过执行fflush()或者使用与我们使用的语言等效的方法来完成相同的操作。

我倾向于将它们保持打开状态-但是使用文件共享权限设置它们以允许其他阅读器打开它们,并确保我们每条消息都刷新日志输出。

我讨厌程序在运行时甚至不允许我们查看日志文件,或者不刷新日志文件并且落后于正在发生的情况的程序。

通常最好保持打开状态。

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

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

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

这是一个权衡。每次打开和关闭文件都会在程序崩溃时更有可能在磁盘上更新文件。另一方面,打开文件,寻找末尾并将数据添加到文件中涉及一些开销。

在Windows上,我们无法在文件打开时对其进行移动/重命名/删除操作,因此,打开/写入/关闭文件可能会对长时间运行的过程有所帮助,在该过程中,我们有时可能希望在不中断旧日志内容的情况下进行归档作家。

在大多数情况下,我会保持打开状态,并使用fflush()使文件更有可能在程序崩溃时保持最新状态。

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

这意味着运行时库在包含大量数据之前不会尝试缓冲写入操作-在写入之前,我们可能会崩溃!

我会在每次写入(或者批量写入)时打开和关闭。如果这样做在桌面应用程序中导致性能问题,则可能是我们写入日志文件的频率太高了(尽管我确信进行大量写入操作可能有正当的理由)。

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

通常,正如其他人所说,请保持文件打开以提高性能(打开是一个相对较慢的操作)。但是,我们需要考虑如果保持打开文件状态并且人们删除日志文件或者截断日志文件会发生什么情况。而这取决于开放时间使用的标志。 (我针对Unix的类似考虑也可能适用于Windows,但我会接受比我更精通的技术人员的校正)。

如果有人看到日志文件增长到例如1 MiB,然后将其删除,则应用程序将不再是明智之举,而Unix将保持日志数据的安全,直到应用程序关闭日志为止。而且,用户会感到困惑,因为他们可能创建了一个与旧名称相同的新日志文件,并对为什么应用程序"停止记录"感到困惑。当然没有。它只是记录到其他任何人都无法获得的旧文件。

如果有人注意到日志文件已增长到例如1 MiB,然后将其截断,则该应用程序也不是更明智的选择。但是,根据打开日志文件的方式,我们可能会得到奇怪的结果。如果未使用O_APPEND(说POSIX)打开文件,则程序将继续以其当前偏移量写入日志文件,并且文件的前1 MiB将显示为零字节流,这很容易混淆了查看文件的程序。

如何避免这些问题?

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

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

意见建议:

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

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

我们可能还会看看诸如Apache之类的哪些系统具有用于处理日志文件的机制,并且具有用于处理日志轮换的工具。注意:如果应用程序确实使单个文件保持打开状态,不使用添加模式,并且不计划日志轮换或者大小限制,那么除了开始时日志文件增长或者块头为零,我们无能为力。定期重新启动应用程序。

我们应确保尽快完成对日志的所有写操作。如果我们使用文件描述符,则只能使用内核缓冲。这可能是可以接受的,但请考虑将open()的O_SYNC或者O_DSYNC选项。如果我们使用文件流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(并更改)。反社会的,但可能的。