【发布时间】:2008-10-02 19:14:35
【问题描述】:
日志类应该在每次写入日志文件时打开/关闭日志文件流,还是应该在应用程序的整个生命周期中保持日志文件流打开,直到所有日志记录完成?
我是在桌面应用程序的上下文中询问的。我看到人们用两种方式都这样做,并且想知道哪种方法可以为记录器产生最好的全面结果。
【问题讨论】:
标签: logging
日志类应该在每次写入日志文件时打开/关闭日志文件流,还是应该在应用程序的整个生命周期中保持日志文件流打开,直到所有日志记录完成?
我是在桌面应用程序的上下文中询问的。我看到人们用两种方式都这样做,并且想知道哪种方法可以为记录器产生最好的全面结果。
【问题讨论】:
标签: logging
一般来说,正如其他人所说,保持文件打开以提高性能(打开是一个相对较慢的操作)。但是,您需要考虑如果您保持文件打开并且人们删除日志文件或截断它会发生什么。这取决于开放时间使用的标志。 (我正在解决 Unix - 类似的考虑可能适用于 Windows,但我会接受比我更有知识的人更正)。
如果有人看到日志文件增长到 1 MiB,然后将其删除,那么应用程序将不会更明智,Unix 将保持日志数据的安全,直到日志被应用程序关闭。更重要的是,用户会感到困惑,因为他们可能创建了一个与旧日志文件同名的新日志文件,并且对应用程序“停止记录”的原因感到困惑。当然,它没有;它只是记录到其他人无法访问的旧文件。
如果有人注意到日志文件已经增长到 1 MiB,然后将其截断,那么应用程序也不会更明智。但是,根据打开日志文件的方式,您可能会得到奇怪的结果。如果文件不是用 O_APPEND (POSIX-speak) 打开的,那么程序将继续在日志文件中的当前偏移量处写入,文件的前 1 MiB 将显示为零字节流——这很恰当混淆查看文件的程序。
如何避免这些问题?
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() 进行了一次调用),或者可能是 @ 987654328@ 在单个操作中写入单独的数据段。
顺便说一句,“包含”零的磁盘块实际上并未在磁盘上分配。你真的可以通过创建每个几个 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(和更改)。反社会,但可能。
【讨论】:
如果您有频繁的读/写操作,使用单次打开/关闭保持文件在整个生命周期内打开会更有效。
您可能希望定期或在每次写入后刷新,但万一您的应用程序崩溃,您可能不会将所有数据写入文件。在基于 Unix 的系统上使用 fflush,在 Windows 上使用 FlushFileBuffers。
如果您也在 Windows 上运行,则可以使用带有 FILE_FLAG_NO_BUFFERING 的 CreateFile API 在每次写入时直接转到文件。
最好在整个生命周期内保持文件打开,因为每次打开/关闭时,如果文件正在使用中,您可能会遇到故障。例如,您可能有一个在备份文件时运行和打开/关闭文件的备份应用程序。而这可能会导致您的程序无法访问您自己的文件。理想情况下,您希望始终保持文件打开并在 Windows (FILE_SHARE_READ) 上指定共享标志。在基于 Unix 的系统上,共享将是默认设置。
【讨论】:
为了性能,保持打开状态。为了安全起见,经常冲洗。
这意味着运行时库在有大量数据之前不会尝试缓冲写入——在写入之前您可能会崩溃!
【讨论】:
我倾向于让它们保持打开状态——但打开它们时需要设置文件共享权限以允许其他阅读者,并确保在每条消息中刷新日志输出。
我讨厌那些在运行时甚至不允许你查看日志文件的程序,或者日志文件没有刷新并且落后于正在发生的事情的程序。
【讨论】:
通常最好让它们保持打开状态。
如果您担心能否从其他进程读取它们,则需要确保用于打开/创建它们的共享模式允许其他人读取它们(但显然不能写入它们)。
如果您担心在发生崩溃时丢失数据,您应该定期刷新/提交其缓冲区。
【讨论】:
这是一个权衡。每次打开和关闭文件都更有可能在程序崩溃时在磁盘上更新文件。另一方面,在打开文件、查找到末尾以及向其附加数据时会产生一些开销。
在 Windows 上,您将无法在文件打开时移动/重命名/删除文件,因此打开/写入/关闭可能有助于您偶尔需要归档旧日志内容的长时间运行的进程不打扰作者。
在我完成此类日志记录的大多数情况下,我保持文件打开,并使用 fflush() 使程序崩溃时文件更有可能是最新的。
【讨论】:
打开和关闭。可以在系统崩溃时将您从损坏的文件中拯救出来。
【讨论】:
我认为没有任何理由关闭它。
另一方面,关闭和重新打开需要一些额外的时间。
【讨论】:
我能想到几个你不想让文件保持打开状态的原因:
另一方面,打开文件可能会很慢,即使在附加模式下也是如此。最后,这取决于您的应用在做什么。
【讨论】:
每次关闭文件的好处是操作系统会保证将新消息写入磁盘。如果您将文件保持打开状态并且您的程序崩溃,则可能不会写入整个内容。您也可以通过执行 fflush() 或您使用的语言中的任何等效项来完成相同的操作。
【讨论】:
作为您的应用程序的用户,我希望它不要打开文件,除非它是应用程序的真正要求。在系统崩溃等情况下可能会出错的另一件事。
【讨论】:
我会在每次写入时打开和关闭(或批量写入)。如果这样做会导致桌面应用程序出现性能问题,则可能是您过于频繁地写入日志文件(尽管我确信大量写入可能是合理的原因)。
【讨论】:
对于大型密集型应用程序,我通常会在应用程序期间保持日志文件打开,并有一个单独的线程定期将内存中的日志内容刷新到 HDD。文件的打开和关闭操作需要系统调用,如果你深入到较低的级别,这是很多工作。
【讨论】: