|
1 | 1 | import winston from 'winston'; |
| 2 | +import DailyRotateFile from 'winston-daily-rotate-file'; |
2 | 3 |
|
3 | 4 | const isDev = process.env['NODE_ENV'] !== 'production'; |
4 | 5 |
|
5 | | -export const logger = winston.createLogger({ |
6 | | - level: isDev ? 'debug' : 'info', |
7 | | - format: isDev |
8 | | - ? winston.format.combine( |
9 | | - winston.format.colorize(), |
10 | | - winston.format.timestamp({ format: 'HH:mm:ss' }), |
11 | | - winston.format.printf((info) => { |
12 | | - const { timestamp, level, message, ...meta } = info as Record<string, unknown>; |
13 | | - const metaStr = Object.keys(meta).length |
14 | | - ? '\n' + JSON.stringify(meta, null, 2) |
15 | | - : ''; |
16 | | - return `${timestamp} [${level}] ${message}${metaStr}`; |
17 | | - }), |
18 | | - ) |
19 | | - : winston.format.combine( |
20 | | - winston.format.timestamp(), |
21 | | - winston.format.json(), |
22 | | - ), |
23 | | - transports: [new winston.transports.Console()], |
| 6 | +const devPrintf = winston.format.printf((info) => { |
| 7 | + const { timestamp, level, message, ...meta } = info as Record<string, unknown>; |
| 8 | + const metaStr = Object.keys(meta).length ? '\n' + JSON.stringify(meta, null, 2) : ''; |
| 9 | + return `${timestamp} [${level}] ${message}${metaStr}`; |
24 | 10 | }); |
| 11 | + |
| 12 | +/** Format for the Console transport — colorized in dev for readability. */ |
| 13 | +const consoleFormat = isDev |
| 14 | + ? winston.format.combine( |
| 15 | + winston.format.colorize(), |
| 16 | + winston.format.timestamp({ format: 'HH:mm:ss' }), |
| 17 | + devPrintf, |
| 18 | + ) |
| 19 | + : winston.format.combine(winston.format.timestamp(), winston.format.json()); |
| 20 | + |
| 21 | +/** Format for file transports — no ANSI escape codes, safe for log parsers. */ |
| 22 | +const fileFormat = isDev |
| 23 | + ? winston.format.combine(winston.format.timestamp({ format: 'HH:mm:ss' }), devPrintf) |
| 24 | + : winston.format.combine(winston.format.timestamp(), winston.format.json()); |
| 25 | + |
| 26 | +/** |
| 27 | + * Creates a console-only fallback logger used as the initial singleton value |
| 28 | + * before {@link createLogger} is called by main.ts. |
| 29 | + */ |
| 30 | +function createConsoleOnlyLogger(): winston.Logger { |
| 31 | + return winston.createLogger({ |
| 32 | + level: isDev ? 'debug' : 'info', |
| 33 | + format: consoleFormat, |
| 34 | + transports: [new winston.transports.Console()], |
| 35 | + }); |
| 36 | +} |
| 37 | + |
| 38 | +/** |
| 39 | + * Module-level logger singleton. Initially console-only; reassigned (as a |
| 40 | + * live ESM binding) when {@link createLogger} is called from main.ts so that |
| 41 | + * all existing importers automatically see the upgraded instance. |
| 42 | + */ |
| 43 | +export let logger: winston.Logger = createConsoleOnlyLogger(); |
| 44 | + |
| 45 | +/** |
| 46 | + * Creates a full winston logger with both a Console transport and a |
| 47 | + * DailyRotateFile transport that writes to `logDir`. Also reassigns the |
| 48 | + * exported {@link logger} singleton so that modules that imported it before |
| 49 | + * `main.ts` started still get the upgraded instance. |
| 50 | + * |
| 51 | + * @param logDir - Directory in which daily log files will be written. |
| 52 | + * @returns The newly created {@link winston.Logger} instance. |
| 53 | + */ |
| 54 | +export function createLogger(logDir: string): winston.Logger { |
| 55 | + const newLogger = winston.createLogger({ |
| 56 | + level: isDev ? 'debug' : 'info', |
| 57 | + transports: [ |
| 58 | + new winston.transports.Console({ format: consoleFormat }), |
| 59 | + new DailyRotateFile({ |
| 60 | + format: fileFormat, |
| 61 | + dirname: logDir, |
| 62 | + filename: 'main-%DATE%.log', |
| 63 | + datePattern: 'YYYY-MM-DD', |
| 64 | + maxFiles: '14d', |
| 65 | + }), |
| 66 | + ], |
| 67 | + }); |
| 68 | + |
| 69 | + // Reassign the live ESM binding so importers see the upgraded instance. |
| 70 | + logger = newLogger; |
| 71 | + return newLogger; |
| 72 | +} |
0 commit comments