From f2ffdb64a861a83e63e565f483d366258c58235c Mon Sep 17 00:00:00 2001 From: mineseo-kim Date: Thu, 9 Apr 2026 09:34:15 +0900 Subject: [PATCH] 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 --- .env.example | 8 ++++- package.json | 1 + src/config/env.ts | 4 +++ src/utils/logger.ts | 55 ++++++++++++++++++++++++++++++---- yarn.lock | 73 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 132 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 28823bf..9f81ddd 100644 --- a/.env.example +++ b/.env.example @@ -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" \ No newline at end of file +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 \ No newline at end of file diff --git a/package.json b/package.json index ab568ab..78f03d4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/config/env.ts b/src/config/env.ts index a74e230..2be95c3 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -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(), }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 36cf729..04d51b1 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -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(); diff --git a/yarn.lock b/yarn.lock index 525e724..034a1b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"