サーバーのログ調査・障害対応にAI(LLM)を使う実践テクニック

※本ページには広告(アフィリエイトプログラム等)が含まれます。

※本ページには広告(アフィリエイトプログラム等)が含まれます。掲載しているログと件数は 2026年8月16日時点で運用中の VPS(Debian 12・メモリ 958MB)から採取した実測値です。ユーザー名と IP はマスクしています。

🎯 結論:AI に投げる前に集計する。7 日分 157 件のエラーは、実際に見るべき 9 件まで減った

結論から書きます。サーバーのログを AI に読ませるとき、生ログをそのまま貼るのがいちばん効率が悪いです。

手元の VPS で直近 7 日間のエラーを数えたら 157 件ありました。ところが正規化して集計すると、137 件(87%)は SSH ポートへの総当たりスキャンで、対処のしようがないノイズでした。本当に人間が見るべきだったのは残りの 9 件です。

  • まず `wc -l` で件数を数える → 規模が分からないと打ち手が決まらない
  • IP・ポート・ユーザー名を正規化してから `uniq -c` → 157 行が 8 パターンに畳まれる
  • ノイズを除いた残りだけ AI に投げる → トークンも時間も 1 桁減る
  • ⚠️ ログには IP・ユーザー名・パスが混ざる → 外部の AI に投げる前にマスクする
  • 「このログを見て」と丸投げ → ノイズに引きずられた回答が返る

なぜ生ログをそのまま投げてはいけないのか

ログの大半は同じ 1 行の繰り返し

サーバーのエラーログは、種類が多いのではなく同じ行が大量に繰り返されるのが普通です。IP やポート番号だけが違うので、テキストとしては全部別行に見えます。

そのまま貼ると、AI は「大量に発生している事象」を重要だと解釈します。実際にはただのスキャンなのに、そこに引きずられた回答が返ってきます。

トークンを無駄に使う

157 行を貼るのと、8 パターンに畳んで貼るのとでは、消費するトークンが 1 桁違います。有料 API を使っているなら、そのまま費用差になります。

機密が混ざる

ログには接続元 IP、OS のユーザー名、ホームディレクトリのパスがそのまま出ます。外部のサービスに投げる前に、何が含まれているかを自分で把握しておく必要があります。

手順 1:まず件数を数える

優先度で絞って数える

最初にやるのは読むことではなく数えることです。`journalctl` は優先度で絞れるので、エラー以上だけを対象にします。

$ journalctl --since "-7d" -p err --no-pager | wc -l
157

7 日で 157 件。この数字が出た時点で「全部読む」という選択肢が消えます。人力で読む上限はせいぜい数十件です。

どのサービスが出しているのか

次に発生源を数えます。JSON 出力にすると機械的に集計できます。

$ journalctl --since "-7d" -p err --no-pager -o json \
  | python3 -c "
import sys, json, collections
c = collections.Counter()
for line in sys.stdin:
    d = json.loads(line)
    c[d.get('_SYSTEMD_UNIT') or d.get('SYSLOG_IDENTIFIER') or '?'] += 1
for k, v in c.most_common(8):
    print(f'{v:6d}  {k}')
"
   105  ssh.service
    32  sshd
     7  session-631657.scope
     4  session-631659.scope
     2  session-617752.scope

105 + 32 = 137 件が SSH 由来でした。全体 157 件の 87% です。この時点で「SSH のノイズを除いたら何が残るか」という問いに変わります。

ディスクの使用量も見ておく

$ journalctl --disk-usage
Archived and active journals take up 503.5M in the file system.

メモリ 958MB・ディスク 99GB の VPS で journal が 503.5MB。上限を設定していないと際限なく増えるので、`SystemMaxUse` を明示しておくと安全です。

手順 2:正規化してパターンに畳む

IP・ポート・ユーザー名を伏せてから数える

ここが一番効きます。変動する部分を置換してから `uniq -c` にかけると、157 行が一気に畳まれます。マスクを兼ねられるので、外部の AI に投げる前処理としても都合がいいです。

$ journalctl --since "-7d" -p err --no-pager -o cat \
  | sed -E "s/[0-9]{1,3}(\.[0-9]{1,3}){3}/<IP>/g;
            s/port [0-9]+/port <N>/g;
            s/user [A-Za-z0-9_.-]+/user <USER>/g" \
  | sort | uniq -c | sort -rn | head -8

実際に出てきた 8 パターン

手元の VPS で実行した結果がこれです。

件数 メッセージ 正体
76 kex_exchange_identification: Connection closed by remote host スキャン
25 kex_exchange_identification: read: Connection reset by peer スキャン
23 banner line contains invalid characters スキャン
7 pam_unix(sudo:auth): conversation failed ⚠️ 要確認
7 pam_unix(sudo:auth): auth could not identify password ⚠️ 要確認
5 client sent invalid protocol identifier "GET / HTTP/1.0" スキャン
4 client sent invalid protocol identifier "\026\003\001\001" スキャン
2 a password is required ; USER=root ⚠️ 要確認

157 行が 8 行になりました。この 8 行なら人間がそのまま読めますし、AI に投げるコストもほぼゼロです。

ノイズの見分け方

上位 3 つの `kex_exchange_identification` は、SSH の鍵交換に入る前に切断された記録です。認証にすら到達していないので、鍵認証にしてある限り実害はありません。

