logrotate入門

この記事は[株式会社ネクスト(Lifull) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/lifull)の3日目の記事です。
遅くなってしまいましたが、なんとか当日に間に合~~わせました~~いました。

本記事はLinuxのlogrotateコマンドについての説明をするものであり、その他のログに関する話は一切出てきません。ご了承ください。

# logrotateとは

``man logrotate``にも書いてあることですが``logrotate``は複数のログファイルを圧縮、削除、メールで送信するための機能です。
これにより「ファイルサイズがN以上になったら」とか「日次・週次・月次などで分割」などが容易に可能になります。

logrotate自体はデーモンではなくcrondによって実現されています。
利用する前にcrondが動いていることを確認しましょう。

```shell-session:crondの動作確認
$ /etc/rc.d/init.d/crond status # 動いていなかったらstartしましょう
crond (pid  1939) を実行中...
```

続いてlogrotateがインストールされているかの確認です。
たぶん通常はインストールされていて、ある程度よしなに動くように設定されていると思います。

```shell-session:logrotateのインストール確認
$ yum list installed | grep logrotate
logrotate.x86_64                   3.7.8-26.el6_7                    @updates
```

インストールされてなかったらyumで入れればよいかと思われます。

ではlogrotateの動作を確認していきましょう

# 練習のための前準備

練習用のログファイルを用意します。
適当にapacheのログファイルを/tmp/logにコピーして置いておきました。
すでにある程度logrotate済みのファイルでしたので*-yyyymmmdd形式になってます。

```shell-session:
$ ls /tmp/log/httpd/
-rwxrw-rw- 1 zom dev 2172608 11月 30 23:30 2016 access_log
-rwxrw-rw- 1 zom dev 4563118 11月 30 23:30 2016 access_log-20161106
-rwxrw-rw- 1 zom dev 4841917 11月 30 23:30 2016 access_log-20161113
-rwxrw-rw- 1 zom dev 4760022 11月 30 23:30 2016 access_log-20161120
-rwxrw-rw- 1 zom dev 4285726 11月 30 23:30 2016 access_log-20161127
```

とりあえずこのaccess_logを対象とする設定ファイルを書いてみます。

```shell-session:/tmp/log/logrotate.conf
/tmp/log/httpd/access_log {
}
```

設定ファイル内では対象とするログのファイルパス、もしくはディレクトリパスを指定します。
ファイルパスではワイルドカードの*が利用可能で、パターンにマッチするファイルが全て対象となります。
またディレクトリパスを指定した場合は該当のディレクトリ以下のファイルすべてが対象となりますので、いずれの利用も注意しておこなってください。

早速この設定でlogrotateを実行してみましょう。
このとき-dオプションを付けることで実際にlogrotateは行われずに、どのように動作するのかをデバッグすることができます。
-dオプションを省略すると設定次第ではログが破壊的変更を受けますのでいろいろ試すときはバックアップをとっておくと良いと思います。

```shell-session
$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
reading config file /tmp/log/logrotate.conf
reading config info for /tmp/log/httpd/access_log

Handling 1 logs

rotating pattern: /tmp/log/httpd/access_log  1048576 bytes (no old logs will be kept)
empty log files are rotated, old logs are removed
considering log /tmp/log/httpd/access_log
  log needs rotating
rotating log /tmp/log/httpd/access_log, log->rotateCount is 0
dateext suffix '-20161203'
glob pattern '-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
renaming /tmp/log/httpd/access_log.1 to /tmp/log/httpd/access_log.2 (rotatecount 1, logstart 1, i 1),
renaming /tmp/log/httpd/access_log.0 to /tmp/log/httpd/access_log.1 (rotatecount 1, logstart 1, i 0),
renaming /tmp/log/httpd/access_log to /tmp/log/httpd/access_log.1
disposeName will be /tmp/log/httpd/access_log.1
removing old log /tmp/log/httpd/access_log.1
error: error opening /tmp/log/httpd/access_log.1: そのようなファイルやディレクトリはありません
```

記述した設定ファイルをもとに実行しようとしているのが分かります。
接尾語は「-20161203」だと書いてありますが、dateextの設定（後述）をしていません。
そのため古いログは.1という接尾語が採用され、元のファイル名は``/tmp/log/httpd/access_log.1``になったと言われています。
さらにrotateオプションも使っていないため古いログ(access_log.1)が消されようとしています。
しかし、そこでそんなファイルないよ、とerrorになったところでコマンドが終了しました。

