こんにちは、寺岡です。
これは TECHSCORE Advent Calendar 2015 の16日目の記事です。
シェル芸人への道のりは険しい
アプリケーションの運用業務では日々入ってくる問い合わせに応えるため、ログが並んだコンソールと睨めっこする時間は決して短くありません。
毎回 grep, cut, uniq に sed や awk なんかも駆使してログ調査をすることになるわけなのですが、これがなかなか大変なわけで……。
熟練の技術者は驚異的なシェル芸を駆使して目的のデータをザクザクと釣り上げてくるのものですが、
そんなアクロバティックな職人技は一朝一夕でに身につくものではないでしょう。
そんな時、アプリエンジニアならこう思うわけです。
「嗚呼、この作業が SQL で出来れば俺だって……」
そんな望みを叶えるためには、 Fluentd などを使ってログをインポートして……というのが王道でしょう。
でも、今回はもっとカジュアルに生ログから検索する方法を試してみました。
PostgreSQL FDW
今回、ログ検索にはPostgreSQLの Foreign Data Wrapper(以降FDW)という機能を利用します。
FDW は PostgreSQL9.1 から搭載された機能で、日本語ドキュメントでは「外部データラッパ」と訳されており、PostgreSQL サーバ外部のデータをデータベース内の仮想的なテーブルとしてマッピングすることができるようになりました。
PostgreSQL9.3 からは postgres_fdw というモジュールが標準で組み込まれており、
別サーバにある PostgreSQL のテーブルをあたかもローカルDBに存在するかのように扱うことができます。
また、FDW モジュールが対応していれば、SELECT だけではなく INSERT、UPDATE、DELETE に加えトランザクションにも対応という高機能っぷりで、もちろん postgres_fdw はこれらに全て対応しています。
さらに凄いのは、FDWで操作できるのはPostgreSQLだけではない点です。
こちらの公式Wikiにあるように、様々な FDW モジュールが公開されており、
MySQLやOracleはもちろん、MongoDB や Redis といった NoSQLDB 、さらには CSV や Twitter まで PostgreSQL で操作できてしまうのです。
作ってみた
今回はログファイルから任意の正規表現でパースしたデータを扱うための、 logfile_fdw という FDW モジュールを作成してみました。
本来 FDW はC言語で実装するものですが、 Multicorn というサードパーティのライブラリを利用すれば、 Python を使ってとても簡単に実装することができます。
どのくらい簡単かというと、初めて Python を書いた筆者が数時間で実装できてしまうくらい簡単です。
( FDW の作り方についてはまた後日ご紹介したいと思います)
インストール
まずは、前準備としてログが保管してあるサーバに以下のインストールを済ませておく必要があります。
yum や apt-get でさくっとインストールしてしまえばよいでしょう。
- PostgreSQLサーバ (9.1以上)
- Python, pip
logfile_fdwは以下のコマンドでインストールすることができます。簡単ですね。
1 2 3 |
$ sudo pip install pgxnclient $ sudo pip install git+https://github.com/yuki-teraoka/logfile_fdw.git $ sudo pgxn install multicorn |
1行目は PGXN クライアントのインストールです。これは PostgreSQL の拡張機能のインストールを自動化してくれるツールで、今回は Multicorn のインストールに利用します。
3行目で logfile_fdw をインストールしています。
5行目は pgxn コマンドを使って Multicorn をインストールしています。
(この時、対象の PostgreSQL サーバの pg_config に対してパスが通っている必要があります。複数の PostgreSQL をインストールしている環境では注意してください)
次に PostgreSQL への Multicorn モジュールの組込みと、FDW用サーバ情報の登録を行います。
1 2 3 4 5 6 |
$ psql CREATE EXTENSION multicorn; CREATE SERVER logfile_fdw FOREIGN DATA WRAPPER multicorn options ( wrapper 'logfile_fdw.LogFileForeignDataWrapper' ); |
Multicorn を使った FDW では、サーバ情報登録時に、wrapper オプションで利用する Python の FDW 実装クラスを指定することができます。
このように設定することで、 Multicorn がこのサーバ情報を利用するテーブルへの処理時に Python クラスと PostgreSQL の橋渡しをしてくれるのです。
外部テーブルの作成
今回はサンプルとして yum のログファイル、 /var/log/yum.log を検索してみることにします。
(開発マシンの /var/log の中で一番パースが楽そうだったので選びました)
まず、PostgreSQL サーバの実行ユーザが読み取るために、権限を付与しておきます。
1 |
$ sudo chmod o+r /var/log/yum.log |
FDW でデータを操作するためには CREATE FOREIGN TABLE 文でテーブル定義を作成する必要があります。
1 2 3 4 5 6 7 8 9 10 |
CREATE FOREIGN TABLE yumlog( month text, day text, time text, type text, package text ) SERVER logfile_fdw OPTIONS ( log_pattern '(?P<month>[^ ]*) (?P<day>[^ ]*) (?P<time>[^ ]*) (?P<type>[^: ]*):? (?P<package>.*)', file_pattern '/var/log/yum.log' ); |
構文は通常の CREATE TABLE 文とほぼ同じですが、最後に SERVER サーバ名 OPTIONS (FDWに渡すオプション) を与えます。
logfile_fdw では、log_pattern にログ行をパースする正規表現(各項目を名前付きグループで指定)、
file_pattern に対象ファイル名のパターンを設定します。
テーブル定義の項目名は log_pattern の正規表現で指定したグループ名と一致させるようにしてください。
外部テーブルの作成まで完了し、これで検索の準備が整いました。
SELECT してみる
先ほど作った yumlog テーブルを SELECT してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT * FROM yumlog WHERE type = 'Updated' LIMIT 10; month | day | time | type | package -------+-----+----------+---------+-------------------------------------------- Jan | 30 | 10:57:24 | Updated | nspr-4.10.6-1.el7_0.x86_64 Jan | 30 | 10:57:24 | Updated | nss-util-3.16.2.3-1.el7_0.x86_64 Jan | 30 | 10:57:24 | Updated | nss-softokn-freebl-3.16.2.3-1.el7_0.x86_64 Jan | 30 | 10:57:25 | Updated | nss-softokn-3.16.2.3-1.el7_0.x86_64 Jan | 30 | 10:57:25 | Updated | nss-sysinit-3.16.2.3-2.0.1.el7_0.x86_64 Jan | 30 | 10:57:25 | Updated | nss-3.16.2.3-2.0.1.el7_0.x86_64 Jan | 30 | 10:57:32 | Updated | nss-tools-3.16.2.3-2.0.1.el7_0.x86_64 Jan | 30 | 11:01:40 | Updated | libdb-5.3.21-17.el7_0.1.x86_64 Jan | 30 | 11:01:56 | Updated | libdb-utils-5.3.21-17.el7_0.1.x86_64 Feb | 25 | 22:08:08 | Updated | httpd-tools-2.4.6-19.0.1.el7_0.x86_64 (10 行) |
ご覧のとおり普通に SELECT できています。WHERE も LIMIT もバッチリ効いていますね。
集約関数なども試してみます。
1 2 3 4 5 6 7 8 9 10 |
SELECT month, COUNT(*) FROM yumlog WHERE type = 'Updated' GROUP BY month ORDER BY COUNT(*) DESC; month | count -------+------- Apr | 266 Mar | 15 Dec | 10 Jan | 9 Feb | 3 Nov | 1 (6 行) |
文句なしの結果です。念の為シェルコマンドでの集計と比べてみましょう。
1 2 3 4 5 6 7 |
$ cat /var/log/yum.log | grep Updated | cut -d" " -f1 | sort | uniq -c | sort -nr 266 Apr 15 Mar 10 Dec 9 Jan 3 Feb 1 Nov |
完璧ですね。SQL とコマンドを見比べると、可読性の違いは一目瞭然です。
さらにシェル芸では難しい JOIN なども簡単にできてしまう訳です。
SQL万歳!!
まとめ
筆者は Python 初挑戦なので色々と苦労しましたが、本体40行に満たないスクリプトで
PostgreSQL に新しい機能を追加することができました。
今回作ったソースはlogfile_fdwにアップしてあります。
Apache ログを簡単にパースできる組込みフォーマットなども実装してみたので、よろしければ遊んでみてください。
gz や bz のような圧縮ログへの対応など、今後も手を入れていければと思っています。
データハブとしての可能性が広がる PostgreSQL FDW の世界を、みなさんも試してみてはいかがでしょうか。