ログの調査で役立つLinuxコマンド

　Linux上でログから目的の情報を探す必要がある人を対象にしています。
　私は開発者ですが保守を兼任することもあるので、問い合わせの事象が起きたことの確認やエラーの原因調査で関連処理の動作がどうだったかを確認する場合に大量のログから特定の行を探すことがあります。
　また、開発中でもログがログ設計の通りに出力されているかの確認や、処理が想定通りに動作していることの確認、開発中のエラーの原因調査でもログを確認することは多いです。
　そういう時に役に立つものを、備忘録も兼ねて書き連ねていきます。

## 目次

[1.使用するコマンドとオプション](#使用するコマンドとオプション)
[2.コマンドの例](#コマンドの例)
[2-1.grepのみのシンプルな検索](#grepのみのシンプルな検索)
[2-2.grepでのOR条件](#grepでのOR条件)
[2-3.awkを使った特定の箇所での絞り込み](#awkを使った特定の箇所での絞り込み)
[2-4.複数のファイルを同時に検索](#複数のファイルを同時に検索)
[2-5.圧縮ファイルになったログファイルを検索したい場合](#圧縮ファイルになったログファイルを検索したい場合)
[2-5-1.gz形式](#gz形式)
[2-5-2.tar.gz形式](#targz形式)
[3.最後に](#最後に)

## 使用するコマンドとオプション
　今回使わないオプションは省略しています。
（コマンドの説明は[GNUプロジェクトのドキュメント](https://www.gnu.org/manual/manual.en.html)や、実際のコマンドの`--help`の内容を参考にしました）
 
|コマンド|今回動作を確認したバージョン|オプション|説明|
|:-----------|:------------:|:------------:|:------------:|
|\||||「パイプ」。コマンドの標準出力を別のコマンドの標準入力へ橋渡しする|
|sudo|Sudo バージョン 1.8.19p2||他のユーザ権限でコマンドを実行する。ユーザーの指定が無い場合はroot権限での実行になる|
|find|find (GNU findutils) 4.5.11||ファイルを検索する|
|||-type|検索対象のファイルタイプを指定|
|||-name|検索対象のファイル名|
|xargs|xargs (GNU findutils) 4.5.11||標準入力などで受け取ったリストを元にコマンドを実行する|
|grep|grep (GNU grep) 2.20||ファイルや標準入力で受け取った値から指定されたパターンを検索|
|||-h|複数のファイルが検索対象になった時、自動的に行われる検索結果へのファイル名の付与を防ぐ|
|||-E|拡張正規表現 (ERE)で検索パターンを指定する|
|||-e|検索パターンを複数指定したい時に利用できます。パターンは基本正規表現 (BRE)になる|
|zgrep|zgrep (gzip) 1.5||gz形式で圧縮されたファイルに対して直接grepを行える（使い方はgrepと同じ）|
|gzip|gzip 1.5||gz形式のファイルを圧縮＆解凍する|
|||-d|解凍する|
|||-c|結果を標準出力で出す（解凍の場合、解凍結果のファイルを作らない）|
|tar|tar (GNU tar) 1.26||tar形式のファイルを圧縮＆解凍する|
|||-O|"--to-stdout"の省略。解凍結果を標準出力にする|
|||-x|解凍する|
|||-f|ファイル名を指定して実行する|
|||-z|gzipコマンドも同時に実行する（拡張子tar.gzの取り扱いで用いる）（基本的に自動で判別されるので無くても良いが、コマンドのバージョンが古いと必要になる事がある）|
|awk|GNU Awk 4.0.2||コマンドラインで直接使えるスクリプト言語|
|||-F|標準入力などで受け取った値を指定の文字列で区切る（区切ったものは$(ドル記号)と数値で表す`フィールド変数`に入る）|
|sort|sort (GNU coreutils) 8.22||標準入力で受け取ったテキストを並び替えて標準出力で返す|

## コマンドの例
　ここで紹介するコマンドを使用する場合は、ログの設計に合わせて適宜変更してください。
　またコマンドのバージョンが古すぎるとその通りに動かない場合もあります。

　では、コマンドを紹介する前に検索をするログがどういう内容かの例を出します。
　例えばこういうログが「`/var/log/httpd/error_log.2021-05-03`」としてあったとして……。
```
[Mon May 03 10:14:24.993130 2021] [mpm_event:notice] [pid 974:tid 139624358406336] AH00489: Apache/2.4.29 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16 configured -- resuming normal operations
[Mon May 03 10:14:24.993172 2021] [core:notice] [pid 974:tid 139624358406336] AH00094: Command line: '/usr/sbin/httpd -D FOREGROUND'
[Mon May 03 10:19:52.825534 2021] [php7:error] [pid 1142:tid 139623346992896] [client 10.0.2.2:53334] PHP Fatal error:  Out of memory (allocated 5234495488) (tried to allocate 20480 bytes) in /var/www/html/xxxxxxxxxx/xxxxx.php on line 44
```

#### grepのみのシンプルな検索
　「client 10.0.2.2」を含み、且つ「 May 03 10:1」または「 May 03 10:2」のログを探したい場合は以下の書き方になります。（正規表現の解説は省略します）
```
grep 'client 10.0.2.2' /var/log/httpd/error_log.2021-05-03 | grep -E "\sMay\s03\s10:(1|2)" | sort
```

#### grepでのOR条件
　断片的な条件しか分からない時など、とにかく複数の条件で広く取得したい場合があります。
　そんなOR条件で検索したい時はこういう書き方になります。
```
grep 'client 10.0.2.2' /var/log/httpd/error_log.2021-05-03 | grep -e'pid 1142' -e'PHP Fatal error:' -e'CI_Log::__destruct()' | sort
```

#### awkを使った特定の箇所での絞り込み
　ログが特定の文字で区切られる設計になっていれば`awk`コマンドを活用できます。
　区切り文字はログ設計によりますが、経験上はタブ文字の事が多いです。
　今回ですと、「`[ ]`」である程度区切られているため`awk`コマンドを繋げて以下の様な感じで絞り込めます。

```
 grep 'client 10.0.2.2' /var/log/httpd/error_log.2021-05-03 | grep -E "\sMay\s[0-9]{2}\s10:(1|2)" | awk -F "] " '{if ($2 ~ /error|notic|warning/) {print $0}}' | sort
```

　「`[ ]`」で区切られた二つ目にエラーかどうかの情報があるため、今回は`awk`で`"] "`で区切った二番目（`$2`）に対して正規表現（`/error|notic|warning/`）でのマッチングを確認し、条件に一致したら区切る前のテキスト`$0`を`print`で標準出力しています。
　ちなみに`-F`が未指定の場合はデフォルトのタブ文字とスペースで区切られます。

 　余談ですが`awk`と区切り文字の指定を利用すると検索結果の整形もある程度行えます。
```
awk -F "] " '{if ($2 ~ /error|notic|warning/) {print $0}}'
↓↓↓↓↓
awk -F "] " '{if ($2 ~ /error|notic|warning/) {print $1"] "$5}}' | sort

# こう変わると、出力結果はこうなる。

[Mon May 03 10:19:52.825534 2021] [php7:error] [pid 1142:tid 139623346992896] [client 10.0.2.2:53334] PHP Fatal error:  Out of memory (allocated 5234495488) (tried to allocate 20480 bytes) in /var/www/html/xxxxxxxxxx/xxxxx.php on line 44
↓↓↓↓↓
[Mon May 03 10:19:52.825534 2021] PHP Fatal error:  Out of memory (allocated 5234495488) (tried to allocate 20480 bytes) in /var/www/html/xxxxxxxxxx/xxxxx.php on line 44
```
　`awk`は複雑で保守しにくいですが、便利ですので興味が出た方はさらに調べてみると良いでしょう。
　ただし、何度も似た条件で検索する場合は1行のコマンドに拘らずにシェルスクリプトにしてしまった方がコマンドのコピーミスによる事故を防げると思います。

 #### 複数のファイルを同時に検索
　**複数のファイルに跨って** 探したい場合は`ワイルドカード（*）`が使えます。（複数のファイルが対象になるので`-h`を指定しないと検索結果にファイル名が付与されます）
```
grep -h 'client 10.0.2.2' /var/log/httpd/error_log.2021-05-0* | grep -E "\sMay\s[0-9]{2}\s10:(1|2)" | sort
```
　ただし、**ワイルドカードが使えるのはファイルへのアクセス権限がある場合** のみです。
　権限がなくて`sudo`を挟む必要がある場合は以下の`find`と`xargs`を用います。
```
sudo find /var/log/httpd/ -type f -name 'error_log.2021-05-0*' | xargs sudo grep -h 'client'$'\s''10.0.2.2' | grep -E "\sMay\s[0-9]{2}\s10:(1|2)" | sort
```

 #### 圧縮ファイルになったログファイルを検索したい場合
　システムやサービスの保守では圧縮済みのファイルに対して検索する機会の方が多いのではないでしょうか。
　ここではディスク容量を圧迫しにくい、解凍ファイルを作らずに`grep`で検索する方法を紹介します。

 ##### gz形式
　`gz`形式の圧縮の場合は`zgrep`が使いやすいです。`grep`と同じように使えます。
```
zgrep 'client 10.0.2.2' ./error_log.2021-05-02.gz | grep -E "\sMay\s[0-9]{2}\s21:(1|2)" | sort
```
　最近はほぼ無いですが、`zgrep`コマンドがサーバーにない、又は作業アカウントで許可されてない場合は`gzip`で標準出力しながら`grep`に渡す方法になります。
```
gzip -d -c ./error_log.2021-05-02.gz | grep 'client 10.0.2.2' | grep -E "\sMay\s[0-9]{2}\s21:(1|2)" | sort
```

 ##### tar.gz形式
　この形式の圧縮はフォルダーごと圧縮できるので使い勝手が良い圧縮形式です。
　この形式に対して解凍ファイルを作らずに検索する場合は、先に`-tf`オプションでアーカイブの中身を確認しておきます。

```
 tar -tf 202x99_log.tar.gz | sort
```
　中身を確認したら検索したい対象のファイルを指定して標準出力で解凍し、`grep`へ渡します。このファイル指定もワイルドカードが使えます。
```
tar -O -xf ./202x99_log.tar.gz ./httpd/error_log.2021-05-0* | grep 'client 10.0.2.2' | grep -E "\sMay\s[0-9]{2}\s10:(1|2)" | sort
 ```
　アーカイブの中を全て対象にしたい場合はファイルの指定を外します。
```
tar -O -xf ./202x99_log.tar.gz | grep 'client 10.0.2.2' | grep -E "\sMay\s[0-9]{2}\s10:(1|2)" | sort
 ```

　再び余談ですが、たまに`gz`を入れたディレクトリをさらに`tar.gz`で圧縮したファイルがあります。圧縮が二重になって容量もあまり小さくならないので効率は良くないのですが、たまに見かけます。
　そういう圧縮は`tar -tf`で中身を確認した時に`gz`拡張子が出て来てわかるので、以下のように`gz`用の処理をパイプで繋げると`grep`での検索が可能です。
```
tar -O -xf ./202x99_log_gz.tar.gz ./httpd/error_log.2021-05-03.gz | zgrep 'client 10.0.2.2' | grep -E "\sMay\s[0-9]{2}\s21:(1|2)" | sort
```

## 最後に
　こんにちは。休日に興味のある技術の実験や検証を時々やってるLAMP(Linux+Apache+MySQL+PHP)エンジニアです。

　今回は『[LTSグループ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ltsgroup)』の12月14日分を担当させていただきました。
　これは[WAKUTO](https://qiita.com/organizations/wakuto-inc)社内で行っている勉強会で私が担当する2024年分として予定しているものの一部を先出ししたものです。
　ちょうど入社後2年目～3年目の初心者を脱し始める頃のサーバーサイドの技術者が、実務で触れる機会が出始める内容になると思います。
　それ以外にも、ベテランに対してもコマンドをど忘れしたときの備忘録として役立てれば幸いです。


　最後に、ここまでお読み頂きありがとうございました。