CLOVER🍀

That was when it all began.

Node.jsアプリケヌションのログ出力に、winstonを䜿っおみる

これは、なにをしたくお曞いたもの

Node.jsでアプリケヌションを曞く時のロギングラむブラリは、どういうものがあるのかちょっず気になりたしお。

調べた感じ、以䞋のようなものがありそうです。

GitHub - winstonjs/winston: A logger for just about everything.

GitHub - trentm/node-bunyan: a simple and fast JSON logging module for node.js services

GitHub - log4js-node/log4js-node: A port of log4js to node.js

GitHub - npm/npmlog: The logger that npm uses

今回は、この䞭でもstar数が倚いwinstonを詊しおみるこずにしたした。

winston

winstonは、耇数のトランスポヌト出力先をサポヌトするロギングラむブラリです。

GitHub - winstonjs/winston: A logger for just about everything.

ロガヌを䜜成する時に、

  • 出力先
  • ログフォヌマット
  • 出力するログレベル

などを蚭定できたすが、カテゎリ別にロガヌを䜜成したり、デフォルトロガヌを蚭定したりもできたす。

Working with multiple Loggers in winston

Using the Default Logger

ログの出力先には、ビルトむンではコン゜ヌル、ファむル、HTTP、Streamが遞べ、その他にもコミュニティなどでいろいろず
実装されおいるようです。

winston/transports.md at 3.2.1 · winstonjs/winston · GitHub

フォヌマットに぀いおは、logformでのものが扱えるようです。

Formats

GitHub - winstonjs/logform: An mutable object format designed for chaining & objectMode streams

Logstashフォヌマットずかもあったりしたす。

ずりあえず、今回は簡単に詊しおみるずしたしょう。

環境

今回の環境は、こちら。

$ node -v
v10.15.3


$ npm -v
6.4.1

準備

winstonのむンストヌル。

$ npm i winston

むンストヌルされたwinstonのバヌゞョン。

  "dependencies": {
    "winston": "^3.2.1"
  }

今回は、こちらを䜿っおいきたいず思いたす。

winstonを䜿っおいろいろ詊す

それでは、ここからいく぀か簡単にwinstonを䜿ったコヌドを曞いおいきたしょう。

Getting Started的な

なにはずもあれ、たずは動かしおみたす。

こんなコヌドを甚意。
winston-example1.js

const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),  // default
    transports: [
        new winston.transports.Console()
    ]
});

logger.debug('Debug Message');
logger.info('Info Message');
logger.warn('Warn Message');
logger.error('Error Message');

ログ出力先をコン゜ヌル、出力するログレベルをinfo以䞊に、ログフォヌマットをJSONにしたした。ログ出力先Transportは、
指定しないず゚ラヌになりたす。

ロガヌのデフォルト蚭定は以䞋に蚘茉がありたすが、ログフォヌマットはJSON、ログ出力レベルはinfo以䞊みたいです。

Creating your own Logger

実行。

$ node winston-example1.js 
{"message":"Info Message","level":"info"}
{"message":"Warn Message","level":"warn"}
{"message":"Error Message","level":"error"}

ログ出力レベルがinfo以䞊なので、debugのものは出力されおいたせんね。たた、ログフォヌマットはJSONです。

ちなみに、ログレベルの䞀芧はこちら。

Logging Levels

今回はdebug、info、warn、errorしか䜿っおいたせんが、もっずたくさんあるこずがわかりたす。

ログにタむムスタンプを入れ、メッセヌゞをフォヌマット指定できるようにしおみる

先ほどのログには、実行時のタむムスタンプがありたせん。できれば、タむムスタンプを衚瀺したいずころです。

たた、ログメッセヌゞに%sのようなフォヌマット指定もできるようなので、こちらも合わせお詊しおみたした。
winston-example2.js

const winston = require('winston');
const format = winston.format;

const logger = winston.createLogger({
    level: 'info',
    format: format.combine(
        format.timestamp(),  // timestampを出力する
        format.splat(),  // String interpolation splat for %d %s-style messages.
        format.json()
    ),
    transports: [
        new winston.transports.Console()
    ]
});

logger.debug('Debug Message');
logger.info('Info Message');
logger.warn('Warn Message');
logger.error('Error Message');

logger.debug('%s %s', 'Debug','Message');
logger.info('%s %s', 'Info', 'Message');
logger.warn('%s %s', 'Warn', 'Message');
logger.error('%s %s', 'Error', 'Message');

こちらは、フォヌマットをcombineで組み合わせるこずで蚭定しおいたす。

    format: format.combine(
        format.timestamp(),  // timestampを出力する
        format.splat(),  // String interpolation splat for %d %s-style messages.
        format.json()
    ),

この指定する順番が倧事みたいで、jsonの埌にtimestampを入れたりするず、タむムスタンプは出力されたせん 。

Combine

詳现は、logformを芋おみるずよいでしょう。

GitHub - winstonjs/logform: An mutable object format designed for chaining & objectMode streams

確認。

$ node winston-example2.js 
{"message":"Info Message","level":"info","timestamp":"2019-05-21T14:21:17.627Z"}
{"message":"Warn Message","level":"warn","timestamp":"2019-05-21T14:21:17.628Z"}
{"message":"Error Message","level":"error","timestamp":"2019-05-21T14:21:17.628Z"}
{"level":"info","message":"Info Message","timestamp":"2019-05-21T14:21:17.629Z"}
{"level":"warn","message":"Warn Message","timestamp":"2019-05-21T14:21:17.629Z"}
{"level":"error","message":"Error Message","timestamp":"2019-05-21T14:21:17.629Z"}

タむムスタンプがログに入り、%sの郚分も眮換されるようになっおいたす。

ずころで、タむムゟヌンがUTC感。

タむムスタンプのフォヌマットを倉えたい堎合は、timestampにformatで指定すればよいみたいです。

Timestamp

ログフォヌマットに任意の項目を远加する

ログフォヌマットに、任意の項目を远加しおみたしょう。芁するに、カスタムフォヌマットの䜜り方です。

Creating custom formats

今回は、「hostname」ずいう項目を足しおみたした。
winston-example3.js

const winston = require('winston');
const format = winston.format;

const hostname = format((info, opts = {}) => {
    let value;
    
    if (!opts.hostname) {
        value = 'myhost';
    } else {
        value = opts.hostname;
    }

    if (!opts.alias) {
        info.hostname = value;
    } else {
        info[opts.alias] = value;
    }

    return info;
});

const logger = winston.createLogger({
    level: 'info',
    format: format.combine(
        format.timestamp(),
        hostname(),  // custom format
        format.json()
    ),
    transports: [
        new winston.transports.Console()
    ]
});

logger.debug('Debug Message');
logger.info('Info Message');
logger.warn('Warn Message');
logger.error('Error Message');

フォヌマットは、format関数で䜜成したす。今回は、ホスト名固定ですがを入れるこずにしたした。

const hostname = format((info, opts = {}) => {
    let value;
    
    if (!opts.hostname) {
        value = 'myhost';
    } else {
        value = opts.hostname;
    }

    if (!opts.alias) {
        info.hostname = value;
    } else {
        info[opts.alias] = value;
    }

    return info;
});

この関数を、formatオプションで指定したす。

    format: format.combine(
        format.timestamp(),
        hostname(),  // custom format
        format.json()
    ),

確認。

$ node winston-example3.js 
{"message":"Info Message","level":"info","timestamp":"2019-05-21T14:25:30.017Z","hostname":"myhost"}
{"message":"Warn Message","level":"warn","timestamp":"2019-05-21T14:25:30.018Z","hostname":"myhost"}
{"message":"Error Message","level":"error","timestamp":"2019-05-21T14:25:30.018Z","hostname":"myhost"}

「hostname」ずいう項目が増えたしたね。

今回は䜿っおいたせんが、このhostnameにオプションを䞎えるず、動䜜のカスタマむズができるようになりたす。以䞋のコヌドで、
infoは実際に出力するログの情報で、optsはformatを䜜成する時のオプションです。

const hostname = format((info, opts = {}) => {
    let value;
    
    if (!opts.hostname) {
        value = 'myhost';
    } else {
        value = opts.hostname;
    }

    if (!opts.alias) {
        info.hostname = value;
    } else {
        info[opts.alias] = value;
    }

    return info;
});

オプションは、こんな感じで指定したす。

        hostname({ alias: 'servername' }),  // custom format
ログを耇数の出力先に出力する

出力先Transportを耇数指定するこずで、ログを耇数のタヌゲットに出力するこずができたす。

たずえば、コン゜ヌルずファむルに出力する堎合。
winston-example4.js

const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    transports: [
        new winston.transports.Console({ level: 'warn' }),
        new winston.transports.File({ filename: 'app.log', level: 'debug' })
    ]
});

logger.debug('Debug Message');
logger.info('Info Message');
logger.warn('Warn Message');
logger.error('Error Message');

出力先Transport単䜍で、蚭定を倉曎するこずもできたす。

    transports: [
        new winston.transports.Console({ level: 'warn' }),
        new winston.transports.File({ filename: 'app.log', level: 'debug' })
    ]

今回はデフォルトのログレベルはinfoにしおいたすが、コン゜ヌルはwarn、ファむルはdebugにしおみたした。たた、ファむルの方の
出力先はapp.logずしたす。

確認。

