4. 关键事件提取与异常标记

从原始日志到事件时间线

本案例的输入是一份已对齐时间基准的AUV任务日志(例2、例3的产物),每行包含t(秒,统一时钟)、src(来源子系统)、msg(原始消息)。任务是从中提取五类关键事件,并按规则标记异常,最终输出按时间排序的事件时间线。

先明确事件识别规则。日志消息采用固定前缀编码,提取时用前缀匹配而非全文搜索,避免把描述性文本误判为事件:

事件类型匹配模式关键字段异常判定规则
任务开始MISSION_START任务号重复出现 >1 次 → 异常
到达目标点WAYPOINT_REACHED目标点编号、偏差偏差 > 5 m → 异常
避障触发OBSTACLE_AVOID最近距离最近距离 < 2 m → 异常
通信中断COMM_LOST / COMM_RESTORED中断时长中断时长 > 120 s → 异常
任务结束MISSION_END结束状态状态非 NORMAL → 异常

其中“通信中断”是成对事件:COMM_LOST 开启一个中断区间,COMM_RESTORED 关闭它,时长由两者时间差计算。若日志结束时仍有未闭合的 COMM_LOST,按“持续至日志末尾”处理并标记为异常。

处理链:前缀匹配 → 区间配对 → 阈值判定 → 时间线 原始日志行 t / src / msg 前缀匹配 5 类事件模式 区间配对 LOST ↔ RESTORED 阈值/模式判定 标记异常事件 事件时间线 按 t 排序 示例事件时间线(t 单位:秒) 0 200 400 600 800 1000 通信中断 180 s(>120 s 异常) 任务开始 t=10 到达 WP1 偏差3.2m 避障 最近1.4m ⚠ 通信中断 t=460 ⚠ 通信恢复 t=640 任务结束 t=980 任务边界事件 正常事件 阈值异常 中断类异常 ⚠ = 已标记异常

提取与标记的实现

下面是一段可直接运行的 Python 实现。它读取日志行列表,输出事件字典列表,每条事件含类型、时间、来源、是否异常及异常原因。示例日志为教学构造数据,用于演示规则如何生效。

import re

# 教学示例日志(已对齐时间基准)
raw_log = [
    (10,  "MISSION", "MISSION_START id=M07"),
    (150, "NAV",     "WAYPOINT_REACHED wp=1 dev=3.2"),
    (330, "SONAR",   "OBSTACLE_AVOID dist=1.4"),
    (460, "COMM",    "COMM_LOST"),
    (640, "COMM",    "COMM_RESTORED"),
    (980, "MISSION", "MISSION_END status=NORMAL"),
]

PATTERNS = {
    "MISSION_START":     re.compile(r"^MISSION_START\b"),
    "WAYPOINT_REACHED":  re.compile(r"^WAYPOINT_REACHED\b"),
    "OBSTACLE_AVOID":    re.compile(r"^OBSTACLE_AVOID\b"),
    "COMM_LOST":         re.compile(r"^COMM_LOST\b"),
    "COMM_RESTORED":     re.compile(r"^COMM_RESTORED\b"),
    "MISSION_END":       re.compile(r"^MISSION_END\b"),
}

def parse_kv(msg):
    """提取 key=value 字段,如 dev=3.2 -> {'dev': '3.2'}"""
    return dict(re.findall(r"(\w+)=([\w.]+)", msg))

def extract_events(raw_log):
    events = []
    for t, src, msg in raw_log:
        for etype, pat in PATTERNS.items():
            if pat.match(msg):
                events.append({
                    "t": t, "type": etype, "src": src,
                    "fields": parse_kv(msg),
                    "abnormal": False, "reason": ""
                })
                break
    return events

def mark_abnormal(events, log_end_t):
    # 1) 任务开始重复
    starts = [e for e in events if e["type"] == "MISSION_START"]
    if len(starts) > 1:
        for e in starts[1:]:
            e["abnormal"], e["reason"] = True, "MISSION_START 重复"

    # 2) 目标点偏差 > 5 m
    for e in events:
        if e["type"] == "WAYPOINT_REACHED":
            dev = float(e["fields"].get("dev", 0))
            if dev > 5.0:
                e["abnormal"], e["reason"] = True, f"偏差 {dev} m > 5 m"

    # 3) 避障最近距离 < 2 m
    for e in events:
        if e["type"] == "OBSTACLE_AVOID":
            d = float(e["fields"].get("dist", 99))
            if d < 2.0:
                e["abnormal"], e["reason"] = True, f"最近距离 {d} m < 2 m"

    # 4) 通信中断区间配对与时长判定
    lost = None
    for e in events:
        if e["type"] == "COMM_LOST":
            lost = e
        elif e["type"] == "COMM_RESTORED" and lost is not None:
            dur = e["t"] - lost["t"]
            lost["fields"]["duration"] = dur
            if dur > 120:
                lost["abnormal"], lost["reason"] = True, f"中断 {dur} s > 120 s"
            lost = None
    if lost is not None:  # 未闭合
        dur = log_end_t - lost["t"]
        lost["fields"]["duration"] = dur
        lost["abnormal"], lost["reason"] = True, f"中断未恢复,持续 {dur} s"

    # 5) 任务结束状态非 NORMAL
    for e in events:
        if e["type"] == "MISSION_END":
            st = e["fields"].get("status", "")
            if st != "NORMAL":
                e["abnormal"], e["reason"] = True, f"结束状态 {st}"

    return events

events = extract_events(raw_log)
events = mark_abnormal(events, log_end_t=1000)
events.sort(key=lambda e: e["t"])

for e in events:
    flag = "⚠ " + e["reason"] if e["abnormal"] else "OK"
    print(f"t={e['t']:>4}  {e['type']:<16} {flag}")

按上述示例数据运行,预期输出为:MISSION_START 与 MISSION_END 为 OK;WAYPOINT_REACHED 偏差 3.2 m 为 OK;OBSTACLE_AVOID 因 1.4 m < 2 m 标记异常;COMM_LOST 因 640−460=180 s > 120 s 标记异常;COMM_RESTORED 本身不判异常,仅作为区间闭合点。这正是 SVG 时间线上两个 ⚠ 标记的来源。

前缀匹配使用 ^ 锚定行首,可避免 COMM_LOST 被 COMM_LOST_RETRY 之类的衍生消息误触发;若日志存在多种变体,应显式列出模式而非放宽为子串匹配。

结果校验与常见偏差

  • 事件数量核对:五类事件中,COMM_LOST 与 COMM_RESTORED 应成对出现;若配对数为奇数,说明存在未闭合中断,需检查日志是否被截断。
  • 时间单调性:排序后相邻事件时间差不应为负;若出现负值,说明时间对齐环节仍有残留问题,应回到例2的校正结果复查。
  • 阈值边界:规则使用严格不等号(>5、<2、>120),恰好等于阈值时不标记异常。若任务规范要求“达到即异常”,需改为 >= / <=,并在文档中写明。
  • 缺失关键事件:若时间线中没有 MISSION_START 或 MISSION_END,应作为日志完整性问题单独报告,而不是静默跳过。

完成上述步骤后,得到的事件时间线即为后续阶段分析、深度剖面清洗与轨迹重构的输入基础。