第3章 日志格式解析:正则表达式入门归纳总结

搞自动化审计日志巡检,逃不开一个基本功——正则表达式。

说白了,就是教Python怎么从一堆文本里“揪”出你想要的信息。我刚开始做日志解析的时候,手写字符串切片,又慢又容易崩。后来老老实实用正则,效率直接翻倍。

3.1 为什么非用正则不可?

审计日志的内容通常长这样:

2025-04-01 14:23:45,123 [INFO] user=admin action=LOGIN result=SUCCESS ip=192.168.1.10

你想提取时间、用户、动作、结果、IP。用split()?日志格式一变就全废。用正则?写一次,管它空格多一个少一个,照抓不误。

我在项目里遇到过好几次:客户说“日志格式跟文档一样”,结果实际日志里多了个制表符……正则的\s+一次搞定。

小提示:正则不是万能的。超长日志、嵌套结构,还是得走解析器。但审计日志多数是结构化文本,正则已经够用。

3.2 正则表达式的核心“零件”

记住这张表,后面直接套用。

元字符含义示例
.匹配任意单个字符(换行除外)a.b → 匹配 a1b、aXb
\d数字 [0-9]\d{4} → 四位数字
\w字母、数字、下划线\w+ → 一个单词
\s空白字符(空格、制表、换行)\s+ → 连续空白
^行开头^ERROR → 行首的ERROR
$行结尾SUCCESS$ → 行尾的SUCCESS
*前一个字符出现 0 次或多次ab*c → ac, abc, abbc
+前一个字符出现 1 次或多次ab+c → abc, abbc
?前一个字符出现 0 次或 1 次https? → http 或 https
{n,m}重复 n 到 m 次\d{2,4} → 2~4位数字
[...]字符集,匹配其中任意一个[aeiou] → 任何一个元音
(...)分组,用于提取或引用(\d{4})-(\d{2}) → 年-月
|或error|warning → error 或 warning

核心口诀:圆括号提取,方括号选一个,反斜杠转义或特殊含义。记牢这三句,实战够用。

3.3 Python 正则三步走

Python 的 re 模块,我日常只用三个函数:

  1. re.search() —— 在整段文本里找第一处匹配。适合判断“有没有”。
  2. re.findall() —— 找出所有匹配,返回列表。适合批量提取。
  3. re.sub() —— 替换。我用来做日志脱敏,比如把IP换成***。

看个例子:提取上面那条日志的时间、用户和IP。

import re

log = "2025-04-01 14:23:45,123 [INFO] user=admin action=LOGIN result=SUCCESS ip=192.168.1.10"

# 写法一:一个个抓
time_match = re.search(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3})', log)
user_match = re.search(r'user=(\w+)', log)
ip_match   = re.search(r'ip=([\d.]+)', log)

print(f"时间: {time_match.group(1)}")
print(f"用户: {user_match.group(1)}")
print(f"IP:   {ip_match.group(1)}")

运行结果:

时间: 2025-04-01 14:23:45,123
用户: admin
IP:   192.168.1.10
我个人的编写习惯:正则前面加 r(原始字符串),省得转义搞晕自己。比如 r"\d+" 而不是 "\\d+"。

如果想一行代码全提取,用 findall + 分组:

pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) .*? user=(\w+) .*? ip=([\d.]+)'
result = re.findall(pattern, log)
print(result)  # [('2025-04-01 14:23:45,123', 'admin', '192.168.1.10')]

3.4 常见的坑,我替你踩过了

  • 忘加 r 前缀:"\n" 会被当成换行。我一晚上没查出 bug,后来发现是 Python 把 \n 解析成了换行符。
  • 点号不匹配换行:默认 . 不包括 \n。跨行日志?用 re.DOTALL 模式。
  • 贪婪 vs 非贪婪:.* 会一直匹配到最后。想停在一个地方?用 .*?。我曾经拿它匹配日志里的 [INFO] ... [END],不加问号直接把整个日志吞了。

注意:正则如果写得太复杂,性能会爆炸。审计日志一天几十万条,re.findall 里嵌套回溯,我见过跑 10 分钟没结果的案例。简洁优先,不行就用 re.compile 预编译。

3.5 本章知识体系

下面这张图,帮你把正则的核心逻辑串起来。

正则表达式入门知识体系 正则表达式核心知识体系 正则表达式 元字符 \d \w \s . * + ? ^ $ [...] (...) | {n,m} 分组与提取 ( ) 捕获组 .group() .groups() Python API re.search() re.findall() re.sub() 常用模式修正符 re.I(忽略大小写) re.DOTALL(点匹配换行) 性能优化 re.compile() 预编译 避免贪婪匹配 常见坑 忘记 r 前缀 点号不乘换行 用脑子记元字符,用手敲分组提取,用眼盯性能坑

3.6 实战:解析真实审计日志

来一个完整的例子。假设我们有这么几条审计日志:

2025-04-01 09:12:33,456 [WARN] 用户=张三 动作=文件删除 文件=/tmp/test.log
2025-04-01 09:13:01,789 [ERROR] 用户=李四 动作=登录失败 来源IP=10.0.0.5
2025-04-01 09:15:22,012 [INFO]  用户=王五 动作=创建用户 目标=admin

我要提取时间、级别、用户、动作,并按级别统计条数。

import re

logs = [
    "2025-04-01 09:12:33,456 [WARN] 用户=张三 动作=文件删除 文件=/tmp/test.log",
    "2025-04-01 09:13:01,789 [ERROR] 用户=李四 动作=登录失败 来源IP=10.0.0.5",
    "2025-04-01 09:15:22,012 [INFO]  用户=王五 动作=创建用户 目标=admin",
]

pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) \[(\w+)\]\s+用户=(\S+)\s+动作=(\S+)'

count = {"ERROR": 0, "WARN": 0, "INFO": 0}
for log in logs:
    m = re.search(pattern, log)
    if m:
        time, level, user, action = m.groups()
        print(f"{time} | {level:5s} | {user:6s} | {action}")
        if level in count:
            count[level] += 1

print("\n统计:", count)

输出:

2025-04-01 09:12:33,456 | WARN  | 张三    | 文件删除
2025-04-01 09:13:01,789 | ERROR | 李四    | 登录失败
2025-04-01 09:15:22,012 | INFO  | 王五    | 创建用户

统计: {'ERROR': 1, 'WARN': 1, 'INFO': 1}

注意:正则里的\S+匹配非空白字符,比\w+更通用,因为用户名里可能有中文(虽然\w在Python 3里也支持Unicode,但\S更安全)。