express

express向けconnect-ltsv-loggerを作った

ltsvのビッグ・ウェーブを眺めてたら最初にgem作ってたのが知人でびっくりして、俺もなんかやってみたいなーと思いつつ作ってみました。

connect-ltsv-logger

Node.js用のparserはもうあったし、それならstreamでって思ったけど Stream難しいので挫折してるうちに、ltsv-streamも登場していたので、 logを出力する側からアプローチしてみた。

こんな感じでexpressやconnectのMiddlewareとして使用します。

サンプル


              var express = require("express"),
                  ltsvlogger = require('connect-ltsv-logger');
              
              // define output WriteStream
              var out = fs.createWriteStream("ltsv-access.log",{flags: 'a+'}),
              
              // define tokens
              var ltsv = [];
              ltsv.push("host");
              ltsv.push("ident");
              ltsv.push("user");
              ltsv.push("time");
              ltsv.push("req");
              ltsv.push("status");
              ltsv.push("size");
              ltsv.push("referer");
              ltsv.push("ua");
              
              var app = express();
              app.configure(function(){
                // app.set(/*snip*/)
                // ...
              
                app.use(ltsvlogger({format:ltsv,stream:out}));
              
                // app.use(/*snip*/)
                // ...
              });
              

指定したTOKENをltsv形式でログ出力します。connect.loggerのラッパーなので元のオプションもそのまま使えます。

引数

  • format: String or 例のようにTokenのArrayが使えます。
  • stream :本家と同じ。ltsvにするのでstdoutじゃなくてファイルに書いた方が使いがいがあるかも。
  • buffer: 本家と同じ。
  • immediate: 本家と同じ。

使用可能なFormat

connect.logger'sのformatsをltsv形式にオーバーライドしているだけです。

  • default
    host:127.0.0.1ident:-user:-time:[Wed, 13 Feb 2013 10:00:55 GMT]req:GET / HTTP/1.1status:200size:110referer:-ua:-`
  • short
    host:127.0.0.1ident:-req:GET / HTTP/1.1status:200size:-response-time:1 ms`
  • tiny
    req:GET /status:200size:-response-time:1 ms
  • dev

    concise output colored by response status for development use (Not ltsv format).

使用可能なTokens

