@trojs/logger 2.0.7 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/helpers/stackdriver.js +13 -9
- package/src/logger.js +102 -8
- package/src/loggers/console.js +117 -11
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trojs/logger",
|
|
3
3
|
"description": "Winston logger for TroJS",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pieter Wigboldus",
|
|
7
7
|
"url": "https://trojs.org/"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@trojs/error": "^5.0.0",
|
|
37
37
|
"@trojs/lint": "^0.4.0",
|
|
38
38
|
"eslint": "^9.15.0",
|
|
39
|
-
"globals": "^
|
|
39
|
+
"globals": "^17.0.0",
|
|
40
40
|
"jscpd": "^4.0.0",
|
|
41
41
|
"prettier": "^3.3.3"
|
|
42
42
|
},
|
|
@@ -19,14 +19,18 @@ const levels = {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const stackdriver
|
|
22
|
-
= ({ level, defaultLevel }) =>
|
|
23
|
-
(info) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
= ({ level = 'info', defaultLevel = 'info' } = {}) =>
|
|
23
|
+
(info) => {
|
|
24
|
+
const currentLevel = info?.level ?? level ?? defaultLevel ?? 'info'
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...info,
|
|
28
|
+
severity: levelToSeverity[currentLevel] || levelToSeverity.info,
|
|
29
|
+
level: levels[currentLevel] || levels.info,
|
|
30
|
+
time: Date.now(),
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
hostname: os.hostname()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
31
35
|
|
|
32
36
|
export default stackdriver
|
package/src/logger.js
CHANGED
|
@@ -23,20 +23,114 @@ const levels = {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {string
|
|
30
|
-
* @param {
|
|
31
|
-
* @
|
|
26
|
+
* Creates a Winston logger instance with custom log levels and transports.
|
|
27
|
+
* Also attaches global process event handlers for uncaught exceptions, unhandled rejections, and warnings.
|
|
28
|
+
* @param {object} [options={}] - Logger configuration options.
|
|
29
|
+
* @param {Array<{[key: string]: string}>} [options.loggers=defaultLoggers] - Array of logger transport configurations.
|
|
30
|
+
* @param {string} [options.level='info'] - Minimum log level for the logger.
|
|
31
|
+
* @param {object} [options.meta={}] - Default metadata to include in all log messages.
|
|
32
|
+
* @returns {LoggerType} Winston logger instance with custom level wrappers.
|
|
33
|
+
* These handlers will log errors and warnings using the logger, and are only attached once per process.
|
|
34
|
+
* @example
|
|
35
|
+
* import createLogger from './logger.js';
|
|
36
|
+
* const logger = createLogger({ level: 'debug', meta: { service: 'api' } });
|
|
37
|
+
* logger.info('Service started');
|
|
32
38
|
*/
|
|
33
|
-
export default ({ loggers = defaultLoggers, level = 'info', meta = {} }) => {
|
|
39
|
+
export default ({ loggers = defaultLoggers, level = 'info', meta = {} } = {}) => {
|
|
34
40
|
const winstonLoggers = makeLoggers({ winston, loggers })
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
const logger = winston.createLogger({
|
|
37
43
|
level,
|
|
38
44
|
levels,
|
|
39
45
|
defaultMeta: meta,
|
|
40
46
|
transports: winstonLoggers
|
|
41
47
|
})
|
|
48
|
+
|
|
49
|
+
const wrapLevel = (lvl) => {
|
|
50
|
+
const orig = logger[lvl].bind(logger)
|
|
51
|
+
logger[lvl] = (first, ...rest) => {
|
|
52
|
+
if (first instanceof Error) {
|
|
53
|
+
const message = typeof first.message === 'string'
|
|
54
|
+
? first.message
|
|
55
|
+
: (() => {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(first)
|
|
58
|
+
} catch {
|
|
59
|
+
return String(first)
|
|
60
|
+
}
|
|
61
|
+
})()
|
|
62
|
+
|
|
63
|
+
const restInfo = Object.assign(
|
|
64
|
+
{},
|
|
65
|
+
...rest.filter((x) => x && typeof x === 'object')
|
|
66
|
+
)
|
|
67
|
+
const info = {
|
|
68
|
+
...restInfo,
|
|
69
|
+
level: lvl,
|
|
70
|
+
message: message,
|
|
71
|
+
error: first,
|
|
72
|
+
stack: first.stack
|
|
73
|
+
}
|
|
74
|
+
return logger.log(info)
|
|
75
|
+
}
|
|
76
|
+
return orig(first, ...rest)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
;['fatal', 'error', 'warn', 'info', 'debug', 'trace'].forEach((lvl) => {
|
|
81
|
+
if (typeof logger[lvl] === 'function') wrapLevel(lvl)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (!process.__trojsLoggerHandlersAttached) {
|
|
85
|
+
process.__trojsLoggerHandlersAttached = true
|
|
86
|
+
|
|
87
|
+
process.on('uncaughtException', (err) => {
|
|
88
|
+
try {
|
|
89
|
+
logger.error(err instanceof Error ? err : new Error(String(err)))
|
|
90
|
+
} catch {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.error('UNCAUGHT_EXCEPTION', err)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
process.on('unhandledRejection', (reason) => {
|
|
97
|
+
let err
|
|
98
|
+
if (reason instanceof Error) {
|
|
99
|
+
err = reason
|
|
100
|
+
} else if (typeof reason === 'string') {
|
|
101
|
+
err = new Error(reason)
|
|
102
|
+
} else {
|
|
103
|
+
try {
|
|
104
|
+
err = new Error(JSON.stringify(reason))
|
|
105
|
+
} catch {
|
|
106
|
+
err = new Error(String(reason))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
logger.error(err)
|
|
111
|
+
} catch {
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.error('UNHANDLED_REJECTION', err)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
process.on('warning', (warning) => {
|
|
118
|
+
try {
|
|
119
|
+
logger.warn(
|
|
120
|
+
warning instanceof Error
|
|
121
|
+
? warning
|
|
122
|
+
: (
|
|
123
|
+
new Error(
|
|
124
|
+
`${warning.name}: ${warning.message}\n${warning.stack || ''}`
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
} catch {
|
|
129
|
+
// eslint-disable-next-line no-console
|
|
130
|
+
console.warn('PROCESS_WARNING', warning)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return logger
|
|
42
136
|
}
|
package/src/loggers/console.js
CHANGED
|
@@ -1,25 +1,131 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
1
2
|
import stackDriver from '../helpers/stackdriver.js'
|
|
2
3
|
|
|
4
|
+
const SYMBOL_MESSAGE = Symbol.for('message')
|
|
5
|
+
|
|
3
6
|
export default ({ winston, logger }) => {
|
|
4
7
|
const defaultLevel = 'trace'
|
|
8
|
+
const stackTrace = logger?.debug ?? false
|
|
9
|
+
|
|
10
|
+
const stackHead = (stack) =>
|
|
11
|
+
stack ? (stack.split('\n')[0] || '').trim() : ''
|
|
12
|
+
|
|
13
|
+
const ensureErrorProps = winston.format((info) => {
|
|
14
|
+
if (info instanceof Error) {
|
|
15
|
+
info.message = info.message || info.toString()
|
|
16
|
+
}
|
|
17
|
+
return info
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const extractSymbolMessage = (info) => {
|
|
21
|
+
if (
|
|
22
|
+
(!info.message || info.message === '')
|
|
23
|
+
&& info[SYMBOL_MESSAGE]
|
|
24
|
+
&& typeof info[SYMBOL_MESSAGE] === 'string'
|
|
25
|
+
) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(info[SYMBOL_MESSAGE])
|
|
28
|
+
if (typeof parsed === 'string') {
|
|
29
|
+
info.message = parsed
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
info.message = info[SYMBOL_MESSAGE]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const attachEmbeddedError = (info) => {
|
|
38
|
+
const embedded
|
|
39
|
+
= (info.error instanceof Error && info.error)
|
|
40
|
+
|| (info.exception instanceof Error && info.exception)
|
|
41
|
+
|
|
42
|
+
if (info instanceof Error) {
|
|
43
|
+
if (!info.message || info.message === '') {
|
|
44
|
+
info.message = info.toString()
|
|
45
|
+
}
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (info.message instanceof Error) {
|
|
50
|
+
info.message = info.message.message || info.message.toString()
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (embedded) {
|
|
55
|
+
info.message = embedded.message || embedded.toString()
|
|
56
|
+
if (!info.stack && embedded.stack) {
|
|
57
|
+
info.stack = embedded.stack
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const stringifyNonStringMessage = (info) => {
|
|
63
|
+
if (info.message && typeof info.message !== 'string') {
|
|
64
|
+
try {
|
|
65
|
+
info.message = JSON.stringify(info.message)
|
|
66
|
+
} catch {
|
|
67
|
+
info.message = String(info.message)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const deriveMessageFromStack = (info) => {
|
|
73
|
+
if (info.stack && (!info.message || info.message === '')) {
|
|
74
|
+
info.message = stackHead(info.stack)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const finalizeEmptyMessage = (info) => {
|
|
79
|
+
if (!info.message || info.message === '') {
|
|
80
|
+
const clone = { ...info }
|
|
81
|
+
delete clone.level
|
|
82
|
+
delete clone.stack
|
|
83
|
+
delete clone.error
|
|
84
|
+
delete clone.exception
|
|
85
|
+
delete clone[SYMBOL_MESSAGE]
|
|
86
|
+
const keys = Object.keys(clone)
|
|
87
|
+
info.message = keys.length > 0 ? JSON.stringify(clone) : ''
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const duplicateStackTraceIfDebug = (info) => {
|
|
92
|
+
if ((logger?.debug ?? false) && info.stack) {
|
|
93
|
+
info.stacktrace = info.stack
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const normalizeMessage = winston.format((info) => {
|
|
98
|
+
extractSymbolMessage(info)
|
|
99
|
+
attachEmbeddedError(info)
|
|
100
|
+
stringifyNonStringMessage(info)
|
|
101
|
+
deriveMessageFromStack(info)
|
|
102
|
+
finalizeEmptyMessage(info)
|
|
103
|
+
duplicateStackTraceIfDebug(info)
|
|
104
|
+
return info
|
|
105
|
+
})
|
|
5
106
|
|
|
6
107
|
const jsonFormatter = winston.format.combine(
|
|
7
|
-
|
|
8
|
-
winston.format(
|
|
9
|
-
|
|
10
|
-
)(),
|
|
108
|
+
ensureErrorProps(),
|
|
109
|
+
winston.format.errors({ stack: stackTrace }),
|
|
110
|
+
normalizeMessage(),
|
|
111
|
+
winston.format(stackDriver({ level: logger?.level, defaultLevel }))(),
|
|
11
112
|
winston.format.json()
|
|
12
113
|
)
|
|
13
114
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
winston.format.
|
|
115
|
+
const simpleFormatter = winston.format.combine(
|
|
116
|
+
ensureErrorProps(),
|
|
117
|
+
winston.format.errors({ stack: stackTrace }),
|
|
118
|
+
normalizeMessage(),
|
|
119
|
+
winston.format.printf(({ level, message, stack }) => {
|
|
120
|
+
const base = `${level}: ${message || stackHead(stack)}`
|
|
121
|
+
return stack && (logger?.debug ?? false) ? `${base}\n${stack}` : base
|
|
122
|
+
})
|
|
17
123
|
)
|
|
124
|
+
|
|
18
125
|
return new winston.transports.Console({
|
|
19
126
|
level: logger?.level || defaultLevel,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
: defaultFormatter
|
|
127
|
+
handleExceptions: true,
|
|
128
|
+
handleRejections: true,
|
|
129
|
+
format: logger?.format === 'json' ? jsonFormatter : simpleFormatter
|
|
24
130
|
})
|
|
25
131
|
}
|