|
1 | | -import winston from 'winston'; |
2 | | - |
3 | | -/** |
4 | | - * Factory to create a Winston logger instance. |
5 | | - * @param {Object} [options] |
6 | | - * @param {string} [options.level] - Log level (default: process.env.LOG_LEVEL or 'info') |
7 | | - * @param {Array} [options.transports] - Array of Winston transports (default: Console) |
8 | | - * @returns {winston.Logger} |
9 | | - */ |
10 | | -export const createLogger = ({ |
11 | | - level = process.env.LOG_LEVEL || 'info', |
12 | | - transports = [new winston.transports.Console()] |
13 | | -} = {}) => { |
14 | | - const logger = winston.createLogger({ |
15 | | - level, |
16 | | - format: winston.format.printf(({ level, message, ...meta }) => { |
17 | | - let msg = `[${level.toUpperCase()}] ${message}`; |
18 | | - const metaKeys = Object.keys(meta).filter(k => k !== 'level' && k !== 'message'); |
19 | | - if (metaKeys.length > 0) { |
20 | | - // Custom replacer to handle BigInt and circular references |
21 | | - const seen = new WeakSet(); |
22 | | - const replacer = (key, value) => { |
23 | | - if (typeof value === 'bigint') return value.toString() + 'n'; |
24 | | - if (typeof value === 'object' && value !== null) { |
25 | | - if (seen.has(value)) return '[Circular]'; |
26 | | - seen.add(value); |
27 | | - } |
28 | | - return value; |
29 | | - }; |
30 | | - msg += ' ' + JSON.stringify(Object.fromEntries(metaKeys.map(k => [k, meta[k]])), replacer); |
31 | | - } |
32 | | - return msg; |
33 | | - }), |
34 | | - transports |
35 | | - }); |
36 | | - |
37 | | - // Patch logger methods to support primitive/array as meta |
38 | | - const levels = Object.keys(logger.levels || winston.config.npm.levels); |
39 | | - levels.forEach((method) => { |
40 | | - const orig = logger[method]; |
41 | | - logger[method] = function (msg, meta) { |
42 | | - if (arguments.length === 2 && (typeof meta !== 'object' || meta === null || Array.isArray(meta))) { |
43 | | - return orig.call(this, msg, { value: meta }); |
44 | | - } |
45 | | - return orig.apply(this, arguments); |
46 | | - }; |
47 | | - }); |
48 | | - return logger; |
49 | | -}; |
50 | | - |
51 | | -const log = createLogger(); |
52 | | -export default log; |
53 | | -export { log }; |
| 1 | +import winston from 'winston'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Factory to create a Winston logger instance. |
| 5 | + * @param {Object} [options] |
| 6 | + * @param {string} [options.level] - Log level (default: process.env.LOG_LEVEL or 'info') |
| 7 | + * @param {Array} [options.transports] - Array of Winston transports (default: Console) |
| 8 | + * @returns {winston.Logger} |
| 9 | + */ |
| 10 | +export const createLogger = ({ |
| 11 | + level = process.env.LOG_LEVEL || 'info', |
| 12 | + transports = [new winston.transports.Console()] |
| 13 | +} = {}) => { |
| 14 | + // Safe serializer: shallowly summarize objects without invoking toJSON/getters |
| 15 | + const safeSerialize = (obj) => { |
| 16 | + try { |
| 17 | + if (obj === null) return null; |
| 18 | + if (typeof obj !== 'object') return obj; |
| 19 | + const out = {}; |
| 20 | + for (const k of Object.keys(obj)) { |
| 21 | + try { |
| 22 | + const v = obj[k]; |
| 23 | + if (v === null) { out[k] = null; continue; } |
| 24 | + if (typeof v === 'object') { |
| 25 | + const info = { type: v && v.constructor && v.constructor.name ? v.constructor.name : 'Object' }; |
| 26 | + try { if ('id' in v && (typeof v.id === 'string' || typeof v.id === 'number')) info.id = v.id; } catch (e) {} |
| 27 | + try { if ('name' in v && typeof v.name === 'string') info.name = v.name; } catch (e) {} |
| 28 | + out[k] = info; |
| 29 | + } else if (typeof v === 'function') { |
| 30 | + out[k] = `[Function: ${v.name || 'anonymous'}]`; |
| 31 | + } else { |
| 32 | + out[k] = v; |
| 33 | + } |
| 34 | + } catch (e) { |
| 35 | + out[k] = '[Unserializable]'; |
| 36 | + } |
| 37 | + } |
| 38 | + return out; |
| 39 | + } catch (e) { |
| 40 | + try { return String(obj); } catch (ee) { return '[Unserializable]'; } |
| 41 | + } |
| 42 | + }; |
| 43 | + |
| 44 | + const logger = winston.createLogger({ |
| 45 | + level, |
| 46 | + format: winston.format.printf(({ level, message, ...meta }) => { |
| 47 | + let msg = `[${level.toUpperCase()}] ${message}`; |
| 48 | + const metaKeys = Object.keys(meta).filter(k => k !== 'level' && k !== 'message'); |
| 49 | + if (metaKeys.length > 0) { |
| 50 | + // Custom replacer to handle BigInt and circular references |
| 51 | + const seen = new WeakSet(); |
| 52 | + const replacer = (key, value) => { |
| 53 | + if (typeof value === 'bigint') return value.toString() + 'n'; |
| 54 | + if (typeof value === 'object' && value !== null) { |
| 55 | + if (seen.has(value)) return '[Circular]'; |
| 56 | + seen.add(value); |
| 57 | + } |
| 58 | + return value; |
| 59 | + }; |
| 60 | + // Build a safe meta object to avoid invoking toJSON on library objects |
| 61 | + const safeMeta = {}; |
| 62 | + for (const k of metaKeys) { |
| 63 | + try { |
| 64 | + safeMeta[k] = safeSerialize(meta[k]); |
| 65 | + } catch (e) { |
| 66 | + safeMeta[k] = '[Unserializable]'; |
| 67 | + } |
| 68 | + } |
| 69 | + try { |
| 70 | + msg += ' ' + JSON.stringify(safeMeta, replacer); |
| 71 | + } catch (e) { |
| 72 | + try { |
| 73 | + msg += ' ' + String(safeMeta); |
| 74 | + } catch (ee) { |
| 75 | + msg += ' [Unserializable meta]'; |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + return msg; |
| 80 | + }), |
| 81 | + transports |
| 82 | + }); |
| 83 | + |
| 84 | + // Patch logger methods to support primitive/array as meta |
| 85 | + const levels = Object.keys(logger.levels || winston.config.npm.levels); |
| 86 | + levels.forEach((method) => { |
| 87 | + const orig = logger[method]; |
| 88 | + logger[method] = function (msg, meta) { |
| 89 | + if (arguments.length === 2 && (typeof meta !== 'object' || meta === null || Array.isArray(meta))) { |
| 90 | + return orig.call(this, msg, { value: meta }); |
| 91 | + } |
| 92 | + return orig.apply(this, arguments); |
| 93 | + }; |
| 94 | + }); |
| 95 | + return logger; |
| 96 | +}; |
| 97 | + |
| 98 | +const log = createLogger(); |
| 99 | +export default log; |
| 100 | +export { log }; |
0 commit comments