第 06 课 · 实验2:网络日志分类与统计工具
完成你的网络日志统计工具
把第三课保存的日志交给第五课学过的函数。亲自组织筛选、计数、排名与阈值判断,让程序回答:哪些事件多、哪些来源常见、多少记录需要关注耗时?
完成后:运行自己的日志统计程序,并用不同输入解释每一项结果
看清知识之间的关系
沿着学习路线,把知识点连成完整任务。点击知识点,可以跳到对应内容。
先认清数据与任务
明确输入、函数分工和统计对象
逐步完成并解释工具
亲自编写、运行、比较,再整理成果
开始前准备好
- 接着上课做
- select_valid 只留下有效记录;本课扩展为同时保留两组记录。
- 亲自写什么
- 三个任务:分组函数、分析函数、主程序中的调用。
- 最后得到什么
- 能更换日志和参数的工具,以及能解释结果的实验报告。
看图设计,动手完成
把已有函数组合成一件作品
先看清要回答的问题,再动手组织程序。沿主线找到第五课的函数在哪里发挥作用,再找到无法使用的记录被保留在哪里。
手机上可左右滑动;点击图片可以查看完整图一份报告,回答四个问题
所有频次、排名与耗时统计都使用有效记录。
左右滑动表格,可查看完整内容
| 想知道什么 | 程序交回什么 |
|---|---|
| 有多少记录可以使用 | 总数、有效数、无效数;无效记录单独保留 |
| 什么出现得多 | 级别、IP、设备、事件四种频次 |
| 来自多少不同来源 | 不同 IP 数、不同设备数 |
| 哪些结果值得继续观察 | IP 与事件 Top-K、耗时达到阈值的数量 |
先预测熟悉的八条日志
回看第五课的数据,暂时不写新代码。
- 写下总数 8、有效 6、无效 2,并指出两条无效记录。
- 说出 INFO 3、WARN 1、ERROR 2 是从哪一组记录统计的。
- 给“次数最多的 IP”和“不同 IP 数”各写一句解释。
下载第六课实验材料
解压后,在 VS Code 选择“文件 → 打开文件夹”,打开 lesson-6-exercises。保留完整文件夹和 data 子文件夹。
- 读取程序和第五课工具函数已提供;三个编程任务逐步完成。
- 包含熟悉的八条日志、综合练习日志和边界输入。
- 不需要额外安装程序库。
看图设计,动手完成
接入第三课保存的日志
先确认程序读到了什么,再让这些数据参加统计。耗时原来是文件中的文字,读取后变成数值;单位始终是毫秒。
手机上可左右滑动;点击图片可以查看完整图本课使用配套读取程序
load_logs 接收日志文件名,返回 records 和 unreadable_lines。前者是字段完整、类型确定的记录;后者保留无法转换的行号、原文和原因。这里先使用它,下一课开始学习具体的读取与转换方法。
先选择哪份数据
每次只选一份输入,原始文件保持不变。
左右滑动表格,可查看完整内容
| 文件 | 用途 |
|---|---|
| data/familiar.log | 第四、第五课的八条日志,用来确认方法 |
| data/network.log | 先看随附的实验1三条演示日志,再用本人第三课文件的副本替换 |
| data/practice.log | 24行综合练习,比较多个来源与事件 |
| data 中的边界文件 | 观察空输入、无效记录和阈值附近的结果 |
读取与观察
- 01
打开终端
这样做在 VS Code 左侧右击 lesson-6-exercises 文件夹,选择“在集成终端中打开”。确认当前位置能看到 main.py 和 data 文件夹。
完成后应看到
命令从本课文件夹开始运行。
- 02
读取熟悉的八条日志
这样做运行 python observe_input.py data/familiar.log。命令中程序名后面的内容是本次传入的文件名,配套程序会接收它。
完成后应看到
八行都能转换成字典;这还不表示八条都符合有效规则。
- 03
换成本人的日志
这样做保留第三课原文件,将它的副本复制到本课 data 文件夹并命名为 network.log,再运行 python observe_input.py data/network.log。
完成后应看到
第一条记录的六项信息与本人原始文件一致;不要把随附演示数据当作本人结果。
records, unreadable_lines = load_logs(filename)
print(f"可转换记录:{len(records)} 条")
print(f"无法转换行:{len(unreadable_lines)} 行")filename 已由运行命令给出。下面是观察程序中已有的几行,不用再次粘贴;此时不需要完成分析函数。

