Elixirでパース処理、しよう!
ログファイルやJSONテキストなど、一定の文法に従って書かれた複雑なテキストを解析し扱いやすい構造に変換する処理全般をパース(parse)といいます。
もっと簡単に言えば、データを解析し必要なデータを取り出すことです。
本日はこのパース処理を、Elixirという言語で行う一例をご紹介します。
なぜ、Elixirがいいの?
Elixirは関数型言語の一種で、処理を簡潔に書きやすく、並列処理を簡単に実装でき、さらに耐障害性にも優れているという特徴があります。
並列処理とはその名の通り、複数のプロセスを並列に実行して処理を進める方法で、実行順序が保証されない代わりに、1つのプロセスによる逐次処理もはるかに高速で処理ができるメリットがあります。
その性質からElixirは、時として1分間に数百万件超のリクエストを捌く必要のあるソーシャルゲームのバックエンドシステムや監視システムへの採用事例が多く、同じように多数のリクエストが想定されるパース処理にも適した言語なのです。
コード
それでは早速サンプルコードを見ていきましょう。
今回はパーサコンビネーター※として、多機能で利用者数も多いNimbleParsecライブラリを利用しています。(※パーサコンビネーター = 複数のパーサーを連結して、より複雑なパーサーを作るためのツール。)
defmodule MyParser do
import NimbleParsec
date = ignore(string("["))
|> ascii_string([not: ?]], min: 1)
|> ignore(string("]"))
|> tag(:date)
level = ignore(string("["))
|> ascii_string([not: ?]], min: 1)
|> ignore(string("]"))
|> tag(:level)
pid = ignore(string("["))
|> ascii_string([not: ?]], min: 1)
|> ignore(string("]"))
|> tag(:pid)
client = ignore(string("["))
|> ascii_string([not: ?]], min: 1)
|> ignore(string("]"))
|> tag(:client)
message = ascii_string([],min: 1) |> unwrap_and_tag(:note)
defparsec :apache_log, date
|> ignore(string(" "))
|> concat(level)
|> ignore(string(" "))
|> concat(pid)
|> ignore(string(" "))
|> concat(client)
|> ignore(string(" "))
|> concat(message)
# https://www.xserver.ne.jp/manual/man_server_logerror.php
#MyParser.apache_log("[Fri Nov 02 11:56:17.072078 2018] [core:error] [pid 100956] [client XXX.XXX.XXX.XXX:58482] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace., referer: http://XXXX.xsrv.jp/")
end
上記はApacheのエラーログを解析するパーサーの一例です。
macのshellでiexを立ち上げ、試しにログを解析してみます。
iex -S mix
iex(1)> MyParser.apache_log("[Fri Nov 02 11:56:17.072078 2018] [core:error] [pid 100956] [client XXX.XXX.XXX.XXX:58482] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace., referer: http://XXXX.xsrv.jp/")
{:ok,
[
date: ["Fri Nov 02 11:56:17.072078 2018"],
level: ["core:error"],
pid: ["pid 100956"],
client: ["client XXX.XXX.XXX.XXX:58482"],
note: "AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace., referer: http://XXXX.xsrv.jp/"
], "", %{}, {1, 0}, 325}
パース処理を通したことで日付やエラーレベルなどの情報がタグづけされ、扱いやすい形になっているのがお分かりでしょうか。
この状態であれば、日付やエラーレベルに応じて異なる処理を行うことも簡単です。
具体的な例を挙げれば、例えばエラーレベルがerrorより上であれば管理者へメールで通知、それ以下であれば通知しないといった判断などが行えますね。
また、NimbleParsecの特徴として、dateならdate、levelならlevelといった具合に部品ごとにパースの定義が行えるので、パーサーの構造が一目で理解しやすく、万が一将来的にエラー対象の構文に変更があったとしても、正規表現などに比べて比較的容易に調整できるのも嬉しいポイントです。
終わりに
以上、Elixirによるパース処理の一例でした。
Elixirは関数型言語ということでオブジェクト指向型言語しか触ったことのない方からは敬遠されがちですが、並行処理やプロセスの概念にさえ慣れてしまえば実用レベルに達するのは実はさほど難しくはありません。(まだまだ日本語のドキュメントが少ないのは若干辛いですが…)
他の言語にはないはっきりとした強みがあり、それに何より使っていてとても楽しい言語なので興味があれば是非一度試してみてください。
それでは。
コメント