解析是归一化的第一步
不同来源的时间戳格式各异:Apache 用 02/Jan/2026:03:20:11 +0800,系统日志用 Jan 2 03:20:11,云审计日志用 2026-01-02T03:20:11Z。要合并成一条时间线,必须先逐条解析成带时区的时间对象,再统一换算到 UTC。Python 的 datetime.strptime 是最常用的工具,关键是格式串要和各段样本逐字段对应。
失败行不能静默丢弃
脚本化的解析一定会遇到格式不符合预期的行,比如多了毫秒、时区用了缩写、月份缩写受语言环境影响。正确做法是把解析失败的行连同原始文本写入一个单独的文件,人工过一遍再决定补哪条规则。静默跳过的行等于悄悄从时间线上挖掉一块,而这块恰好可能是关键时段。
基本信息
| 适用平台 | 跨平台(Python 3 标准库 datetime) |
|---|---|
| 核心函数 | datetime.strptime(字符串, 格式串) |
| 时区处理 | 格式串中的 %z 捕获 +0800 这类偏移,再用 astimezone 转 UTC |
| 输出建议 | 统一写成带 Z 的 ISO 字符串,便于排序与比较 |
| 失败行 | 写入 bad-lines.txt,保留原始行与来源文件名 |
典型使用场景
- 合并多来源日志:先把每类格式解析成统一表示的 UTC 时间
- 批量为时间线 CSV 生成可排序的时间列:字符串排序不再受格式干扰
- 校验时区假设:解析后核对结果是否落在业务与事件窗口内
- 排查日志缺行:解析失败行单独统计,可发现采集或格式变更
- 为可视化准备数据:统一后的时间列可直接交给绘图脚本
解析与归一小脚本
解析与归一小脚本
from datetime import datetime, timezone
import sys
# 每种来源对应一条格式串,与样本逐字段校对后填写
fmts = [
'%d/%b/%Y:%H:%M:%S %z', # Web 访问日志
'%Y-%m-%dT%H:%M:%SZ', # 云审计日志(UTC)
'%Y-%m-%d %H:%M:%S', # 通用带秒格式
]
def parse(line):
for f in fmts:
for part in line.strip().split(' ', 2):
try:
dt = datetime.strptime(part, f)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc) # 无偏移时按已知时区假设补
return dt.astimezone(timezone.utc).isoformat()
except ValueError:
continue
return None
for line in sys.stdin:
ts = parse(line)
print(ts if ts else 'PARSE_FAIL', line.rstrip())
if ts is None:
print('# 失败行请记录到 bad-lines.txt,不要直接丢弃', file=sys.stderr)常用格式串对照
| 日志片段 | 格式串 | 易错点 |
|---|---|---|
| 02/Jan/2026:03:20:11 +0800 | %d/%b/%Y:%H:%M:%S %z | %b 受语言环境影响,英文环境才稳定 |
| Jan 2 03:20:11 | %b %d %H:%M:%S | 日字段前有空格,%d 可容忍;无年份需外部补 |
| 2026-01-02T03:20:11Z | %Y-%m-%dT%H:%M:%SZ | Z 要写成字面量,%z 不认 Z |
| 2026-01-02 03:20:11,123 | %Y-%m-%d %H:%M:%S,%f | 毫秒前是逗号,%f 需要配合 |
| 2026-01-02T03:20:11+08:00 | %Y-%m-%dT%H:%M:%S%z | 带冒号的偏移用 %z 可直接解析 |
%b 依赖操作系统语言环境,容器或精简镜像里可能是 C 以外的 locale;解析月份缩写失败时先检查 locale,而不是改格式串。
没有时区信息的时间只能按已知假设处理(例如「该主机配置为 UTC+8」),并把假设写进报告;同一批数据里不要对不同来源使用互相冲突的假设。