先看转换数量,再对照第一条记录;未知级别和负耗时仍要交给下一步判断。
手机上可左右滑动;点击图片可以查看完整图看图设计,动手完成
先确定谁调用谁
沿用第五课的模块分工,再增加负责组合任务的分析函数。蓝线表示发起调用,返回线回到各自的调用处。工具函数之间不会自动接力执行。
手机上可左右滑动;点击图片可以查看完整图四个文件,各自负责一件事
打开文件顶部的说明,找出本课三个需要完成的位置。
左右滑动表格,可查看完整内容
| 文件 | 负责什么 | 你要做什么 |
|---|---|---|
| log_loader.py | 读取文字,返回记录与无法转换的原始行 | 使用已提供的函数,说明输入与返回 |
| log_tools.py | 判断、分组、计数、排名 | 任务1:亲自定义并编写 split_records |
| analyzer.py | 把多个统计步骤组织成一次分析 | 任务2:亲自定义并编写 analyze_logs |
| main.py | 选择输入、发起分析、显示结果 | 任务3:连接调用,传入阈值和 K |
一个函数,也能交回很多项结果
analyze_logs 返回一个字典:每个键给结果起名字,例如 level_counts 对应级别次数,slow_count 对应达到阈值的数量。先完成内部各项统计,再把结果交给 main;显示文字由 main 负责。
先画自己的处理顺序
在实验报告第五部分画流程图,或写一段伪代码。
- 写出读取、分组、四类计数、去重与耗时判断、排名、返回、显示的顺序。
- 用箭头标出 main 与 analyze_logs 的调用和返回。
- 给 split_records、count_by_field、analyze_logs 各写一句职责,并说明输入和返回。
看图设计,动手完成
保留两组记录,完成四种统计
从第五课自己写的筛选函数出发,逐步扩展同一个工具。新建的是两份分类列表;原始记录仍然保留。
手机上可左右滑动;点击图片可以查看完整图任务1 · 编写 split_records
打开 log_tools.py,找到分组任务注释,用第五课 select_valid 的思路写出函数定义和函数体。
- 定义 split_records(records),每次调用先新建 valid_records 和 invalid_records 两个空列表。
- 用 for 逐条调用 is_valid;按结果追加到对应列表,不删除原记录,也不改写字段。
- 循环结束后按有效、无效的顺序返回两个列表,并写一句函数说明。
- 保存后运行 python observe_steps.py split data/familiar.log,应得到 6 条有效、2 条无效。split 表示观察分组步骤。
# 在这里自己定义 split_records(records)。
# 功能:按本课有效规则,把记录分到两个新列表中。
# 输入:读取工具已转换好的完整记录列表。
# 返回:valid_records, invalid_records,一次返回含两个列表的元组。
# 保持原来的先后顺序,不增删输入列表,不修改其中字典。
# 空输入返回两个空列表;全无效输入也必须保留全部无效记录。
# 升级第五课的 select_valid:这一次同时保留没有通过规则的记录。在下载文件对应位置完成这个函数。is_valid 已在同一文件中定义,可直接调用;其余工具函数保留。

