Skip to content

App Log 1.2: 本家とコピーの乖離を解消し、監査ログの土台として使えるようにする #7

Description

@pm-hiroshi

背景

wp10-sql-runner の監査ログ(caitsith2022-main#1748)を実装する過程で、App Log 側に 2 種類の問題が見つかりました。

  1. 本家と各 PJT のコピーが双方向に乖離している(しかもバージョン番号が同じ)
  2. 監査ログの土台として使うには機能が足りない

SQL Runner の監査ログを「監査ログです」と顧客に申告すると、その時点で顧客側の保持ルールの対象になります。その前提として App Log 側の対応が要る、というのがこの Issue の動機です。

バージョンは 1.2 とします。


1. 本家と caitsith のコピーが乖離している

caitsith2022-mainsrc/wp-content/plugins/app-log/ と、このリポジトリの内容を比較しました。どちらも Version: 1.1.4 を名乗っていますが、中身が違います。

本家にあって、caitsith のコピーに無いもの

項目 場所
app_log_log_lifetime フィルタ(保持期間を上書きできる) admin/aplg-settings.php
app_log_date_format フィルタ(ログファイル名の日付書式) classes/class-aplg-logger.php
format_date_by_wp_version()(WP 5.3 以降は wp_date() を使う) classes/class-aplg-logger.php

caitsith のコピーにあって、本家に無いもの

項目 内容
書き込みが file_put_contents( $log_file, $message, FILE_APPEND | LOCK_EX ) 本家は fopen/fwrite/fclose

なぜ問題か

  • 保持期間を変えられないのはコピー側だけでした。本家には既にフィルタがあります。つまり「保持期間を変更できるようにする」は新規開発ではなく同期の問題です
  • バージョン番号でコピーを識別できません。 両方 1.1.4 なので、どの PJT がどの実装を持っているかは差分を取らないと分かりません
  • 各 PJT が個別に直した改善(上記の書き込み方式)が本家に還らないため、次に vendoring した PJT がまた同じ問題を踏みます

やること

  • 本家 ⇄ caitsith の双方向マージ(本家を正とし、コピー側の改善を取り込む)
  • 他の PJT にも vendoring されたコピーが無いか棚卸しし、同様に差分を取る
  • バージョンを 1.2 に上げる。 以後、コピーを配る時はバージョンを上げてから配る運用にする

2. 監査ログの土台として足りないもの(本家にも無い)

2-1. 書き込みの成否が分からない

Aplg_Logger::log()void で、書き込み結果を呼び出し側に返しません。加えて、記録が黙って落ちる経路が 3 つあります。

$pre = apply_filters( 'pre_applog_write', null, ... );
if ( ! is_null( $pre ) ) {
    return;                      // ① フィルタで握り潰せる(戻り値は呼び出し側に伝わらない)
}

$message = apply_filters( 'app_log_write_log_before', $message, ... );
if ( false === $message ) {
    return;                      // ② 同上
}

$fp = fopen( $log_file, 'a' );   // ③ 失敗しても検知されない(後述)
fwrite( $fp, $message );
fclose( $fp );

監査ログの用途では「書けたつもりで書けていない」が最悪の失敗です。呼び出し側が applog_info() を呼んで正常に戻ってきた以上、記録は残っていると考えます。

  • log() が成否を返す(bool、あるいは失敗理由つき)
  • 書き込み関数の戻り値を確認する
  • フィルタで抑止された場合と、書き込みに失敗した場合を区別できるようにする

2-2. 書き込み失敗で Fatal になりうる

上記③について。fopen() が失敗すると false を返し、PHP 8 では fwrite( false, ... )TypeError になります。ディスクフルや権限変更でログ出力が原因のホワイトスクリーンが起きえます。

caitsith のコピーは file_put_contents() を使っており、この経路では落ちません。取り込む価値があるのはこの性質です。

  • 書き込み方式を統一し、失敗しても Fatal にしない

LOCK_EX については判断が要ります(未測定)

「本家はロックが無いから危険」という単純な話ではありません。

  • FILE_APPENDfopen($f,'a')O_APPEND で開くため、1 回の write に収まる大きさなら、どちらも行が混ざりません
  • ただし O_APPEND の不可分性は書き込みサイズに依存します。実運用のログには 462KB の 1 行が存在する実績がありますkingsman#4469 の AppLog Viewer 実装時に実測)。この規模では分割されうるので、巨大な行に対しては LOCK_EX だけが効きます
  • 一方でコストがあります。caitsith の出力先は uploads = EFS マウントで、LOCK_EX はログ 1 行ごとにネットワーク越しのロックになります

どちらが最適かは測定していません。 現時点の提案は「LOCK_EX を採る(巨大行の実績がある以上、外す根拠が無い)/ただしフィルタで外せるようにし、EFS で遅いと分かったら切れる状態にしておく」です。

  • LOCK_EX の有無をフィルタで切り替えられるようにする
  • 高頻度書き込み環境での実測(別途)

2-3. 行頭に前置しない出力(raw / JSON モード)が無い

log() は必ず [日時] [LEVEL] (PID: N) を前置します。構造化ログを 1 行 JSON で出したい場合、JSON Lines として読めなくなります[...] {...} になる)。

参考として、kingsman の pm_log()(mu-plugin 版)には既にこの口があります。

function pm_log( mixed $message, string $dirname = '/log/', string $format = 'text', bool $include_msec = false ): void

$format = 'json'{timestamp, process_id, message} の 1 行 JSON、$include_msec でミリ秒まで出せます。後継である App Log に、前身の分家が持っている機能が無い状態です。

  • 前置しない出力モードを追加する(applog_raw() のような別関数か、$format 引数か)
  • kingsman の pm_log()$format / $include_msec を取り込む

2-4. 自動削除の glob が末尾スラッシュに依存している

$files = glob( $log_dir . '*' );

$log_dir/ で終わらない呼ばれ方をすると、ディレクトリ内のファイルではなく兄弟パスに当たりますlog() 側は realpath( $log_dir ) . '/' . $filename と明示的に / を足しているのに、log_auto_delete() に渡すのは正規化前の生パスで、非対称です。

  • $log_dir を正規化してから glob する

(delta-lint finding dl-6c702523

2-5. delete_log() が書き込み先と別のディレクトリを見る

log() は第 2 引数 $dirname でサブディレクトリへ書けますが、delete_log()get_path_to_log_dir()引数なしで呼ぶため、常にルートのログディレクトリしか参照しません。書き込み先と削除・閲覧の探索先が一致しません。

  • delete_log() / ダッシュボードが $dirname を扱えるようにする

(delta-lint finding dl-3a3bfc83


進め方

同期 → 機能追加 → 書き込み方式の統一の順で進めます。

  1. 同期(1 章)— 本家を正としてコピー側の差分を取り込み、バージョンを 1.2 に。ここで保持期間のフィルタが各 PJT に届く
  2. 機能追加(2-1 / 2-3 / 2-4 / 2-5)— 成否の返却、raw モード、glob、delete_log()
  3. 書き込み方式の統一(2-2)— LOCK_EX の判断を含む。測定が要るので最後

1 が終わった時点で、SQL Runner の監査ログを 210 日保持に乗せられます。

影響範囲

  • App Log を使っているすべての PJT
  • 保持期間のフィルタが届くことで、これまで 90 日で消えていたログが消えなくなる環境が出ます。容量の見積もりが要ります
  • log() の戻り値変更は後方互換(voidbool

関連

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions