log4js-node の使い方【v5.2.2】

# 1. はじめに

`log4js-node`は、log4js を javascript 用に移植した log4js を node 用に書き直したものらしい。
Java の log4j とは大きく違うとのこと。
ロガーの使い方がいまいち分かっていなかったので、調査ついでにまとめた。

- [log4js-node/log4js-node: A port of log4js to node.js](https://github.com/log4js-node/log4js-node)
- Official-Docs: [log4js-node by log4js-node](https://log4js-node.github.io/log4js-node/)
- GitHub-Docs: [log4js-node by log4js-node](https://log4js-node.github.io/log4js-node/)

## テスト環境
- Windows10, MINGW64, VSCode
- node: 10.14.2
- babel-node: 7.6.1
- log4js: 5.2.2
- ES6 形式で書きたかったので `babel-node` を使用
- ログファイルは VSCode + Log File Highlighter で表示 (ちゃんと設定してないです…)

```console:console
$ npm install log4js
```

> 参考：
>
> - [log4js-node - Installation](https://github.com/log4js-node/log4js-node#installation)




# 2. 基本的な使い方

## ログの出力(logging)
```javascript:test.js
import log4js from 'log4js'

const logger = log4js.getLogger()
logger.level = 'all'

logger.trace('Some trace messages')
logger.debug('Some debug messages')
logger.info('Some info messages')
logger.warn('Some warn messages')
logger.error('Some error messages')
logger.fatal('Some fatal messages')

log4js.shutdown((err) => {
  if (err) throw err
  process.exit(0)
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/fb2d44dd-ad2b-6f28-5f93-4fbb1e177a9d.png)

- `logger.level` は ALL (最小) < **TRACE < DEBUG < INFO < WARN < ERROR < FATAL** < MARK < OFF (最大)
- default は `OFF` (console へ出力しない)
- level は独自のものを設定することも可能だが取り扱わない
- `log4js.shutdown(cb(err))` はログを確実に保存してから終了する際に使用する (flush 処理)
- 定義されている level は以下を参照

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/61ab65d1-988c-0649-da15-d5bb66d3a312.png)

> 参考：
>
> - [log4js-node - API](https://log4js-node.github.io/log4js-node/api.html)
> - [log4js-node/levels.js at master · log4js-node/log4js-node](https://github.com/log4js-node/log4js-node/blob/master/lib/levels.js#L90)



## ログのカテゴリ (category)
```javascript:test.js
const logger = log4js.getLogger()
logger.level = 'all'
logger.info('Some info messages', 'append', 'more')

const cheseLogger = log4js.getLogger('cheese')
cheseLogger.level = 'all'
cheseLogger.info('Cheese is Comte.')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/7ec20a0f-51ba-79fc-0b59-5385d8449e17.png)

- ログを **category** ごとにグループ化して出力することができる
- category は `log4js.getLogger([category])`で指定する
- default は `default` というグループ
- category ごとに出力する level を指定できる
- 前項で触れなかったが、複数の引数を使用するとスペース区切りで出力される

> 参考：
>
> - [log4js-node - API](https://log4js-node.github.io/log4js-node/api.html)





# 3. 高度な使い方(Configure)

## コンソールとファイルに出力 (stdout / file)
```javascript:test.js
log4js.configure({
  appenders: {
    out: { type: 'stdout' },
    app: { type: 'file', filename: 'application.log' }
  },
  categories: {
    default: { appenders: ['out', 'app'], level: 'debug' }
  }
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/2c503e12-b3bd-3048-3a73-2e70afa8b864.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/c94f4224-df7f-a639-95ef-91f41894df16.png)

- **appender** は渡されたログを出力する機械と考える
- `default` の category を、 stdout に出力する `out` と file に出力する `app` という appender を定義した
- appender の `name(key)` は自由に指定可能
- categories で category ごとに使用する appender と level などを定義できる
- `logger.level` は この level と同じ意味となる
- ファイルに出力されるログは末尾に追記される

> 参考：
>
> - [Log4js-node - Appenders](https://log4js-node.github.io/log4js-node/appenders.html)



## ログのローリング (file / fileSync)
```javascript:test.js
log4js.configure({
  appenders: {
    app: { type: 'file', filename: 'application.log', maxLogSize: 100, backups: 1 }
  },
  categories: {
    default: { appenders: ['app'], level: 'all' }
  }
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/483ed7c0-9915-c85a-d0d0-7b98029bca16.png)

- file 系の appender は書き込むファイルをローテートすることが可能
- 上記は `application.log.2` が保持上限を超えて削除されている
- `type: fileSync` で同期的に書き込むことも可能 (test などの用途)
- options
  - type: `file` / `fileSync`
  - filename: string - ログファイルの名称 (xxx.ext)
  - maxLogSize: integer - 1ファイルの最大サイズ (byte)、default は上限なし
  - backups: integer - 保持するファイル数、default は `5`
  - compress: boolean - `true` で ローリングしたファイルを圧縮する(.gz)
  - keepFileExt: boolean - `true` で 拡張子を保持する (xxx.ext.1 を xxx.1.ext にする)
  - encoding: string - default は `utf-8`

> 参考：
>
> - [Log4js-node - File Appender](https://log4js-node.github.io/log4js-node/file.html)
> - [log4js-node - Synchronous File Appender](https://log4js-node.github.io/log4js-node/fileSync.html)



## 日付ごとのローリング(dateFile)
```javascript:test.js
log4js.configure({
  appenders: {
    app: { type: 'dateFile', filename: 'application.log', pattern: '.yyyyMMdd-hhmmss' }
  },
  categories: {
    default: { appenders: ['app'], level: 'all' }
  }
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/6e48c432-2b6d-861f-9d54-72be003a70e4.png)

- `pattern` で指定した日付の粒度でローテートすることが可能
- 上記は1秒ごとに切り替える設定で二回実行した結果
- file 系と違って保持数は指定できない？(要検証)
- 保持日数は `daysToKepp` で指定できる
- option
  - type: `dateFile`
  - filename: string - ログファイルの名称 (xxx.ext)
  - pattern: string - ローテートするタイミング、default は `.YYYY-MM-dd`、参照：[date-format](https://github.com/nomiddlename/date-format#formatting-dates-as-strings)
  - daysToKeep: integer - 保持する日数、 default は上限なし
  - alwaysIncludePattern: boolean - `true` で現在のログファイル名にも日付を付与する
  - compress: boolean - `true` で ローリングしたファイルを圧縮する(.gz)
  - keepFileExt: boolean - `true` で 拡張子を保持する (xxx.ext.date を xxx.date.ext にする)
  - encoding: string - default は `utf-8`

> 参考：
>
> - [Log4js-node - Date Rolling File Appender](https://log4js-node.github.io/log4js-node/dateFile.html)
> - [nomiddlename/date-format](https://github.com/nomiddlename/date-format#formatting-dates-as-strings)



## appendars のレベルフィルター (logLevelFilter)
```javascript:test.js
log4js.configure({
  appenders: {
    out: { type: 'stdout' },
    app: { type: 'file', filename: 'application.log' },
    wrapErr: { type: 'logLevelFilter', appender: 'app', level: 'warn' }
  },
  categories: {
    default: { appenders: ['out', 'wrapErr'], level: 'all' }
  }
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/20b62ae3-dfc7-88d6-d0c6-8e0bd35a0489.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/bdd42b7d-f81e-bcf0-60e3-0bf3b22926e9.png)

- `logLevelFilter` を用いると appender ごとに出力する level を指定可能
- 出力する appender にラップするように使用する
- これによりエラーログだけを別途で処理することも可能
- ※ appender は上から処理されるので logLevelFilter は最後に配置する必要がある
- options
  - type: `logLevelFilter`
  - level: string - 出力する最小の level
  - maxLevel: string - 出力する最大の level 、default は `FATAL`

> 参考：
>
> - [Log4js-node - Log Level Filter](https://log4js-node.github.io/log4js-node/logLevelFilter.html)


## その他

- 他にも SMTP や Slack に出力するものなどがある
- [log4js-node by log4js-node](https://log4js-node.github.io/log4js-node/appenders.html)






# 4. 出力ログのレイアウト

## 標準レイアウト(Built-in Layout)
```javascript:test.js
log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'basic' } }
  },
  categories: {
    default: { appenders: ['out'], level: 'all' }
  }
})
```

- 各種 appender に layout を指定することができる
- 必須ではないので必要に応じて…
- **※ file 系の appenders に `colores` を指定しないこと！** (色付けは制御文字を使用しているため)

type: `basic`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/145091ef-4a61-4abc-6c72-1ce1c15708b4.png)

type: `coloured` / `colores`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/de518385-ae86-dabb-96ce-f3f21fc0af3c.png)

type: `messagePassThrough`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/05369b93-a65b-2c5e-4452-18c5d836c809.png)

> 参考：
>
> - [log4js-node - Layout](https://log4js-node.github.io/log4js-node/layouts.html)



## 出力パターン (Pattern format)
```javascript:test.js
log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'pattern', pattern: '%d %[%5p%] %c %m' } }
  },
  categories: {
    default: { appenders: ['out'], level: 'all' }
  }
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/36e2c676-ccc9-4e06-c5b3-7eac87f4a558.png)

- 出力する文字列のパターンを独自に設定できる
- field は `%[padding].[truncation][field]{[format]}` で定義されている
  - padding: integer - 空白埋め文字数、右詰め
  - truncation: integer - 最大文字数、切り捨て
  - field: string - フィールド名
  - format: string(option) - フィールドのオプション (任意)
- 上記の場合、「日付 level(5文字埋め)を色付きで カテゴリ 本文」となる
- ※ file 系の appenders に `%[` `%]` を指定しないこと！ (色付けは制御文字を使用しているため)
- pattern field (サンプル)
  - %r: ローカル時刻 (02:19:11)
  - %p: ログ level (INFO)
  - %c: ログ category (default)
  - %h: ホスト名 (PCの名前等)
  - %m: メッセージ
  - %d: 日時、{ISO8601 | `yyyy-MM-ddThh:mm:ss.SSS`} (2019-10-23T02:13:53.028)、参照：[date-format](https://github.com/nomiddlename/date-format#formatting-dates-as-strings)
  - %%: %のエスケープ
  - %n: 改行
  - %z: プロセスID、process.pid (28076)
  - %[: 色付け開始
  - %]: 色付け終了
  - `enableCallStack: true` 時の field (後述)
     - %f: ログ出力元のファイル名、{depth | `0`} (C:\xxx\test.js)
     - %l: ログ出力元の行番号 (13)
     - %o: ログ出力元の列位置 (8)
     - %s: スタックトレース (    at Object.info (C:\xxx/test.js:13:8) \n ...)
- トークンは取り扱わない

> 参考：
>
> - [log4js-node - Pattern Format](https://log4js-node.github.io/log4js-node/layouts.html#pattern-format)



## enableCallStack
```javascript:test.js
log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'pattern', pattern: '%[[%d] %p %c -%] %m%n%f %l %o%n%s' } }
  },
  categories: {
    default: { appenders: ['out'], level: 'all', enableCallStack: true }
  }
})

const logger = log4js.getLogger()
logger.info('Some info messages')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/cfab039b-e113-deda-85b6-e785f78bdfb2.png)

- category で `enableCallStack: true` を設定すると出力箇所のスタックトレースが取得できる
- `%s` の前に `%n` で改行を入れないと出力が崩れるので注意
- `enableCallStack: false` の際、だった各種 field は出力されないが `%n` は出力される点も注意
- （スルーできる field があれば便利だったかも）
- error 出力などの場面で使用するのが良い？

> 参考：
>
> - [log4js-node - API](https://log4js-node.github.io/log4js-node/api.html)
> - [log4js-node - Pattern Format](https://log4js-node.github.io/log4js-node/layouts.html#pattern-format)



## Tokens
- key - value の値を埋め込める仕組み
- token はハードコートせざるを得ないので、どの key を使用しているか定義しておく必要がある
- これを使うなら message に変数を結合させるのがベストのような？
- 発行ユーザーなどの用途なら悪くはなさそう
- ここでは取り扱わない

> 参考：
>
> - [log4js-node - Tokens](https://log4js-node.github.io/log4js-node/layouts.html#tokens)



## カスタムレイアウト (Custom Layout)
```javascript:test.js
log4js.addLayout('json', function(config) {
  return function(logEvent) {
    return JSON.stringify(logEvent, null, 2) + config.separator
  }
})

log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'json', separator: ',' } }
  },
  categories: {
    default: { appenders: ['out'], level: 'all', enableCallStack: true }
  }
})

const logger = log4js.getLogger()
logger.info('Some info messages')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/f2953bc4-f63e-7489-68b7-c2c3c4774708.png)

- 独自のレイアウトを定義することも可能
- appender で指定した設定が config から取得できる
- `functionName` 以下は `enableCallStack: true` が必要
- callback の引数は `logEvent` の Object なので、これを加工して return に返す

> 参考：
>
> - [log4js-node - Adding your own layouts](https://log4js-node.github.io/log4js-node/layouts.html#adding-your-own-layouts)



# 5. サンプル
```javascript:test.js
import os from 'os'
import log4js from 'log4js'
import dateFormat from 'date-format'
import chalk from 'chalk'

const LOG_LEVEL = 'ALL' // DEBUG: TRACE or DEBUG, PRODUCTION: INFO or OFF

const levelColors = {
  TRACE: { meta: 'grey', body: 'grey', trace: null },
  DEBUG: { meta: 'green', body: 'grey', trace: null },
  INFO: { meta: 'cyan', body: 'white', trace: null },
  WARN: { meta: 'yellow', body: 'yellow', trace: null },
  ERROR: { meta: 'red', body: 'red', trace: 'white' },
  FATAL: { meta: 'magenta', body: 'magenta', trace: 'white' }
}

const coloring = function(color, text) {
  if (color) {
    return chalk[color](text)
  }
  return text
}

log4js.addLayout('origin', function({ addColor }) {
  return function(e) {
    const date = new Date(e.startTime)
    const level = e.level.levelStr.toUpperCase() // 大文字
    const hasCallStack = e.hasOwnProperty('callStack') // callStack を持っているか

    const dateStr = dateFormat('yyyy-MM-dd hh:mm:ss.SSS', date)
    const message = e.data.join(' ') // データはスペース区切り
    const levelStr = level.padEnd(5).slice(0, 5) // 5文字
    const color = levelColors[level]

    // メタ情報
    const meta = `${levelStr} ${dateStr} [${e.categoryName}]`
    const prefix = addColor ? coloring(color.meta, meta) : meta

    // ログ本体
    const body = addColor ? coloring(color.body, message) : message

    // スタックトレース
    let suffix = ''
    if (hasCallStack && color.trace) {
      const callStack = e.callStack
      suffix += os.EOL
      suffix += addColor ? coloring(color.trace, callStack) : callStack
    }

    return `${prefix} ${body}${suffix}`
  }
})

log4js.configure({
  appenders: {
    out: { type: 'stdout', layout: { type: 'origin', addColor: true } },
    logFile: { type: 'file', filename: 'logs/application.log', layout: { type: 'origin', addColor: false } },
    errFile: { type: 'file', filename: 'logs/error.log', layout: { type: 'origin', addColor: false } },
    log: { type: 'logLevelFilter', appender: 'logFile', level: 'info' },
    err: { type: 'logLevelFilter', appender: 'errFile', level: 'warn' },
  },
  categories: {
    default: { appenders: ['out', 'log', 'err'], level: LOG_LEVEL, enableCallStack: true }
  }
})

const logger = log4js.getLogger()
logger.trace('Some trace messages')
logger.debug('Some debug messages')
logger.info('Some info messages')
logger.warn('Some warn messages')
logger.error('Some error messages')
logger.fatal('Some fatal messages')

log4js.shutdown(() => {})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/ca41846e-7f37-c65b-c7a3-eac9c1fa0564.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105139/5528ac1b-9b22-f548-a26c-834b86ddedcf.png)

- console のカラーリングと、2つのファイルに書き出す形式
  - console への出力は `LOG_LEVEL` で制御する
- ファイルは `INFO` 以上と `WARN` 以上の二種類を用意 (error を把握しやすくする)
  - これにローリングを加えれば完成となる (見やすさのため省略)
  - terminal が 256色 をサポートしていない場合、通常色出力となる
- 独自の level を定義することは可能だが、カラーリングはサポートしないらしい？
  - [Allow overriding levels/colors · Issue #821 · log4js-node/log4js-node](https://github.com/log4js-node/log4js-node/issues/821)

> 参考：
>
> - color package
>   - [chalk/chalk: 🖍 Terminal string styling done right](https://github.com/chalk/chalk)
>   - [Marak/colors.js: get colors in your node.js console](https://github.com/Marak/colors.js)
> - [log4js-node - Adding your own layouts](https://log4js-node.github.io/log4js-node/layouts.html#adding-your-own-layouts)
> - [ログ設計指針 - Qiita](https://qiita.com/nanasess/items/350e59b29cceb2f122b3)

以上