LoginSignup
33
33

More than 5 years have passed since last update.

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

Posted at

Node.jsでサーバを作るときに気をつけること の続きのようなものです。

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

winstonモジュール

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

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)

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


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

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

33
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
33