node.js の logging library の winston のソースを読む

node.js を使い始めて javascript かわいいなーと思い始めたこのごろ
まだ javascript の扱い方から勉強してるんだけど
そんなわけなのでソースを読んで勉強しようかなと winston を選んでみた

winston は nodejitsu 社が作ってる Flatiron ってオープンソースフレームワークのモジュールの一つとして開発されてるす

いくつか便利機能があるんすが query とか、例外ハンドリングとか、profiler とか
そのへんは今回は触らずにロギング周りを見ていくす

この記事は 東京Node学園祭2012 アドベントカレンダー の 26 日目の記事です。

ファイル

lib の中身はシンプル

├── winston
│   ├── common.js
│   ├── config
│   │   ├── cli-config.js
│   │   ├── npm-config.js
│   │   └── syslog-config.js
│   ├── config.js
│   ├── container.js
│   ├── exception.js
│   ├── logger.js
│   ├── transports
│   │   ├── console.js
│   │   ├── file.js
│   │   ├── http.js
│   │   ├── transport.js
│   │   └── webhook.js
│   └── transports.js
└── winston.js

winston.js だけを require すれば使えるように作られてる
てことで winston.js を見てみよう

winston.js

大きくわけて 2 つの役割を持ってます

  • 1) ロガーを作ったりするのに便利なコンストラクタ的な関数やユーティリティ
  • 2) winston 自体がロガーとして動作するようになってる

(1) から見ていくす

1) ロガーを作ったりするのに便利なコンストラクタ的な関数やユーティリティ

expose されてるのを図にするとこんな感じ
f:id:bufferings:20121110180905p:plain
オレンジはコンストラクタ的な関数で緑はプロパティ的なの

winston.transports.Console
winston.transports.File
winston.Container
winston.Logger
winston.config.cli
winston.config.npm

とかが呼べる感じ

transports

transports は出力先ですね
こんな風にして作れます

new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'somefile.log' })

ちなみに継承みたいな構造はこんな感じ
f:id:bufferings:20121110175841p:plain

Container

Container は複数のロガーをひとまとめにして持っとくための箱です
それぞれのロガーにはIDをつけておいて、それで取り出すことができます

以下の例では指定したオプションでロガーを生成して、それを category1 という名前で container に保持してるっつーわけすね

var winston = require('winston'),
      container = new winston.Container();

  container.add('category1', {
    console: {
      level: 'silly',
      colorize: 'true'
    },
    file: {
      filename: '/path/to/some/file'
    }
  });

Logger

Logger はログを出力するためのものです
Transport を複数持つことができて log 関数でそこに出力する

javascript っぽくて面白いなーと思ったのが
setLevel 関数でそのレベルごとの関数を作ってるところ

実際は common.js#L23 に実装してある

大雑把に書くとこんな感じ

targetLogger[level] = function (msg) {
  return targetLogger.log(level, msg, meta, callback);
}

config

config はレベルと、レベルごとの色を保持している設定ファイルです
例えば syslog config は

var syslogConfig = exports;

syslogConfig.levels = {
  debug: 0,
  info: 1,
  notice: 2,
  warning: 3,
  error: 4,
  crit: 5,
  alert: 6,
  emerg: 7
};

syslogConfig.colors = {
  debug: 'blue',
  info: 'green',
  notice: 'yellow',
  warning: 'red',
  error: 'red',
  crit: 'red',
  alert: 'yellow',
  emerg: 'red'
};

まとめ

ということで。ここまでで。

  • Transport を作ることができて
  • それを指定して Logger を作ることができて
  • config でレベルや色が変えられて
  • 複数の Logger をひとまとめで管理する Container を作ることができる

んでも。もっと気軽に使いたいよね。ということで winston はそれ自身がログを出力できるようになってます。

2) winston 自体がロガーとして動作するようになってる

winston 自身が Logger を defaultLogger という名前で持っていて
そこに処理を委譲しているような印象す

var methods = [
  'log',
  'query',
  'stream',
  'add',
  'remove',
  'clear',
  'profile',
  'startTimer',
  'extend',
  'cli',
  'handleExceptions',
  'unhandleExceptions'
];
common.setLevels(winston, null, defaultLogger.levels);
methods.forEach(function (method) {
  winston[method] = function () {
    return defaultLogger[method].apply(defaultLogger, arguments);
  };
});

なので winston 自体に Transports を設定したり setLevels したりして
そのまま winston.log('info', 'msg'); などが可能です。

winston の使い方

ここまで読んで、ドキュメントの意味がわかったす

https://github.com/flatiron/winston#logging

Using the Default Logger

var winston = require('winston');

// winston から直接ログ出力できるよー
winston.log('info', 'Hello distributed log files!');
winston.info('Hello again distributed logs');

// こんなこともできるよー
winston.add(winston.transports.File, { filename: 'somefile.log' });
winston.remove(winston.transports.Console);

winston が内部に持ってる defaultLogger に対しての処理になるすね。

Instantiating your own Logger

こっちは作ってるってことね。

var logger = new (winston.Logger)({
    transports: [
      new (winston.transports.Console)(),
      new (winston.transports.File)({ filename: 'somefile.log' })
    ]
  });

  logger.log('info', 'Hello distributed log files!');
  logger.info('Hello again distributed logs');

  logger.add(winston.transports.File)
        .remove(winston.transports.Console);

面白かったー。