0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

専用ログを読むためだけのPHP CLIを作った話 ― 2014年の小さな自動化を今振り返る

0
Posted at

はじめに

2014年ごろ、組み込み機器の開発で使うログを、確認しやすい形へ変換するための小さなPHPスクリプトを何本か作りました。

この記事は、PHPの基本構文を読める初心者向けの振り返りです。ログ解析全般ではなく、決まった形式のテキストをCLIで整形する狭い範囲に絞り、一部の実装詳細は省略します。

いま見返すと、1本あたり約30行です。フレームワークも、クラスも、設定ファイルもありません。入力ファイルを1行ずつ読み、区切り文字で分解し、必要な形で標準出力へ出すだけです。

いわゆる「誰得ツール」です。

何分短縮できたか、何回使ったかは、もう覚えていません。特定の大きな不具合を見つけた主役でもありませんでした。目的はもっと素朴で、日々の作業を少し便利にすることでした。

立派なアプリケーションを作るほどではないものの、目視とコピー&ペーストを続けるのは面倒です。その狭い隙間を埋めるための、便利Tipsに近いコードでした。

この記事では、当時のスクリプトが何をしていたのかを振り返り、公開できる形に抽象化して作り直します。

この記事の入力例、名前、メッセージは説明用に新しく作った架空データです。製品の実ログ、製品名、ソースコード上の識別子は掲載していません。

この記事の前提

  • 対象読者: 手作業のテキスト整形を小さなCLIで自動化したい方
  • 前提知識: PHPの変数、配列、条件分岐、ファイル読み込みの基礎
  • 経験時点: 元のスクリプトは2014年ごろ
  • 2026年時点の補足: 書き直した例はPHP 8以降を対象とし、元の実行環境を再現するものではありません
  • 対象外: 汎用ログ収集基盤、巨大ファイルの高速処理、複数行にまたがるレコード

なぜPHPだったのか

現在なら、この種のテキスト処理を見てPythonを思い浮かべる方も多いと思います。しかし、当時はまだPythonが社内で一般的ではなく、PHPのほうが身近で、社内にも根付いていました。

最適な言語を比較して選んだというより、必要な処理を、その場で使える言語ですぐ形にしたという判断です。

過去のツールを現在の定番だけで評価すると、「なぜPythonを使わなかったのか」という話になりがちです。しかし、道具の選択は、言語機能だけでなく、当時の社内環境、既存資産、実行できるOS、周囲が読めるかどうかにも左右されます。

先に結論

この小さなツールから持ち帰れるのは、次の3点です。

  1. 一度しか使わないように見えても、同じ整形を繰り返すなら自動化の候補になる
  2. 使い捨てツールほど、入力形式と異常行の扱いを決めておく
  3. 過去の業務コードを公開するときは、伏せ字にせず、架空データと新しい実装で再構成する

当時のコードは目的を果たしました。一方で、入力チェック、文字コード、エラー終了、テストという点では、公開サンプルにそのまま使える品質ではありません。

「昔のコードをきれいに見せる」のではなく、当時なぜ必要だったかと、いまならどこを直すかをセットで残すのが、今回の記事化の方針です。

何を自動化していたのか

残っていたPHPスクリプトは、大きく2種類に分かれます。

定義一覧から確認用のメッセージを作る

入力は、呼び出し元、項目名、値の種類などをタブで区切った一覧です。

Sensor::setThreshold()	temperature	number
Network::open()	socket	system_error
Storage::write()	path	text

これを、レビューや確認で読みやすいメッセージへ変換します。

*ERR* [Sensor::setThreshold()] temperature is out of range => [値]
*ERR* [Network::open()] socket => [システムエラーメッセージ]

元の一覧を見ながら1行ずつメッセージを組み立てる代わりに、機械的な部分をスクリプトへ任せていました。

ログらしき1行から必要な列を抜き出す

もう一方は、時刻や識別子、関数の引数などを区切り文字で取り出し、カンマ区切りで出力するスクリプトです。

表計算ソフトへ貼り付けたり、差分を見たりする前処理として使う発想でした。

機能は小さいのですが、入力と出力がファイルではなく標準入出力に寄っていた点は、今見ても悪くありません。シェルのリダイレクトと組み合わせられるからです。

php format_log.php definitions.tsv > expected.log

当時の実装

当時のコードは、ほぼ次の流れでした。

<?php
$filePath = $argv[1];
$fp = fopen($filePath, 'r');

while (!feof($fp)) {
    $line = fgets($fp);
    $line = str_replace(["\n", '"'], '', $line);
    $columns = explode("\t", $line);

    print('*ERR* [' . $columns[0] . '] ');
    print($columns[1] . PHP_EOL);
}

fclose($fp);

短く、やりたいことはすぐ分かります。一方、入力が想定どおりでなければ簡単に崩れます。

  • 引数がない場合を処理していない
  • ファイルを開けなかった場合の終了コードがない
  • while (!feof(...))により、終端で空行を1回処理する可能性がある
  • 列数を確認せず、配列の要素を参照している
  • 改行や引用符を一律に削除している
  • スキップした行や変換できなかった行が分からない
  • Shift_JISとUTF-8が混在すると、コメントや出力が文字化けする

自分だけが、決まった入力へ使うなら動きます。しかし、公開リポジトリに置くと、利用者は別の改行、空行、不足列、余分なタブを持ち込んできます。

使い捨てであっても、壊れた入力を黙って正常出力に混ぜないことが重要です。

公開用に最小限作り直す

当時の処理を、製品固有の情報を使わずに書き直すと、次のようになります。

<?php
declare(strict_types=1);

if ($argc !== 2) {
    fwrite(STDERR, "Usage: php format_log.php <definitions.tsv>\n");
    exit(2);
}

