Node.jsのエラーに付き合う

[Node.jsでサーバを作るときに気をつけること](http://qiita.com/wf9a5m75/items/caf8f15e53fb650a1762) の続きのようなものです。

コードを書いたらバグは付き物。でもNode.jsの場合はプログラム全体が死亡するので、ちゃんとエラーを把握しておくことが大事です。

## winstonモジュール

私が仕事で使っているのは、[winston](https://github.com/winstonjs/winston)というロガーモジュール。
こいつがいいのは、ログを出したら、複数の方法でログを記録できるということ。
例えば、
- ログレベルでerror以上だけメールで送りたい
- でもファイルにも記録しておきたい
みたいのは余裕です。

```js
var winston = require('winston');
var transports = [
  // デバッグできるようにコンソールにも出力
  new (winston.transports.Console)({
    level: 'verbose',
    timestamp: true,
    colorize: true,
    json: true,
    prettyPrint: true
  })
];

// error以上でメール通知
transports.push(
  new (require('./winston-nodemail').NodeMail)({
    'host': configs.SMTP.HOST,
    'username': configs.SMTP.USERNAME,
    'password': configs.SMTP.PASSWORD,
    'to': configs.MAIL_LOGGER.TO,
    'from': configs.MAIL_LOGGER.FROM,
    'secure': true,
    'level': 'error'
  })
);

// 日にち単位でログファイルを作成
transports.push(
  new (winston.transports.DailyRotateFile)({
    'dirname': __dirname + '/../logs/',
  })
);

// ロガーの作成
var logger = new (winston.Logger)({
  transports: transports,        // 通常用のtransports
  exceptionHandlers: transports, // エラーが起きたとき用のtransports
  exitOnError: true              // エラーが起きたら、さっさと終了させる (で、foreverで再起動)
});

logger.verbose("Hello world");

logger.error("エラーだよー");     //メールが届くはず

logger.debug({                   //JSONだってそのままOK
  "key1": 222,
  "key2": "value2"
});
```

メールを送るwinston用のモジュールは（たしか）デフォルトで用意されているんだけど、私の場合は書式を整えたい都合があったので、自分で作りました。

(以下、winston-nodemail.js)

```js
var util = require('util'),
    winston = require('winston'),
    sprintf = require('sprintf').sprintf,
    typeOf = require('typeof'),
    nodemailer = require('nodemailer'),
    smtpTransport;

var NodeMail = exports.NodeMail = function (options) {
  //
  // Name this logger
  //
  this.name = 'winstonNodeMail';

  //
  // Set the level from your options
  //
  this.level = options.level || 'info';


  //
  // Configure for node-mailer module
  //
  this.username = options.username || '',
  this.password = options.password || '',
  this.host = options.host || 'localhost';
  this.port = options.port || 25;
  this.from = options.from || 'admin@' + this.host;
  this.to = options.to || 'admin@' + this.host;
  this.secure = options.secure || false;

  this.createSmtpTransport();
};

//
// Inherit from `winston.Transport` so you can take advantage
// of the base functionality and `.handleExceptions()`.
//
util.inherits(NodeMail, winston.Transport);

NodeMail.prototype.createSmtpTransport = function () {
  this.smtpTransport = nodemailer.createTransport({
    host: this.host,
    secure: this.secure,
    auth: {
      user: this.username,
      pass: this.password
    },
    debug: true
  });
}

NodeMail.prototype.log = function (level, msg, meta, callback) {

  var now = new Date();
  var  params, formattedMessage;
  if (!this.smtpTransport) {
    this.createSmtpTransport();
  }
  formattedMessage = sprintf(
    [
      "%02d/%2d %02d:%02d",
      "--------------------------------",
      "%s",
      "",
      "%s"
    ].join("\n"),

    now.getMonth() + 1, now.getDate(), now.getHours(), now.getMinutes(),
    msgToString(msg), msgToString(meta)
  );
  var subject = sprintf("%s", msgToString(msg));
  subject = subject.replace(/\n.*$/, "");


  params = {
    from : this.from,
    to : this.to,
    //subject : sprintf('report level=%s %02d:%02d', level, now.getHours(), now.getMinutes() ),
    subject : sprintf('%s %02d:%02d', subject, now.getHours(), now.getMinutes() ),
    text : formattedMessage
  };

  var self = this;
  this.smtpTransport.sendMail(params, function (error, response) {
    if (error) {
      console.log(error);
      //self.smtpTransport.close();
      callback(error, response);
      return;
    }
    //console.log(response);
    //self.smtpTransport.close();
    callback(null, response);
  });
};

function msgToString(input) {

  var converter = function(msg) {
    if (msg instanceof Error) {
      msg = {
        name: msg.name,
        message: msg.message,
        fileName: msg.fileName,
        lineNumber: msg.lineNumber,
        stack: msg.stack.split("\n")
      };
    } else if (typeOf(msg) == "string") {
      if (msg.substring(0, 1) == "{" &&
          msg.substring(-1, 1) == "}") {
        try {
          msg = JSON.parse(msg);
        }catch(e) {};
      }
    } else if (typeof(msg) == "object") {
      for (var key in msg) {
        msg[key] = converter(msg[key]);
      }
    }
    return msg;
  };
  return JSON.stringify(converter(input), null, 4);
}
```

## 予期せぬエラーもwinston

winstonロガーはエラー処理されていない場合でも、ちゃんとキャッチしてくれます。
そのログを自作の winston-nodemail.js が適当にいい感じに書式化して、エラー情報をメールで送ってくれます。

![Screen Shot 2015-09-23 at 8.15.18 PM.png](https://qiita-image-store.s3.amazonaws.com/0/39529/c96e0dff-569b-e4f8-82e9-9a52ecd07b01.png)

----

foreverで動かしていると、プログラムが死んでもすぐに立ち上げ直してくれます。
でも単純なミスがあるとすぐに死亡して、メール通知して、また死亡して、、、と、自分のメールボックスがエラー通知で一杯になるので注意しましょう。

完璧な対応というわけではないですが、いまのところこれでかなり救われてます。
参考までにどうぞ。