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告警频率最高?”——这些问题,结构化数据几秒钟就能回答。
嗯,最后说一句。正则表达式这东西,刚开始会觉得像天书。但别怕,我当年也是从“抄别人的正则”开始的。你只要记住:先写一个能跑通的,再慢慢优化。别追求一步到位,那反而容易把自己绕晕。
下一讲,我们会聊“日志清洗与预处理”。到时候我会分享一个真实案例——某省干线的日志里混入了乱码,我是怎么用正则把它们揪出来的。敬请期待。