2. 基于时间窗口的日志分段与故障事件切分
为什么固定窗口不能直接当事件边界
航次日志是连续时间流,故障事件本身有起止时刻。若只按固定窗口(如 60 s)机械切分,一个跨越窗口边界的故障会被拆成两段,导致后续分类时同一故障出现两个标签。本例的做法是:先用固定窗口做粗分段,再用事件触发规则做合并与边界修正,让每个故障事件片段尽量完整。
核心参数有两个:窗口长度 W 决定粗分段粒度;切分阈值 G(相邻两条日志的最大允许间隔)决定何时判定"事件已结束"。当两条相邻日志的时间差 Δt ≤ G 时视为同一事件延续,Δt > G 时切出新事件。窗口只用于限制单段最大长度,不直接决定事件边界。
分段与切分的实现
下面代码读取已对齐字段的结构化日志(字段:ts 秒级时间戳、source 来源、level 级别、msg 描述),先按窗口长度 W 打上窗口编号,再按阈值 G 做事件切分,最后输出事件片段表。示例数据为教学构造,用于演示切分逻辑。
import pandas as pd
# 教学示例:已对齐的结构化日志(ts 为航次内相对秒)
logs = pd.DataFrame([
{"ts": 90, "source": "sensor", "level": "WARN", "msg": "推进器电流偏高"},
{"ts": 150, "source": "sensor", "level": "WARN", "msg": "推进器电流持续偏高"},
{"ts": 210, "source": "control", "level": "ERROR", "msg": "推进器过流保护"},
{"ts": 300, "source": "control", "level": "ERROR", "msg": "推进器输出受限"},
{"ts": 360, "source": "sensor", "level": "WARN", "msg": "深度读数漂移"},
{"ts": 420, "source": "sensor", "level": "WARN", "msg": "深度读数漂移扩大"},
{"ts": 500, "source": "control", "level": "ERROR", "msg": "深度控制偏差超限"},
{"ts": 620, "source": "comm", "level": "ERROR", "msg": "通信超时"},
{"ts": 700, "source": "comm", "level": "ERROR", "msg": "通信重连失败"},
]).sort_values("ts").reset_index(drop=True)
W = 60 # 窗口长度(秒)
G = 45 # 切分阈值(秒)
# 1) 固定窗口粗分段:仅打标签,不决定事件边界
logs["window_id"] = (logs["ts"] // W).astype(int)
# 2) 事件触发切分:相邻日志间隔 > G 则新事件
logs["gap"] = logs["ts"].diff().fillna(0)
logs["event_id"] = (logs["gap"] > G).cumsum()
# 3) 汇总事件片段
events = logs.groupby("event_id").agg(
start_ts=("ts", "min"),
end_ts=("ts", "max"),
n_logs=("ts", "size"),
windows=("window_id", lambda s: sorted(set(s))),
sources=("source", lambda s: sorted(set(s))),
).reset_index()
print(events.to_string(index=False))
预期输出(由上述输入直接推出):
event_id start_ts end_ts n_logs windows sources
0 90 300 4 [1, 3, 5] [control, sensor]
1 360 500 3 [6, 8] [control, sensor]
2 620 700 2 [10, 11] [comm]
窗口长度与阈值的影响
事件 0 的 windows 为 [1,3,5],说明它跨越了多个固定窗口——这正是固定窗口不能直接当事件边界的原因。若把窗口当作事件边界,事件 0 会被拆成 3 段,同一故障出现 3 个标签。
| 参数组合 | 事件数 | 事件 0 是否完整 | 问题 |
|---|---|---|---|
| W=60, G=45 | 3 | 完整 | 无(推荐基线) |
| W=60, G=90 | 2 | 完整 | 事件 B 与 C 被错误合并 |
| W=60, G=20 | 5 | 被拆开 | 事件 0 内部 Δt=30s 被误切 |
| W=30, G=45 | 3 | 完整 | 窗口更细,但事件边界仍由 G 决定 |
结论:事件完整性主要由 G 决定,W 只影响粗分段粒度。G 应大于同一故障内相邻日志的典型间隔,小于不同故障之间的最小间隔。本例中同一故障内最大间隔为 30s,不同故障间最小间隔为 60s,故 G 取 45s 合适。
ts 排序并去重,否则 diff() 会产生负间隔或零间隔,导致事件切分错误。本例已用 sort_values 排序。验证切分结果
验证分两步:一是检查每个事件片段内相邻日志间隔是否都 ≤ G;二是检查不同事件之间的间隔是否都 > G。两条同时成立,说明切分满足"不拆同一故障、不并不同故障"的约束。
# 片段内最大间隔应 ≤ G
inner_max = logs.groupby("event_id")["gap"].max()
assert (inner_max <= G).all(), "存在片段内间隔超过阈值"
# 片段间最小间隔应 > G
starts = events["start_ts"].tolist()
ends = events["end_ts"].tolist()
between = [starts[i+1] - ends[i] for i in range(len(events)-1)]
assert all(b > G for b in between), "存在片段间间隔未超过阈值"
print("切分校验通过:事件数 =", len(events))
对示例数据运行上述校验,inner_max 为 30s(≤45s),片段间间隔为 60s 与 120s(均 >45s),校验通过,事件数为 3。若把 G 改为 90s,片段间间隔 60s 不满足 >90s,校验会失败并提示"存在片段间间隔未超过阈值",从而暴露错误合并。