题目重点:先确定日志语法和失败率分母,再单遍解析、校验与计数;附 11,111 组独立序列核验。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练格式为四字段以 TAB 分隔:IP、带时区的 ISO-8601 时间、以 `/` 开头且无空白的 path、三位十进制状态码 100–599。IPv4 与 IPv6 都允许;生产系统应签真实日志协议。
- `total` 代表输入行数;valid/invalid 分开统计;失败率的分母是有效请求数,分子为有效请求中 4xx+5xx。无有效请求时训练版返回 0.0,报表也保留分母 0 供 UI 显示 N/A。
解题路线
- 先签 schema 与非法行处理,否则分隔符、timestamp 时区和百分比分母不同会导致完全不同答案。
- 逐行拆成恰好四字段;用 IP 解析器而非仅靠点号,时间须能解析且带时区,path 与状态码检查格式和范围。
- 只对合法请求累加 valid 与 4xx/5xx;最后用计数算 failure rate,避免每行浮点累加。非法行数由总行数减 valid 得出。
- 对 0–4 行的 11,111 组组合序列,用与实现独立的正则/规则参考实现核验,覆盖非法 IP、时间、path、status、零分母与混合状态。
手动走一遍
三行有效记录的状态分别 200、404、503,再加一行坏 IP:total=4、valid=3、invalid=1、4xx=1、5xx=1、failure_rate=2/3。若业务要求非法行也计入请求总数,应改变指标命名与分母,而不是悄悄让错误率下降。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from ipaddress import ip_address
@dataclass(frozen=True)
class LogReport:
total: int
valid: int
invalid: int
client_errors: int
server_errors: int
failure_rate: float
def summarize_logs(lines: list[str]) -> LogReport:
"""Training format: IP TAB ISO-8601-UTC TAB /path TAB status.
Failure rate denominator is valid requests only; 4xx and 5xx fail.
"""
valid = client_errors = server_errors = 0
for line in lines:
fields = line.split("\t")
if len(fields) != 4:
continue
ip_text, stamp, path, status_text = fields
try:
ip_address(ip_text)
parsed = datetime.fromisoformat(stamp.replace("Z", "+00:00"))
if parsed.tzinfo is None or parsed.utcoffset() is None:
continue
if not path.startswith("/") or any(char.isspace() for char in path):
continue
if len(status_text) != 3 or not status_text.isascii() or not status_text.isdigit():
continue
status = int(status_text)
if not 100 <= status <= 599:
continue
except ValueError:
continue
valid += 1
client_errors += 400 <= status <= 499
server_errors += 500 <= status <= 599
failures = client_errors + server_errors
return LogReport(len(lines), valid, len(lines) - valid, client_errors,
server_errors, failures / valid if valid else 0.0)
复杂度
设总字符数为 L,单遍解析时间 O(L),计数空间 O(1)(不保存所有行);输入列表本身不算新增空间。生产日志可逐行流式读取,不能对无界输入先 `readlines()`。
易错点与追问
容易失分的边界
- 未经确认把空格 `split()` 当原日志语法,带空格 URL/时间戳会歧义。
- 非法行也进失败率分母却只对合法行统计失败,造成误导。
- 把所有非 2xx 当失败,却仍只报 4xx/5xx 两类。
- 状态码接受 99 或 600,或字符串 `四零四` 被粗糙判断误收。
- 把时间戳去掉时区后无法统一排序与聚合。
继续深挖
- 若日志里 path 可带查询字符串和转义空白,该如何解析?
- 如何按 5 分钟窗口输出失败率并处理乱序事件?
- 大量非法行时如何限速采样并保护敏感 path?
- 若要按 IP 或 API 分组,内存上限与基数爆炸怎么处理?
