TypescriptでもLog4jみたいなことをしたい!
ありますよ、 Log4js
が。
この記事で書くこと
- Log4jsってなにそれおいしいの?
- configってなにそれおいしいの?
- 巷にあるLog4jsの設定は当てにならない!?
- Typescriptに組み込むならば……
Log4jsとはなんぞ
Java使いであれば超定番のロガーライブラリLog4JのJavascript版です。
ログ処理を設定ファイルであれこれ制御できるので便利。
Typescript(Javascriptでもですが)で設定ファイルを記載するとなると便利になるのがconfigというモジュールです。
configとはなんぞ
NODE_ENV環境変数に応じて読み込む設定ファイルを変更してくれる、かつ、簡単にソースから設定を取得できるモジュール。
今回お話する内容でのディレクトリ構成
なお、Node.jsアプリの構成です。
- node-root/ (
npm init
したところ)- bin/
- app/
- config/ -> ../../config
- hoge.js
- hoge.js.map
- test/
- hoge.test.js
- hoge.test.js.map
- app/
- src/
- app/
- hoge.ts
- test/
- hoge.test.ts
- config/
- development.json
- bin/
※configはnode-rootの直下のディレクトリにないとtscコンパイルがうまく行かなかったです。
かつ、トランスパイル後のファイルからも読める必要があるので、node-root/bin/app配下にシンボリックリンクでconfigを用意しています。
なお、NODE_ENVはdevelopmentに設定しています。
Log4jsとconfigのバージョン
library | version |
---|---|
Log4js | 2.3.3 |
config | 1.26.2 |
まずはconfigを使えるようにする
まずはnpmでインストール。
npm install config
npm install @types/config
んで、config/${NODE_ENV}.jsonを用意。
一旦はコレでconfigの下地は完成。あとはコレを使うLog4jsを導入。
いざLog4js……?
こちらもまずはインストール。
npm install log4js
npm install @types/log4js
各モジュールをインストールした後は、
- configの設定を記述
- ソースからconfigを読み込むコードを実装
- configから取得したJSONを元にLog4jsを初期化
- ログを吐きまくる
という流れです。
では早速、configを書き始めます。 ワタシはここでハマった
先人たちのナレッジを参考に設定するわけですねまずは。
よくある設定ですと、こんな感じになります。
{
"log4js": {
"appenders": [
{
"category": "system",
"type": "dateFile",
"filename": "/var/log/app/system.log"
},
{
"category": "access",
"type": "dateFile",
"filename": "/var/logs/app/access.log"
},
{
"category": "error",
"type": "dateFile",
"filename": "/var/logs/app/error.log"
}
}
}
そして、実装の中でこう使うんですね。
import * as Config from "config";
import * as Path from "path";
var configure = Config.util.loadFileConfigs(Path.join(__dirname,"config")).log4js;
……
……
……あれ、エラーする。
「typeがObjectであるプロパティappendersが見つからない」
appendersプロパティは設定しているのに……
ん?
「typeがObjectであるプロパティ」……
Migrating from log4js versions older than 2.x
The main changes are a need for you to name your appenders, and you also have to define the default category.
Log4js v1.Xではappendersは配列だったけれど、Log4js v2.Xではappendersはオブジェクト
という大きな仕様変更があったみたいです。
ドキュメントを整理をすると、
- log4js v2ではappenndersプロパティ、categoriesプロパティが必須のJSONで設定を行う。
- appendersプロパティはオブジェクトとして、
設定名:{出力設定}
というプロパティの列挙で各出力設定を用意する。 - categoriesプロパティはオブジェクトとして、
カテゴリ名:{適用設定,出力ログレベル}
というプロパティの列挙でログ定義をする。 - categories::defaultプロパティは必須。
コレを上の設定ファイルに当てはめると以下のようになりました。
{
"log4js": {
"appenders": {
"access": {
"type": "dateFile",
"filename": "/var/log/app/access.log"
},
"error": {
"type": "dateFile",
"filename": "/var/logs/app/error.log"
},
"system": {
"type": "dateFile",
"filename": "/var/log/app/system.log"
},
"console": {
"type": "console"
},
"stdout": {
"type": "stdout"
}
},
"categories": {
"default": {
"appenders": [
"access"
,"console"
,"stdout"
]
,"level": "INFO"
},
"access": {
"appenders": [
"access"
,"console"
,"stdout"
]
,"level": "INFO"
},
"system": {
"appenders": [
"system"
,"console"
,"stdout"
]
,"level": "ALL"
},
"error": {
"appenders": [
"error"
,"console"
,"stdout"
]
,"level": "WARN"
}
}
}
}
この設定では、出力設定としては
- access
- error
- system
- console
- stdout
の5つを用意していて、ログ設定としては
- access
- 出力設定→access,console,stdout
- error
- 出力設定→error,console,stdout
- system
- 出力設定→system,console,stdout
の3つを用意しています。この時、アプリケーションから使えるログはaccess,error,systemです。
categoriesプロパティで設定する項目がログ項目となるわけです。
この設定をして、
import * as Config from "config";
import * as Path from "path";
var configure = Config.util.loadFileConfigs(Path.join(__dirname,"config")).log4js;
が機能するようになりました。
いざ本当にLog4js
Log4jsを利用する流れは大きく2段階です。
- 初期化
- ロギングしまくる
初期化
import * as Config from "config";
import * as Path from "path";
import * as Log4js from "log4js";
var configure = Config.util.loadFileConfigs(Path.join(__dirname,"config")).log4js;
Log4js.configure(<Log4js.IConfig>configure);
@types/configで定義されているutil.loadFileCOnfigs(path: string).<<定義キー>>
で必要なJSONを取得します。
今回、log4jsをキーとしているため、util.loadFileCOnfigs(path: string).log4js
としてます。
そして、取得したJSONオブジェクトを@types/log4jsで定義されているconfigure(config: IConfig): void
を実行し設定を行います。
Log4js.configure(<Log4js.IConfig>configure);
で強制的な型変換を行っているのは、util.loadFileCOnfigs(path: string).<<定義キー>>
で取得できるオブジェクトはTypescript上any型だからです。
普通に行うとconfigure(config: IConfig): void
ではなくconfigure(path: string): void
に向かってしまい、正しく動いてくれないのです。
ロギングしまくる
初期化を終えたらあとは気が向くままにロギングするだけです。
import * as Config from "config";
import * as Path from "path";
import * as Log4js from "log4js";
let message = "hogehoge"
let logger = Log4js.getLogger("access");
logger.info(message);
getLogger(logname: string): Logger
メソッドで対応するロガーインスタンスを取得します。logname
は設定ファイルのcategoriesで用意した名称を指定します。
取得したLoggerインスタンスに対して、インスタンス.<<ログレベル>>(message: string)
でロギングします。
<<ログレベル>>はLog4jsのv1と変わらない感じなので、ここは他の紹介ページを参考にして問題ないです。
上記コードでは、ログ名称accessのLoggerインスタンスを取得して、infoレベルのメッセージをロギングしています。
最終的な実装なこんな感じ
Typescriptに組み込んだ時、このような実装になりました。
import * as Path from "path";
import * as Config from "config";
import * as Log4js from "log4js";
export class Logger {
public static initialize() {
let configure = Config.util.loadFileConfigs(Path.join(__dirname,"config")).log4js;
Log4js.configure(configure as Log4js.Configuration);
}
public static LogAccessInfo(message: string): void {
let logger = Log4js.getLogger("access");
logger.info(message);
}
public static LogAccessWarning(message: string): void {
let logger = Log4js.getLogger("access");
logger.warn(message);
}
public static LogAccessError(message: string): void {
let logger = Log4js.getLogger("access");
logger.error(message);
}
public static LogSystemInfo(message: string): void {
let logger = Log4js.getLogger("system");
logger.info(message);
}
public static LogSystemWarning(message: string): void {
let logger = Log4js.getLogger("system");
logger.warn(message);
}
public static LogSystemError(message: string): void {
let logger = Log4js.getLogger("system");
logger.error(message);
}
public static LogError(message: string): void {
let logger = Log4js.getLogger("error");
logger.error(message);
}
}
import {Logger} from "./logger";
public main() {
Logger.initialize();
execute();
}
private execute() {
Logger.LogAccessInfo("accessログのINFOメッセージ");
Logger.LogSystemError("systemログのERRORメッセージ");
}
main()
エントリポイントとなる箇所でLogger.initialize(): void
を実行して初期化を行い、必要に応じてLogger.LogXXXX
を実行する実装です。