OpenCode 实践课:5 分钟用 Python 写出一个命令行日志分析器

项目介绍

后端开发免不了要查日志。每次都要 grep + awk + sort 组合拳打完才能看出一点端倪,效率实在不高。

今天我们用 OpenCode,花 5 分钟写一个命令行日志分析器——输入一个日志文件,自动输出请求量 Top 10 的接口、状态码分布、以及按分钟统计的 QPS。

技术栈:Python 3,纯标准库(collections.Counterargparse),无需安装任何第三方依赖。

最终效果python logz.py access.log --top 5 就能看到清晰的统计报表。

准备工作

  • Python 3.8+
  • OpenCode 已安装并配置好 API Key
  • 一个 Nginx/Apache 格式的 access.log 文件(示例数据见下文)

实践过程

步骤一:搭建命令行骨架

我先用一句话描述需求,不对技术细节做任何干涉:

> 我对 OpenCode 说
> 写一个 Python CLI 工具,名字叫 logz.py。用 argparse 接收两个参数:日志文件路径、--top N 控制输出接口数量(默认 10)。先搭空壳。

OpenCode 读完指令后自动创建了文件,生成以下骨架:

import argparse

def main():
    parser = argparse.ArgumentParser(description="日志分析器")
    parser.add_argument("file", help="日志文件路径")
    parser.add_argument("--top", type=int, default=10, help="显示 Top N 接口")
    args = parser.parse_args()
    print(f"分析文件: {args.file}, Top: {args.top}")

if __name__ == "__main__":
    main()

要点:我没有告诉它用什么库、怎么定义参数,OpenCode 自己选了 Python 社区最主流的方式 argparse。这就是 AI 编程助手的一个核心价值——它帮你做"惯例选择",省去查文档的时间。

步骤二:实现日志解析

下一步我给出更具体的解析规则:

> 我对 OpenCode 说
> 现在实现日志解析。日志格式是 Nginx combined:
> 127.0.0.1 - - [01/Aug/2026:12:00:00 +0800] "GET /api/users HTTP/1.1" 200 1234
> 用正则匹配后,每行解析出 method、path、status、time 四个字段。
> 把解析逻辑放在 parse_log 函数里,返回字典列表。解析失败的行跳过。

OpenCode 输出:

import re
from datetime import datetime

LOG_PATTERN = re.compile(
    r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] '
    r'"(?P<method>\S+) (?P<path>\S+) \S+" '
    r'(?P<status>\d+) \d+'
)

def parse_log(filepath):
    rows = []
    with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
        for line in f:
            m = LOG_PATTERN.search(line)
            if m:
                rows.append({
                    'method': m.group('method'),
                    'path': m.group('path'),
                    'status': m.group('status'),
                    'time': m.group('time'),
                })
    return rows

我把这段塞进 logz.py,用之前准备好的示例日志跑了 python logz.py sample.log,确认解析无误。

要点:正则表达式这类"写一次调一天"的东西,交给 AI 生成最划算。你只需要描述格式,不用自己手撸 \S+

步骤三:Top N 接口统计

解析完日志下一步就是统计各项指标:

