awkでログ分析

awkでログファイルを分析＆集計する際によくやる方法

##環境・前提

- awkが実行できる
- awkの列概念（$1が1列目）くらいは知ってる

##解析イメージ

- ログファイルは処理ログ
- 関数単位のデバッグメッセージが出力されている
- 一つの関数ログは開始、件数、成功か失敗の3行が必ずまとまって出力される
- **成功した関数だけ件数を集計し関数名と共に出力**する

##ログファイル
**funcX**はerror終了なので集計しない
**funcA**は全てsuccessなので件数を全て集計(計30件)
**funcB**は一つerrorがある(計22件)


```log:sample.log
debug funcX start
debug reccnt 100
debug funcX error
--
debug funcA start
debug reccnt 10
debug funcA success
--
debug funcA start
debug reccnt 8
debug funcA success
--
debug funcA start
debug reccnt 12
debug funcA success
--
debug funcB start
debug reccnt 10
debug funcB success
--
debug funcB start
debug reccnt 8
debug funcB error
--
debug funcB start
debug reccnt 12
debug funcB success

```

##実装検討

### 仕様決め（ざっくり）

- start, reccnt, successのキーワードが使えるな
- startがあれば関数名取得
- reccntがあれば取得済み関数名にカウントをセット
- successがあれば取得済み関数名とカウントを出力用変数にセット
- 全行処理できたら出力する
- 先頭行にヘッダだけは出力しておこう
- ヘッダは結果出力がなくても無条件に出力する

##実装

### start判定
startがあれば関数名を取得する処理

まずはstart行の取得を確認

```bash
$ awk '$3~/start/{ print $0}' sample.log
debug funcX start
debug funcA start
debug funcA start
debug funcA start
debug funcB start
debug funcB start
debug funcB start
```

判定中の関数がわかるように**tmp**という変数に関数名($2)をセットしておこう

```bash
$ awk '$3~/start/{tmp=$2}' sample.log
```

### reccnt判定
reccntがあれば取得済み関数名にカウントをセットする処理

まずはreccntの取得を確認

```bash
$ awk '$3~/start/{tmp=$2} $2~/reccnt/{print $3}' sample.log
100
10
8
12
10
8
12
```

件数を一時的に保持しておくために**cnt[関数名]**という配列に件数($3)をセットしておこう

```bash
awk '$3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3}' sample.log
```

### success判定
successがあれば取得済み関数名とカウントを出力用変数にセットする処理

まずはsuccess行の取得を確認

```bash
$ awk '$3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{print $0}' sample.log
debug funcA success
debug funcA success
debug funcA success
debug funcB success
debug funcB success
```

出力用の**out[関数名]**という配列に件数(cnt[$2])を加算する

```bash
$ awk '$3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{out[$2]+=cnt[$2]}' sample.log
```

###整形

#### END処理

全行処理後に出力を行う

まずは配列を展開しの関数名が正しくセットできているか確認

```bash
$ awk '$3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{out[$2]+=cnt[$2]} END{for(f in out){print f}}' sample.log
funcA
funcB
```

件数も出力

```bash
$ awk '$3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{out[$2]+=cnt[$2]} END{for(f in out){print f,out[f]}}' sample.log
funcA 30
funcB 22
```

#### BEGIN処理

先頭行にヘッダを出力しよう
csv形式にするのでOFS（Output Field Separator）にカンマをセットしcsvヘッダを出力すれば完成！

```bash
$ awk 'BEGIN{FS=" ";OFS=",";print "function,count"} $3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{out[$2]+=cnt[$2]} END{for(f in out){print f,out[f]}}' sample.log
function,count
funcA,30
funcB,22
```

実際はファイルに出力するので**out.csv**にリダイレクト

```bash
$ awk 'BEGIN{FS=" ";OFS=",";print "function,count"} $3~/start/{tmp=$2} $2~/reccnt/{cnt[tmp]=$3} $3~/success/{out[$2]+=cnt[$2]} END{for(f in out){print f,out[f]}}' sample.log > out.csv
```

結果ファイル

```csv:out.csv
function,count
funcA,30
funcB,22
```

##おまけ

awkを使う時は今回のように簡単に処理する場合が多いのでワンライナーで書くことが多いですがせっかく書いたのでファイル化して保存しておこう。

### 処理をfile化

```awk:func_cnt.awk
BEGIN {
        FS=" "; /* 入力フィールドセパレータ */
        OFS=","; /* 出力フィールドセパレータ */
        print "function,count";
}

$3 ~ /start/ {
        tmp=$2;
}

$2 ~ /reccnt/ {
        cnt[tmp]=$3;
}

$3 ~ /success/ {
        out[$2] += cnt[$2];
}

END {
        for(f in out){
                print f,out[f];
        }
}
```

実行例

```
$ awk -f func_cnt.awk sample.log
function,count
funcA,30
funcB,22
```

##あとがき
現在**Talend**を扱っているのですが実はその記事を書くための参照ネタとして今回のawkログ解析を書きました。
個人的感想ですが**tJavaFlex**というコンポーネントが**awk**の考え方にそっくりなんですよね。