ちょっと話題の記事

Node.js のログを Fluentd を使って MongoDB に集約する

2013.01.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

最近 Fluentd をプロジェクトで導入しようと考えています。

Fluentd は、あらゆるデータのログを json を使ってシンプルに転送、集約させることができるツールです。
クラウド環境やビッグデータを扱う上で、今後需要が高くなりそうです。
ソーシャルゲーム業界などではかなり実績があるみたいですね。

今回お試しとして、Node.js のエラーログを Fluentd を使って MongoDB に保存するサンプルを作ってみます。
こんな感じ。とてもシンプルです。。

    Input                              Output
+------------------------------------------------+
|                                                |
|  Node.js --------> Fluentd --------> MongoDB   |
|                                                |
+------------------------------------------------+

インストール

パッケージ形式の td-agent と gem 形式の Fluentd があるみたいですが、
今回はお試しなので、gem を使ってインストールしてみました。(ちなみに Mac OS 10.8 です)
大規模環境では td-agent を使う方がいいと思います。
Ruby の1.9.2以上が必要です。

$ gem install fluentd --no-ri --no-rdoc

起動確認

マニュアルに書いてある通りやってみます。
セットアップ

$ fluentd --setup ./fluent
Installed ./fluent/fluent.conf.

起動

$ fluentd -c ./fluent/fluent.conf -vv &

すんなり起動しました。

MongoDB Output Plugin をインストール

Fluentd はプラグインが豊富です。
今回は、MongoDB の プラグインを fluent-gem というのを使ってインストールします。(td-agentの場合は入っている)

$ fluent-gem install fluent-plugin-mongo

ログの設定

MongoDB のプラグインをインストールしたら、fluent.conf に追記します。

# Single MongoDB
<match mongo.**>
  type mongo
  host localhost
  port 27017
  database node
  collection error

  # for capped collection
  capped
  capped_size 1024m

  # flush
  flush_interval 10s
</match>

match mongo.** で受け取るタグを正規表現で指定できます。
この場合、mongo、mongo.x、mongo.x.x... の条件にマッチします。
type 〜 collection は、MongoDB の接続先と保存先のコレクションです。
capped collection というのは、MongoDB のサイズ固定のコレクションです。
これはロギング処理に向いていますので、Fluentd ではこれが推奨されています。
10秒間隔で MongoDB にフラッシュします。

Logger のインストール(Node.js)

続いて、Node.js 側です。
ちなみに express を使ってます。
以下のようにスケルトンを作成したら、package.json を編集します。

express fluentd-sample

「package.json」

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app"
  },
  "dependencies": {
    "express": "3.0.3",
    "fluent-logger": "latest",
    "jade": "*"
  }
}

fluentd の Logger である fluent-logger を使う指定をします。
続けて、npm start。

$ npm start
fluent-logger@0.2.1 node_modules/fluent-logger
└── msgpack@0.1.7

0.2.1がインストールされました。

例外処理のコード(Node.js)

例外処理を記述します。
ちなみに CoffeeScript です。

「app.coffee」

'use strict'

###
Module dependencies.
###

express = require 'express'
routes = require './routes'
path = require 'path'

logger = require 'fluent-logger'
logger.configure 'mongo', {host: 'localhost', port: 24224}

app.configure ->
  app.set 'port', process.env.PORT or 3000
  app.set 'views', __dirname + '/views'
  app.set 'view engine', 'jade'
  app.use express.favicon()
  app.use express.logger 'dev'
  app.use express.bodyParser()
  app.use express.methodOverride()
  app.use app.router
  app.use express.static path.join(__dirname, 'public')
  app.use (err, req, res, next) ->
    logger.emit 'test', {error: err.message}
    res.render 'error', {status: 500, title: '500 Internal Server Error', err: err}

app.get '/', routes.index

app.listen process.env.PORT || 3000, ->
  console.log 'Express server listening on port ' + app.get 'port'

エラーをハンドリングしたら、Fluentd にログを飛ばし、その後エラー画面に遷移させています。
その際のタグとラベルの組み合わせが mongo.test と指定しているので、先ほどの fluent.conf の構文にマッチするというわけです。

routes/index では、テスト的にわざとエラーを発生させるようにします。

「routes/index.coffee」

'use strict'

###
Module dependencies.
###

exports.index = (req, res, next) ->
  next new Error('fluentd test')

エラーを発生させる

Fluentd を起動しておいて、MongoDB も起動します。

$ sudo mongod

そして Node.js を起動し、curl でアクセスします。

$ node app.js 
$ curl localhost:3000/
<!DOCTYPE html><html><head><title>500 Internal Server Error</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>500 Internal Server Error</h1><h1>Error: Fluentd test</h1></body></html>

ログの確認

ちょっと待って MongoDB を確認してみます。

$ mongo
$ use node
$ db.error.find()
{ "_id" : ObjectId("5105183acaf52f08dd00000e"), "error" : "Fluentd test", "time" : ISODate("2013-01-27T12:06:15.513Z") }

ちゃんと出力されてますね。

まとめ

Fluentd と MongoDB はデータを json 形式で扱うため、高いスループットでの挿入が実現できます。
(もちろん Node.js も JavaScript なので json は親和性が高いです)
MongoDB の capped コレクションに保存すると、古いデータが消えていってしまうので、同時にS3にも保存した方がいいかもしれません。
同時にS3にも保存する方法は、以下のブログに書いてあります。
Amazon LinuxにFluentdをインストールしてS3とMongoDB連携する

あとは定期的に Amazon Glacier にアーカイブするなどもいいですね。