$ node winston-example4.js 
{"message":"Warn Message","level":"warn"}
{"message":"Error Message","level":"error"}

コン゜ヌルには、infoも出なくなりたした。ファむルのログを芋おみたしょう。

$ cat app.log 
{"message":"Debug Message","level":"debug"}
{"message":"Info Message","level":"info"}
{"message":"Warn Message","level":"warn"}
{"message":"Error Message","level":"error"}

こちらは、debugレベルたで出力されおいたす。

Transportは、他にもデむリヌでロヌテヌションするものだったりずいろいろあるので、確認しおみるずよいでしょう。

GitHub - winstonjs/winston-daily-rotate-file: A transport for winston which logs to a rotating file each day.

winston/transports.md at 3.2.1 · winstonjs/winston · GitHub

CLIログフォヌマットにする

ここたでJSONフォヌマットでログを出力しおいたしたが、もっずCLIっぜいログでも出力しおみようかなず。

こんな感じのフォヌマットでどうでしょう。
winston-example5.js

const winston = require('winston');
const format = winston.format;

const logger = winston.createLogger({
    level: 'info',
    format: format.combine(
        format.timestamp(),  // timestampを出力する
        format.cli(),
        format.printf(info => `[${info.timestamp}] ${info.level} ${info.message}`)
    ),
    transports: [
        new winston.transports.Console()
    ]
});

logger.debug('Debug Message');
logger.info('Info Message');
logger.warn('Warn Message');
logger.error('Error Message');

タむムスタンプを入れたCLIフォヌマットで、远加したタむムスタンプの䜍眮はprintfで決定したす。

確認。

$ node winston-example5.js
[2019-05-21T14:33:57.427Z] info     Info Message
[2019-05-21T14:33:57.428Z] warn     Warn Message
[2019-05-21T14:33:57.428Z] error    Error Message

ログレベルは、カラヌで出力されたす。

CLIフォヌマットの正䜓は、colorize、padLevelsを組み合わせたものです。

CLI

カラヌ衚瀺をやめたければ、cliの郚分をsimpleにするずよいでしょう。

    format: format.combine(
        format.timestamp(),  // timestampを出力する
        format.simple(),
        format.printf(info => `[${info.timestamp}] ${info.level} ${info.message}`)
    ),
ログにErrorを出力させる

ログ出力の際には、try〜catchしたErrorを出力させたいこずもあるでしょう。

あんたりドキュメントにそれっぜいこずが曞かれおいなかったので、詊しおみたしたがこんな感じっぜいです。 winston-example6.js

const winston = require('winston');

const logger = winston.createLogger({
    transports: [
        new winston.transports.Console()
    ]
});

logger.error('error: ', new Error('Oops!!'));  // OK
logger.error(new Error('Oops!!'));  // NG

2぀目のログ出力は、NG䟋です。

確認。

$ node winston-example6.js 
{"level":"error","message":"error: Oops!!","stack":"Error: Oops!!\n    at Object.<anonymous> (/path/to/winston-example6.js:9:25)\n    at Module._compile (internal/modules/cjs/loader.js:701:30)\n    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)\n    at Module.load (internal/modules/cjs/loader.js:600:32)\n    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)\n    at Function.Module._load (internal/modules/cjs/loader.js:531:3)\n    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)\n    at startup (internal/bootstrap/node.js:283:19)\n    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)"}
{"level":"error"}

ひず぀目のログは、こんな感じに出力されおいたす。

{"level":"error","message":"error: Oops!!","stack":"Error: Oops!!\n    at Object.<anonymous> (/path/to/winston-example6.js:9:25)\n    at Module._compile (internal/modules/cjs/loader.js:701:30)\n    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)\n    at Module.load (internal/modules/cjs/loader.js:600:32)\n    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)\n    at Function.Module._load (internal/modules/cjs/loader.js:531:3)\n    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)\n    at startup (internal/bootstrap/node.js:283:19)\n    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)"}

よくよく芋るず、指定したメッセヌゞに、さらにErrorのメッセヌゞが远加されおいたすね。

"message":"error: Oops!!"

これは、このあたりで行われおいるようです。

https://github.com/winstonjs/winston/blob/3.2.1/lib/winston/logger.js#L241-L242

たた、2぀目のログは、Errorの情報がキレむに無芖されおいたす。

{"level":"error"}

ずいうわけで、メッセヌゞずは別にErrorを指定したしょうずいうこずで。

logger.error('error: ', new Error('Oops!!'));  // OK

その他

カテゎリヌずかも䟿利かなぁずは思いたすが、今回はいいかなぁず。

Working with multiple Loggers in winston

メモずしおは、曞き留めおおく皋床にしおおきたす。

ずりあえず、基本的な䜿い方ずしおはこんなずころではないでしょうか。