第2章:日志标准与规范

日志标准化这件事,说实话,我早年吃过不少亏。那时候公司日志格式五花八门,有的用竖线分隔,有的用逗号,有的干脆就是自由文本。每次做安全分析,光解析日志就得花掉一半时间。后来我学乖了——标准化,必须从一开始就做。

2.1 Syslog协议:日志传输的老祖宗

Syslog是日志传输领域最古老的协议之一,1980年代就有了。你别看它老,现在绝大多数网络设备、Linux服务器还在用它。我自己的经验是,理解Syslog是理解日志体系的第一步。

Syslog的核心机制很简单:发送端把日志扔给接收端,接收端根据设施代码和严重级别做处理。说白了,就是「谁发的、多严重、说了啥」这三件事。

Syslog的三个核心要素:

  • 设施(Facility):标识日志来源,比如内核、邮件系统、认证系统等。代码范围0-23。
  • 严重级别(Severity):从0(紧急)到7(调试),共8级。
  • 消息体(Message):实际日志内容,通常包含时间戳、主机名、进程名等。

举个例子,一条典型的Syslog消息长这样:

<34>Oct 11 22:14:15 myhost sshd[1234]: Failed password for root from 192.168.1.100 port 22 ssh2

前面的<34>是优先级值,计算方式是 Facility×8 + Severity。34对应的设施是4(auth),级别是2(critical)。嗯,这里要注意,不同设备对设施代码的实现可能不一样,我遇到过某品牌交换机把认证日志扔到了local0里,排查了好久。

2.2 RFC 5424:Syslog的现代化升级

传统Syslog有个硬伤——格式不统一。时间戳格式随意,缺少结构化信息。RFC 5424就是来解决这个问题的。它定义了标准化的Syslog消息格式,增加了结构化数据字段。

RFC 5424的消息格式长这样:

<165>1 2024-10-11T22:14:15.003Z myhost.example.com sshd 1234 - [meta sequenceId="1"] Failed password for root

你看,多了版本号(1)、标准化的ISO 8601时间戳、应用名称、进程ID,还有结构化数据段[meta ...]。我个人习惯在生产环境中强制使用RFC 5424格式,尤其是做集中日志采集时,解析效率能提升30%以上。

我的建议:如果你的日志系统支持RFC 5424,尽量用它。虽然消息体积会大一些,但带来的可解析性和可追溯性完全值得。我在帮某金融客户做日志审计时,就是因为用了RFC 5424的结构化数据,才把合规检查的时间从3天缩短到2小时。

2.3 日志格式标准化:JSON、CEF、LEEF

标准化不只是传输协议的事,日志内容的格式同样重要。目前业界主流的三种格式是JSON、CEF和LEEF。我分别说说我的使用感受。

2.3.1 JSON格式

JSON是目前最通用的日志格式。它的优势是自描述、易解析、支持嵌套结构。几乎所有现代编程语言都有现成的JSON库。

{
  "timestamp": "2024-10-11T22:14:15.003Z",
  "host": "myhost.example.com",
  "source": "sshd",
  "event": "authentication_failure",
  "user": "root",
  "src_ip": "192.168.1.100",
  "severity": "critical",
  "message": "Failed password for root from 192.168.1.100 port 22 ssh2"
}

JSON的缺点也很明显——体积大。每条日志都带字段名,带宽和存储成本会高一些。我在做高吞吐量日志采集时,会考虑用压缩传输或者精简字段名。

2.3.2 CEF格式

CEF(Common Event Format)是ArcSight定义的格式,在SIEM领域用得很多。它的特点是字段位置固定,解析速度快。

CEF:0|Security|sshd|1.0|100|Authentication failure|5|src=192.168.1.100 dst=10.0.0.1 duser=root msg=Failed password for root

CEF用竖线分隔前8个固定字段,后面是键值对扩展字段。说实话,CEF的阅读体验不如JSON,但解析性能确实好。我记得有一次做每秒10万条日志的实时分析,CEF格式比JSON格式的CPU占用低了15%。

避坑指南:我曾经遇到过一个坑——CEF的扩展字段名没有统一标准。不同厂商可能用不同的键名表示同一个含义,比如源IP有的用src,有的用sourceAddress。做日志归一化时一定要提前做好字段映射表。

2.3.3 LEEF格式

LEEF(Log Event Extended Format)是IBM QRadar定义的格式。它和CEF类似,但扩展性更好。

LEEF:1.0|Security|sshd|1.0|Authentication failure|devTime=2024-10-11T22:14:15Z|src=192.168.1.100|usrName=root|msg=Failed password for root

LEEF用竖线分隔前6个字段,后面也是键值对。它的优势在于支持自定义扩展字段,而且时间戳格式更灵活。我个人觉得,如果你在用QRadar,LEEF是首选;如果用的是其他SIEM,JSON可能更通用。

2.4 如何选择日志格式?

这个问题没有标准答案,但我可以分享一些经验:

场景 推荐格式 理由
通用日志采集 JSON 兼容性好,解析工具多
高性能实时分析 CEF 解析速度快,字段固定
QRadar环境 LEEF 原生支持,扩展性强
老旧设备 Syslog传统格式 兼容性优先,无法改变

一个小技巧:如果你需要同时兼容多种格式,可以在日志采集层做一次格式转换。我常用的做法是:所有设备统一输出Syslog(RFC 5424),然后在日志采集服务器上转换成JSON存入Elasticsearch。这样既保证了传输层的标准化,又保留了分析层的灵活性。

2.5 实战:用Python实现日志格式转换

说了这么多理论,我们来点实际的。下面是我写的一个小工具,能把传统Syslog转换成JSON格式:

import re
import json
from datetime import datetime

def syslog_to_json(raw_message):
    """
    将传统Syslog消息转换为JSON格式
    """
    # 解析Syslog头部
    pattern = r'<(\d+)>(\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})\s+(\S+)\s+(\S+)\[(\d+)\]:\s+(.*)'
    match = re.match(pattern, raw_message)
    
    if not match:
        return {"error": "无法解析的日志格式", "raw": raw_message}
    
    priority = int(match.group(1))
    facility = priority // 8
    severity = priority % 8
    timestamp_str = match.group(2)
    hostname = match.group(3)
    app_name = match.group(4)
    pid = match.group(5)
    message = match.group(6)
    
    # 时间戳标准化
    current_year = datetime.now().year
    timestamp = datetime.strptime(f"{current_year} {timestamp_str}", "%Y %b %d %H:%M:%S")
    
    # 构建JSON
    log_entry = {
        "timestamp": timestamp.isoformat(),
        "hostname": hostname,
        "app_name": app_name,
        "pid": pid,
        "facility": facility,
        "severity": severity,
        "message": message
    }
    
    return json.dumps(log_entry, ensure_ascii=False)

# 使用示例
raw_log = "<34>Oct 11 22:14:15 myhost sshd[1234]: Failed password for root"
json_log = syslog_to_json(raw_log)
print(json_log)

这段代码我用了好几年,基本没出过问题。你想想看,如果每条日志都手动解析,那得累死。自动化才是正道。

好了,关于日志标准与规范,我就讲这么多。下一章我们会聊日志采集架构,到时候我会分享一些分布式环境下的实战经验。