> 我对 OpenCode 说
> 写一个 analyze 函数,接收 parse_log 返回的列表。
> 1. 用 Counter 统计每个 path 的出现次数,按 top 参数截取
> 2. 用 Counter 统计 status 分布
> 3. 按分钟粒度统计 QPS(取出时间戳前 17 个字符 [01/Aug/2026:12:00 作为桶)
> 4. 返回一个包含这三项结果的字典

OpenCode 给出的实现简洁直接:

from collections import Counter

def analyze(rows, top_n=10):
    path_counts = Counter(r['path'] for r in rows)
    status_counts = Counter(r['status'] for r in rows)

    minute_buckets = Counter(
        r['time'][:17] for r in rows if len(r['time']) >= 17
    )

    return {
        'top_paths': path_counts.most_common(top_n),
        'status_dist': status_counts,
        'qps_by_minute': sorted(minute_buckets.items()),
        'total_requests': len(rows),
    }

要点Counter 配合生成器表达式是 Python 里最 Pythonic 的写法。我自己写可能会下意识用 for 循环加 dict,而 OpenCode 直接给出了最地道的方案——这也是它的隐性优势:总能产出符合社区最佳实践的代码。

步骤四:格式化输出

分析结果有了,最后一步是把它打印得像样:

> 我对 OpenCode 说
> 写一个 print_report 函数,接收 analyze 的返回值,用表格形式输出。
> Top 接口用序号 + 路径 + 次数 + 占比显示,status 分布用一行中文标签展示,
> QPS 用"分钟 -> 请求数"呈现。总请求数放在顶部。

OpenCode 的输出:

def print_report(result, args):
    total = result['total_requests']
    print(f"\n{'='*60}")
    print(f"  日志分析报告")
    print(f"{'='*60}")
    print(f"  文件: {args.file}")
    print(f"  总请求数: {total}")
    print(f"{'='*60}\n")

    print(f"  📊 请求量 Top {args.top} 接口")
    print(f"  {'序号':<6}{'接口':<40}{'次数':<8}{'占比'}")
    for i, (path, cnt) in enumerate(result['top_paths'], 1):
        pct = cnt / total * 100
        print(f"  {i:<6}{path:<40}{cnt:<8}{pct:.1f}%")

    print(f"\n  📋 状态码分布")
    for code in sorted(result['status_dist']):
        cnt = result['status_dist'][code]
        print(f"  {code}: {cnt} ({cnt/total*100:.1f}%)")

    print(f"\n  ⏱️  每分钟 QPS")
    for minute, cnt in result['qps_by_minute']:
        bar = '█' * (cnt // 10)
        print(f"  {minute}: {cnt:>5}  {bar}")
    print()

要点:我对输出格式只给了粗略描述,OpenCode 自动加上了 :{'='*60} 这样的分隔线,还顺手画了 QPS 柱状图——这些"UI 美化"超出了我的指令,这就是 AI 的主动性。你把它们留着也好,删掉也行,零成本。

步骤五:组装与调通

最后我把所有函数串进 main()

> 我对 OpenCode 说
> 在 main 里串联:parse_log → analyze → print_report。加上 try/except 处理文件不存在的错误。

补完后完整代码如下。

完整代码

以下是最终版 logz.py,共计 102 行

import argparse
import re
from collections import Counter

LOG_PATTERN = re.compile(
    r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] '
    r'"(?P<method>\S+) (?P<path>\S+) \S+" '
    r'(?P<status>\d+) \d+'
)


def parse_log(filepath):
    rows = []
    with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
        for line in f:
            m = LOG_PATTERN.search(line)
            if m:
                rows.append({
                    'method': m.group('method'),
                    'path': m.group('path'),
                    'status': m.group('status'),
                    'time': m.group('time'),
                })
    return rows


def analyze(rows, top_n=10):
    path_counts = Counter(r['path'] for r in rows)
    status_counts = Counter(r['status'] for r in rows)

    minute_buckets = Counter(
        r['time'][:17] for r in rows if len(r['time']) >= 17
    )

    return {
        'top_paths': path_counts.most_common(top_n),
        'status_dist': status_counts,
        'qps_by_minute': sorted(minute_buckets.items()),
        'total_requests': len(rows),
    }


def print_report(result, args):
    total = result['total_requests']
    print(f"\n{'='*60}")
    print(f"  日志分析报告")
    print(f"{'='*60}")
    print(f"  文件: {args.file}")
    print(f"  总请求数: {total}")
    print(f"{'='*60}\n")

    print(f"  请求量 Top {args.top} 接口")
    print(f"  {'序号':<6}{'接口':<40}{'次数':<8}{'占比'}")
    for i, (path, cnt) in enumerate(result['top_paths'], 1):
        pct = cnt / total * 100
        print(f"  {i:<6}{path:<40}{cnt:<8}{pct:.1f}%")

    print(f"\n  状态码分布")
    for code in sorted(result['status_dist']):
        cnt = result['status_dist'][code]
        print(f"  {code}: {cnt} ({cnt/total*100:.1f}%)")

    print(f"\n  每分钟 QPS")
    for minute, cnt in result['qps_by_minute']:
        bar = '█' * (cnt // 10)
        print(f"  {minute}: {cnt:>5}  {bar}")
    print()


def main():
    parser = argparse.ArgumentParser(description="Nginx 日志分析器")
    parser.add_argument("file", help="日志文件路径")
    parser.add_argument(
        "--top", type=int, default=10, help="显示 Top N 接口 (默认 10)"
    )
    args = parser.parse_args()

    try:
        rows = parse_log(args.file)
    except FileNotFoundError:
        print(f"错误: 文件 '{args.file}' 不存在")
        return

    if not rows:
        print("未解析到任何日志行,请检查文件格式")
        return

    result = analyze(rows, args.top)
    print_report(result, args)


if __name__ == "__main__":
    main()

运行方式:

python logz.py access.log
python logz.py access.log --top 5

小结

从搭空壳到完整可运行,这 102 行代码的每一个函数、每一段正则,都是我对 OpenCode 说一句话后自动生成的。整个过程我没打开过一次 Python 文档,也没写过一行正则表达式。

效率上,手写这个工具从构思到调通大约需要 30 分钟(查 Nginx 日志格式、调正则、查 Counter API),而用 OpenCode 完成只花了不到 5 分钟。差距不在打字速度,在"上下文切换"——你不需要跳出思考去做搜索、读文档、试错,这些全部被 AI 消化了。

如果你想复现这套工作流,记住一个原则:用自然语言描述"输入 → 处理 → 输出",AI 负责补全中间的技术细节。别去纠结该用哪个库、怎么写正则,这些正是 AI 最擅长的事。