では対象のログファイルがなかった場合はどのような結果になるのでしょうか？

```shell-session
$ cat /tmp/log/logrotate.conf
/tmp/log/httpd/access_lo {
# ファイル名のgを消しました
}

$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
reading config file /tmp/log/logrotate.conf
reading config info for /tmp/log/httpd/access_lo

Handling 1 logs

rotating pattern: /tmp/log/httpd/access_lo  1048576 bytes (no old logs will be kept)
empty log files are rotated, old logs are removed
considering log /tmp/log/httpd/access_lo
error: stat of /tmp/log/httpd/access_lo failed: そのようなファイルやディレクトリはありません
```

対象のログファイルがないというエラーでコマンドが終了しました。

ということで何も指定せずに実行した場合、このようなことが分かります。

* logrotate対象のファイルがある場合は.nという接尾語をつけてリネームする
* 古いログは削除される（ファイルがなくてエラーを吐いて終了する）
* 対象ファイルがない場合はファイルを開けずにエラーを吐いて終了する

# ファイルが存在しない場合にエラーを吐かないようにする

ファイルがない場合にエラーを吐いて終了するのはいいこととは思えません。
ですのでmissingokオプションをつけて実行してみましょう。

```shell-session
$ cat /tmp/log/logrotate.conf
/tmp/log/httpd/access_lo {
    missingok
}

$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
reading config file /tmp/log/logrotate.conf
reading config info for /tmp/log/httpd/access_lo

Handling 1 logs

rotating pattern: /tmp/log/httpd/access_lo  1048576 bytes (no old logs will be kept)
empty log files are rotated, old logs are removed
considering log /tmp/log/httpd/access_lo
  log /tmp/log/httpd/access_lo does not exist -- skipping

$ echo $?
0
```

エラーではなくskippingに結果が変わりました。
``echo $?``で終了ステータスも0（正常終了）に変わったことが確認できます。

# 世代管理をする

次に古いログがすぐに消されるのは困りますので1世代だけ残してもらうようにしましょう。

```shell-session
$ cat /tmp/log/logrotate.conf
/tmp/log/httpd/access_log {
    # gをもとに戻しました
    missingok
    rotate 1
}

$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
### 長いので中略 ###
renaming /tmp/log/httpd/access_log to /tmp/log/httpd/access_log.1
removing old log /tmp/log/httpd/access_log.2
error: error opening /tmp/log/httpd/access_log.2: そのようなファイルやディレクトリはありません
```

先ほどから接尾語が.2に変わりました。世代管理をするようになり、さらに古いファイルから削除しようとしているようです。
しかし削除するときにエラーになってしまうのもなんなので試しに該当のファイルを置いて実行してみます。

```shell-session
$ touch /tmp/log/httpd/access_log.2
$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
### 長いので中略 ###
renaming /tmp/log/httpd/access_log.1 to /tmp/log/httpd/access_log.2 (rotatecount 1, logstart 1, i 1),
renaming /tmp/log/httpd/access_log.0 to /tmp/log/httpd/access_log.1 (rotatecount 1, logstart 1, i 0),
renaming /tmp/log/httpd/access_log to /tmp/log/httpd/access_log.1
removing old log /tmp/log/httpd/access_log.2
```

エラーが出なくなりました。では次に行きましょう。

# 接尾語を設定する

先述のdateextの設定をしてみます。

```shell-session
$ cat logrotate.conf
/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
}
$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
### 長いので中略 ###
removing /tmp/log/httpd/access_log-20161106
removing old log /tmp/log/httpd/access_log-20161106
removing /tmp/log/httpd/access_log-20161113
removing old log /tmp/log/httpd/access_log-20161113
removing /tmp/log/httpd/access_log-20161120
removing old log /tmp/log/httpd/access_log-20161120
renaming /tmp/log/httpd/access_log to /tmp/log/httpd/access_log-20161203
removing old log /tmp/log/httpd/access_log-20161127
```

rename時のファイル名が-YYYYMMDDに変わりました。
でもファイル名にハイフンを使うのは好きじゃないんだよね、_で区切りたいんだよね、というケースもあるかと思います。
その場合はdateformatが利用可能です。

