ログ保管を自動化する — 世代管理と棚卸しルールの作り方

技術解説

3年分が、消えていた。

自動化したはずの保管ルールを、最後に見直したのはいつですか。

見直されないルールは、静かに間違った方向へ働き続けます。

ログの保管は、たいてい「とりあえず logrotate を書く」から始まります。そして、そこで止まります。世代数は誰かが決めた数字のまま、圧縮の境目は経験則のまま、削除の判断は年に一度の棚卸しで思い出したように行われます。今回はその4場面——世代管理の自動化、遅いディスクに積み上がった過去ログ、棚卸しの判断基準、そして「開く待ち時間」という見えないコスト——を、それぞれ「なぜルールが形骸化するのか」から順に見ます。

先に結論: UwView Pro は保管したファイルを約1/9に圧縮したまま検索でき、一度開いたファイルは2回目以降、行番号付きのまま開き直せます(47.73GBのテキストで実測0.02〜0.07秒。特定環境での測定例で、環境により異なります)。「圧縮すると読めなくなる」という前提が外れると、世代管理のルールは「生/圧縮/削除」ではなく「生/圧縮」の二択で書けるようになります(詳細は記事末尾)

本記事は保管ルールの設計の話です。ログの保存期間・削除可否・持ち出し範囲は、所属組織および委託元の規程、ならびに適用される法令に従ってください。


1. logrotate は書いた。そのあと誰も見ていない

状況

サーバを立てたとき、保管ルールを書きました。

/var/log/app/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
}

30世代。1か月ぶん。当時はそれで足りていました。

2年後。ログの量は1日あたり8倍になっています。rotate 30 はそのままです。30日ぶんのつもりで、ディスクの7割を使っています。 そして誰も気づいていません。

なぜ起きるか

世代管理が「日数」で書かれていて、「量」で書かれていないからです。

rotate 30 が保証するのは日数だけです。1日あたりのサイズが変われば、占有量は勝手に変わります。そしてログの量は、たいてい増える方向にしか変わりません。

形骸化はもう2つの形で進みます。

  • delaycompress の意味を誰も覚えていない — 直近1世代を圧縮しないのは、書き込み中のプロセスがファイルハンドルを握っている場合への配慮です。しかし多くの環境では copytruncatepostrotate で対処済みで、残しておく理由がないまま残っています。その1世代が数十GBなら、無意味に生のまま置いていることになります
  • 削除だけが手動で残るrotate は世代数を超えたものを消しますが、「別の場所へ退避したもの」は管理外です。バックアップ先のNASや外付けディスクには、logrotate が知らないファイルが積み上がります

汎用ツールでの対処と限界

日数ではなく量で見張るのが、最初の一歩です。