$handle = fopen($argv[1], 'rb');
if ($handle === false) {
    fwrite(STDERR, "Cannot open input file: {$argv[1]}\n");
    exit(1);
}

$lineNumber = 0;
$errorCount = 0;

while (($line = fgets($handle)) !== false) {
    $lineNumber++;
    $line = rtrim($line, "\r\n");

    if ($line === '' || str_starts_with($line, '#')) {
        continue;
    }

    $columns = str_getcsv(
        string: $line,
        separator: "\t",
        enclosure: '"',
        escape: ''
    );
    if (count($columns) !== 3) {
        fwrite(
            STDERR,
            "Line {$lineNumber}: expected 3 columns, got "
            . count($columns)
            . "\n"
        );
        $errorCount++;
        continue;
    }

    [$context, $item, $kind] = $columns;

    $detail = match ($kind) {
        'number' => '[VALUE]',
        'system_error' => '[SYSTEM ERROR]',
        'text' => '[TEXT]',
        default => '[UNKNOWN]',
    };

    printf("*ERR* [%s] %s => %s\n", $context, $item, $detail);
}

fclose($handle);
exit($errorCount === 0 ? 0 : 1);

機能は大きく増やしていません。変えたのは、主に境界の扱いです。

  • 引数の数を確認する
  • 読めないファイルをエラーにする
  • fgets()の戻り値でループを制御する
  • 空行とコメント行を明示的に扱う
  • TSVとして解析する
  • 列数が違う行を標準エラーへ出す
  • 1行でも変換に失敗したら、終了コードを0にしない

この例は2026年時点のPHPで説明するために書き直したコードです。str_starts_with()matchを使うため、PHP 8以降を前提にしています。当時のPHP 5系へそのまま戻すサンプルではありません。また、str_getcsv()escapeは省略せず、空文字列を明示しています。

「動いた行」と「捨てた行」を分ける

ログ整形ツールで怖いのは、プログラムが停止することだけではありません。変換できなかった行を黙って捨て、欠けた出力を完全な結果だと思ってしまうことです。

そのため、公開用の実装では次の役割を分けます。

出力先 内容
標準出力 正常に変換できた行
標準エラー 行番号と変換できなかった理由
終了コード すべて変換できたかどうか

PHP CLIで正常行、異常行、終了コードを分ける流れ

これなら、正常出力だけをファイルへ保存しつつ、問題のあった行を画面で確認できます。

php format_log.php definitions.tsv > expected.log

自動テストやバッチ処理から呼ぶ場合も、終了コードで成否を判断できます。

1本へまとめすぎない

残っていたコードには、用途が少しずつ違う複数のスクリプトがありました。

  • 通常エラーの整形
  • 範囲外エラーの整形
  • システムエラーの整形
  • ログから特定の引数を抽出

現在なら、共通のパーサーと出力形式を用意して1本へ寄せる設計もできます。ただし、設定項目を増やしすぎると、今度は使うたびにオプションを思い出す必要があります。

この規模では、「1ファイルを渡すと1種類の結果が出る」という単純さにも価値があります。

共通化するなら、次の条件がそろってからで十分です。

  • 入力形式が本当に同じ
  • 異常行の扱いが同じ
  • 出力を使う相手が同じ
  • 修正時に複数ファイルへ同じ変更が発生している

似ているだけで早くまとめると、用途ごとの小さな差が条件分岐として残ります。

公開リポジトリには実ログを入れない

今回、最も注意が必要なのはコードよりデータです。

実ログや変換元一覧には、次の情報が入り得ます。

  • 製品名や機種名
  • 内部クラス名、関数名、ファイル名、行番号
  • ホスト名、ユーザー名、IPアドレス
  • 起動時刻、障害時刻、ソフトウェアのバージョン
  • チャンネル数やアラーム名などの製品仕様
  • 実際に発生した障害と、その再現条件

名称だけをDEVICE_Aへ置換しても、項目の組み合わせや件数から元の仕様が見えることがあります。

そこで公開用リポジトリは、元の作業フォルダをコピーして不要ファイルを削るのではなく、空のリポジトリから作ります。

php-log-helper/
├─ README.md
├─ LICENSE
├─ src/
│  └─ format_log.php
├─ samples/
│  └─ definitions.tsv
└─ tests/
   └─ format_log_test.php

サンプルログも、実ログの一部を切り出さず、説明用に新しく作ります。これなら、削除し忘れたファイルや過去のGit履歴から、元データが公開される事故を避けやすくなります。

今振り返って残したいこと

このPHPスクリプトは、汎用ログ解析基盤ではありませんでした。

決まった形式を読み、決まった形式へ変えるだけです。それでも、同じ確認を何度も手で行うよりは確実で、作業の入口をそろえる役には立ちました。

過去の小さなツールを記事にするとき、コードの古さを隠す必要はないと思っています。

  • 当時は何に困っていたのか
  • その短いコードで、どの手作業を消したのか
  • どの前提に強く依存していたのか
  • 公開するなら、何を作り直す必要があるのか

ここまで書けば、30行の誰得ツールにも、別の現場へ持ち帰れるものが残ります。

覚えていない成果を作らない

このツールについて、「作業時間を何分減らした」「何件の不具合を発見した」といった数字は残っておらず、記憶にもありません。

そのため、この記事では効果を後から作らず、次の事実だけを残します。

  • 手作業を便利にする目的で作った
  • 当時の社内で根付いていたPHPを使った
  • 製品データを整形する小さな専用スクリプトだった
  • コードは自分でゼロベースから作った
  • 実データは機微情報なので公開しない

劇的な成功談がなくても、「この程度の面倒なら30行ほどの専用ツールで片付ける」という判断自体が、当時の便利Tipsとして残せる内容だと思っています。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?