迷わない!シェルスクリプトのログ設計|どこに出して、どう残す?

こんにちは、さるまりんです 🐒🔧

シェルスクリプトを書いていると、

  • とりあえず echo している
  • ログは出しているけど見返さない
  • エラーが起きたときに原因がわからない

こんな状態になってしまうこと、ありませんか?

実はログは「出しているかどうか」ではなく、

“設計されているかどうか”

がとても重要です。

今回は、

「どこに出して、どう残すか」

という視点で、シェルスクリプトのログ設計を整理してみます。


■ ログは「後から読むためのもの」

まず前提として、

ログは「その場で見るもの」ではなく、
後から読むためのものです。

例えばこんな場面があります。

  • バッチが夜中に失敗していた
  • 一部の処理だけうまくいっていない
  • たまにしか起きない不具合がある

こういうときに、

→ ログがなければ何もわからない
→ ログがあれば原因に近づける

この差はかなり大きいです。


■ 標準出力と標準エラーを分ける

シェルスクリプトには、ログの出力先が2つあります。

  • 標準出力(stdout)
  • 標準エラー(stderr)

これを分けるだけで、ログの価値がかなり上がります。

echo "正常処理です"

echo "エラーが発生しました" >&2

■ なぜ分けるのか

例えばこんな実行です。

./batch.sh > output.log 2> error.log
  • 正常ログ → output.log
  • エラーログ → error.log

と分けることができます。

→ エラーだけ見たい
→ 正常処理だけ確認したい

こういうときに便利です。

また、あとから監視や通知を入れるときも、
stderr を分けていると扱いやすくなります。


■ ログに最低限入れておきたい情報

ただ echo するだけだと、あとで読みにくくなります。

最低限これだけは入れておくと安心です。


① タイムスタンプ

echo "$(date '+%Y-%m-%d %H:%M:%S') 処理開始"

→ 「いつ起きたか」がわかる


② レベル(INFO / ERROR など)

echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') start process"

echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') something failed" >&2

→ ログの意味が一目でわかる


③ メッセージ(何をしているか)

echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') downloading file..."

→ 「何の処理で失敗したか」が追いやすくなる


■ ログ出力を関数化する

毎回これを書くのは大変なので、関数にしてしまいます。

log_info() {
  echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1"
}

log_error() {
  echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1" >&2
}

使い方:

log_info "処理を開始します"

if ! command; then
  log_error "コマンドが失敗しました"
  exit 1
fi

→ 書きやすさと統一感がかなり上がります


■ ログは「どこに残すか」も設計する

ここがかなり大事なところだと思っています。


■ パターン①:コンソールだけ(開発用)

./batch.sh

→ ローカル確認だけなら十分なことも多いです


■ パターン②:ファイルに出力

./batch.sh > batch.log 2>&1

→ stdout と stderr を1つのファイルにまとめる

2>&1 は、

「標準エラー(2)を標準出力(1)と同じ場所に出す」

という意味です。


■ パターン③:日付ごとに分ける

LOG_FILE="batch_$(date '+%Y%m%d').log"

./batch.sh > "$LOG_FILE" 2>&1

→ 運用によりますが、日付つきのファイルに書くことはよくあります

ログローテーションや調査のしやすさを考えると、
あとからかなり助かります。

一方で、AWS の CloudWatch Logs のように、
同じ Log Stream に流し続ける構成もあります。

「どこで読むのか」によって、残し方も変わってきます。


■ 「ログがあるのに使えない」状態を避ける

ありがちなのがこれです。

  • ログはある
  • でも長すぎて読めない
  • どこで失敗したかわからない

原因はだいたいこのあたりです。

  • タイムスタンプがない
  • レベルがない
  • メッセージが曖昧
  • なんでも出しすぎて追えない

“出している”だけでは意味がない

ログは「あとから読む人」のために作るものなので、
自分が未来で困らないようにしておく感覚が大事だと思っています。


■ set -euo pipefail との関係

以前の記事でも触れたように、

set -euo pipefail

を使うと、

  • エラーで即終了する
  • 未定義変数で止まる
  • パイプ内の失敗も拾いやすくなる

といった安全性が上がります。

ただし、

止まるだけでは原因はわからない

だからこそログが必要です。

「安全に止める」と「原因を追える」は、
セットで考えるとかなり強くなります。


■ まとめ

シェルスクリプトのログ設計は、

  • 標準出力と標準エラーを分ける
  • タイムスタンプ・レベル・メッセージを入れる
  • 関数化して統一する
  • 出力先(ファイル)を設計する

このあたりを意識するだけで、

→ 「動くスクリプト」から
「運用できるスクリプト」

に変わっていきます。


■ おわりに

ログは後回しにされがちですが、

トラブルが起きたときに一番頼れるものです。

あとから困らないために、
最初に少しだけ設計しておく。

それだけで、かなり楽になります。


次は、

「再実行できるバッチ設計(冪等性)」

このあたりもつながってくるので、また整理していきたいと思います。

読んでくださってありがとうございました。
それではまた!