正文
为了应对这些挑战,我们推出了 ByteBrain-LogParser,这是一个专为云服务环境量身定制的综合框架。本章将介绍 ByteBrain-LogParser 的整体框架和核心思路,展示它如何应对云环境中日志解析的挑战。
整体流程
ByteBrain-LogParser 包括离线训练阶段和在线匹配阶段。
在离线训练阶段,系统首先从目标日志流中采样一批日志数据。这些日志经过预处理后,使用层次聚类算法将其组织成树状结构。我们特别选择了层次聚类算法,正是考虑到云环境中不同应用场景对日志解析精度的多样化需求。层次聚类能够自然地构建一个多层次的模板体系,使系统能够在后续匹配阶段提供不同精确度的解析结果,而无需重新训练模型。聚类过程采用自底向上的方法,从单条日志作为初始叶节点开始,逐步合并相似的日志聚类,形成一个多层次的模板树。树中的每个节点代表一个日志模板,而树的不同层次对应不同的精度级别——越接近叶子节点的模板越精确,越接近根节点的模板越通用。聚类过程使用位置敏感的相似度计算和饱和度评分机制,确保生成的模板既准确又有效率。
离线训练不是一次性执行的过程,而是根据系统配置定期执行。具体来说,系统会在以下两种情况下触发离线训练:一是按照预设的时间间隔(如每天或每周)定期执行,确保模板库能够适应日志模式的渐进变化;二是当新的日志主题(topic)积累了足够数量的日志记录后立即执行,以快速适应新出现的日志类型。这种双重触发机制确保了系统既能保持对现有日志模式的高效解析,又能迅速适应新出现的日志格式,提高了整个框架的适应性和鲁棒性。
在在线匹配阶段,系统接收实时流入的日志,并将其与预先训练好的模板进行匹配。用户可以通过设置精度阈值参数来控制匹配过程,较低的阈值会导致系统使用树的较深层次进行更精细的模板匹配,而较高的阈值则使系统使用树的较浅层次进行更通用的模板匹配。这种动态调整精度的能力是 ByteBrain-LogParser 的关键创新,它使用户能够根据具体应用场景的需求灵活平衡精度和效率,无需重新处理原始日志。匹配完成后,系统从原始日志中提取变量值,并将解析结果返回给用户。
ByteBrain-LogParser 还实现了增量更新机制,利用定期执行的离线训练结果更新模板树,确保系统能够适应不断演变的日志模式。此外,系统采用了多种优化技术,包括哈希编码、模板索引和结果缓存,进一步提高了处理效率和存储效率。这些设计使 ByteBrain-LogParser 能够在保持高解析精度的同时,实现卓越的吞吐量和灵活的适应性,满足云环境中日志解析的严苛需求。
预处理
在离线训练和在线匹配阶段,日志都先会被预处理为便于计算的数值向量,然后根据日志的长度和前缀等进行预分组来,不同的预分组可以并行。
首先,日志会被分割成词(token)序列。分词是许多日志解析方法的基础步骤,它使系统能够在词级别上比较日志之间的相似性,而不是简单地进行字符级别的比较。我们选择正则表达式进行分词,因为它具有高效率、简单性和可定制性的优势。虽然正则表达式只能基于常见分隔符进行分段而无法考虑语义上下文,但它处理速度快且允许用户为每个主题轻松定义自定义分词规则。在默认的分词正则中, 我们考虑了常见的标点符号、URL 和小数点等情况。为了保持效率,系统禁止在用户定义的表达式中使用高复杂度的正则特性(如环视),这些特性可能使复杂度从 O(n)增加到 O(2^n)。
(?:://)|(?:(?:[\s'";=()[]{}?@&<>:\n\t\r,])|(?:[.](\s+|$))|(?:\["']))+
其次,尽管 ByteBrain-LogParser 专注于自动日志解析而不需要手动规则,但系统允许用户选择性地指定明显变量的正则表达式模式以优化性能。这些用户定义的模式通常针对在日志中一致出现的常见、特定领域的变量。提前替换这些已知变量可以显著降低后续自动解析的复杂性。对于每个主题,系统提供默认模式来识别常见变量(如时间戳、IP 地址、MD5 哈希值、UUID 等),同时用户可以添加特定领域的规则来进一步提高效率。
然后,ByteBrain-LogParser 去重输入的日志数据。日志数据经常包含大量重复记录,这种冗余在替换常见变量后变得更加明显。这不仅增加了存储开销,还降低了处理和分析效率。去重涉及识别并合并重复的日志条目,同时维护每个唯一日志语句出现次数的计数。这种方法通过减少冗余数据显著提高了计算效率。
为了计算效率,系统将标记编码为数值向量。传统的词袋编码(bad of words)方法忽略了标记的顺序,且无法直接从聚类生成模板文本。而序号编码(ordinal encoding)虽然保留顺序,但需要存储每个不同标记与其对应数值 ID 之间的映射,考虑到日志中可能存在的大量不同标记,这会导致显著的存储开销。
为解决这些挑战,ByteBrain 采用哈希编码,利用确定性哈希函数将每个标记映射到 64 位整数。在离线聚类和在线匹配中使用相同的哈希函数,消除了存储标记到 ID 映射的需求。与序号编码不同,哈希编码支持日志的并行处理,因为哈希函数可以独立处理每个标记,从而提高可扩展性。哈希碰撞的概率极低(对于 1000 万个不同标记,碰撞概率仅为 0.000271%),可以忽略不计。这种编码方式显著减少了存储开销,提高了系统效率,使得日志解析服务的成本大幅降低。
最后,ByteBrain-LogParser 使用初始分组(Initial Grouping)将那些不太可能属于同一模板的日志直接分开。初始分组的首要目的是使后续的聚类过程能够并行化,将日志分成较小的组还能降低后续聚类算法的计算复杂度并提高准确性。ByteBrain-LogParser 采用了两种简单而有效的初始分组策略:
-
长度分组
:具有不同标记数量的日志被假定属于不同的模板。这基于一个观察:同一代码生成的日志通常具有相同数量的标记,而标记数量的差异往往表明它们来自不同的日志语句。
-
前缀分组
:日志通过比较前 k 个标记(由用户配置,默认为 0)进行分组,将前缀不同的日志分到不同组中。这种策略特别有效,因为日志语句通常以表示操作类型或模块名称的固定前缀开始,这些前缀对区分不同类型的日志至关重要。
另外,后续的聚类算法依赖逐位置的单词比较计算日志间的相似性,因此按照长度分组也是必需的。然而,日志中可能存在可变长度的变量,比如直接打印 list 和 dict 等。我们特意选择不实现动态匹配解决方案(如使用最长公共子序列比较不同长度的日志),因为允许通配符匹配可变数量的标记需要在在线匹配过程中进行搜索,以确定每个通配符的最佳标记范围。这种方法会显著增加在线匹配的计算复杂性,使其在需要每秒匹配数百万条日志的云环境中变得不切实际。我们认为可以在查询结果处理层做一个简单而有效的优化:例如,对于由语句
print(f'users={users}')
生成的三个模板(其中 users 列表包含一个、两个和三个元素):
users *