記事一覧:2013年03月25日

Connectミドルウェア「logger」でExpressアプリケーションのアクセスログ/エラーログを記録する

 Expressでは、Connectミドルウェア「logger」を使うことで詳細なアクセスログやエラーログを記録することができる。Expressコマンドで生成したスケルトンコードでは、以下のようにloggerミドルウェアを利用するように設定されている。

app.configure(function(){
  :
  :
  app.use(express.logger('dev'));
  :
  :
}

 このように第一引数に'dev'引数を与えて生成されたloggerミドルウェアを使用すると、標準出力にカラー付きでアクセスログが出力される。これは開発用という目的なので、実サービスの運用には適していない。そこで、Apacheのデフォルト設定で使われる形式のアクセスログおよびエラーログをファイルに出力するよう設定していく。

 なお、下記ではExpress 3.1.0およびこれが依存しているConnect 2.7.2ベースを使用している。

logger型オブジェクトを生成する

 loggerミドルウェアオブジェクトのファクトリ関数は、以下のような引数を取る。

logger([options])

 options引数にはオプション情報を格納したオブジェクト、文字列、関数オブジェクトを与えることができる。省略された場合は空のオブジェクト({})が与えられたものと見なされる。また、文字列もしくは関数オブジェクトが与えられた場合、次のようなオブジェクトが与えられたものと見なされる。

{format: <与えられた文字列もしくは関数オブジェクト>}

 options引数に与えられたオブジェクトのうち、表1のプロパティが挙動に影響を与える。

表1 options引数で有効なプロパティ
プロパティ名説明デフォルト値
immediate非falseの値を指定すると、リクエストを受信した時点でログ出力を実行する。false、もしくはundefinedの場合、レスポンスの送信が完了した時点(responseオブジェクトのendイベントハンドラが呼び出されるタイミング)でログ出力を実行するundefined
format出力フォーマットを指定する。表2のどれかに該当する文字列が指定された場合、それに対応する定義済みフォーマットが使用される。それ以外の文字列が指定された場合、その文字列をフォーマット文字列として使用する'default'
streamログを出力するストリームを指定するprocess.stdout
bufferバッファを利用する場合、非falseの値を指定する。数値が指定された場合、フラッシュする間隔をミリ秒で指定するundefined

 また、options.formatプロパティでは出力形式を指定できるが、ここでは表2の定義済みフォーマット値が利用できる。「tiny」と「dev」はほぼ同じ内容を出力するが、devはコンソール出力時に見やすいよう色付き表示のためのエスケープシーケンス付きで出力される点が異なる。

表2 使用できる定義済みフォーマット
定義済みフォーマット名フォーマット文字列
default:remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
short:remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms
tiny:method :url :status :res[content-length] - :response-time ms
dev:method :url :status :response-time ms - :res[content-length]

 なお、フォーマット文字列では表3のトークンが利用できる。これらは出力時に適切な値に変換される。

表3 フォーマット文字列で使用できるトークン
トークン名変換後の文字列
:urlreq.originalUrl || req.url
:methodreq.method
:response-timenew Date - req._startTime
:datenew Date().toUTCString()
:statusres.statusCode
:referrerreq.headers['referer'] || req.headers['referrer']
:remote-addrreq.ipもしくはsock.socket.remoteAddress、sock.remoteAddress
:http-versionreq.httpVersionMajor + '.' + req.httpVersionMinor
:user-agentreq.headers['user-agent']
:req[<リクエストヘッダ名>]指定したリクエストヘッダの値
:res[<レスポンスヘッダ名>]指定したレスポンスヘッダの値

 定義済みフォーマットで所望の出力が得られない場合、表3のフォーマット文字列を使って独自のフォーマットを定義することも可能だ。さらに、options.formatプロパティに独自のログ整形処理を実装した関数オブジェクトを与えることもできる。この場合、この関数オブジェクトはログを出力するタイミングで(exports, req, res)という引数が与えられて実行される。exports引数にはloggerモジュールが、req引数にはリクエストオブジェクトが、res引数にはレスポンスオブジェクトが与えられ、この関数の戻り値がログとして指定されたストリームに出力される。

Apacheのデフォルト設定で使われる形式のログを出力する

 以下はApacheのデフォルト設定で出力されるログの例だ。

192.168.0.1 - - [24/Mar/2013:06:07:06 +0900] "GET /feed/ HTTP/1.1" 304 - "-" "hogehoge user-agent"

 このとき、各フィールドには次のような情報が格納されている。なお、定義されていない/取得できない値に対応するフィールドには「-」が出力される。

<リモートホスト名> <identd(mod_ident)が提供するリモートログ名> <リモートユーザー名> <アクセス日時> "<HTTPリクエストヘッダ>" <ステータスコード> <転送バイト数> "<Refererリクエストヘッダの値>" "<User-Agentリクエストヘッダの値>"

 これと同じ形式のログをloggerミドルウェアを使って出力するには、定義済みフォーマットの「default」を利用すればよい。たとえば/home/foobar/access_logというファイルにログを出力するには、以下のようにする。

app.configure(function(){
  :
  :
  var logStream = fs.createWriteStream('/home/foobar/access_log', {mode: 'a'});
  app.use(express.logger({
    format: 'default',
    stream: logStream || process.stdout
    }));
  :
  :
}