Windowsローカル環境で使う、elasticsearch-1.4.1 + kibana-4.0.0-BETA2.0  データ投入編

#0. はじめに
[前回](http://qiita.com/Aco-gt/items/ad351387c1ab3ad2561f)は、セットアップについて説明しました。
今回は、セットアップして起動済の elasticsearchにデータを投入したいと思います。
##0.1 処理概要
HTTPアクセスログをPowershellを使ってElasticsearchにインポートします。
データを投入する方法は、cURLを使用する方法がよく知られていますが、今回使用するログは、一旦、Key-Value型で切り出した後にデータ投入する必要がありますので、Powershellを使ってログの切り出しとインポートの両方を一度に実施することにします。
投入するログは手持ちのものでもかまわないのですが、どういうことができるのかをわかりやすく説明するために公開されていてダウンロード可能なログをお試し解析用として使用します。
##0.2 Elasticsearchで押さえておくべき用語（Windowsローカル環境で使用する場合）
DBMSでたとえるならば

* databaseがindex
* tableがtype
* 各カラムの型定義がmapping

に対応しますが、物理的には、ひとつのindexに対してひとつのフォルダが作成されアクセスログ等をインポートするとデータはすべてそのディレクトリ内に格納されます。
ひとつのindexには複数のtypeを作成することができますが、アクセスログの解析などではひとつのindexに対してひとつのtypeを作成すればいいのではないかと思います。
尚、Kibanaの解析単位は、index単位になります。

#1．お試し解析用ログ準備 epa-http.log
お試し解析用ログとして以下サイトでダウンロード可能なHTTPアクセスログを使用します。（米国の公的機関が提供しているログです）
The Internet Traffic Archive
http://ita.ee.lbl.gov/html/contrib/EPA-HTTP.html
下部、Distributionにある「compressed ASCII」をクリックすると圧縮されたログをダウンロードできます。
解凍して適当なフォルダに格納してください。（拡張子が".Z"でOS標準のツールでは解凍できないと思いますので適当なツールを使用して解凍してください）
格納ディレクトリ
（例）C:\work\logs
ファイル名（作成したスクリプトの都合上、拡張子を".log"に変更してください）
（例）epa-http.log

#2. ログのパーサ準備 EpaHttpParser.psml
ログをフィールド単位に切り出すパーサスクリプトを作成します。Powershellのモジュールとして配置しておくとElasticsearchだけでなくMongoDBやその他、Key-Value型のデータを必要とするときにメインのスクリプトから呼び出せば使用できるので便利です。
作成したスクリプトは以下ディレクトリに配置します。（ディレクトリがない場合は作成が必要）
C:\Users\\<ユーザ名>\Documents\WindowsPowerShell\Modules\EpaHttpLogParser\EpaHttpLogParser.psml

```ps1:EpaHttpLogParser.psm1


function Read-ApacheLog
{
	param(
		[Parameter(Mandatory=$true)]
		[string]
		$Path
	)
	# ファイル名
	$file_name = Split-Path $Path -Leaf
	# ファイル行数
	$file_num = (Get-Content $Path | Measure-Object).count
	# 読み込み行数
	$read_num = 0
	Write-host "ファイル名:`t" $Path
	Write-host "ファイル行数:`t" $file_num
	
	Get-Content -Path $Path | Foreach-Object {
		if ($_ -match ("^(?<Host>.*?) \[(?<TimeString>.*?)\] `"(?<Method>.*?) (?<Path>.*?) (?<Version>.*?)`" (?<Status>.*?) (?<BytesSent>.*?)$") -or ($_ -match "^(?<Host>.*?) \[(?<TimeString>.*?)\] `"(?<Method>.*?) (?<Path>.*?)`" (?<Status>.*?) (?<BytesSent>.*?)$")) {
			$entry = $matches
			$entryDateTime = "1995:08:" + $entry.TimeString
			$entry.Time = [DateTime]::ParseExact($entryDateTime, "yyyy:MM:dd:HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)
			#### 一般的なApacheアクセスログの日付フォーマット用
			#### $entry.Time = [DateTime]::ParseExact($entry.TimeString, "dd/MMM/yyyy:HH:mm:ss zzz", [System.Globalization.CultureInfo]::InvariantCulture)
		
			# ログ読込進捗プログレスバー用
			$read_num ++
			# 進捗のパーセンテージを求める
			$p = $read_num / $file_num * 100
			# プログレスバーを表示　500行毎に更新
			if(($read_num % 500) -eq 0){
				Write-Progress "reading... $file_name : $file_num lines" ($p.Tostring("#.") + "%") -percentComplete $p
			}
			return New-Object PSObject -Property $entry
		}else{
			# 想定したログのパターンにmatchしない時は、ログを取り込まずにその内容を出力する
			Write-Host "Invalid requests: $_" -ForeGroundColor Red
		}
	}
}
```
補足
ログを見てもらうとわかるのですが標準的なApacheログとは少し形式が異なっています。
今回使用したログを切り出したいフィールド単位で分類すると以下の3パターンがありました。

* パターン1. すべてのフィールドがある  
141.243.1.172 [29:23:53:25] "GET /Software.html HTTP/1.0" 200 1497
* パターン2. Version(HTTP/1.0部分)の情報がない  
sandy.rtptok1.epa.gov [30:10:48:24] "GET /OWOW/PubList/" 200 1933
* パターン3. Version、Method(PUT、GET等)の情報がない  
willard-ibmpc12.cac-labs.psu.edu [30:15:17:12] "cons/circle_logo_small.gif" 400 -  

今回作成したパーサーでは、パターン1とパターン2を取り込んでパターン3は"Invalid requests"のメッセージを出力してスキップするようにしました。
この辺は、使用用途に合わせて作成すれば良いと思いますが、想定パターンにマッチしない行は時々発生する可能性があるのでその行がどのような内容なのか出力する処理を入れておくと便利だと思います。  

日付は"年月"部分が省略されてますのでダウンロードサイトに記載されている情報（1995年8月）を付加しました。尚、ログのタイムゾーンはEDT（東部夏時間）ですが、わかりやすくするためにJST+9で取り込むつくりにしました。

パーサの作成に関しては以下サイトを参考に作成しました。
d.sunnyone.org
PowerShellでApacheのログを集計する
http://d.sunnyone.org/2012/12/powershellapache_15.html

#3. データ投入用メインスクリプト準備 EPA-HTTP_log_to_ES.ps1
データ投入は試行錯誤の結果なのでベストな方法かどうかは自信がありませんが、とりあえず思い通りのマッピングでデータを投入できました。

```ps1:EPA-HTTP_log_to_ES.ps1
# Paser追加
Import-Module EpaHttpLogParser -force
[void][Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")

$ESEndPoint = "http://localhost:9200"
$indexname = "epa-http_log_index"
$typename = "epa-http_log_type"

$indexedEndpoint = $ESEndPoint + "/" + $indexname + "/" + $typename
$webClient = new-object System.net.WebClient

# ログディレクトリ設定
# 下記ディレクトリ内に存在する拡張子".log"のファイルが処理対象になります
$files_path = "C:\work\logs\"

$files = Get-ChildItem -Path $files_path -Recurse | where {$_.Extension -eq ".log"}
Write-host "↓↓↓ 対象ファイル一覧 ↓↓↓"
$files | %{
	$_.fullname
}
Write-host "↑↑↑ 対象ファイル一覧 ↑↑↑"

# 処理時間計測開始
$sw = New-Object System.Diagnostics.StopWatch
$sw.Start()

# ファイル読み込み開始
$files | %{
	# document数カウンター初期化
	$doc_num = 0
	
	# パーサー呼び出し
	Read-ApacheLog $_.fullname | %{
		# Elasticserchに投入するデータを格納するオブジェクトを作成
		$metrics = new-object 'System.Collections.Generic.Dictionary[string,object]'
		# 各フィールドのKeyとValueをオブジェクトに追加する
		# Remote host
		$metrics.Add("host", $_.Host)
		# Time the request was received ※わかりやすいように日本時間としてデータ追加する
		$metrics.Add("@timestamp", ($_.Time.ToString("yyyy-MM-ddTHH:mm:ss+09:00")))
		# Request method
		$metrics.Add("method", $_.Method)
		# Request URI
		$metrics.Add("uri", $_.Path)
		# Status code
		$metrics.Add("status", $_.Status)
		# Size of response in bytes ※int型の数値として投入するとNumber型にMappingされる
		$metrics.Add("size", ($_.ByteSent -as [int]))
		
		$serializedMetric = new-object System.Text.StringBuilder
		$javaScriptSerializer = new-object System.Web.Script.Serialization.JavaScriptSerializer
		$javaScriptSerializer.Serialize($metrics, $serializedMetric);
		
		[void]$webClient.UploadString($indexedEndpoint, $serializedMetric.ToString())
		
		# 処理ドキュメント数
		$doc_num ++
	}
	Write-host "処理ファイル：`t" $_.name
	Write-host "処理行数：`t" $doc_num
	$sum_doc_num += $doc_num
}
# 処理時間計測停止
$sw.Stop()
Write-host "所要時間：`t" $sw.Elapsed.ToString()
Write-host "総処理行数：`t" $sum_doc_num
```

数値としてデータセットするとnumber型でmappingされ、合計、平均、最大最小値等を求めることが可能となります。今回はレスポンスサイズの値をnumber型、時刻をdate型、その他はstring型にmappingしました。
こういったスクリプトを作成したときにありがちな取り込みもれに気づくように最低限のチェック（処理行数のチェック）を入れています。また、大量ログを処理するときにかかる時間を見積もれるように処理時間の計測処理も入れています。

データ投入に関しては以下サイトを参考に作成しました。
READY.
Elastic Search: Pushing data into Elastic Search from Windows PowerShell
http://thereadyprompt.blogspot.jp/2014/01/eleastic-search-pushing-data-into.html

#4. PowerShell スクリプト実行
##4-1．PowerShellコンソール起動
はじめてPowerShellを実行する場合は管理者でコンソールを起動し、下記のようにして実行ポリシーの変更が必要です。
Set-ExecutionPolicy RemoteSigned

<参考サイト>Windows管理者のためのPowerShell
http://powershell.wiki.fc2.com/wiki/PowerShell%E3%81%AE%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95

##4-2．スクリプト実行
PS> EPA-HTTP_log_to_ES.ps1

##4-3．データ投入確認
PS C:\work\scripts> .\EPA-HTTP_log_to_ES.ps1
↓↓↓ 対象ファイル一覧 ↓↓↓
C:\work\logs\epa-http.log
↑↑↑ 対象ファイル一覧 ↑↑↑
ファイル名:      C:\work\logs\epa-http.log
ファイル行数:    47748
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:12] "cons/circle_logo_small.gif" 400 -
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:13] "cons/book.gif" 400 -
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:13] "ogos/small_gopher.gif" 400 -
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:13] "ogos/small_ftp.gif" 400 -
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:13] "ogos/us-flag.gif" 400 -
Invalid requests: willard-ibmpc12.cac-labs.psu.edu [30:15:17:13] "cons/ok2-0.gif" 400 -
処理ファイル：   epa-http.log
処理行数：       47742
所要時間：       00:01:27.2963457
総処理行数：     47742

ログファイルの総行数が47748行でこの内6行をInvalid requestsとしてスキップして差引47742行をElasticsearchにインポート出来ました。
これでログの投入は完了です。

[次回](http://qiita.com/Aco-gt/items/ee60d2c9cf472d37825d)は、このindexをKibanaで参照してみたいと思います。