观察 UNKNOWN 和负耗时的记录被保留到无效组,后面的记录仍会继续处理。
手机上可左右滑动;点击图片可以查看完整图四种频次都使用同一组有效记录,各自次数相加都应等于 6。
手机上可左右滑动;点击图片可以查看完整图任务2 · 先完成分析函数的前半段
打开 analyzer.py,定义 analyze_logs(records, threshold_ms=1000, k=3),接住分组结果,再完成计数与去重。
- 文件已用 import log_tools 导入工具模块;先调用 log_tools.split_records,再用四次 log_tools.count_by_field 分别传入 log_level、source_ip、device、event。
- 在函数内用 set() 新建两个集合。遍历有效记录时,ip_names.add(record["source_ip"]) 把 IP 放入集合;已有值不会增加集合长度。设备同样处理,len 得到种类数。
- 保留 valid_records 与 invalid_records,后面要将它们与各项统计一起返回。
- 这一段完成后继续下一模块;排名、阈值和返回结果全部具备后,再运行完整分析。
import log_tools
# 在这里自己定义 analyze_logs(records, threshold_ms=1000, k=3)。
# 输入:读取工具已经转换好的完整记录列表、耗时阈值和 K。
# 任务1:调用 log_tools.split_records,接住有效与规则无效的两个列表。
# 任务2:只对有效记录调用 count_by_field,统计级别、IP、设备和事件。
# 任务3:统计不同 IP、不同设备的数量;统计达到耗时阈值的有效记录。
# 任务4:用 top_k 得到 IP 和事件排名,次数并列沿用首次出现顺序。
# 任务5:返回一个字典,键名按下面的约定填写:
# valid_records, invalid_records, level_counts, ip_counts,
# device_counts, event_counts, unique_ip_count, unique_device_count,
# ip_top, event_top, slow_count。
# 不修改传入记录,不读取文件,不在分析函数里打印整份报告。
# 空输入也返回完整字典:列表与排名为空列表,计数为空字典,数量为 0。
if __name__ == "__main__":
print("请先完成 log_tools.py 中的 split_records,再写出本文件的 analyze_logs。")
print("函数写好后,运行 observe_steps.py counts data/familiar.log 观察结果。")按注释分段完成同一个函数,不把短例另存成同名模块。结果字典的键名要与 main.py 的显示函数一致。
看图设计,动手完成
加入排名与耗时判断
把计数字典交给 Top-K,把每条有效记录的耗时交给 is_slow。排名比较出现次数;阈值比较毫秒数。不要把频次最多直接解释成设备故障。
手机上可左右滑动;点击图片可以查看完整图任务2 · 补齐分析函数并返回
继续编辑 analyzer.py,使用已有的 ip_counts、event_counts 和 valid_records。
- slow_count 从 0 开始;遍历有效记录,调用 log_tools.is_slow(record["duration_ms"], threshold_ms),成立时加 1。
- 逐条处理结束后,调用 log_tools.top_k(ip_counts, k) 与 log_tools.top_k(event_counts, k),保存两份排名。
- 按下载文件列出的键名组织结果字典,放在所有循环之后返回。
- 不要在统计函数内部修改原级别;这里只观察有效记录是否达到阈值。
把返回结果放对位置
一个结果字典包含这些有名字的项目。
左右滑动表格,可查看完整内容
| 字典中的键 | 值的含义 |
|---|---|
| valid_records / invalid_records | 两组记录列表 |
| level_counts / ip_counts / device_counts / event_counts | 四种频次字典 |
| unique_ip_count / unique_device_count | 两种集合的长度 |
| ip_top / event_top | 两份前 K 项排名 |
| slow_count | 有效记录中耗时大于等于阈值的数量 |
任务3 · 让主程序接上分析函数
打开 main.py,保留已提供的读取与 show_report 显示函数,按注释连接两个调用。
- 调用 analyzer.analyze_logs,将 records、threshold_ms 和 k 传入;用 report 接收返回字典。
- 把 report 与读取结果交给 show_report,替换末尾三行读取提示;保存所有文件。
- 运行 python main.py data/familiar.log 1000 3。文件名后面依次是毫秒阈值和K,由配套程序接收。
- 运行 python main.py data/familiar.log 1500 3:慢记录从 3 条变为 2 条,四种频次保持不变。
def main(filename="data/practice.log", threshold_ms=1000, k=3):
records, unreadable_lines = load_logs(filename)
# 完成两个自写函数后,用下面两次调用替换末尾的三行提示。
# report = analyzer.analyze_logs(records, threshold_ms, k)
# show_report(filename, records, unreadable_lines, report, threshold_ms, k)
print(f"已读取 {filename}:可转换 {len(records)} 条,无法转换 {len(unreadable_lines)} 行。")
print("请完成 split_records 与 analyze_logs,再在 main 中连接分析和显示。")
print("当前只完成读取,还没有产生分类统计结果。")只改文件中 main 的对应位置,保留已提供的导入、show_report 和文件末尾入口。入口接收命令参数后调用 main;两个函数都完成后,再运行完整报告。

分析函数写好后,可运行 python observe_steps.py counts data/familiar.log 单独观察计数。IP 出现三次,仍只代表一个来源。
手机上可左右滑动;点击图片可以查看完整图
用 python observe_steps.py ranking data/familiar.log 1000 3 单独观察排名,再把1000改为1500比较耗时数量。
手机上可左右滑动;点击图片可以查看完整图看图设计,动手完成
换一组输入,还能得到合理结果吗
先写预期,再运行;每次只改变一个因素,找到结果变化的原因。至少完成这些比较
在报告第六部分记录输入、参数、预期、实际和解释。
左右滑动表格,可查看完整内容
| 输入或改变 | 应观察到什么 | 说明什么 |
|---|---|---|
| 熟悉的八条日志 | 总数8,有效6,无效2;四种频次各自合计6 | 各项统计都从同一组有效记录出发 |
| 空日志文件 | 总数0;频次字典和排名为空,种类数与慢记录数为0 | 不进入循环也能交回完整结果 |
| 全是未知级别或负耗时 | 有效0,记录全部保留在无效组 | 无效记录不混入频次与排名 |
| 999 / 1000 / 1001毫秒 | 1000阈值下,后两条达到阈值 | 相等也满足 >= |
| K为0;K超过项目数 | 返回空排名;返回已有的全部项目 | 不会凭空产生项目 |
| 次数并列 | 按有效日志中首次出现的顺序排列 | 排名规则前后一致 |
| 同一输入连续调用两次 | 两次结果相同,原输入内容不变 | 每次调用独立建立结果 |
| 耗时文字无法转换或字段不足 | 保留原文和原因,计入无效总数 | 不能只统计成功转换的行 |
总数怎样对得上
原始行数 = 已转换记录数 + 无法转换的行数。已转换记录再分成有效与规则无效两组。因此报告中的无效总数,要同时包含规则无效和无法转换;四类频次只统计有效记录。

