アクセスログを空白で区切って集計したら、10言語すべてが壊れた行を数えた ― よく使う書き方を10言語で④

C#
B!

Web サーバーのアクセスログを読んで、ステータスごとの件数と、応答の遅いリクエストの上位3件を出す。障害の調べものでよく書く、小さなツールです。いちばん手軽な「空白で区切って何番目かを取る」書き方を10言語で用意して、同じログを読ませました。

ステータスが 2OO(数字のゼロのつもりが英字のオー)の壊れた行を、10言語すべてが読めた行として数え、2OO 1 と集計しました。

何をしたか

CODE Polyglot の「よく使う書き方」 で、ログの1行の形と出力を先に決めました(正解表8件)。

203.0.113.5 - - [30/Oct/2026:09:00:01 +0900] "GET /index.html HTTP/1.1" 200 5120 0.012
  • 形に合わない行は「読めない行」として数える。ステータスは3桁の数字、応答秒は「数字.数字」
  • 遅い順は、応答秒の大きい順。同じ秒なら先に出た行を先に
  • 改行が \r\n でも、最後に改行が無くても同じ答えにする

よく見かける書き方は、空白で区切って11個あれば読めた行とするものです。Python ならこうです。

for line in sys.stdin:
    p = line.split()
    if len(p) != 11:
        bad += 1
        continue
    rows.append({"method": p[5].lstrip('"'), "path": p[6], "status": p[8], "sec": p[10]})

落ちたところ

ケース落ちた言語何が起きたか
c002 壊れた行が混ざる10言語中10言語区切った数が11個なので、2OO の行も読めた行になった
c003 応答秒の桁数が違うPython・Kotlin・Swift・Java(10言語中4言語)応答秒を文字のまま並べ、10.5 が 9.8 より小さくなった
c005 改行が \r\nJavaScript・C#・Rust(10言語中3言語)\r が行に残り、出力に混ざった。Rust は数への変換に失敗して止まった
c004 同じ応答秒が4件Ruby(10言語中1言語)sort_by のあとの reverse で、同じ秒の行の順が逆になった
c001・c005・c008Java件数を HashMap に入れて回したので、ステータスが 200, 401, 500, 404 の順に出た

"10.5" と "9.8" を文字として比べると、1文字目の 1 と 9 で決まるので、10.5秒の方が「小さい」ことになります。PHP の <=> は、数字だけの文字列どうしなら数として比べるので、同じ書き方でもたまたま正しく並びました。

同じ秒の並び順は、並べ替えが「安定」(同じ値の順番を変えない)かどうかで決まります。Python の sorted、Java の List.sort、Kotlin の sortedByDescending は安定です。Go の sort.Slice と C# の List.Sort は安定と決まっていませんが、今回の4件では差が出ませんでした(要素が少ないと挿入ソートになるため)。件数が増えると順番が入れ替わることがあります。

どう書いたか

行全体が決めた形に合うかを正規表現で確かめ、応答秒は数にしてから比べました。Python ならこうです(抜粋)。

LOG = re.compile(r'\S+ \S+ \S+ \[[^\]]+\] "(\S+) (\S+) [^"]+" ([0-9]{3}) ([0-9]+|-) ([0-9]+\.[0-9]+)')
for line in sys.stdin.read().split("\n"):
    line = line.rstrip("\r")
    m = LOG.fullmatch(line)
    ...
# sorted は安定(同じ値なら元の順)。reverse=True でも同じ秒どうしの順は崩れない
for sec, method, path, _ in sorted(rows, key=lambda r: float(r[0]), reverse=True)[:3]:

Rust は標準に正規表現が無いので、 [・] "・"・空白の順に切り分けて形を確かめています。安定と決まっていない並べ替えしか無い言語(Swift・Ruby の sort_by)では、何行目かを2つ目のキーにしました。10言語のコードは、どれも8件すべてで決まりどおりの答えを出しています。

まとめ

  • 空白で区切って数を数えるだけでは、壊れた行を見分けられない。使う欄の形を確かめる
  • 数として並べる値は、数にしてから比べる
  • 「同じ値の順」を決めているなら、安定な並べ替えを使う(または行番号を2つ目のキーにする)

10言語のコードと全8件の結果は CODE Polyglot「アクセスログを集計する」 にあります。

連載「よく使う書き方を10言語で」(全6回)
  1. 第1回:HTML のエスケープ、標準の関数の答えが言語ごとに違った
  2. 第2回:「2026-02-30T09:00:00Z」を、4言語の標準のパーサーが3月2日として読んだ
  3. 第3回:「getHTTPResponseCode」をスネークケースに、よく見かける書き方は10言語中9言語で崩れた
  4. 第4回:アクセスログを空白で区切って集計したら、10言語すべてが壊れた行を数えた(この記事)
  5. 第5回:重複を消したら順番が変わった、Swift は「が」を1つにまとめた
  6. 第6回:İstanbul の slug が5通りに割れた、日本語のタイトルはそのまま URL に入った
B!
← 一覧へ戻る