node.js & expressで簡単なAPIを作る際、ログ出力の設定はlog4jsで管理していると思います。
ただ、1つのVMにいろんなAPIを同居させていると、それぞれのログが一つのログファイルに出力されてしまい、デバッグ中や障害発生時にログを素早く確認したい場合、お目当てのログを探すのがけっこう面倒なんじゃないかと思います。
そこで、ログファイルのファイル名を、自動的にそれぞれのAPIのURLルート名にして、URLのルートパスごとにログファイルを別々に生成する方法を考えてみました。
(log4jsのconfig内appendersの、"type": "multiFile"の使い方を説明している日本語サイトをあまり見かけず苦労したため、書き残しておきます。)
##環境
もろもろのバージョンは以下の通りとします。
ubuntu:18.04
node.js : 10.24.1
npm:6.14.12
express: 4.17.2
log4js : 6.4.1
そして、今回のデモ用アプリ(logtest_apps)のツリー構造は以下のようになっているとします。
図中のlogsディレクトリに、logtest.jsのログを溜め込んでいきたいとします。
さらに、logtest.jsに紐づくログファイル名を「logtest.log」として、他のAPI(この図ではotherAPI1.js)のログと分離したい、というのがこの記事の目的です。
logtest_apps
|-- README.md
|-- app.js
|-- config
| |-- development.json
| |-- log4js.config.json
| `-- production.json
|
|-- logs
| |-- logtest.log ←こういうようにlogファイルを生成したい.
| `-- otherAPI1.log ←他のURLのログは、logtest同様URLルート名でログファイルを作りたい.
|
|-- node_modules
|-- package-lock.json
|-- package.json
`-- routes
|-- logtest
| `-- logtest.js ←logをとっているJSファイル.
`-- otherAPI1
`-- otherAPI1.js
##log4jsの設定をしていく
log4jsの設定(log4js.config.json)は、以下のようにしてみました。
{
"appenders": {
"everything": {
"type": "multiFile",
"base": "logs/",
"property": "route_name",
"extension": ".log",
"maxLogSize": 2000000,
"backups": 3,
"compress": true}
},
"categories": {
"default": {
"appenders":["everything"],
"level": "info"
}
}
}
大事な点は、
まず"type"をmultiFileとする点です。
また、"base": **"logs/"とすることで、logsディレクトリにログを吐くように設定します。
そして、"property": "route_name"**ですが、このpropertyに設定した変数(今回はroute_name)の値が、ログファイル名となるようです("route_name"は文字列ではなく、変数名であることに注意。route_nameに、例えば'テスト'という文字列をlogtest.js内で代入したならば、ログファイル名も"テスト.log"となります。後述のlogtest.js内部に、URLルート名をroute_nameに代入している記述があります。)
**"extension": ".log"**は、そのログファイルの拡張子を指定します。
"maxLogSize": 2000000は、そのログファイルの最大サイズを指定します。
そのサイズに達すると、ログファイル内の古い行から削除されていきます。
appendersを設定できたら、categoriesの任意のカテゴリ(今回は設定必須であるdefault)に、先ほど設定したappendersであるeverythingを指定します。
**"level": "info"**は、とりあえず何でも良いです。
そして、以下のようにapp.jsと、その下の階層にあるlogtest.jsを用意し、ログを吐き出すテストをしてみます。
//今回のデモ用に、必要最低限の記述のみにしています.
const config = require('config');
const log4js = require ('log4js');
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.use('/logtest', require('./routes/logtest/logtest'));
app.listen(port, () => {
console.log(`listening at http://127.0.0.1:${port}`);
});
const config = require('config');
const log4js = require ('log4js');
const express = require("express");
const router = express.Router();
log4js.configure('config/log4js.config.json');
const route_logger = log4js.getLogger ("route_log");
//loggerのインスタンスを生成.route_loggerというインスタンス名は何でもいいです.
router.use(express.json());
router.get('/', (req, res) => {
try {
let file_name = req.originalUrl.replace(/\?.*$/,"").replace("/","");
//↑URLルート名を、正規表現を使って動的に取得. reqを参照するため、router.get()内部に書く.
route_logger.addContext('route_name', file_name);
//↑route_loggerインスタンスのroute_name要素に、先ほど動的に取得したfile_nameを代入.
route_logger.info("テスト:route_loggerでinfoレベルのログを出力しました。");
//↑infoレベルのログをログファイルに出力する例.
route_logger.error("テスト:route_loggerでerrorレベルのログを出力しました。");
//↑errorレベルのログをログファイルに出力する例.
res.send('ログファイルへの出力完了');
} catch(e) {route_logger.error(e);} ;
});
module.exports = router;
そして、npm start でnode.jsを起動させ,
http://127.0.0.1:3000/logtest でlogtestを呼び出してみると、、
先ほどのツリー図のlogsディレクトリに、
logtest_apps
|
:
|
|-- logs
| `-- logtest.log ←コレ
:
というように、ログファイルが、URLのルート名と同じ名前(logtest)で生成されました!
logtest.logの中身はこんな感じです。
[2022-02-10T16:52:31.682] [INFO] route - テスト:route_loggerでinfoレベルのログを出力しました。
[2022-02-10T16:52:31.688] [ERROR] route - テスト:route_loggerでerrorレベルのログを出力しました。
logtest.jsで生成したroute_loggerというインスタンスのメソッドをinfoやerrorなどとしているため、
それぞれのエラーレベルのログを出力することができました。
これで、URLが 〜"../logtest" のログを確認したいときは、このログファイルだけを見れば、
logtest.jsのログだけがひとまとまりになっているため、目的のログをすぐに探せて便利です!
運用上、さらにaccessログのファイル、errorログのファイルなど、ログの種別を切り分けたい場合は、今回は割愛しますがlog4js.config.jsonのappendersとcategoriesを適宜増やして、組合せを工夫すれば大丈夫かと思います。)
参考にした投稿:https://github.com/log4js-node/log4js-node/blob/master/docs/multiFile.md