```shell-session
$ cat logrotate.conf
/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
    dateformat _%Y%m%d
}
$ /usr/sbin/logrotate -d /tmp/log/logrotate.conf
### 長いので中略 ###
dateext suffix '_20161203'
glob pattern '_[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
glob finding old rotated logs failed
renaming /tmp/log/httpd/access_log to /tmp/log/httpd/access_log_20161203
```

dateformatには%Y%m%dか%sが利用可能です。
%sですとUnixtimeになりますがシステムクロックが2001年9月9日以降に設定されている必要があります。（ってmanで出てきました）
また、並び順にはDDMMYYYY形式などを指定することもできますファイル名などのソートが使えなくなる可能性があることに気をつけて使ってください（ってmanで出てきました）

# 古いファイルを圧縮する（＆stateファイルについて）

次に古いファイルはそんなに見ることはないでしょうから圧縮してファイルサイズを節約してみましょう。
圧縮したい場合はcompressです。本当に圧縮できるのかを確認したいので-dオプションを消してやってみます。

```shell-session
$ cat logrotate.conf
/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
    dateformat _%Y%m%d
    compress
}
$ /usr/sbin/logrotate /tmp/log/logrotate.conf
error: error creating unique temp file: 許可がありません
```

コケました。何か一時ファイルを作ろうとするも許可がないとのことです。
（これは推測混じりですが）logrotateするときにステータスをファイルで管理しています。
デフォルトでは/var/lib/logrotate.stateです。こちらがroot権限であるために一般ユーザで実行すると権限で怒られてしまうのではないかと。

```shell-session
$ ls /var/lib/logrotate.status
-rw-r--r-- 1 root root 499 12月  3 05:05 2016 /var/lib/logrotate.status
```

stateファイルも引数で指定することが可能ですので指定して実行してみましょう。

```shell-session
$ /usr/sbin/logrotate -s /tmp/log/logrotate.state /tmp/log/logrotate.conf
$ ls /tmp/log/logrotate.state
-rw-r--r-- 1 zom dev 67 12月  3 22:09 2016 /tmp/log/logrotate.state

$ cat /tmp/log/logrotate.state
logrotate state -- version 2
"/tmp/log/httpd/access_log" 2016-12-3

$ ls /tmp/log/httpd
-rwxrw-rw- 1 zom dev 4563118 11月 30 23:30 2016 access_log-20161106*
-rwxrw-rw- 1 zom dev 4841917 11月 30 23:30 2016 access_log-20161113*
-rwxrw-rw- 1 zom dev 4760022 11月 30 23:30 2016 access_log-20161120*
-rwxrw-rw- 1 zom dev 4285726 11月 30 23:30 2016 access_log-20161127*
-rw-r--r-- 1 zom dev      0 12月  3 21:33 2016 access_log.2 # 先程touchしたファイル
-rwxrw-rw- 1 zom dev 118394 12月  3 21:54 2016 access_log_20161203.gz
```

stateファイルができました。
最後のrotateの日付が2016-12-3になってますね。
また思惑通り、ファイルの圧縮がされ1世代前のファイルがgzip圧縮されています。


# 新しいログファイルの設定をする

おや？上の実行後にaccess_logが存在していません。これでは次のrotateに引っかかりません。
（通常はapache側で再作成してくれると思いますが）
新しいログファイルを作成してほしい場合はcreateの設定を使います。
createではファイルパーミッションや所有者・所有グループを指定できます。
とっておいたバックアップを元に再度実行し直してみます。

```shell-session
$ cat /tmp/log/logrotate.conf
/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
    dateformat _%Y%m%d
    compress
    create 766 zom dev
}

$ ls /tmp/log/httpd
-rwxrw-rw- 1 zom dev       0 12月  3 23:02 2016 access_log
-rwxrw-rw- 1 zom dev 4563118 12月  3 23:01 2016 access_log-20161106
-rwxrw-rw- 1 zom dev 4841917 12月  3 23:01 2016 access_log-20161113
-rwxrw-rw- 1 zom dev 4760022 12月  3 23:01 2016 access_log-20161120
-rwxrw-rw- 1 zom dev 4285726 12月  3 23:01 2016 access_log-20161127
-rwxrw-rw- 1 zom dev  118394 12月  3 23:02 2016 access_log_20161203.gz
```