面白いのは下のほうの 2 つです。`"GET / HTTP/1.0"` は SSH ポートに HTTP を投げてきたスキャナ、`"\026\003\001\001"` は TLS のハンドシェイク(0x16 0x03 0x01)を投げてきたスキャナです。ポート番号を変えていても、片っ端から総当たりされていることが分かります。

手順 3:残ったものだけを AI に投げる

ノイズを除外して抽出する

スキャン由来を除くと、残るのは sudo 関連の 9 件でした。ここだけ切り出します。

$ journalctl --since "-7d" -p err --no-pager -o cat \
  | grep -v "kex_exchange_identification" \
  | sed -E "s/\[[A-Za-z0-9_-]+\]/[<USER>]/g; s#/home/[A-Za-z0-9_-]+#/home/<user>#g"

置換をここでも噛ませておくのが要点です。ユーザー名とホームディレクトリのパスがそのまま出るので、外部の AI に渡すなら必須の一手間になります。

聞き方を具体的にする

投げるときは「見て」ではなく、判断してほしいことを明示します。

$ journalctl --since "-7d" -p err --no-pager -o cat \
  | grep -v kex_exchange_identification \
  | claude -p "このログのうち、対処が必要なものと、
無視してよいものに分類してください。
判断の根拠も 1 行ずつ付けてください。"

今回の 9 件は「非対話のシェルから `sudo` を実行してパスワードを求められ、入力できずに失敗した」記録でした。攻撃ではなく自分の運用の副作用です。ここまで絞れていれば、AI の回答も検証しやすくなります。

それでも自分で確かめる

AI の分類は出発点であって結論ではありません。「無視してよい」と言われたものこそ、1 件だけ手で裏を取るくらいでちょうどいいです。

機密ログはローカル LLM に投げる

外に出せないログがある

アクセスログには利用者の IP が入りますし、アプリケーションログにはメールアドレスやトークンが混ざることがあります。置換で消しきれる自信がないものは、そもそも外に出さないのが確実です。

Ollama をローカルに置く

その場合はローカル LLM を使います。手元のマシンで動かして、ログはネットワークの外に出しません。

$ ollama run qwen2.5-coder:7b "次のログで対処が必要なものを挙げて" < errors.txt

⚠️ VPS 上で動かすなら容量に注意

今回ログを採取した VPS はメモリ 958MBです。7B クラスのモデルは載りません。ローカル LLM を動かすなら、ログを収集するサーバーとは別に、メモリに余裕のある環境を用意してください。

丸投げしないための鉄則

件数を先に出す

読む前に数える。157 件なのか 5 件なのかで、打ち手がまったく変わります。

正規化してから渡す

変動部分を置換して `uniq -c`。マスクと圧縮を同時にやれます。今回は 157 行 → 8 行まで畳めました。

AI の答えを別経路で確かめる

「無視してよい」という回答をそのまま信じない。1 件でいいので元のログを開いて突き合わせます。ログ調査でいちばん高くつくのは、ノイズを重大事だと誤認することと、その逆です。

よくある質問

Q1. journalctl のエラーは何件くらいが正常ですか?

結論:件数ではなく内訳で判断します。今回の VPS は 7 日で 157 件ありましたが、137 件(87%)は SSH ポートへのスキャンでした。インターネットに露出したサーバーなら、この種のノイズは日常的に出ます。

Q2. kex_exchange_identification のエラーは危険ですか?

結論:鍵認証のみにしてあるなら実害はありません。SSH の鍵交換に入る前に切断された記録で、認証にすら到達していません。ただし件数が急増したら、遮断の設定を見直す合図にはなります。

Q3. ログを AI に投げるとき何をマスクすべきですか?

結論:IP アドレス・OS のユーザー名・ホームディレクトリのパス・トークンの 4 つが最低ラインです。`sed` で置換してから渡せば、マスクとログの圧縮を同時に達成できます。

Q4. ローカル LLM は VPS 上で動かせますか?

結論:メモリ次第です。今回の VPS はメモリ 958MB で、7B クラスのモデルは載りません。ログ収集用のサーバーと、モデルを動かす環境は分けて考えてください。

Q5. journal が肥大化したらどうすればいいですか?

結論:`SystemMaxUse` を明示します。今回の環境では journal が 503.5MB を占めていました。上限を設定しておかないと、ディスクを圧迫してから気づくことになります。

まとめ

AI にサーバーのログを読ませるときは、投げる前に集計するかどうかで結果がまったく変わります。今回の実測では 7 日 157 件 → 8 パターン → 要対処 9 件まで絞れました。

  • 🎯 まず数える → `journalctl -p err | wc -l`
  • 🎯 正規化して畳む → `sed` で置換してから `uniq -c`(マスクも兼ねる)
  • 🎯 残りだけ投げる → 判断してほしいことを明示して聞く

数値は 2026年8月16日時点で運用中の VPS から採取したものです。環境が違えば内訳も変わるので、まずは自分のサーバーで同じコマンドを 1 回叩いてみてください。サーバーの初期構築から見直すならさくらの VPS の初期セットアップ、常駐プロセスの管理はsystemd のユニットファイル、遮断の設定はufw の設定にまとめています。

読んで頂いて有り難うございます!