Log filesの構文解析をPowershellで簡易にやってみる

# はじめに
会社でログファイルを解析していてPowershellで簡易に出来たので共有します。

# 何が出来るの？
Plain Textのログファイルを構文解析して、JSONとかCSVとかHTMLとか、良い感じの形式にExportが出来ます。
その後の二次加工はExcelでも何でも好きな解析ツールに入れてね。

今回のコードのこだわりは、正規表現の中を切り替えるだけでいろんな形式のログに対応出来ることです。
下記のコードはサンプル残します。　（暇があったら自分用に増やします。）

* Apache access_log
* Squid access_log

# 前提条件
* Powershellが読めること。
* Windows環境が在ること。（テストとかはPowershell 5.1でやってます）
* 正規表現が読めること。

一応環境は、Powershell 5.1だけど、Windows 7とかの Powershell 2.0でも出来るんじゃ無いかな？

# コード
ざっくりと、下のコードで動きます。　必要箇所は書き換えてください。
（Apache access_logをJSON形式に変換）

```powershell
Get-Content 'access_log' -Encoding utf8 | 
%{ $_ -match '^(?<remoteHost>[\w\.\-]+)\s(?<clientId>(?:-|[\w\.\-]+))\s(?<authenticateUser>(?:-|[\w\.\-]+))\s\[(?<time>[^\]]+)\]\s+"(?<requestUrl>[^"]+)"\s(?<status>\d{3})\s(?<responseSize>(?:-|\d+))(?:\s+"(?<referer>[^"]+)"\s+"(?<userAgent>[^"]+)")?$'} | 
%{ New-Object PSObject -Property $Matches} | 
Select-Object * -Exclude 0 | 
ConvertTo-JSON | 
Add-Content 'access_log.json' -Encoding utf8
```

# 注意点
解析に正規表現を用いているので、アンマッチの情報は無視されます。
``` -notmatch ``` で検出するなど、情報落ちが無いように注意してください。

# 何やっているの？
簡単に解説します。

## 1行目

```powershell
Get-Content 'access_log' -Encoding utf8
```

ファイルを読み込んでいるだけです。
-Encodingとか、-Headとか、-Tailとか、良い感じのオプションを付けて読み出してあげてください。
Reference [Get-Content](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content)

## 2行目

```powershell
%{ $_ -match '^(?<remoteHost>[\w\.\-]+)\s(?<clientId>(?:-|[\w\.\-]+))\s(?<authenticateUser>(?:-|[\w\.\-]+))\s\[(?<time>[^\]]+)\]\s+"(?<requestUrl>[^"]+)"\s(?<status>\d{3})\s(?<responseSize>(?:-|\d+))(?:\s+"(?<referer>[^"]+)"\s+"(?<userAgent>[^"]+)")?$'}
```

こだわりポイント。
正規表現の中に**名前付き部分式**を使うことで、一致した場所に可読性のある名前を付けています。

