分析包含多行条目的日志文件

Parsing log file with multi-line entries

本文关键字:日志 文件 包含多      更新时间:2023-10-16

我正在努力从第三方应用程序解析一个合理大小的日志文件(最多 50Mb,此时它包装),以检测在指定时间范围内发生的KEY_STRINGs。此日志文件中的典型条目可能如下所示

DEBUG 2013-10-11@14:23:49 [PID] - Product.Version.Module
(Param 1=blahblah Param2=blahblah Param3 =blahblah
Method=funtionname)
String that we usually don't care about but may be KEY_STRING  

条目由空行分隔(\r 在条目末尾,\r 在下一个条目开始之前)

这是针对Windows特定的实现,因此不需要可移植,并且可以是C/C++/Win32

逐行读取此内容将非常耗时,但具有能够解析时间戳并检查条目是否在给定时间范围内的好处,然后再检查条目中是否存在任何KEY_STRINGs。如果我按块读取文件,我可能会找到一个KEY_STRING但块没有更早的时间戳,或者块边框甚至可能在KEY_STRING的中间。将整个文件读入内存并解析它不是一种选择,因为当前要成为其中一部分的应用程序占用空间相对较小,因此不能证明仅仅为了解析文件而将其增加 ~10 倍(即使是暂时的)。有没有办法通过分隔块(特别是"\r\r")来读取文件?还是没有我想到的另一种/更好的方法?

任何这方面的帮助将不胜感激!

一种可能的解决方案是使用内存映射文件。我个人从未将它们用于玩具应用以外的任何东西,但知道它背后的一些理论。

从本质上讲,它们提供了一种访问文件内容的方法,就好像它们是内存一样,我相信它们的行为方式与虚拟内存类似,因此所需的部分将根据需要分页,并在某个时候分页(您应该阅读文档以制定其背后的规则)。

在伪代码

中(因为我们都喜欢伪代码),你会按照以下思路做一些事情:

HANDLE file = CreateFile(...);
HANDLE file_map = CreateFileMapping(file, 0, PAGE_READONLY, 0, 0, ...);
LPVOID mem = MapViewOfFile(file_map, FILE_MAP_READ, 0, 0, 0);
// at this point you can use mem to access data in the mapped part of the file...
// for your code, you would perform parsing as if you'd read the file into RAM.
// when you're done, unmap and close the file:
UnmapViewOfFile(mem);
CloseHandle(file_map);
CloseHandle(file);

我现在很抱歉没有提供最优秀的建议,而是鼓励进一步阅读 - Windows提供了许多功能来处理您的内存,并且非常值得一读。

  1. 确保你不能使用记忆,也许你有点太"偏执"了?过早优化等等。
  2. 逐行读取(因为这样可以更轻松地分隔条目),但用缓冲读取来包装行读取,一次读取尽可能多的内容,也许是 1 MB。这样可以最大程度地减少磁盘 I/O,这通常有利于提高性能。

假设(通常情况下)文件中的所有条目都按时间排序,您应该能够使用二分搜索的变体来查找正确的起点和终点,然后解析两者之间的数据。

基本思想是寻找文件的中间,然后读取几行,直到你到达以"DEBUG"开头的行,然后读取时间戳。如果它早于你关心的时间,请向前寻找 3/4 标记。如果晚于你关心的时间,请回到1/4th。马克。重复基本想法,直到找到开始。然后在末世做同样的事情。

一旦您搜索的数量低于某个阈值(例如,64K),那么寻找 64K 对齐块的开头可能会更快,然后从那里继续向前读取而不是再进行任何搜索。

另一种需要考虑的可能性是,您是否可以在后台执行一些工作,以便在修改文件时构建文件的索引,然后在实际需要结果时使用索引。例如,索引将在写入条目后立即读取每个条目的时间戳(例如,使用ReadDirectoryChangesW在日志文件被修改时被告知)。例如,它会将文本时间戳转换为time_t,然后在索引中存储一个条目,提供该条目的time_t和文件偏移量。这应该足够小(对于 50 MB 的日志文件可能低于 1 兆字节),以便完全在内存中使用它很容易。