2012年11月7日水曜日

Fluentdってなんじゃ?(Mongo&Node編 ブラウザでtail)

前回までで、fluentdを利用してApacheのログをmongoDBに保存することができました。

構成はこのようになっていました。
  +----------------------------+   +------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16)|
  |----------------------------|   |------------------------|
  |          fluentd           |   |                        |
  |                            |   |                        |
  | +---------+     +--------+ |   |      +----------+      |
  | | input   |     | output | |   |      |  fluent  |      |
  | |---------|+--> |--------| +--------->|----------|      |
  | |  tail   |     |  mongo | |   |      |   test   |      |
  | +---------+     +--------+ |   |      +----------+      |
  +----------------------------+   +------------------------+

今回はnode.jsを使用して、mongoDBに保存されたログをブラウザでリアルタイムにtailしてみます。
構成は以下のとおりです。
  +----------------------------+   +-------------------------+
  | web server (10.0.0.8)      |   | mongo server(10.0.0.16) |
  |----------------------------|   |-------------------------|
  |          fluentd           |   |         mongod          |
  |                            |   |                         |
  | +---------+     +--------+ |   |      +----------+       |
  | | input   |     | output | |   |      |  fluent  |       |
  | |---------|+--> |--------|+---------->|----------|       |
  | |  tail   |     |  mongo | |   |      |   logs   |       |
  | +---------+     +--------+ |   |      +-----+----+       |
  +----------------------------+   +------------|------------+
                                                |
                                 +--------------+
                                 | +-------------------------+
                                 | | node server(10.0.0.100) |
                                 | |-------------------------|
                                 | |         node.js         |
                                 | |                         |
                                 | |      +-----------+      |
                                 +------->| mongoose  |      |
                                   |      |-----------|      |
                                   |      | socket.io +----------> Brower
                                   |      +-----------+      |
                                   +-------------------------+


今回、web serverとmongo serverは、ほとんど前回の設定のままですが、
新規nodeサーバーでmongodbにアクセスするmongooseはデフォルトで複数系の名前のコレクションにアクセスするようになっています。
そこで、webサーバーのtd-agentのmongo outputプラグインの書き出し先をlogsという名前に変更しておきます。
また、リアルタイムに表示させたいのでflush_intervalを0sに設定しておきます。
<source>
  type tail
  format apache
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access="apache.access">
  type mongo
  flush_interval 0s
  database fluent
  collection logs

  host 10.0.0.16
  user memorycraft
  password *******
</match>


それでは、nodeサーバーを設定します。
新規にEC2インスタンスをたちあげてnodeとhttpdをインストールします。
# yum install httpd -y
# /etc/init.d/httpd start

# yum install -y wget
# cd /usr/local/src/
# wget http://nodejs.org/dist/v0.8.14/node-v0.8.14.tar.gz
# tar xzvf node-v0.8.14.tar.gz 
# cd node-v0.8.14
# ./configure
# make
# make install


次にnpmをインストールしてからsocket.io, log, forever, mongooseをnpmインストールします。
# curl https://npmjs.org/install.sh | sh
# npm install -g socket.io
# npm install -g forever


nodeを動かすためのユーザーを作成します
# useradd appadmin
# passwd appadmin
# chmod 755 /home/appadmin
# su - appadmin


ノードモジュールへのパスを通すために環境変数を設定します。
$ vi ~/.bash_profile 
$ source ~/.bash_profile 
---
export NODE_PATH=/usr/local/lib/node_modules
---

$ source ~/.bash_profile


ここで、nodeサーバーとHTML用のコンテンツ置き場を用意します。
ディレクトリ構成は以下のとおりです。
cd ~/nodetest
tree
.
|-- node
|   |-- logs
|   |   `-- app.log
|   `-- server.js (nodeサーバー)
`-- public
    |-- assets
    |   |-- css
    |   |-- img
    |   `-- js
    |       `-- client.js (nodeクライアント)
    `-- index.html (tail用の画面)


また、publicをhttpdのDocumentRootにするため、以下のように設定します。
$ su -
# cd /var/www
# mv html html.org
# ln -s /home/appadmin/nodetest/public html
# vi /etc/httpd/conf/httpd/conf
---
~略~
<directory html="html" var="var" www="www">
    Options FollowSymLinks
    AllowOverride All
~略~
---
</directory>

# /etc/init.d/httpd restart


そして、httpdとnodeに接続させるため、SecurityGroupを以下のように設定します。



次にサーバーとコンテンツを実装してみます。 それぞれの内容は以下の通りです。

node/server.js
//3001番でlistenします
var server = require('http').createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('server connected');
});
server.listen(3001);

//logsコレクションのスキーマ定義をしておきます。
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AccessSchema = new Schema({
    host: String,
    user: String,
    method: String,
    path: String,
    code: Number,
    size: Number,
    referer: String,
    agent: String,
    time : Date
});
mongoose.model('log', AccessSchema);

//fluentデータベースに接続します。
mongoose.connect('mongodb://10.0.0.16/fluent');
var Access = mongoose.model('log');

//socket.ioを起動します。
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
    /**
     * クライアント接続時に、初期データとして、
     * 保存されているlogsコレクションをすべてemitします。
     */
    Access.find({}).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            socket.emit('init', docs);
        }
    });
});

/** 
 * 1.5秒おきに最終取得ログの日時以降のログを取得して
 * 全員にブロードキャストし、最終日時を保存します。
 */
var last = new Date();
setInterval(function(){
    Access.find({}).where('time').gt(last).sort({time:1}).exec(function(e, docs){
        if(e){
        }
        else{
            for(var i=0; i<docs.length; i++){
                if(last < docs[i].time){
                    last = docs[i].time;
                }
            }
            io.sockets.emit('log', docs);
        }
    });
}, 1500);


public/assets/js/client.js
$(function(){
  //接続します
  var socket = io.connect('http://' + location.hostname + ':3001/');
  socket.on('connect', function() {
   /**
     * 接続成功時に初期データを時系列降順で表示させます
     */
    socket.on('init', function(data){
        $("#log").empty();
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

    /**
     * 随時送られてくるデータは一番上に表示させます
     */
    socket.on('log', function(data){
        for(var i=0; i<data.length; i++){
            row = data[i];
            $("#log").prepend(row.host+" "+row.time+" "+row.path+"<br/>");
        }
    });

  });
});


public/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
    function load(){
        $.getScript("assets/js/client.js");
    }
    $.getScript("http://" + location.hostname + ":3001/socket.io/socket.io.js", function(){
        load();
    });
});
</script>
<body>
    <div id="content">
        <h1>ログ</h1>
        <div id="log" width="500px" height="100%">
        </div>
    </div>
</body>
</html>


早速動かしてみます。
$ cd ~/nodetest/node
$ forever start server.js

これでnodeサーバーが起動しました
それでは、ブラウザを見てみます。



初期ログが表示されています。うまく動いているようです。
そこで、fluentdのtail対象サーバーであるwebサーバーへアクセスをしてみます。
わかりやすくするために、何度かリロードしておきます。


そして、さきほどのnodeサーバーの画面をみると、、、


おお!ログが追加で現れました!
これであるサーバーのログデータを別のサーバーでリアルタイムに表示させることができました。

以上です。