# 世代ごとの実サイズを出す(どこで効いているかを見る)
du -sh /var/log/app/*.log* | sort -h | tail -20

# 「圧縮済みなのに大きい」ものを拾う
find /var/log/app -name '*.gz' -size +1G -printf '%s\t%p\n' | sort -rn

# 日数ではなく合計サイズで切る(maxsize / 合計の見張り)
#   logrotate 側: size 2G / maxsize 2G を rotate と併用する
du -sb /var/log/app | awk '{ if ($1 > 200*1024*1024*1024) print "over 200GB" }'

maxsize は効きます。日次と併用すれば、「1日経つか2GBを超えたらローテート」という二重の条件になります。

限界は3つ。

ひとつめ、logrotate は「どれを残すべきか」を知らないこと。判断材料は日付と番号だけです。障害調査で重要だったファイルも、平穏な日曜日のファイルも、同じ規則で消えます。

ふたつめ、閾値を決める根拠が手元にないこと。「1か月ぶん残す」の1か月は、たいてい誰かの感覚です。実際に何日前まで遡って読んだかの記録がないので、30を15にしていいのか、90にすべきなのかが判断できません

みっつめ、圧縮したものの中身を確認するコストが高いこと。rotate 30 が妥当か検証するには、20世代目を実際に開いて「これはもう要らない」と確かめる必要があります。その確認自体が重いので、誰も確かめず、数字だけが残ります第4回で判断基準そのものを扱いました)。


2. 外付けHDDの中で、過去ログが死んでいる

状況

過去ログは外付けHDDに退避してあります。容量は十分です。

半年前のログを見る必要が出ました。HDDを繋ぎ、フォルダを開き、40GBのファイルをダブルクリックします。

進捗バーが伸びません。 10分後、コーヒーを淹れて戻ってきても、まだ読み込み中です。

なぜ起きるか

遅いディスクの上では、「ファイル全体を読む」設計のツールがそのまま待ち時間に変わるからです。

回転式のHDDやUSB接続のディスクは、シーケンシャル読み出しでも内蔵SSDの数分の一です。ここで効いてくるのがツール側の前提です。

  • 全体をメモリへ読み込むツールは、ファイルサイズ÷転送速度がそのまま待ち時間になります。40GBを毎秒100MBで読めば、それだけで6分半。途中で中断もできません
  • 索引を先に作るツールは、その索引作りで同じ量を読みます。読み終わるまで先頭しか見えないので、体感は同じです
  • 展開してから読む運用だと、二重に払うことになります。gunzip で作業ディスクへ展開し、そのうえで開く。転送と展開と読み込みで3回ぶんの時間がかかります
  • しかも展開先の空きが要る。1/9に圧縮されているなら、展開には9倍の空き容量が必要です。外付けが一杯だから退避したのに、退避先で展開できません

汎用ツールでの対処と限界

展開せずに、必要な部分だけを取り出すのが基本です。

# 展開せずに中身を検索する(ただし毎回全部を展開しながら読む)
zgrep -n 'OutOfMemoryError' /mnt/archive/app-20260317.log.gz | head -20

# 先頭だけ見て、フォーマットの当たりを付ける
zcat /mnt/archive/app-20260317.log.gz | head -100

# 見たい範囲だけを作業ディスクへ切り出す
zcat /mnt/archive/app-20260317.log.gz | sed -n '41284451,41284491p;41284492q'

# 読み出し速度そのものを測っておく(見積もりの根拠にする)
dd if=/mnt/archive/app-20260317.log.gz of=/dev/null bs=1M count=2000 status=progress

zgrep は容量の問題を解きます。展開先を用意せずに探せるのは大きい。

限界は2つ。

ひとつめ、zgrep は毎回すべてを展開しながら読むこと。gzip はブロック単位でのランダムアクセスができないので、40GBのアーカイブの末尾付近を見るためには、先頭から全部を展開して捨てる必要があります。1回の検索が毎回フルコストです。2語で絞り込みたければ2回払います(第8回)。

ふたつめ、遅いディスクではその全走査が支配的になること。展開の計算コストより、ディスクから読む時間のほうが大きくなります。CPUを増やしても速くなりません。改善できるのは「読むバイト数を減らすこと」だけです。


3. 棚卸しが「担当者の気分」になっている

状況

年に一度、ストレージの棚卸しをします。

「この2023年のフォルダ、消していいですか」

誰も断言できません。 消して困った例は思い出せないけれど、消して困る可能性は否定できない。結局、今年も残ります。来年も同じ会話をします。

なぜ起きるか

「消してよい」の判断基準が、データ側の属性ではなく人間の記憶に置かれているからです。

棚卸しが進まないとき、実際に欠けているのは次の3つです。

  • 何のログかが分からない — ファイル名が app-20230412.log.gz としか書かれていません。中身がアクセスログなのか、バッチの実行ログなのか、開かないと分かりません。そして開くのが重いので、開きません
  • 調査済みかどうかが分からない — 一度調べて「問題なし」と確認したファイルと、一度も開いていないファイルが、同じ場所に同じ顔で並んでいます。前者は消してよく、後者は判断できません。しかしその区別がファイルに書かれていません
  • 保存義務の有無が分からない — 監査や契約で保存期間が決まっているものと、単に「念のため」のものが混ざっています。前者を消すのは事故で、後者を残すのは無駄です(第20回

つまり棚卸しは、「調べる」作業が重いせいで止まっています。判断のための情報が、ファイルを開かないと得られない。

汎用ツールでの対処と限界

判断材料をファイルの外に書き出し、台帳にするのが定石です。

# 台帳を作る: パス・サイズ・最終更新・先頭1行の要約
for f in /mnt/archive/*.gz; do
  printf '%s\t%s\t%s\t%s\n' \
    "$f" "$(stat -c %s "$f")" "$(stat -c %y "$f" | cut -d' ' -f1)" \
    "$(zcat "$f" | head -1 | cut -c1-80)"
done > inventory.tsv

# 「最後に読まれた日」で古いものを拾う(atime が有効な場合のみ)
find /mnt/archive -name '*.gz' -atime +365 -printf '%s\t%p\n' | sort -rn | head

# 調査済みの印を、ファイル側ではなく台帳に付ける
awk -F'\t' '$2 > 10*1024^3 { print $1 }' inventory.tsv > big-files.txt

台帳は効きます。一度作れば、次の棚卸しは差分だけになります。

限界は3つ。

ひとつめ、zcat | head -1 でも先頭ブロックの展開が要ること。ファイルが数百本あれば、台帳を作るだけで一晩かかります。しかも1行目から分かるのは書式だけで、「何が起きた日か」は分かりません。

ふたつめ、atime は信用できないこと。多くの環境は relatimenoatime で運用されており、読んだ記録が残りません。「最後に誰かが見た日」を根拠にしたいのに、その根拠が取れません。

みっつめ、台帳とファイルがずれること。台帳はファイルの外にあるので、移動・リネーム・再圧縮のたびに手で直す必要があります。直さなければ、次の棚卸しでは「台帳が古いから当てにならない」と言われて、また最初から作り直します。


4. 「開くのを待った時間」を測ったことがあるか

状況

見積もりを聞かれます。「ログの調査、どのくらいかかりそう?」

30分と答えます。実際には2時間かかります。差の90分は何だったのか、あとから説明できません。

説明できないので、来年も同じ見積もりを出します。

なぜ起きるか

待ち時間が「作業」として記録されないからです。

ファイルを開く2分は、タスク管理ツールに載りません。載るのは「ログ調査」という1つの塊で、その内訳は誰も測っていません。そして測っていないものは、改善の対象になりません。

実際には、次の形で積もります。

  • 1回あたりが小さく、回数が多い — 1回2分でも、1日に10回開き直せば20分です。週に2日そういう日があれば、月に3時間弱(第10回
  • 待ちのあいだに文脈が切れる — 2分の待ちは、他のタブを見るのに十分な長さです。戻ってきたとき、何を探していたかを思い出す時間が別途かかります。待ち時間の実コストは、待ち時間そのものより長い
  • 重いから試さない、という判断が入る — 「この仮説も確かめたいが、また2分待つのか」と思った瞬間、確認が省かれます。省かれた確認は記録に残らず、あとで手戻りとして現れます
  • 測る道具がないtime で測れるのはコマンドだけです。GUIで開いた待ち時間、スクロールが引っかかった時間、検索結果が出るまでの時間は、どこにも残りません

汎用ツールでの対処と限界

せめてコマンド側だけでも測っておくのが現実的です。

# 1回あたりの実コストを測る(キャッシュを避けて3回)
for i in 1 2 3; do
  /usr/bin/time -f '%e sec  %M KB' grep -c 'ERROR' /mnt/archive/app.log
done

# 検索1回のバイト数とスループットから、回数ぶんを見積もる
#   40GB を 100MB/s で読む = 約 410 秒。1日10回なら 68 分
echo $(( 40*1024 / 100 * 10 / 60 )) min/day

# 調査セッションの実時間を、シェルの履歴のタイムスタンプから拾う
HISTTIMEFORMAT='%F %T ' history | tail -40

見積もりの式は作れます。「読むバイト数 ÷ 転送速度 × 回数」で、待ちの総量は概算できます。

限界は2つ。

ひとつめ、式に入れる「回数」が分からないこと。1日に何回開き直したかを数えている人はいません。体感では3回でも、実際は10回のことがあります。分母が取れないので、改善の効果も測れません。

ふたつめ、測っても行き先がないこと。「年間で数十時間を待ちに使っている」と分かったとして、汎用ツールの側に打つ手がありません。ディスクを速いものに替えるか、ファイルを小さく分割するか。前者は予算、後者は原本を壊す判断です。測定が改善に繋がらないので、測るのをやめます。


4つに共通していたもの

場面 形骸化するもの 詰まる原因 汎用ツールでの対処 残る問題
世代管理 rotate 30 の30 日数で書き、量で書いていない maxsizedu で量を見張る 「残すべきか」の根拠が取れない
遅いディスク 退避したまま開かない過去ログ 全体を読む設計が待ちに直結 zgrep・切り出し 毎回フル展開。読むバイト数が減らない
棚卸し 「消していいですか」の会話 判断材料が開かないと得られない 台帳(inventory.tsv) 台帳作りが重い。atime が当てにならない
待ち時間 見積もりと実績の差 待ちが作業として記録されない time・概算式 回数が分からない。測っても打ち手がない

4つとも、止まっているのは意思決定です。ルールを書く力が足りないのではなく、ルールを見直すための確認が高すぎます。

そして右端の列が似た形をしているのは、4つとも「保管したものを開いて確かめる」コストに突き当たるからです。世代数が妥当かは20世代目を開かないと分からない。棚卸しの可否は中身を見ないと決められない。待ち時間の改善効果は、開き直してみないと測れない。確認が高いから、ルールは書いた日のまま固まります。

裏を返すと、条件は1つです。

  • 保管した形のまま、安く開けること — 展開せずに、遅いディスクの上でも、必要な部分だけを読めること

これが満たされると、順番が変わります。いまは「圧縮したら読めなくなるから、読むかもしれないものは生で置く」という設計をしています。生で置くから場所を食い、場所を食うから消す判断を迫られ、消す判断が重いから棚卸しが止まります。圧縮したまま読めるなら、この連鎖の入口が消えます。

冒頭の「3年分が消えていた」に戻ります。あれは誰かがミスをしたのではありません。誰も中身を確かめないまま、書いた日のルールが3年ぶん働き続けただけです。


使っている道具

私が開発している UwView(無料)は、巨大なテキストを開いた瞬間から表示・スクロール・検索できるビューアです。ファイル全体をメモリへ読み込まないので、RAM より大きいファイルでも開けます。索引はバックグラウンドで作られ、完成すると行番号が付きます(他のビューアの多くは索引が終わるまで先頭しか見えません)。分割も抽出もしないので、原本は1本のまま・無改変のまま扱えます。

  • 遅いディスクの上のファイルでも、開いた瞬間から先頭が読めます: 2章の「進捗バーが伸びない」が起きません。全体を読む前に表示が始まるので、書式の確認や当たりを付ける作業は待たずに済みます。ただし索引が完成するまで行番号は付きません(その間もスクロールと検索はできます)
  • 展開先の空き容量が要りません: .gz を作業ディスクへ広げてから読む必要がないので、退避先が一杯でも中身を確認できます
  • 文字コードは開いたまま切り替えられます(UTF-8 / Shift-JIS(CP932) / EUC-JP / UTF-16 を自動判定)。古い世代のログが別の文字コードでも、ファイルを作り直さずに読めます
  • 処理はすべて手元のマシンで完結します。保管しているログを外部のサービスへ送りません

ここから先が UwView Pro の領分です。

  • 約1/9で保管しつつ、そのまま検索できる: 2章と3章に直接効きます。「圧縮すると読めなくなる」が外れるので、世代管理のルールを「生/圧縮/削除」ではなく「生/圧縮」の二択で書けます。削除の判断を先送りできるぶん、棚卸しの会話が軽くなります(第8回
  • 索引と圧縮を保存する: 一度開いたファイルは2回目以降行番号付きのまま瞬時に開き直せます(47.73GBのテキストで実測0.02〜0.07秒。特定環境での測定例で、環境により異なります)。4章の「1日10回の開き直し」が積もらなくなるのは、この2回目以降の側です
  • 多段階検索(ドリルダウン): 3章の台帳作りに効きます。1本のアーカイブに何度も聞き直しても、2回目以降は保存された索引が使われます。原本の行番号は最後の段まで保持されます多段階検索の記事
  • 検索条件を保存して、翌日そこから再開する: 棚卸しのような年次作業は、去年と同じ条件を当て直す作業です。条件が成果物として残ります(アーカイブ×セッション復元の記事

正直に書いておきます。UwView はログ管理システムではありません。

  • ローテートも、削除も、退避もしません。 1章の logrotate を置き換えるものではなく、その世代数が妥当かを確かめる側の道具です
  • 保存期間の管理も、コンプライアンス台帳もありません。 3章の台帳は別途作る必要があります
  • 集計・可視化・アラートはしません。 4章の待ち時間を自動で記録する機能もありません
  • .gz を直接高速検索できるわけではありません。 速くなるのは、一度 .uwvz として索引・圧縮を作り直したあとです。初回は読むぶんの時間がかかります
  • 対応するのはテキストです。バイナリのダンプやデータベースのファイルは対象外です

コマンドラインから同じことをする

v1.6.0 で uvp コマンドが付きました(無料版には uvf)。GUI と同じ .uwvz を使うので、CLI で作った索引はそのまま GUI でも効きます。この記事の4章に対応させると、こう書きます。

# 1章: 20世代目に本当に中身があるか、消す前に確かめる
uvp /var/log/app/app.log.20 'ERROR'

# 2章: 遅いディスクの上のアーカイブへ、展開せずに聞く
uvp /mnt/archive/app-20260317.log.uwvz 'OutOfMemoryError' -C 5

# 3章: 棚卸しの材料として、そのファイルに何が入っているかの内訳を出す
uvp /mnt/archive/app-20260317.log.uwvz -uniq '\[([A-Z]+)\]' -head 20

# 3章の続き: 判断に使った根拠を、圧縮したまま台帳の隣へ書き出す
uvp /mnt/archive/app-20260317.log.uwvz 'FATAL' -C 3 -out inventory/20260317-fatal.txt.gz

# 4章: 2回目以降の実コストを測る(1回目は索引作りぶん遅い)
time uvp /mnt/archive/app-20260317.log.uwvz 'ERROR' 'timeout'

終了コードは grep と同じ 0=あり・1=なしに、2=上限に当たって打ち切った(既定は無制限。-limit N で上限を決めたときだけ)が加わります。if uvp app.log.20 'ERROR'; then がそのまま書けるので、1章の「消す前の確認」を棚卸しスクリプトに混ぜられます。

正直に書くと、1問目は ripgrep のほうが 15〜20% 速いです(uvp は先に索引を作るため)。3GB程度でメモリに収まるファイルなら、2問目以降も rg のほうが速いままです。uvp が効くのは10GBを超えて、同じファイルに2回以上聞くときです(実測記事。Mac M4・外付けUSB SSD・OpenStreetMap XML での測定例で、環境により異なります)。

できるのは、保管した形のまま中身を確かめること・その場所を原本の座標で示すこと・同じ確認をもう一度当て直せる形で残すことです。保管ルールが書いた日のまま固まっているなら、まず20世代目を開いてみてください。

そして、ストレージを専有している巨大ログを圧縮して保管し、さらに高速に検索したいなら UwView Pro をどうぞ。永続索引・圧縮キャッシュ検索・約1/9保管で、開き直しも検索も一段速くなります(全OS対応・買い切り/月額プランあり)。

リンク

  • 第1回: 巨大ファイルに沈む4つの定番ツールと、その先: https://uvp.y42u.net/blog/uwview-ps01-huge-file-tool-limits/
  • 第4回: 消す・残す・圧縮するの判断基準: https://uvp.y42u.net/blog/uwview-ps04-log-retention-decision/
  • 第8回: 圧縮保管と検索の両立: https://uvp.y42u.net/blog/uwview-ps08-compressed-archive-search/
  • 第10回: 深夜2時の自分を助ける4つの仕込み: https://uvp.y42u.net/blog/uwview-ps10-oncall-night-preparation/
  • 第15回: コマンドライン職人芸の限界線4本: https://uvp.y42u.net/blog/uwview-ps15-cli-craft-limits/
  • 第16回: 保全・切り出し・改ざん対策の4原則: https://uvp.y42u.net/blog/uwview-ps16-log-as-evidence/
  • 第20回: 監査対応を仕組みで乗り切る4項目: https://uvp.y42u.net/blog/uwview-ps20-audit-log-retrieval/
  • 第22回: 引き継げるチームの4つの工夫: https://uvp.y42u.net/blog/uwview-ps22-shareable-log-investigation/
  • 翌日そこから開き直す(アーカイブ×セッション復元): https://uvp.y42u.net/blog/uwview-archive-session-restore-workflow/
  • 絞り込んだ先からさらに絞り込む(多段階検索): https://uvp.y42u.net/blog/uvp-drilldown-search/
  • uvp コマンドを付けました(ripgrep との実測比較): https://uvp.y42u.net/blog/uvp-cli-release-vs-ripgrep/
  • ソースコード(GitHub): https://github.com/amru195704/UwView

開発者より: アプリ・Kindle本・オープンソースの一覧は GitHub: amru195704 にまとめています。


お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。掲載したログ例・行数・ファイルサイズ・世代数・時間の試算はすべて説明のための例であり、特定の実在する案件やシステムを示すものではありません。コマンド例は環境(GNU/BSD、statfindtime の実装差、シェルの種類、logrotate のバージョン等)により調整が必要です。オプション名や既定値は必ず手元の man で確認してください。logrotate の挙動は設定やアプリケーション側の実装により異なり、atime の記録可否はマウントオプションに依存します。記載した実測値は特定の1環境での測定例であり、同じ結果を保証するものではありません。ディスクの種類・接続方式・ファイルシステム・断片化・暗号化・ページキャッシュの状態・同時に動いている他のプロセスにより、結果は大きく変わります。ログの保存期間・削除・持ち出しの可否は、所属組織および委託元の規程、ならびに適用される法令に従ってください。誤記・不正確な情報がございましたら、コメント欄よりご指摘いただければ、確認のうえ修正いたします。

タイトルとURLをコピーしました