[Bash][Tips]ログファイルを探索するために知っておきたいシェルコマンド [初心者向け]
こんにちは。こむろ@札幌です。昨日・今日はとても涼しくて過ごしやすい気候です。
涼し気な写真を2枚ほど。
オロロンラインの途中にある白銀の滝
夕暮れ時の豊平川
はじめに
皆さんはログファイルの解析、目的の項目の抽出などはどのように行っているでしょうか?自分は普段Pythonを使い *1、テキスト処理などを行っていますが、シェルのコマンドの方が更にフットワーク軽く、文字列処理に特化した場合、かなり強力な機能があるよ、とのことなので今回はシェルを使ってログファイルのテキスト処理を実行します。
この記事の対象者は、以下のような意識低めな人たちです。 *2
- iTerm2などのコンソール画面はそこそこ見慣れてるし
- コマンドはそこそこ打つけど
- 自分でシェルのコマンドを駆使してテキスト処理をしたことがないし
- 大体ググッてコピペすればどうにかなるでしょ的なノリで生きて来た
そのため、パイプ(|
)等のコマンドの要素一つ一つについては、知ってる前提ですっ飛ばします。
ちなみに自分もコマンドやシェルスクリプトに関しては、できるだけ自分で書くのを避けてきた人間なので、基本コマンド程度しか知りません(lsやgrep, findなど)
環境について
基本的に全てAmazon Linux上で動作確認しています。
- Amazon Linux AMI release 2016.03
テキストファイルを検索する
障害の調査を行う際には、どこかの段階でログファイルそのものに深く潜って原因を探しに行くことがあるかと思います。自分はログファイルのテキストを確認する時には less
を利用してます。
$ less /path/to/current_log.log
space
でページ送り:
が表示されている状態でq
を入力すると終了
単語を検索
less
でテキストを開いた状態で画面左下に :
が表示されています。
/request_id
その状態で /
を入力しその後調べたい単語を入力します。 Enter
で検索。
n
で次のヒットした行までジャンプshift
+n
で前のヒットした行までジャンプ
tailの代わり
tail -f
と同じく更新を監視するためには以下のコマンド。
$ less +F /path/to/current_log.log
もしくは
$ less /path/to/current_log.log
- ドキュメントを開いた後に
shift
+f
でもOK。
更新を追随して画面を更新してくれます。
ctrl
+c
で更新監視を停止。更新監視を停止していれば単語検索などは可能です。
大体これくらいしか使っていません。
目的のログの行だけ抽出
grep
を使っています。
$ grep 'request_id:' /path/to/current_log.log
gzのファイルを検索したい
ログローテーション設定をしていると、アーカイブされたファイルは .gz
等で圧縮されてる事も多いかと思います。 z
付きのコマンドは、 gz
ファイルをそのまま操作することができるので便利でした。
圧縮ファイルを解凍せずに閲覧
zless
を利用します。
$ zless /path/to/archive_log.gz
使い方は less
と同じです。アーカイブなので多分 +F
はないと思います(未確認)
圧縮ファイルから目的の行を抽出
予想通りやっぱりありました。grepのgzip対応版 zgrep
を利用します。
$ zgrep 'request_id:' /path/to/arcive_log.gz
圧縮されたファイルの中から、条件に合致する行を表示してくれます。
特定の項目のみを抽出する
こんなログが出力されるとしましょう。Log4jを利用したログの一例です。こちらもアーカイブされてるものとします。
2016-07-06 01:53:01.686 INFO 5818 --- [http-nio-8080-exec-8] jp.classmethod.sample.Hoge : requestId:xxxxx-2712-eeee-a667-c7b4f0d3d8e9 hashId:seeecreeeeet==== INFO : call sample method GET statusCode:200
前提条件: ログのトレーサビリティを向上させるために、1リクエストに対して必ず一意なrequestId
を発行しています。リクエストの中で処理しているログは全てこの統一した requestId
に紐付いているため、障害時にはこの値をキーを探すことで原因特定が容易になります。自分の関わっている案件はこの requestId
のおかげで調査時にとても効率よく作業ができています。
さて、これらのログから requestId のみを抽出したリストを作成したい場合どうしましょう。まずはやることをまとめてみます。
- テキストを読み込む
requestId:xxxxx
のみを取り出すrequestId:
を消す- 値なしの行もあるため、空行を削除する
- 1リクエストでいくつも処理があるため、同じ
requestId
がいくつも出るため重複をなくす
1. gzファイルを読めるようにする
zless
を使います。
$ zless /path/to/archive_log.gz > test.txt
2. 目的のものだけ取り出す
awk
を使います。awkはテキスト処理を行うためのコマンドです。
$ awk '{ print $9 }' test.txt > test2.txt
空白で全ての項目が区切られているため、 $9
が requestId:xxxx
にあたります。念の為確認してみます。
index | 項目 |
---|---|
1 | 2016-07-06 |
2 | 01:53:01.686 |
3. | INFO |
4. | 5818 |
5. | --- |
6. | [http-nio-8080-exec-8] |
7. | jp.classmethod.sample.Hoge |
8. | : |
9. | requestId:xxxxx-2712-eeee-a667-c7b4f0d3d8e9 |
10. | hashId:seeecreeeeet==== |
... | .... |
3. requestId:を削除
sed
を利用します。正規表現を使うことができるため、置換処理などが出来るようです。
$ sed 's/requestId://g' test2.txt > test3.txt
4. 空行を削除
空行を削除するにも sed
を利用します。
$ sed '/^$/d' test3.txt > test4.txt
5. 重複を排除
最後に重複をなくします。
$ uniq test4.txt > final.txt
uniq はソートされている前提という記事を見かけました。こちらの指摘通り、 sort -u の方が正しい気がします。今回実際に案件で利用したものは、たまたま重複は存在しないデータだったため、 uniq は指定していませんでした。
全部つなぐ
中間ファイルにいちいち出力していられないので、全てパイプでつなぎます。
$ zless /path/to/archive_log.gz | awk '{ print $9 }' | sed 's/requestId://g' | sed '/^$/d' | uniq > final_answer.txt
awk
には zawk
にあたるものがなかったため、一旦 zless
でgzファイル内のテキストを読めるようにしました。gzファイルでない場合は不要かもしれません。
テキストファイルの中のJSONを操作
弊社にもたくさんエントリがありますが、jq
を利用すると楽ちんでした。以下の様なJSONが記述されているログファイルを想定します。
{ "timestamp": "2016-08-10T14:37:46.263+0900", "request_id": "3e76b784-0e3c-41b9-8176-3ecbb697b711", "hash_id": "123456789==", "event_type": "Update", "details": { "nickname": "Deleted" }} { "timestamp": "2016-08-10T14:37:46.987+0900", "request_id": "2cf8fedc-5fef-4e53-8bb2-0bdd28e9e377", "hash_id": "123456789==", "event_type": "Delete", "details": {}} { "timestamp": "2016-08-10T14:37:47.012+0900", "request_id": "e50b02d3-0434-4156-b951-5435c4c735fd", "hash_id": "123456789==", "event_type": "Delete", "details": {}} { "timestamp": "2016-08-10T14:37:48.012+0900", "request_id": "b21858cf-5f6c-4a3c-b581-93e21b43225a", "hash_id": "123456789==", "event_type": "Delete", "details": {}} { "timestamp": "2016-08-10T14:37:48.012+0900", "request_id": "b21858cf-5f6c-4a3c-b581-93e21b43225a", "hash_id": "123456789==", "event_type": "Delete", "details": {}}
1行に付き1つのJSON文字列が記載されているログです。このファイルから、 request_id
のリストを作成します。
$ jq '.request_id' json_log.log
これを実行すると、 request_id
の値のみを抽出することができます。
"3e76b784-0e3c-41b9-8176-3ecbb697b711" "2cf8fedc-5fef-4e53-8bb2-0bdd28e9e377" "e50b02d3-0434-4156-b951-5435c4c735fd" "b21858cf-5f6c-4a3c-b581-93e21b43225a" "b21858cf-5f6c-4a3c-b581-93e21b43225a"
重複を削除してソート
重複を削除した上にソートしたい場合は以下で良いようです。
$ jq '.request_id' json_log.log | sort -u
実行するとソートと重複削除も実行されます。
"2cf8fedc-5fef-4e53-8bb2-0bdd28e9e377" "3e76b784-0e3c-41b9-8176-3ecbb697b711" "b21858cf-5f6c-4a3c-b581-93e21b43225a" "e50b02d3-0434-4156-b951-5435c4c735fd"
重複していた "b21858cf-5f6c-4a3c-b581-93e21b43225a"
が消えました。
「jqのオプションでソートも重複削除も指定出来るよ」という指摘を受けました。ほんとだ!!jqすごい!
アーカイブされたgzファイルの中からrequestIdのリストを作成する
$ zless archive-20160724.gz | jq '.request_id' > id-list.txt
zless
で一度展開し jq
へ渡します。取り出した request_id
の値を id-list.txt
に保存します。
RequestIdのリストから合致するログのみ抽出
先ほど作成したRequestIdのリストに合致するもののみ、ログファイルから抽出したい場合どうしましょうか。 less
や zless
でテキストを開いて検索しながら確認していくのも一つの方法ですが、こちらもコマンドで処理してみます。
xargsの利用
やりたいことは以下です。
- requestIdのリストファイルから1つずつ値を取り出す
- requestIdに合致するテキストを全て出力
- 出力結果を保存
UNIXシェルスクリプトコマンドブック こちらの第二版を参照していたのですが、 xargs
が載っていなかったため、泣く泣く自分で調べました。
xargsは標準入力を引数に受け取り、指定したコマンドを実行できるコマンドのようです。様々なオプションがありますが、今回は -I
を利用します。
-I
を調べてみると以下の様な使い方をすることが多いようです。
xargs -I{} <command> {}
{}
これはプレースホルダーを示し、入力された引数の別名を定義するもののようです。そのため {}
である必要もなく param
など適当な文字列でも良いようです。ただ、 {}
は滅多に衝突する名前ではないため、多用されているようです。そのため、以下の様に解釈できます。
xargs -Iパラメータ command パラメータ
command
にあたるものは何でも良いようです。今回は文字列の検索をgzファイルから実行したいので以下のように記述します。
xargs -I{} zgrep {} archive-20160724.gz
全ての手順をまとめると以下のように書けました。
$ less id-list.txt | xargs -I{} zgrep {} archive-20160724.gz > filtered_log.log
こちらを実行すると、対象のrequest_idのみを抽出したログファイルが作成することができます。
まとめ
はじめてきちんとシェルのコマンドを使いました。まだまだ使っていないオプションなどもありますし、シェルスクリプトとしてもう少し色々と組み合わせた方法もあるようなので、今後も精進していこうかと思います。
ここ最近は案件で結構なサイズのログファイルを探索することも増えてきましたし、さすがに目grepは目が疲れる上に途中からゲシュタルト崩壊を起こしかけるし、となりそうなので、必要な情報のみフィルタすることは大事です。Linux系のOSさえ入っていればパッと試すことができるのもの強いですね。
参照
- jq
- awk /sed
- elasticsearch / count
- xargsコマンドで覚えておきたい使い方・組み合わせ7個
- xargsのオプション無し、-Iオプション、-0オプションの挙動に関する勘違い
- Linuxテキスト編集コマンドのすべて