/
logger.js
245 lines (219 loc) · 6.59 KB
/
logger.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/* eslint no-underscore-dangle: ["error", { "allow": ["_log"] }] */
const debug = require('debug')('log4js:logger');
const LoggingEvent = require('./LoggingEvent');
const levels = require('./levels');
const clustering = require('./clustering');
const categories = require('./categories');
const configuration = require('./configuration');
const stackReg = /^(?:\s*)at (?:(.+) \()?(?:([^(]+?):(\d+):(\d+))\)?$/;
/**
* The top entry is the Error
*/
const baseCallStackSkip = 1;
/**
* The _log function is 3 levels deep, we need to skip those to make it to the callSite
*/
const defaultErrorCallStackSkip = 3;
/**
*
* @param {Error} data
* @param {number} skipIdx
* @returns {import('../types/log4js').CallStack | null}
*/
function defaultParseCallStack(
data,
skipIdx = defaultErrorCallStackSkip + baseCallStackSkip
) {
try {
const stacklines = data.stack.split('\n').slice(skipIdx);
if (!stacklines.length) {
// There's no stack in this stack
// Should we try a previous index if skipIdx was set?
return null;
}
const lineMatch = stackReg.exec(stacklines[0]);
/* istanbul ignore else: failsafe */
if (lineMatch && lineMatch.length === 5) {
// extract class, function and alias names
let className = '';
let functionName = '';
let functionAlias = '';
if (lineMatch[1] && lineMatch[1] !== '') {
// WARN: this will unset alias if alias is not present.
[functionName, functionAlias] = lineMatch[1]
.replace(/[[\]]/g, '')
.split(' as ');
functionAlias = functionAlias || '';
if (functionName.includes('.'))
[className, functionName] = functionName.split('.');
}
return {
fileName: lineMatch[2],
lineNumber: parseInt(lineMatch[3], 10),
columnNumber: parseInt(lineMatch[4], 10),
callStack: stacklines.join('\n'),
className,
functionName,
functionAlias,
callerName: lineMatch[1] || '',
};
// eslint-disable-next-line no-else-return
} else {
// will never get here unless nodejs has changes to Error
console.error('log4js.logger - defaultParseCallStack error'); // eslint-disable-line no-console
}
} catch (err) {
// will never get error unless nodejs has breaking changes to Error
console.error('log4js.logger - defaultParseCallStack error', err); // eslint-disable-line no-console
}
return null;
}
/**
* Logger to log messages.
* use {@see log4js#getLogger(String)} to get an instance.
*
* @name Logger
* @namespace Log4js
* @param name name of category to log to
* @param level - the loglevel for the category
* @param dispatch - the function which will receive the logevents
*
* @author Stephan Strittmatter
*/
class Logger {
constructor(name) {
if (!name) {
throw new Error('No category provided.');
}
this.category = name;
this.context = {};
/** @private */
this.callStackSkipIndex = 0;
/** @private */
this.parseCallStack = defaultParseCallStack;
debug(`Logger created (${this.category}, ${this.level})`);
}
get level() {
return levels.getLevel(
categories.getLevelForCategory(this.category),
levels.OFF
);
}
set level(level) {
categories.setLevelForCategory(
this.category,
levels.getLevel(level, this.level)
);
}
get useCallStack() {
return categories.getEnableCallStackForCategory(this.category);
}
set useCallStack(bool) {
categories.setEnableCallStackForCategory(this.category, bool === true);
}
get callStackLinesToSkip() {
return this.callStackSkipIndex;
}
set callStackLinesToSkip(number) {
if (typeof number !== 'number') {
throw new TypeError('Must be a number');
}
if (number < 0) {
throw new RangeError('Must be >= 0');
}
this.callStackSkipIndex = number;
}
log(level, ...args) {
const logLevel = levels.getLevel(level);
if (!logLevel) {
if (configuration.validIdentifier(level) && args.length > 0) {
// logLevel not found but of valid signature, WARN before fallback to INFO
this.log(
levels.WARN,
'log4js:logger.log: valid log-level not found as first parameter given:',
level
);
this.log(levels.INFO, `[${level}]`, ...args);
} else {
// apart from fallback, allow .log(...args) to be synonym with .log("INFO", ...args)
this.log(levels.INFO, level, ...args);
}
} else if (this.isLevelEnabled(logLevel)) {
this._log(logLevel, args);
}
}
isLevelEnabled(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel);
}
_log(level, data) {
debug(`sending log data (${level}) to appenders`);
const error = data.find((item) => item instanceof Error);
let callStack;
if (this.useCallStack) {
try {
if (error) {
callStack = this.parseCallStack(
error,
this.callStackSkipIndex + baseCallStackSkip
);
}
} catch (_err) {
// Ignore Error and use the original method of creating a new Error.
}
callStack =
callStack ||
this.parseCallStack(
new Error(),
this.callStackSkipIndex +
defaultErrorCallStackSkip +
baseCallStackSkip
);
}
const loggingEvent = new LoggingEvent(
this.category,
level,
data,
this.context,
callStack,
error
);
clustering.send(loggingEvent);
}
addContext(key, value) {
this.context[key] = value;
}
removeContext(key) {
delete this.context[key];
}
clearContext() {
this.context = {};
}
setParseCallStackFunction(parseFunction) {
if (typeof parseFunction === 'function') {
this.parseCallStack = parseFunction;
} else if (typeof parseFunction === 'undefined') {
this.parseCallStack = defaultParseCallStack;
} else {
throw new TypeError('Invalid type passed to setParseCallStackFunction');
}
}
}
function addLevelMethods(target) {
const level = levels.getLevel(target);
const levelStrLower = level.toString().toLowerCase();
const levelMethod = levelStrLower.replace(/_([a-z])/g, (g) =>
g[1].toUpperCase()
);
const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
return this.isLevelEnabled(level);
};
Logger.prototype[levelMethod] = function (...args) {
this.log(level, ...args);
};
}
levels.levels.forEach(addLevelMethods);
configuration.addListener(() => {
levels.levels.forEach(addLevelMethods);
});
module.exports = Logger;