identとか良くわかんないけど、connectも”−”にしてたので...(汗)

  • time

      logger.token("time",function(){
                      return "[" + moment().format("DD/MMM/YYYY:HH:mm:ss Z") + "]" ;
                    });
  • host

      logger.token("host",function(req,res){
                      return req.connection.address().address || '-';
                    });
  • X-Forwarded-For

      logger.token("X-Forwarded-For",function(req,res){
                      return res.getHeader("X-Forwarded-For") || "-";
                    });
  • user

      logger.token("user",function(req,res){
                      return '-';
                    });
  • ident

      logger.token("ident",function(req,res){
                      return '-';
                    });
  • req

      logger.token("req",function(req,res){
                      var ret = [];
                      ret.push(req.method);
                      ret.push(req.url);
                      ret.push("HTTP/"+req.httpVersion);
                      return ret.join(" ");
                    });
  • method

      logger.token("method",function(req,res){
                      return req.method;
                    });
  • uri

      logger.token("uri",function(req,res){
                      return url.parse(req.url).href;
                    });
  • protocol

      logger.token("protocol",function(req,res){
                      return url.parse(req.url).protocol;
                    });
  • status

      logger.token("status",function(req,res){
                      return res.statusCode;
                    });
  • size

      logger.token("size",function(req,res){
                      return res.getHeader("content-length");
                    });
  • reqsize

      logger.token("reqsize",function(req,res){
                      if(req.body) return req.body.length;
                      return "-";
                    });
  • referer

      logger.token("referer",function(req,res){
                      return req.headers['referer'] || req.headers['referrer'];
                    });
  • ua

      logger.token("ua",function(req,res){
                      return req.headers['user-agent'];
                    });
  • vhost

      logger.token("vhost",function(req,res){
                      return req.headers["host"];
                    });
  • reqtime

      logger.token("reqtime",function(req,res){
                      return new Date - req._startTime;;
                    });
  • X-Cache

      logger.token("X-Cache",function(req,res){
                      return res.getHeader('X-Cache');
                    });
  • X-Runtime

      logger.token("X-Runtime",function(req,res){
                      return res.getHeader('X-Runtime');
                    });

            |i
                   \      |.|
                    ト\   /| ト
                    | トヽ   / | | ト
                    | | トヽ\/| | | ト    /
                    | | | ト\≧三ミゞ=イ/
                   ム彡''´ ̄ ̄    ̄ ヽ{__..
                  /             V´
                  ノ  __          ',
               ,. == y ̄, __、\_        )      世 界 的 で す も ん ね
               |i  }-| ゝ二 |/ ̄ ̄  /ニ,l
               ヽ__ノ/ヾ _ ノ       > }}
                / >≦'__        し /        乗 る し か な い
                 Vて二オカ       (_,/}
                 Yこ二ノ!!|          }         こ の ビッ グ ウ ェ ー ブ に
                  Y⌒ 从        ∠)
                  从从从トミ   _.ィニ二 ̄丶
                   ミ三三彡 ' ´      \ \
                      /           \ヽ
                    /            ミ;,. ', ',
                     |   _  _ __    \',.',
                    ノ!   | V7\ ´/
                   / l /_ゝ| ト >__/ /
                   |   ヽン ´  ヽー'
                  i|                l
                  |:! ヽ              |
                  | ト、 `ミ,            l
              

Phantom.jsとimageMagickで.html to .png to .pdf

年末のプレゼンに向けてこんなの作ってみた。 テスト一切書いてなかったり、Cakefileが微妙だったり、デザインがやっつけだったりまだまだ改善の余地ありですが、一旦完成。

  georgeOsdDev/SlideBase

express + socket.IOでスライドショーを共有するみたいな感じです。
  Jxck/SlideStream の劣化版といえばそれまでですが、 js,coffeescriptの勉強という意味で以下の分厚い本を読みつつ1から自分で作ってみた。


webサーバはexpressでadminログインしたプレゼンターの操作をsocket.IOでブロードキャストで基本的なアイデアや作りは冒頭申したとおり、  Jxck/SlideStream まんまなんですが、
以下の構成パッケージで、socket.IOとexpressでセッション共有する際にJxckさんのブログのままだと動かなかったのでcookieのとりかたは pxsta's Memo さんを参考にcookieのパースが必要みたい。

package.json


              "dependencies": {
                "express": "3.0.0",
                "ejs": ">= 0.0.1",
                "connect": "2.6.0",
                "socket.io": "0.9.10",
                "connect-redis": "~1.4.5",
                "cookie": "0.0.5",
                "underscore": "~1.4.2"
              }
              

expressサーバ側


              app.use util.parallel(
                express.methodOverride(), 
                express.session
                  store: sessionStore
                  secret: config.session.secret
                  maxAge: false
                  cookie:
                    httpOnly: false
                )
              

socket.IOサーバ側


              io.set 'authorization', (handshakeData, callback) ->
                if handshakeData.headers.cookie
                  singedCookie = slideBaseUtil.parseCookie decodeURIComponent(handshakeData.headers.cookie)
                  cookie = slideBaseUtil.parseSignedCookies singedCookie
                  sessionID = cookie['connect.sid']
                  store.get sessionID ,(err,session) ->
                    if err
                      log "err", err
                      callback err.message, false
                    else if !session
                      callback "session not found", false
                    else
                      log "session found"
                      handshakeData.cookie = cookie
                      handshakeData.sessionID = sessionID
                      handshakeData.session = session
                      callback null, true
                else
                  callback "cookie not found", true
              

slideBaseUtilの中身はこんな感じ


              connect = require 'connect'
              cookie  = require 'cookie'
              
              parseCookie: (target) ->
                cookie.parse target
              parseSignedCookies: (singedCookie) ->
                connect.utils.parseSignedCookies singedCookie, config.session.secret
              

クライアントサイドでプラグインみたいに別ファイルのいろんなところからsocketを使えるようにするには

              execEmit: (name,data) ->
                console.log "execEmit "+name
                obj =
                  name:'plugin'
                  plugin:
                    name:name
                    data:data
                $('body').trigger 'execEmit', obj
              socket?.on 'connect', ->
                $('body').on 'execEmit', (event,obj) ->
                  socket.emit obj.name, obj
              

みたいにbodyにイベント作ってそれを外から呼ぶみたいにしてみた。
あとはサーバ側にもpluginで受けたらそのままブロードキャストするようにかいて


              socket.on 'plugin', (data) ->
                socket.broadcast.volatile.emit "plugin", data
              

クライアントサイドはpluginを受けたらpluginとして登録してある関数を呼ぶみたいな感じにしておけば未定義のものが仮に送られてきても大丈夫みたいな感じ。


              socket.on 'plugin', (obj) ->
                if not obj.plugin or not obj.plugin.name then return false
                if not isEnableServerPush obj.plugin.name then return false
                func = sbClient.plugins[obj.plugin.name].get("callback") || {}
                func(obj.data)
              

さて、タイトルにあるとおり感動したのはPhantom.js。
このやり方もJxckさんの ブログ を参考にしたのですが、チョット違うのはPhantom.jsからクリックイベントを呼んでページ遷移させるとこと、sam2pとpdftkの代わりに imageMagick っていうツールを使いました。imageMagickを使うと、複数pngから一発で1枚のpdfに変換することができました。

Phantom.jsからクリックイベントを呼ぶには


              page.includeJs 'http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js',->
                offset = page.evaluate ->
                  $('#next').offset()
                page.sendEvent 'click', offset.left+1, offset.top+1
              
みたいな感じで、叩きたいボタンや場所のoffsetを取得してpage.sendEvent('イベント',top,left)を呼べばOK。これでページ送りができますが、それぞれ非同期でやってる?ようなので、この直後にpage.render(outout)を呼んじゃうとまだクリックされてなかったりするのでsetTimeoutで微妙な調整がいるかも。sendEventの完了タイミングとれたりすると便利なのに。
さて、imageMagickインストールはhomebrew経由でカンタン。
              brew install imagemagick
              
これでこんな感じでいくつかコマンドが使えるようになります。
              22:26:33:oshidatakeharu@george-on-mba.local:/usr/local/Cellar/imagemagick/6.7.7-6/bin:master$ ll
              total 304
              -r-xr-xr-x  1 oshidatakeharu  admin  1290 Nov 11 12:08 Magick++-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1256 Nov 11 12:08 Magick-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1264 Nov 11 12:08 MagickCore-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1269 Nov 11 12:08 MagickWand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1251 Nov 11 12:08 Wand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 animate
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 compare
              -r-xr-xr-x  1 oshidatakeharu  admin  9204 Nov 11 12:08 composite
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 conjure
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 convert
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 display
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 identify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 import
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 mogrify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 montage
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 stream
              
このうちconvertっていうのがファイル変換してくれるやつで、
              convert src/*.png dest/output.pdf
              
とかあるいは
              convert -append 1.png 2.png output.pdf
              
みたいな感じでpngが複数ページ構成のpdfファイルに1つにまとまります。
カンタンでイイね。
あーなんか断片的でまとまりのないエントリーですみません。