hatenob

プログラムって分からないことだらけ

一人Web開発~第15夜 ログ管理

前回も書いたけれど、ログは主に調査、解析用のバックアップと、監査用のバックアップの2つがあり、前者は1週間~1ヶ月、後者は1年以上とかになる。昨今のビッグデータ活用により前者も何年とか蓄積するケースもあるけれど、その場合は生ログではなく、それ用の入れ物に入れておくことになると思う。もちろん後者の監査ログもサーバ上に残しておくわけではないので、まぁ一緒かな。いずれにせよ、参照する機会が異なるものであり、サーバから取得するタイミングも異なるものだと覚えておけばよいのではなかろうか。
一般的に、ログファイルは一定のタイミングでローテーションさせるもので、サーバ上の保管期間を越えたら削除するものです。お掃除をするのでハウスキーピングと呼ばれたりもします。

ログローテーション

切替タイミング

ログローテーションには主に2つのタイミングがあって、

  • サイズで切り替える
  • 日時で切り替える

というものがある。ディスク容量の制約とか特別な理由がない限り、サイズ切替は使わないほうが無難。ある障害事象により大量のログが吐かれたとして、サイズ切替だと大量ログでサイズ切替が発生してしまい、大事なログメッセージを消失してしまう可能性があるとかで。
毎日ログが出る可能性があるなら日次で、週末にしか出ないのなら週次で、というようにタイミングを決めてあげると分かりやすい。毎日出るけど量が少ないから週次で、という判断もできるけれど、分かりにくくなるのでお勧めはしない。
あと、ローテーションの指示を外部から行うような場合(ジョブ管理等で)、0時きっかりのローテーションは避けたほうが無難。マネージャとエージェント間で時刻がずれていて思ったようにローテーションされないケースがあるから。

切替方式

プロダクトが出力するログの場合、プロダクトがログローテーションをサポートしている場合がある。特に対象のプロダクトが起動中にローテーションする場合はその機能を使う方が無難。機能を持っていない場合は、

  • 外部プロダクトを使う(logrotate、rotatelogs等)
  • シェル(その他プログラム)で行う

の2択。
logrotateはLinuxで標準的に採用されているローテーションツールで様々なオプションにより、主に性的な状態のファイルに対するローテーションを行う。
roglotateはApache HTTPDに付属するローテーションツールで、標準出力から受け取ったログレコードをファイルに記録し、指定のタイミングでローテーションを行う、動的な状態に使えるツール。標準出力をログに出しつつローテーションしたい場合には便利。
シェルでやる場合には好きにできるけれど、基本は停止をしてローテーションする必要がある。
あと、プロダクト機能の場合、ローテーションはできるけれど削除まではできないケースが多い。
あと、動的に切り替えるものは大体が出力したタイミングでの切替になるので、何も出力されなければ日が替わろうが切り替わらない。

採用方式

今回は下記の方針にしました。

  • プロダクト機能でローテート可能なものはそれを使う
  • そうでないものはlogrotateを使う

logrotate.confはほとんどデフォルトなのだけれど、ローテートタイミングをweeklyからdailyに変更しております。

# see "man logrotate" for details
# rotate log files daily
daily

# keep 7 day worth of backlogs
rotate 7

# create new (empty) log files after rotating old ones
create

# use date as a suffix of the rotated file
dateext

# uncomment this if you want your log files compressed
#compress

# RPM packages drop log rotation information into this directory
include /etc/logrotate.d

デフォルトで登録されているものやら、今回yumでインストールした際に登録されたものなんかがありますが、追加でとりあえずwildflyのコンソールログを対象にしておきます。

/var/log/wildfly/console/console.log {
    copytruncate
    missingok
}

コンソールログはリダイレクトで出力しているので、実行中でもinodeが変わらないようにcopytruncateにしています。コピーから初期化するまでの間のログを消失する可能性があるので、本当は実行中に切り替えるのはよくないのだけれども、今回は許容することにしました。どうしても嫌なら停止してから切り替えれば問題なし。

ログ削除

不要ログの削除、logrotateを使う場合は勝手にやってくれるのだけれど、プロダクト機能でローテーションの場合にはそれができないのでシェルで消してやることにします。
設定ファイルにログのパスと保持期間を書いておいて、その期間残すことにします。
処理は単純にfindでリストを作って消す。
わざわざ1回ファイルに吐くのは、消し対象がどれなのかを後から確認できるように(するかどうかは別として)するためです。

. /opt/shell/lib/constants.env
. /opt/shell/lib/functions.env

CONF_FILE=${SH_CONF}/log.conf
TARGET_FILES=$(UTIL_GET_TMP_FILE)

LOG_INFO "00001"

grep "^id:" ${CONF_FILE} | grep -v ",delete:false," | while read conf
do
  LOG_INFO "00003" "${conf}"

  UTIL_TO_HASH "${conf}"

  dir_name=$(dirname "${HASH["path"]}")
  file_name=$(basename "${HASH["path"]}")

  find ${dir_name} -type f -maxdepth 1 -name "${file_name}" -mtime +${HASH["delete"]} >> ${TARGET_FILES}
done

if [ -s ${TARGET_FILES} ]
then
  LOG_INFO "00004" "${TARGET_FILES}"
  cat ${TARGET_FILES} | xargs rm -f
else
  LOG_INFO "00005"
fi

LOG_INFO "00002"
exit 0

ログ保管

監査ログ、アクセスログは長期保管するため別の場所にコピーをとっておきます。こちらも設定ファイルで対象を絞っておきます。なお、保管したものをどうするのかまでは今回は考えていません。別媒体に保管する、別サイトに転送する等々、その時の都合に合わせて選択する必要があります。
こちらも同じく、findでリストを作ってコピー。ただし、コピー先にすでに存在する場合は上書きしません(-n)。

. /opt/shell/lib/constants.env
. /opt/shell/lib/functions.env

CONF_FILE=${SH_CONF}/log.conf
TARGET_FILES=$(UTIL_GET_TMP_FILE)
BACKUP=${BK_HOME}/audit

LOG_INFO "00001"

grep "^id:" ${CONF_FILE} | grep ",audit:true" | while read conf
do
  LOG_INFO "00003" "${conf}"

  UTIL_TO_HASH "${conf}"

  dir_name=$(dirname "${HASH["path"]}")
  file_name=$(basename "${HASH["path"]}")

  find ${dir_name} -type f -maxdepth 1 -name "${file_name}" >> ${TARGET_FILES}
done

if [ -s ${TARGET_FILES} ]
then
  LOG_INFO "00004" "${TARGET_FILES}"
  cat ${TARGET_FILES} | while read file
  do
    cp -n ${file} ${BACKUP}/
  done
else
  LOG_INFO "00005"
fi

LOG_INFO "00002"
exit 0

設定ファイルとか関数ライブラリとかはGithubを参照ください。
oneman/platform/chef/cookbooks/shell/templates/default at master · nobrooklyn/oneman · GitHub

あとはこれらの処理をジョブとして定時起動すればよいわけです。
もちろんcronでもよいのですが、今回作った環境であれば状況が分かりやすいようにJenkinsを使ってジョブ化するのがよいと思います。

以上で一通りの仕組みができたのではなかろうかと思います。
もう一エントリ、まとめを書いてしめたいと思います。