feat(logging): switch to log4js with file output and env-driven level

- Replace console logger with log4js dateFile under LOG_DIR (default logs).

- No console appender; daily rotation with 7-day retention (numBackups).

- Add LOG_LEVEL and LOG_DIR to env and .env.example; document in env.ts.

Made-with: Cursor
This commit is contained in:
mineseo-kim 2026-04-09 09:34:15 +09:00
parent f5f597d73d
commit f2ffdb64a8
5 changed files with 132 additions and 9 deletions

View File

@ -4,4 +4,10 @@ DISCORD_CLIENT_ID=your_client_id_here
# Database Configuration (PostgreSQL)
# User/pass from docker-compose.yml
DATABASE_URL="postgresql://kord:password@localhost:5432/kord_db?schema=public"
DATABASE_URL="postgresql://kord:password@localhost:5432/kord_db?schema=public"
# Logging (log4js — file only under LOG_DIR, no console appender)
# Levels: trace, debug, info, warn, error, fatal
LOG_LEVEL=info
# Daily log files; keep 7 rotated days (see logger.ts)
LOG_DIR=logs

View File

@ -10,6 +10,7 @@
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"ffmpeg-static": "^5.3.0",
"log4js": "^6.9.1",
"pg": "^8.20.0",
"prism-media": "^1.3.5",
"sharp": "^0.34.5",

View File

@ -16,5 +16,9 @@ export const env = {
DATABASE_URL: process.env.DATABASE_URL || '',
VOICE_WAITING_ROOM_ID: process.env.VOICE_WAITING_ROOM_ID || '',
VOICE_CATEGORY_ID: process.env.VOICE_CATEGORY_ID || '',
/** log4js level: trace | debug | info | warn | error | fatal */
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
/** Directory for rotated kord.log (see src/utils/logger.ts) */
LOG_DIR: process.env.LOG_DIR || 'logs',
INSTANCE_ID: generateInstanceId(),
};

View File

@ -1,6 +1,49 @@
export const logger = {
info: (...args: any[]) => console.log('\x1b[36m[INFO]\x1b[0m', ...args),
warn: (...args: any[]) => console.log('\x1b[33m[WARN]\x1b[0m', ...args),
error: (...args: any[]) => console.error('\x1b[31m[ERROR]\x1b[0m', ...args),
debug: (...args: any[]) => console.debug('\x1b[90m[DEBUG]\x1b[0m', ...args),
};
import { mkdirSync } from 'fs';
import { config } from 'dotenv';
import log4js from 'log4js';
import { resolve } from 'path';
// Load .env before reading LOG_LEVEL / LOG_DIR (same rule as config/env.ts).
config({ path: process.env.DOTENV_CONFIG_PATH || resolve(process.cwd(), '.env') });
const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const;
type LogLevel = (typeof LOG_LEVELS)[number];
function resolveLogLevel(): LogLevel {
const raw = (process.env.LOG_LEVEL || 'info').toLowerCase();
return (LOG_LEVELS as readonly string[]).includes(raw) ? (raw as LogLevel) : 'info';
}
const logDir = resolve(process.env.LOG_DIR || 'logs');
const level = resolveLogLevel();
mkdirSync(logDir, { recursive: true });
log4js.configure({
appenders: {
file: {
type: 'dateFile',
filename: resolve(logDir, 'kord.log'),
pattern: 'yyyy-MM-dd',
alwaysIncludePattern: true,
numBackups: 7,
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS} [%p] %m',
},
},
},
categories: {
default: { appenders: ['file'], level },
},
});
process.on('exit', () => {
try {
log4js.shutdown();
} catch {
// ignore
}
});
export const logger = log4js.getLogger();

View File