gzファイルができた上にaccess_logファイルを新規に作成し直しています。

しかしログファイルはものによってはファイルハンドラを掴みっぱなしにしていてrenameができないことも考えられます。
その場合はcreateではなくcopytruncateの設定の方を使います。
こちらはファイルをrenameではなくcopyして、元のファイルの「内容」を消すというものです。
コピー→削除の間に若干のタイムラグがあるので頻繁に書き込みが行われている場合にはデータの欠損が発生しうることがあります。（ってmanで出てきました）
またcreateとcopytruncateの両方を記述しているとcopytruncateのほうが優先されます。

```shell-session
$ cat /tmp/log/logrotate.conf
/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
    dateformat _%Y%m%d
    compress
    create 766 zom dev
    copytruncate
}

$ /usr/sbin/logrotate -s /tmp/log/logrotate.state /tmp/log/logrotate.conf -d
### 中略 ###
copying /tmp/log/httpd/access_log to /tmp/log/httpd/access_log_20161203
truncating /tmp/log/httpd/access_log
```

先程のrenamingからcopyingに挙動が変わりました。
createかcopytruncateかは利用場面に応じて使い分けてください。

# ローテーションの間隔を指定する

次はローテーションの間隔です。
daily, weekly, monthlyのいずれかが指定可能です。
:pencil2: [version3.8.5からhourlyが追加されていました](https://github.com/logrotate/logrotate/blob/master/ChangeLog.md#385---2013-06-10)。また、yearlyも存在していました。
なぜこのような間隔になるのかというとcron.dailyに登録されているからです。

```shell-session
$ ls /etc/cron.daily/logrotate
-rwx------ 1 root root 180  7月 10 04:36 2003 /etc/cron.daily/logrotate

$ sudo cat /etc/cron.daily/logrotate
#!/bin/sh

/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
    /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0
```

またweeklyの場合はいつrotateされるのかというと日曜日です。cronでは曜日の指定を数字でおこないますが0が日曜日で6が土曜日です。
``logrotate``では、この数字が前回より小さくなったときに週が変わったと認識するため日曜日にrotateされます。
またmonthlyの場合は毎月1日となります。
ログの増加量と相談して選ぶとよいかと思います。

しかし、日付間隔だけでrotateするというのも適切ではありません。
全然ファイルサイズが増えないのであればmonthlyより広い間隔でもいいかもしれませんし、すごい勢いで増えるのであればもっと早いタイミングでrotateしたいでしょう。

その場合はsize, もしくはmaxsize, minsizeなどを使いましょう。

sizeなどにはバイト数を指定できます。さらに単位としてk(キロバイト)、M（メガバイト）、G(ギガバイト）も利用可能です。なぜかkだけ小文字です。

sizeの場合は、時間指定と排他的な設定で、指定した数値を超えた場合にrotateされるようになり、**dailyなどの時間指定は無視されます**。
一方[max|min]sizeは時間指定と排他ではなくともに生きる設定です。

---
**:warning:ここから記述内容に誤りがありました。正確な情報はコメント欄を参照ください**

ですので2MBちょっとのaccess_logに対してrotateするまでもない、という場合に以下のようにmaxsizeを3Mと設定してみます。

```shell-session

/tmp/log/httpd/access_log {
    missingok
    rotate 1
    dateext
    dateformat _%Y%m%d
    compress
    copytruncate
    daily
    maxsize 3M
}

$ /usr/sbin/logrotate -s /tmp/log/logrotate.state /tmp/log/logrotate.conf -d
empty log files are rotated, log files >= 0 are rotated earlier, old logs are removed
considering log /tmp/log/httpd/access_log
  log does not need rotating
```

するとrotateの必要はない、といった感じでrotateされていません。
maxsizeを2Mにしてみるとrotateされます。

**:warning:ここまで記述内容に誤りがありました。**

---


# まとめ
1つずつ設定を解説していきました。
本当はもっといろいろあります。圧縮をgzip以外にするとかメールで送信するとかdelaycompressとか…。
時間の関係上、基本的なところしか書いていませんがご査収ください。（暇があれば追記していきたいですね）

次は4日目の[kaiharaさん](http://qiita.com/kaihar4)の[Service Worker Offline Cache Techniques](https://kaihar4.com/2016/12/04/serviceworker-offline-cache.html)です。よろしくどうぞ！