こんにちは!アイリッジプロダクト開発グループの森山です。
突然ですが、皆さんログの調査はされていますか?僕はしてました。 といっても、今まではAWS CloudWatchのログをぽけーっと眺めるくらいでした。(眺められるくらいの量のログしかなかった)
アイリッジに入社してからはログの量が非常に多いため、ワンライナーでコマンドを打ち、ログを探す技術が必要となったので大慌てで勉強しました。 最近ようやくそれなりに意図したログを抽出できるようになりました。
といっても、完全にマスターしたわけではなく、あくまで私がよく使うコマンドを載せますので 独断と偏見が入っています!ご了承ください。。。
gz形式のログ調査が多いため、今回はzcat
を使用しています。
調査ログの対象はAWS CloudFrontのログ形式を想定しています。詳細は下記をご確認ください。
標準ログ (アクセスログ) の設定および使用 - Amazon CloudFront
TL;DR
zcat *.gz | grep -v "blog" | grep "iridge" | awk '{ if($9 == 404 || $9 == 500){print $8} }'| cut -f 7-10 -d "/" | sort | uniq -c | sort -nr | awk '{ print length(), $2 }' | sort -nr | awk '{ print $2 }’
zcat *.gz | grep -e "blog" -e "iridge" | awk '{ if($9 == 404 || $9 == 500 || $9 == 504){print $2,$5} }' | sort | uniq -c | awk '{ if($2 >= "10:00:00" && $2 <= "12:00:00"){ print $2,$3} }' | sort | uniq -c | awk '{s += $1} END {print s}'
今回紹介するコマンドたち
- grep
- awk
- cut
- sort,uniq
ログ表示の最初の書式
まず調査したいファイルをzcat
で表示します。このあとにパイプでつないで条件を付け足していきます。
# ログを表示する基本書式
zcat *.gz
grep
キーワードを指定することで表示するログを絞り込むことができます。
オプションをつけることで、複数指定や除外指定で絞り込めます。
# 指定した文字列に一致するもので絞りこむ grep "検索したい文字列" # -v:指定した文字列に一致しないもので絞り込む grep -v "除外したい文字列" # -e:複数の条件で絞り込む(OR検索) grep -e "検索したい文字列" -e "検索したい文字列"
優しい例文
# iridge と書かれたログを抽出したいとき zcat *.gz | grep "iridge" # iridge と書かれていないログを抽出したいとき zcat *.gz | grep -v "iridge" # iridge と blogのどちらかが書かれたログを抽出したいとき zcat *.gz | grep -e "iridge" -e "blog"
awk
オークと読むそうです。僕は最初勝手にアウェイクってよんでました笑
awkはプログラマーなら馴染み深いif
やprint
などをワンライナーで記述することができます。
僕が調査しているログの形式で非常に汎用性高く使用できるため、おすすめです。
使い方はzcatでログ全体を表示した後に、awkでログの表示したい部分のみ(ここではログの要素と呼んでます)をprintするといった形になります。
表示したい部分についてはログの仕様によっては異なるためご注意ください。
# print:ログの要素を表示する awk '{ print $2 }' # if:条件に応じた挙動を行う awk '{ if($9 == 404){ print $2 }}' # length:文字列の長さを取得できる awk '{ print length() }'
優しい例文
# 複数のログの要素を表示する zcat *.gz | awk '{ print $2,$9 }' # ステータスコードが404のときのタイムスタンプを表示する zcat *.gz | awk '{ if($9 == 404){print $2} }' # ステータスコードが500または504のログのリクエストを表示する(OR条件) zcat *.gz | awk '{ if($9 == 500 || $9 == 504){print $8} }' # 10:00 ~ 12:00の間のログのステータスコードを表示する(AND条件) zcat *.gz | awk '{ if($2 >= "10:00:00" && $2 <= "12:00:00"){ print $9} }'
cut
表示するログの一部を切り取って表示することができます。オプションの指定によって様々な区切り方が可能です。
awkを使わず、こちらだけでログの要素を表示することもできますが、今回は自分がawkと組み合わせてよく使う2パターン紹介します。
# -b:切り抜く文字をバイトで指定する # 例: 2バイト目から8バイト目を切り抜く cut -b 2-8 # -f -d:特定の文字で区切ったものの何番目かを指定して表示する # 例: / で文字列を区切り、区切った中の7番目から10番目までを切り抜く cut -f 7-10 -d "/"
優しい例文
# タイムスタンプ(00:00:00)の下二桁を除いて表示(00:00と表示) zcat *.gz | awk '{ print $2 }' | cut -b 1-5 # リクエストパスを / で区切り、区切った7番目から10番目を表示 zcat *.gz | awk '{ print $8 }' | cut -f 7-10 -d "/"
sort,uniq
ログを集計する際、よくセットで使うので一括りで紹介します。
sort
表示するログを特定の条件で並べかえます。こちらもいろいろな並べ変え方があります。 デフォルトのsortの動きについては省きます。気になる方は下記記事を参照していただけたらと思います。
# -n:文字列を数値の昇順で並べ替える。 sort -n # -r:逆順で並べ替える(この場合は降順) sort -nr
uniq
重複している行を削除して表示します。オプション次第で抽出する形式を変えられます。
今回は集計に使うcount
のみ紹介します。
# -c:重複していた行の数を先頭に表示 uniq -c
優しい例文
# ステータスコードを集計して重複した数を先頭に表示 zcat *.gz | awk '{ print $9 }' | sort | uniq -c | sort -n
組み合わせの例
最後に一番最初に記述しているコマンドの解説をします。
例題1
zcat *.gz | grep -v "blog" | grep "iridge" | awk '{ if($9 == 404 || $9 == 500){print $8} }'| cut -f 7-10 -d "/" | sort | uniq -c | sort -nr | awk '{ print length(), $2 }' | sort -nr | awk '{ print $2 }’
# 解説 # 1. `blog`というキーワードを除き、`iridge`と記述のあるログのみを`grep`します。 zcat *.gz | grep -v "blog" | grep "iridge" # 2. 次にステータスコードが`404`か`500`となっているログのリクエストを抽出します。 awk '{ if($9 == 404 || $9 == 500){print $8} }' # 3. リクエストパスを`/`で区切り、7番目から10番目のみを表示します。 cut -f 7-10 -d "/" # 4. 表示したログの重複を削除して表示し、重複した数を先頭に表示させ降順で並び替えます。 sort | uniq -c | sort -nr # 5. length()でリクエストパスの文字列の長さを取得します。$2はリクエストパスを表示しており、この後文字列の長さ順に並べ替えた後に表示するためにここでも表示します。 awk '{ print length(), $2 }' # 6. 文字列の長さで並べ替えて表示します。 sort -nr | awk '{ print $2 }’
例題2
zcat *.gz | grep -e "blog" -e "iridge" | awk '{ if($9 == 404 || $9 == 500 || $9 == 504){print $2,$5} }' | sort | uniq -c | awk '{ if($2 >= "10:00:00" && $2 <= "12:00:00"){ print $2,$3} }' | sort | uniq -c | awk '{s += $1} END {print s}'
# 解説 # 1. `blog`または`iridge`のどちらかが記述されているログを`grep`します。 zcat *.gz | grep -e "blog" -e "iridge" # 2. 次にステータスコードが`404`か`500`か`504`となっているログのタイムスタンプとIPアドレスを抽出します。 awk '{ if($9 == 404 || $9 == 500 || $9 == 504){print $2,$5} }' # 3. 表示したログの重複を削除して表示し、重複した数を先頭に表示させます。 sort | uniq -c # 4. タイムスタンプが`10:00:00` ~ `12:00:00`の間であるログのタイムスタンプとIPアドレスを抽出します。 awk '{ if($2 >= "10:00:00" && $2 <= "12:00:00"){ print $2,$3} }' # 5. 表示したログの重複を削除して表示し、重複した数を先頭に表示させます。 sort | uniq -c # 6. 先頭に表示しているデータはここまで条件をしぼって抽出したデータの総数なのでそれをたしあわせて、データの総量を表示します。 awk '{s += $1} END {print s}'
終わりに
ここで紹介したものはほんの一部で、オプションはまだまだあるのでもっと効率的な方法はあると思います。
しかし、ただのコピペで終わらずに意味をしっかり考えて実行することが大事だと思いました。今回は各コマンドの意味を復習する意味でもこの記事を作成してよかったなと思います。
一流のエンジニアへの道は険しいですが、ぜひ一緒に頑張れる仲間を募集中です!アイリッジで僕と握手!
参考
[シェル] Grepでand検索、or検索、not検索、正規表現、再帰的に検索、圧縮ファイル内検索、など行う - YoheiM .NET
【 cut 】コマンド――行から固定長またはフィールド単位で切り出す:Linux基本コマンドTips(60) - @IT