完成分析函数后运行 python check_cases.py。它会调用你的函数并显示比较结果;仍需逐行解释为什么符合预期。
手机上可左右滑动;点击图片可以查看完整图结果不对时,从这里开始
使用相同输入重跑,确认修改是否解决了原问题。
左右滑动表格,可查看完整内容
| 看到的现象 | 回到哪里 | 怎样调整 |
|---|---|---|
| 只统计到第一条记录 | 分组或计数函数的 return | 把整组处理的 return 移到循环结束之后 |
| 有效数与频次合计不同 | count_by_field 的输入 | 四次都传 valid_records,不传未筛选的 records |
| 同一输入重跑后数量累计 | 列表、集合和字典的建立位置 | 每次调用都在函数体内新建结果容器 |
| 总数少了几行 | unreadable_lines 与无效总数 | 保留无法转换的行,并在主程序显示数量与原因 |
| 接到 None,不能显示统计 | 尚未完成的函数或返回语句 | 确认分析函数所有步骤已完成,最后返回结果字典 |
| 找不到模块或文件 | 打开的文件夹与输入名称 | 打开完整实验文件夹,保留四个核心文件与 data;不要命名为 collections.py |
解释一次自己的修正
从本人运行中选一处问题,记录现象、原因、修改与重跑结果。
- 先保存原输入与实际现象,再修正代码,不改数据来迁就答案。
- 如果基础数据已正确,换综合数据和边界输入继续比较;如实记录结果。
- 任选一条无效记录,说明它为什么不进入统计、原文仍保存在哪里。
看图设计,动手完成
运行完整工具,展示你的解释
从熟悉数据走向综合练习,再回到自己的原始日志。完成三次完整运行
- 01
确认熟悉数据
这样做运行 python main.py data/familiar.log 1000 3。
完成后应看到
总数8、有效6、无效2;INFO3、WARN1、ERROR2;不同IP和设备各3,慢记录3。
- 02
分析综合日志
这样做运行 python main.py data/practice.log 1000 3。找出频次最多的IP与事件,并解释并列结果。
完成后应看到
全部24行都有去处;四种频次各自合计等于有效数,无法转换的原始行能找到原因。
- 03
分析本人日志
这样做确认 data/network.log 已换成本人日志副本,运行 python main.py data/network.log 1000 3,保存自己的完整输出截图。
完成后应看到
结果与本人记录对应。日志较少时可以解释排名不满K项,不能据此推断整个网络的运行状况。

从一个数字反向找到它的输入、判断规则和函数;用你自己的运行结果填写报告。
手机上可左右滑动;点击图片可以查看完整图实验报告下载与提交
从模板开始,把自己的函数设计、统计结果和运行分析整理成一份完整报告。
- 下载模板
使用本课实验2报告模板,补充封面个人信息。
- 完成报告
从第四部分开始填写,将本人统计结果、运行截图和分析放入正文。
- 提交实验2
选择本人班级和实验2,核对姓名、学号,上传一份报告。
在新窗口打开 WPS 表单,登录后填写。
报告填写提示
沿用实验1的报告版式。补充个人信息,从第四部分开始填写本人环境、设计、操作、结果与思考。
- 第五部分:流程图或伪代码、函数职责与主要操作。
- 第六部分:四种频次、Top-K、阈值结果,以及正常、边界、异常输入的预期与实际比较。
- 本人截图和真实问题修正写入正文;使用AI时说明用途和自己的验证方法。
确认你的学习成果
整理源代码、原始日志与实验报告;保留可重新运行的完整文件夹。
- 三个编程任务由自己完成,能说出每个函数的输入、返回及调用位置。
- 四种统计、两种去重、两份Top-K、耗时判断都能从有效记录解释。
- 无效记录与无法转换的原始行均保留,原日志没有被统计函数改写。
- 报告包含流程图或伪代码、函数职责、实际结果、输入比较与本人截图。
把本课进度保存下来
登录后可以保存浏览内容、有效学习时间、完成状态和答题参与记录。
