调查日志已经消失的那段时间
目标
收到“昨天下午有几次失败”的问题反馈,但该时间段的日志 不存在。 使用日志之外的其他痕迹缩小时间范围,并进一步确保下次能够留下日志。
环境
在 /root/nolog 下工作。现场由你亲自创建——这是准备工作,不是任务。
mkdir -p /root/nolog/logs /root/nolog/etc && cd /root/nolog
python3 - <<'PY'
import sqlite3, random, datetime
random.seed(11)
base = datetime.datetime(2026, 9, 7, 12, 0, 0)
con = sqlite3.connect('app.db'); cur = con.cursor()
cur.execute("create table orders(id integer primary key, status text, created_at text)")
rows, oid = [], 1
for m in range(240):
t = base + datetime.timedelta(minutes=m)
fail = random.randint(18, 26) if 123 <= m <= 126 else (1 if random.random() < 0.15 else 0)
for _ in range(random.randint(8, 14)):
rows.append((oid, 'PAID', (t + datetime.timedelta(seconds=random.randint(0, 59))).isoformat())); oid += 1
for _ in range(fail):
rows.append((oid, 'FAILED', (t + datetime.timedelta(seconds=random.randint(0, 59))).isoformat())); oid += 1
cur.executemany("insert into orders values (?,?,?)", rows); con.commit(); con.close()
with open('logs/access.log', 'w', encoding='utf-8') as f:
for m in range(240):
if 123 <= m <= 127: continue
t = base + datetime.timedelta(minutes=m)
for i in range(random.randint(5, 9)):
f.write('%s GET /api/pay 200\n' % (t + datetime.timedelta(seconds=i * 6)).isoformat())
with open('logs/error.log', 'w', encoding='utf-8') as f:
for m in range(130, 240):
if random.random() < 0.2:
f.write('%s WARN slow query 1200ms\n' % (base + datetime.timedelta(minutes=m)).isoformat())
PY
printf 'pool_size=2\ntimeout=1\n' > etc/app.conf
printf 'log_level=WARN\n' > etc/other.conf
touch -t 202609071402 etc/app.conf
touch -t 202608311000 etc/other.conf
ls -l --time-style=+%Y-%m-%dT%H:%M etc/
生成的内容如下。
app.db 주문 2,000여 건 (status, created_at)
logs/access.log 접근 로그 — 사고 구간이 비어 있다
logs/error.log 오류 로그 — 보존 기간 때문에 뒷부분만 남았다
etc/app.conf 설정 파일
etc/other.conf 설정 파일
要创建的文件
minute.txt 사고가 시작된 분과 그 근거
gap.txt 비어 있는 구간과, 로그가 사라진 범위
traces.txt 로그가 아닌 흔적 세 가지 이상
cause.txt 의심 대상과 배제한 후보
watch.sh 지금 진행 중인 문제에 붙일 사후 계측기
next.md 다음 조사를 줄이는 네 가지
report.md 정리
步骤
- 创建现场。
- 数据就是日志。 将
created_at按分钟分组, 找出失败集中的分钟。还必须记录日常值,才能证明“集中”。 - 在访问日志中找出空白区间,并写明错误日志从什么时间开始保留。 只有知道消失的范围,才能确定调查范围。
- 收集至少三种日志之外的痕迹。除了文件修改时间, 还必须从启动、进程、软件包、证书中至少选择一种。
- 对照时间缩小候选范围。还要写明已排除的候选项及其依据。
watch.sh——定期记录时间和观测值。评分程序会直接运行它。next.md——具体写明日志语句、保留期限、关联 ID、指标这四项。- 进行总结。
参考
第 2 步中要指出的分钟不止一个。失败集中区间持续了数分钟, 因此指出其中任何一分钟都可以——但必须同时写出该分钟的数量和日常值。
第 5 步中,时间吻合表示的是相关性,而不是因果关系。将这一区别 写入文档,可以避免日后回滚错误对象。
创建日志消失的现场
创建现场。
原样运行说明中的准备代码块。错误日志从事故区间开始缺失,是本练习的前提。
数据就是日志
数据就是日志。 将 created_at 按分钟分组,
找出失败集中的分钟。还必须记录日常值,才能证明“集中”。
将 created_at 按分钟(substr(created_at,1,16))分组并统计 FAILED。数值突增的分钟就是事故时间。还要同时记录日常值,才能进行比较。
不存在本身也是证据
在访问日志中找出空白区间,并写明错误日志从什么时间开始保留。 只有知道消失的范围,才能确定调查范围。
按分钟统计访问日志,就能看到行数为 0 的区间。还要确认错误日志第一行是什么时间——只有知道消失到什么范围,才能确定调查范围。
收集日志之外的痕迹
收集至少三种日志之外的痕迹。除了文件修改时间, 还必须从启动、进程、软件包、证书中至少选择一种。
至少收集以下三种:文件修改时间(ls -l --time-style)、启动时间(uptime -s)、进程启动时间(ps -eo lstart)、软件包安装记录(/var/log/dpkg.log)。痕迹必须连同时间一起记录,才能进行对照。
对照时间缩小范围
对照时间缩小候选范围。还要写明已排除的候选项及其依据。
找出事故开始时间之前刚刚发生变化的对象。还要写明已排除的候选项及其依据,并注明时间吻合只是相关性,而不是因果关系。
如果问题仍在发生,就追加事后测量
watch.sh——定期记录时间和观测值。评分程序会直接运行它。
在问题复现期间定期记录时间和观测值。即使粗糙,也胜过完全没有数据,并可能成为下一次会议的唯一依据。评分程序会直接运行该脚本。
下次要比这次更快
next.md——具体写明日志语句、保留期限、关联 ID、指标这四项。
需要写明日志语句、保留期限、关联 ID 和指标四项。必须具体到“将什么设为多少”才能执行——还要注明当前保留期限为 24 小时。
总结
进行总结。
写明事故时间、没有日志这一事实、怀疑对象和防止复发的措施。不要遗漏本次调查的核心:“不存在本身也是证据”。