```
(?<name>subexpression)
```
Reference [Named Matched Subexpression](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/grouping-constructs-in-regular-expressions#named_matched_subexpression)

普通に正規表現を使うと、後方参照は 1 とか 2 といった数値で作られて、別途名前との対応表が必要になりますが、これを正規表現の中に組み込むことにより、パイプ内に名前付きで放り込んでいます。

ちなみに、コード中の名前付き部分式は下記の通りです。

```
(?<remoteHost>[\w\.\-]+)
(?<clientId>(?:-|[\w\.\-]+))
(?<authenticateUser>(?:-|[\w\.\-]+))
(?<time>[^\]]+)
(?<requestUrl>[^"]+)
(?<status>\d{3})
(?<responseSize>(?:-|\d+))
(?<referer>[^"]+)
(?<userAgent>[^"]+)
```

また、普通にグループ化を行いたい部分では、後方参照が勝手に作られるのを防ぐために**非キャプチャ グループ**を用いています。

```
(?:subexpression) 
```
Reference [Noncapturing Group](https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/grouping-constructs-in-regular-expressions#noncapturing_group)

## 3行目

```powershell
%{ New-Object PSObject -Property $Matches}
```

``` $Matches ``` を ``` PSObject ```に変換しています。
それだけ。

Reference [New-Object PSObject –Property [HashTable]](https://blogs.msdn.microsoft.com/powershell/2009/12/04/new-object-psobject-property-hashtable/)

## 4行目

```powershell
Select-Object * -Exclude 0
```

Match すると自動で作られる、一致全文をあらわす $Matches[0] を消しています。
後で使う場合は、残して置いても良いんじゃないでしょうか？　このあたりは好みです。

Reference [Select-Object](https://docs.microsoft.com/ja-jp/powershell/module/Microsoft.PowerShell.Utility/Select-Object)

## 5行目

```powershell
ConvertTo-JSON
```

JSON形式に変換しています。
よく使うコマンドレットをまとめておきます。

| Cmdlet | メモ | 
|:-:|:--|
| [ConvertTo-Json](https://docs.microsoft.com/ja-jp/powershell/module/Microsoft.PowerShell.Utility/ConvertTo-Json) |  |
| [ConvertTo-CSV](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/convertto-csv) -NoTypeInformation | オプションを付けないと余計な型情報が入ります。 | 
| [Out-GridView](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/out-gridview) | 画面に表示します。 |

## 6行目
```powershell
Add-Content 'access_log.json' -Encoding utf8
```

データを保存しています。
Reference [Add-Content](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/add-content)

# ログファイル用サンプル

## Apache access_log

対応する形式

```
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined
```

スクリプト

```powershell
%{ $_ -match '^(?<remoteHost>[\w\.\-]+)\s(?<clientId>(?:-|[\w\.\-]+))\s(?<authenticateUser>(?:-|[\w\.\-]+))\s\[(?<time>[^\]]+)\]\s+"(?<requestUrl>[^"]+)"\s(?<status>\d{3})\s(?<responseSize>(?:-|\d+))(?:\s+"(?<referer>[^"]+)"\s+"(?<userAgent>[^"]+)")?$'}
```

パラメーター

| Param | 書式指定子 | 意味 |
|:-:|:-:|:--|
| ```remoteHost``` | ```%h``` | 接続元HostのIP,DNS |
| ```clientId``` | ```%l``` | クライアントID(ほとんど使われません) |
| ```authenticateUser``` | ```%u``` | 認証ユーザー(ほとんど使われません) |
| ```time``` | ```%t``` | アクセス時間 |
| ```requestUrl``` | ```%r``` | URL情報 |
| ```status``` | ```%s``` | ステータスコード |
| ```responseSize``` | ```%b``` | レスポンスサイズ |
| ```referer``` | ```%{Referer}i``` | リファラー情報(Combined Log Format) |
| ```userAgent``` | ```%{User-agent}i``` | UserAgent(Combined Log Format) |

Reference [Apache HTTP サーバ バージョン 2.0 ログファイル](https://httpd.apache.org/docs/2.0/ja/logs.html)

## Squid access_log

対応する形式

```
logformat squid      %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
```

スクリプト

```powershell
%{ $_ -match '^(?<unixtime>\d+\.\d+)\s+(?<responseTime>\d+)\s(?<sourceIp>[\d\.]+)\s(?<requestStatus>\w+)/(?<statusCode>\d{3})\s(?<totalSize>\d+)\s(?<requestMethod>\w+)\s(?<requestUrl>[\w\-:/\.\?\+$]+)\s(?<userName>(?:-|[\w\.\-]+))\s(?<hierarchy>\w+)/(?<serverIP>(?:-|[\w\.\-]+))\s(?<mime>[\w\-]+/[\w\-]+)$'}
```

パラメーター

| Param | 書式指定子 | 意味 |
|:-:|:-:|:--|
| ```unixtime``` | ```%ts.%tu``` | 接続時間(UnixTime) |
| ```responseTime``` | ```%tr``` | レスポンスタイム |
| ```sourceIp``` | ```%>a``` | クライアントソースIP |
| ```requestStatus``` | ```%Ss``` | リクエストステータス(TCP_MISS etc) |
| ```statusCode``` | ```%>Hs``` | HTTPステータスコード |
| ```totalSize``` | ```%<st``` | トータルサイズ |
| ```requestMethod``` | ```%rm``` | リクエストメソッド(GET/POST etc) |
| ```requestUrl``` | ```%ru``` | リクエストURL |
| ```userName``` | ```%un``` | ユーザー名 |
| ```hierarchy``` | ```%Sh``` | Squid hierarchy status(DEFAULT_PARENT etc) |
| ```serverIP``` | ```%<a``` | サーバーIP |
| ```mime``` | ```%mt``` | MIME |

Reference [Squid configuration directive logformat](http://www.squid-cache.org/Doc/config/logformat/)