@ -3107,6 +3107,13 @@ __metadata:
languageName: node
linkType: hard
"date-format@npm:^4.0.14":
version: 4.0.14
resolution: "date-format@npm:4.0.14"
checksum: 10c0/1c67a4d77c677bb880328c81d81f5b9ed7fbf672bdaff74e5a0f7314b21188f3a829b06acf120c70cc1df876a7724e3e5c23d511e86d64656a3035a76ac3930b
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.3":
version: 4.4.3
resolution: "debug@npm:4.4.3"
@ -3713,7 +3720,7 @@ __metadata:
languageName: node
linkType: hard
"flatted@npm:^3.2.9":
"flatted@npm:^3.2.7, flatted@npm:^3.2.9":
version: 3.4.2
resolution: "flatted@npm:3.4.2"
checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed
@ -3730,6 +3737,17 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^8.1.0":
version: 8.1.0
resolution: "fs-extra@npm:8.1.0"
dependencies:
graceful-fs: "npm:^4.2.0"
jsonfile: "npm:^4.0.0"
universalify: "npm:^0.1.0"
checksum: 10c0/259f7b814d9e50d686899550c4f9ded85c46c643f7fe19be69504888e007fcbc08f306fae8ec495b8b998635e997c9e3e175ff2eeed230524ef1c1684cc96423
languageName: node
linkType: hard
"fs-minipass@npm:^2.0.0":
version: 2.1.0
resolution: "fs-minipass@npm:2.1.0"
@ -3910,7 +3928,7 @@ __metadata:
languageName: node
linkType: hard
"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
@ -4751,6 +4769,18 @@ __metadata:
languageName: node
linkType: hard
"jsonfile@npm:^4.0.0":
version: 4.0.0
resolution: "jsonfile@npm:4.0.0"
dependencies:
graceful-fs: "npm:^4.1.6"
dependenciesMeta:
graceful-fs:
optional: true
checksum: 10c0/7dc94b628d57a66b71fb1b79510d460d662eb975b5f876d723f81549c2e9cd316d58a2ddf742b2b93a4fa6b17b2accaf1a738a0e2ea114bdfb13a32e5377e480
languageName: node
linkType: hard
"keyv@npm:^4.5.4":
version: 4.5.4
resolution: "keyv@npm:4.5.4"
@ -4778,6 +4808,7 @@ __metadata:
eslint: "npm:^10.1.0"
ffmpeg-static: "npm:^5.3.0"
jest: "npm:^30.3.0"
log4js: "npm:^6.9.1"
pg: "npm:^8.20.0"
prettier: "npm:^3.8.1"
prism-media: "npm:^1.3.5"
@ -4853,6 +4884,19 @@ __metadata:
languageName: node
linkType: hard
"log4js@npm:^6.9.1":
version: 6.9.1
resolution: "log4js@npm:6.9.1"
dependencies:
date-format: "npm:^4.0.14"
debug: "npm:^4.3.4"
flatted: "npm:^3.2.7"
rfdc: "npm:^1.3.0"
streamroller: "npm:^3.1.5"
checksum: 10c0/05846e48f72d662800c8189bd178c42b4aa2f0c574cfc90a1942cf90b76f621c44019e26796c8fd88da1b6f0fe8272cba607cbaad6ae6ede50a7a096b58197ea
languageName: node
linkType: hard
"long@npm:^5.2.1":
version: 5.3.2
resolution: "long@npm:5.3.2"
@ -5852,6 +5896,13 @@ __metadata:
languageName: node
linkType: hard
"rfdc@npm:^1.3.0":
version: 1.4.1
resolution: "rfdc@npm:1.4.1"
checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7
languageName: node
linkType: hard
"rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
@ -6128,6 +6179,17 @@ __metadata:
languageName: node
linkType: hard
"streamroller@npm:^3.1.5":
version: 3.1.5
resolution: "streamroller@npm:3.1.5"
dependencies:
date-format: "npm:^4.0.14"
debug: "npm:^4.3.4"
fs-extra: "npm:^8.1.0"
checksum: 10c0/0bdeec34ad37487d959ba908f17067c938f544db88b5bb1669497a67a6b676413229ce5a6145c2812d06959ebeb8842e751076647d4b323ca06be612963b9099
languageName: node
linkType: hard
"string-length@npm:^4.0.2":
version: 4.0.2
resolution: "string-length@npm:4.0.2"
@ -6470,6 +6532,13 @@ __metadata:
languageName: node
linkType: hard
"universalify@npm:^0.1.0":
version: 0.1.2
resolution: "universalify@npm:0.1.2"
checksum: 10c0/e70e0339f6b36f34c9816f6bf9662372bd241714dc77508d231d08386d94f2c4aa1ba1318614f92015f40d45aae1b9075cd30bd490efbe39387b60a76ca3f045
languageName: node
linkType: hard
"unrs-resolver@npm:^1.7.11":
version: 1.11.1
resolution: "unrs-resolver@npm:1.11.1"