【问题标题】:Reading rotation log files and file locking读取轮换日志文件和文件锁定
【发布时间】:2015-01-29 19:50:06
【问题描述】:

我有一个 Python 服务将日志输出到文本文件。它每 ~400KB 旋转一次。因此 Python 服务打开了文件的句柄,我们称之为 app.log。然后它不时地将内容写入文件,并将其刷新到磁盘。当它达到一定大小时,它会关闭它的句柄,并将其移动到 app.log.1 并在 app.log 上启动一个新的句柄。

所以我无法更改此服务,但我有一个可以读取这些日志的 C# 应用程序。我遇到了 3 个场景:

  • 如果我只是尝试使用 new FileStream(path, FileMode.Open); 读取这些日志,它不会允许我,因为 Python 服务已经处理了它。
  • 如果我尝试使用new FileStream(path, FileMode.Open, FileAccess.Read); 打开它,这允许我读取它,但如果服务尝试轮换日志,它将无法这样做,因为我的 C# 应用程序现在拥有该文件的句柄。
  • 如果我尝试使用 new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Delete); 打开文件,我的 Python 服务在删除文件时不会失败,但它会在 app.log 上创建新句柄作为 C# 失败应用程序仍然可以处理它。

我知道的唯一解决方案是使用 Windows 卷影复制 (VSS) 创建日志快照,然后读取该快照,但这会非常昂贵,因为我们需要每 5 分钟查询一次日志.

另外,我对阅读旋转日志、app.log.1app.log.2 等不感兴趣。

在 Windows 下登录到文本文件似乎很痛苦,因为所有的锁定/句柄。有人有什么建议吗?

【问题讨论】:

  • 因此,如果您的 Python 应用程序在 C# 应用程序读取旧日志文件的过程中启动了一个新日志文件,您希望发生什么? C# 应用程序是否应该在移动到新文件之前继续读取旧文件的末尾?遗憾的是,您无法更新 python 应用程序,因为该编号方案很愚蠢。
  • 主要是我们无法更新 Python 应用程序。 C# 应用程序可以默默地忽略任何读取失败,它应该停止读取并允许 Python 应用程序轮换日志(因此移动文件并创建一个新文件)。 C# 服务检查日志以了解 Python 应用程序正在执行基本操作。
  • 请在此处查看每周日志轮换的答案https://stackoverflow.com/a/45028620/8288059
  • @HarisAhmed 与文件锁定无关,这个问题已有 3 年历史了。

标签: c# python logging locking


【解决方案1】:

您应该能够按照 Dmitry Popov 在他的回答中所建议的那样打开您的文件,并且不会影响 Python 写入文件,但这取决于 Python 应用程序在文件上持有什么锁,它可以完全锁定您并且在那里在不入侵 Windows 的情况下,您无需采取任何措施来防止这种情况发生。

FileSream fs = File.Open(@"c:\Test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)

以这种方式创建的 FileStream 对象在对其执行操作系统文件移动操作重命名后仍将连接到同一个文件。

假设您的 python 应用程序打开了一个名为 Test.log 的文件并开始写入它。您可以使用从上面的行返回的文件流读取写入它的任何数据(在 python 刷新其缓冲区之后)。 python 应用程序可以关闭和重新打开文件,只要它想要每次写入,读取应用程序将保持与它的连接。当 python 应用程序发出文件移动操作将文件重命名为 Test1.log 时,上面返回的文件流仍将连接到现在称为 Test1.log 的文件,因此您可以在开始之前继续阅读到文件末尾新的日志文件,如果这是你想要的。对此有一个警告。 Python 应用程序需要使用移动/重命名操作,而不是将文件复制到新文件并删除旧文件,但如果这是它所做的,我会感到惊讶。

在您的写入应用程序完成读取之前,您的阅读应用程序可能会到达文件末尾。在这种情况下,fs.Read 将在超时后继续返回 0,直到写入应用程序打开文件并写入更多内容。如果您愿意,您可以将超时设置为非常长/无限。

由于您不想在开始新文件之前阅读到一个文件的末尾,您可以定期关闭并重新打开该文件。不带数字后缀的日志文件应始终是最新的。

但是,如果您希望读取应用程序在开始下一个日志文件的开头之前读取到一个日志文件的末尾,您将需要在写入应用程序完成写入日志文件时进行计算。它还需要找出文件现在被调用的内容,以便接下来可以读取 n-1。是否有一些由 python 应用程序编写的标记,您可以寻找它来表示文件的结尾?它会写“日志结束”或类似的东西吗?

还要注意,LogFile n-1 将在短时间内不存在。这是因为如果您有日志文件 0、1、2 和 3,它需要先将日志文件 3 转换为日志文件 4,然后才能将日志文件 2 转换为日志文件 3。在执行此操作时,将有一小段时间您有日志文件 0、1、2、4 和没有 3 的时间。

就我个人而言,我会发现为您的 Python 应用程序编写日志记录的开发人员首先给了他/她一个邪恶的眼睛,因为他/她首先导致了这种头痛。让最近的日志文件数量最多有什么问题?

【讨论】:

    【解决方案2】:

    你可以组合FileShare标志:

    FileShare.Write | FileShare.Delete
    

    这是一个演示:

    using (var cSharp = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Write | FileShare.Delete))
    {
        // The Python service will be able to change and to rename the file:
        using (var python = new FileStream(filename, FileMode.Open, FileAccess.Write, FileShare.Read))
        {
        }
        File.Move(filename, newFilename);
    }
    

    您将不得不处理并发问题。您可以使用FileSystemWatcher 来监控文件更改。

    【讨论】:

    • 您能否详细说明“处理并发”的含义?
    • 通常,您会打开一个文件并锁定其句柄,以确保文件的内容在读取时不会被意外修改。在你描述的场景中,文件句柄是共享的,可以通过Python编写的服务修改内容,也可以重命名文件。
    • @MartinBrown 在他的回答中更详细地介绍了它。
    【解决方案3】:
    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
      //Do works
    }
    

    C# 线程在这种情况下不会锁定文件,您的 Python 脚本可以写入和关闭文件以创建另一个文件而不会死锁。

    【讨论】:

    • 如果 Python 试图删除文件并且 C# 线程正在读取文件会发生什么?我对FileShare.Read 的理解是它将允许其他文件读取该文件,但不能写入或删除它。来自 MSDN“允许随后打开文件进行读取”:msdn.microsoft.com/en-us/library/…
    猜你喜欢
    • 2020-07-17
    • 1970-01-01
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多