4、日志解析基础:正则表达式入门、Python re模块使用、日志字段提取、结构化转换

各位同仁,欢迎来到第四讲。说实话,日志解析是咱们做OTN运维的看家本领。你想想看,每天面对成百上千条告警日志,如果全靠肉眼去翻,那不得累死?我刚开始干这行的时候,就吃过这个亏。有一次凌晨三点,某干线站点报“OTU_LOS”,我愣是在日志文件里翻了半小时才找到关键时间戳。后来我学乖了——正则表达式这东西,必须得拿下。

4.1 正则表达式入门:从“找茬”开始

正则表达式,说白了就是一种“模式匹配”的语言。它帮你在一堆文本里,快速找到你想要的内容。比如,你想从日志里提取所有“2024-08-15”这样的日期,或者所有“OTU-1-1-1”这样的端口号,正则就是干这个的。

我个人习惯把正则分成三块来记:

  • 普通字符:就是字母、数字、汉字,写什么就匹配什么。比如“OTU”就匹配“OTU”。
  • 元字符:这才是正则的灵魂。比如.匹配任意字符,\d匹配数字,\s匹配空格。
  • 量词:控制前面字符出现的次数。比如*表示0次或多次,+表示1次或多次,{3}表示恰好3次。

核心元字符速查表(我常用的)

元字符含义示例
.匹配任意单个字符(除换行)O.U 匹配 OTU、O1U 等
\d匹配数字(0-9)\d{4} 匹配四位数字
\w匹配字母、数字、下划线\w+ 匹配一个单词
\s匹配空白字符(空格、制表符)\s+ 匹配连续空格
^匹配字符串开头^2024 匹配以2024开头的行
$匹配字符串结尾LOS$ 匹配以LOS结尾的行
[]字符集,匹配其中任意一个[A-Z] 匹配大写字母
()分组,用于提取内容(\d{4})-(\d{2}) 提取年月

嗯,这里要注意:. 在正则里是“万能符”,但千万别滥用。我在项目中遇到过有人用 .* 去匹配整行,结果把不该匹配的也匹配进来了,导致解析结果全乱套。记住,能用具体字符就用具体字符,实在不行再用 .

4.2 Python re模块:把正则变成代码

光会写正则还不够,得能跑起来。Python的 re 模块就是干这个的。我一般只用四个函数,够够的:

  • re.search():在字符串里找第一个匹配的位置。找到了返回一个match对象,找不到返回None。
  • re.findall():找到所有匹配的内容,返回一个列表。我最常用这个。
  • re.match():从字符串开头开始匹配。相当于 ^ 的效果。
  • re.sub():替换匹配到的内容。比如把日志里的敏感信息替换掉。

举个例子,假设我们有这样一条OTN日志:

2024-08-15 14:23:45 [ALARM] OTU-1-1-1 LOS (Loss of Signal) on slot 3, port 2

我想提取时间、告警类型、端口号和告警描述。正则可以这么写:

import re

log_line = "2024-08-15 14:23:45 [ALARM] OTU-1-1-1 LOS (Loss of Signal) on slot 3, port 2"

# 正则表达式,用分组提取关键字段
pattern = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\] (\S+) (\w+) \((.+?)\)"

match = re.search(pattern, log_line)
if match:
    timestamp = match.group(1)   # 2024-08-15 14:23:45
    alarm_type = match.group(2)  # ALARM
    port = match.group(3)        # OTU-1-1-1
    alarm_code = match.group(4)  # LOS
    description = match.group(5) # Loss of Signal
    print(f"时间: {timestamp}, 告警: {alarm_code}, 端口: {port}")
else:
    print("未匹配到日志格式")

我的小技巧:写正则的时候,先用 re.findall() 调试。它返回的是列表,一眼就能看出匹配结果对不对。等确认无误了,再换成 re.search()re.finditer() 做正式处理。

4.3 日志字段提取:从“杂乱”到“有序”

日志字段提取,说白了就是把一行文本拆成一个个有意义的“零件”。比如上面那条日志,我们拆出了时间、告警类型、端口、告警码和描述。但实际运维中,日志格式往往更复杂。

我曾经处理过一个客户的日志,格式是这样的:

Aug 15 14:23:45 NODE-A OTDR[1234]: ALARM: OTU-1-1-1, LOS, Threshold: -28.5dBm, Current: -35.2dBm

注意看,时间格式变了(没有年份),还多了节点名、进程ID和光功率值。这时候正则就得跟着调整:

pattern = r"(\w{3} \d{2} \d{2}:\d{2}:\d{2}) (\S+) \w+\[\d+\]: \w+: (\S+), (\w+), Threshold: ([\d.-]+)dBm, Current: ([\d.-]+)dBm"

提取出来的字段就是:

  • 时间:Aug 15 14:23:45
  • 节点:NODE-A
  • 端口:OTU-1-1-1
  • 告警码:LOS
  • 阈值:-28.5
  • 当前值:-35.2

避坑指南:我曾经因为日志里时间格式不统一(有的带年份,有的不带),导致解析程序跑飞。后来我学乖了——先做一次“日志格式归一化”,把所有时间统一成 YYYY-MM-DD HH:MM:SS 格式,再去做字段提取。这一步虽然麻烦,但能省掉后面90%的bug。

4.4 结构化转换:把日志变成“数据库”

字段提取出来之后,下一步就是结构化。说白了,就是把散乱的字段装进一个“容器”里,方便后续查询和分析。我一般用两种方式:

  • 字典(dict):适合单条日志。键是字段名,值是提取的内容。
  • Pandas DataFrame:适合批量日志。把多条日志的字典拼成一个表格,想怎么查就怎么查。

举个例子,把上面提取的字段装进字典:

log_dict = {
    "timestamp": "2024-08-15 14:23:45",
    "alarm_type": "ALARM",
    "port": "OTU-1-1-1",
    "alarm_code": "LOS",
    "description": "Loss of Signal",
    "threshold_dbm": -28.5,
    "current_dbm": -35.2
}

然后,如果你有1000条这样的日志,就可以用Pandas做成表格:

import pandas as pd

# 假设 logs 是一个包含多个字典的列表
df = pd.DataFrame(logs)

# 现在你可以做各种分析
# 比如:统计每种告警出现的次数
print(df['alarm_code'].value_counts())

# 比如:找出所有光功率低于阈值的记录
low_power = df[df['current_dbm'] < df['threshold_dbm']]
print(low_power)

为什么这一步重要?

你想想看,原始日志是文本,没法直接做统计。但一旦转成结构化数据,你就可以用SQL、Pandas甚至Excel去分析。比如“过去24小时哪个端口告警最多?”“哪个节点的LOS告警频率最高?”——这些问题,结构化数据几秒钟就能回答。

嗯,最后说一句。正则表达式这东西,刚开始会觉得像天书。但别怕,我当年也是从“抄别人的正则”开始的。你只要记住:先写一个能跑通的,再慢慢优化。别追求一步到位,那反而容易把自己绕晕。

下一讲,我们会聊“日志清洗与预处理”。到时候我会分享一个真实案例——某省干线的日志里混入了乱码,我是怎么用正则把它们揪出来的。敬请期待。