infront-logger 1.0.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/README.MD +43 -0
- package/dist/index.es.js +355 -0
- package/dist/index.umd.js +359 -0
- package/package.json +35 -0
package/README.MD
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Infront Logger
|
|
2
|
+
## Logging done right
|
|
3
|
+
|
|
4
|
+
The logger supports both writhing to file and to console.
|
|
5
|
+
|
|
6
|
+
npm i @infront/logger
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
const {BaseLogger} = require('@infront/logger');
|
|
10
|
+
let options = {};
|
|
11
|
+
let logger = new BaseLogger("component1", options);
|
|
12
|
+
logger.log("this is a test message");
|
|
13
|
+
|
|
14
|
+
## Loggers
|
|
15
|
+
|
|
16
|
+
### BaseLogger
|
|
17
|
+
#### Methods
|
|
18
|
+
- session(id)
|
|
19
|
+
- log(msg)
|
|
20
|
+
- info(msg)
|
|
21
|
+
- error(err)
|
|
22
|
+
- profile(action, options)
|
|
23
|
+
- profileMem(options)
|
|
24
|
+
|
|
25
|
+
### HttpLogger (extends BaseLogger)
|
|
26
|
+
#### Methods
|
|
27
|
+
- request(req)
|
|
28
|
+
- response(res)
|
|
29
|
+
- body(data)
|
|
30
|
+
- success(req, res, body)
|
|
31
|
+
- error(err)
|
|
32
|
+
|
|
33
|
+
## options
|
|
34
|
+
|
|
35
|
+
- dirname - The directory in which the logs will be created (default: "logs")
|
|
36
|
+
- levels - An object of "level name : hierarchical value"
|
|
37
|
+
- level - default level (default: info)
|
|
38
|
+
- dateFormat : default "YYYY-MM-DD HH:mm:ss:ms"
|
|
39
|
+
- maxFileSize: The maximum file size for a rotating file strategy (default: "30m")
|
|
40
|
+
- maxFiles: The maximum number of files for a rotating file strategy (default: 2)
|
|
41
|
+
- filename - name of the default log file (default: "logs.log")
|
|
42
|
+
- errorFilename - name of the default error log file (default: "error.log")
|
|
43
|
+
- console - boolean - should log to console (default: true)
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
require("winston-daily-rotate-file");
|
|
2
|
+
const { format: format$1, createLogger, transports, addColors } = require("winston");
|
|
3
|
+
const { inspect } = require("util");
|
|
4
|
+
const colors = {
|
|
5
|
+
error: "red",
|
|
6
|
+
warn: "yellow",
|
|
7
|
+
info: "green",
|
|
8
|
+
http: "magenta",
|
|
9
|
+
debug: "white"
|
|
10
|
+
};
|
|
11
|
+
addColors(colors);
|
|
12
|
+
const OPTIONS = {
|
|
13
|
+
dirname: "logs",
|
|
14
|
+
levels: {
|
|
15
|
+
error: 0,
|
|
16
|
+
warn: 1,
|
|
17
|
+
info: 2,
|
|
18
|
+
http: 3,
|
|
19
|
+
debug: 4
|
|
20
|
+
},
|
|
21
|
+
level: "info",
|
|
22
|
+
zippedArchive: false,
|
|
23
|
+
dateFormat: "YYYY-MM-DD HH:mm:ss:ms",
|
|
24
|
+
maxFiles: 2,
|
|
25
|
+
maxFileSize: "30m",
|
|
26
|
+
filename: "logs.log",
|
|
27
|
+
errorFilename: "error.log",
|
|
28
|
+
console: true,
|
|
29
|
+
exclude: [],
|
|
30
|
+
createSymlink: true,
|
|
31
|
+
symlinkName: "logs.log"
|
|
32
|
+
};
|
|
33
|
+
function errorToJSON(error) {
|
|
34
|
+
const { message, name, stack } = error;
|
|
35
|
+
return {
|
|
36
|
+
message,
|
|
37
|
+
name,
|
|
38
|
+
stack
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function fileFormatter(options) {
|
|
42
|
+
return format$1.combine(
|
|
43
|
+
// format.errors({stack: true}),
|
|
44
|
+
{
|
|
45
|
+
transform: (info) => {
|
|
46
|
+
const args = [info.message, ...info[Symbol.for("splat")] || []];
|
|
47
|
+
info.message = args.filter(Boolean).map((arg) => {
|
|
48
|
+
if (arg instanceof Error) {
|
|
49
|
+
return errorToJSON(arg);
|
|
50
|
+
}
|
|
51
|
+
return arg;
|
|
52
|
+
});
|
|
53
|
+
const msg = args.map((arg) => {
|
|
54
|
+
if (typeof arg == "object")
|
|
55
|
+
return inspect(arg, { compact: false, depth: Infinity });
|
|
56
|
+
return arg;
|
|
57
|
+
}).join(" ");
|
|
58
|
+
info[Symbol.for("message")] = `${info[Symbol.for("level")]}: ${msg}${info.stack ? " " + info.stack : ""}`;
|
|
59
|
+
if (options.exclude.some((string) => msg.includes(string)))
|
|
60
|
+
return null;
|
|
61
|
+
return info;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
format$1.timestamp({ format: options.dateFormat }),
|
|
65
|
+
format$1.json()
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
function consoleFormatter(options) {
|
|
69
|
+
return format$1.timestamp({ format: options.dateFormat }), format$1.colorize({ all: true });
|
|
70
|
+
}
|
|
71
|
+
function getFormatter(type, options) {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case "file":
|
|
74
|
+
return fileFormatter(options);
|
|
75
|
+
case "access":
|
|
76
|
+
break;
|
|
77
|
+
case "console":
|
|
78
|
+
return consoleFormatter(options);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function createTransport(options) {
|
|
82
|
+
let formatter = getFormatter(options.format, options);
|
|
83
|
+
if (formatter) {
|
|
84
|
+
options.format = formatter;
|
|
85
|
+
} else {
|
|
86
|
+
delete options.format;
|
|
87
|
+
}
|
|
88
|
+
return new transports.DailyRotateFile(options);
|
|
89
|
+
}
|
|
90
|
+
class Logger {
|
|
91
|
+
constructor(options = {}) {
|
|
92
|
+
this.options = { ...OPTIONS, ...options };
|
|
93
|
+
let trans = [
|
|
94
|
+
createTransport({
|
|
95
|
+
...this.options,
|
|
96
|
+
filename: this.options.filename,
|
|
97
|
+
symlinkName: this.options.filename
|
|
98
|
+
}),
|
|
99
|
+
createTransport({
|
|
100
|
+
...this.options,
|
|
101
|
+
filename: this.options.errorFilename,
|
|
102
|
+
symlinkName: this.options.errorFilename,
|
|
103
|
+
level: "error"
|
|
104
|
+
})
|
|
105
|
+
];
|
|
106
|
+
if (this.options.console) {
|
|
107
|
+
trans.push(new transports.Console({
|
|
108
|
+
format: getFormatter("console", this.options)
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
this.logger = createLogger({
|
|
112
|
+
level: this.options.level,
|
|
113
|
+
levels: this.options.levels,
|
|
114
|
+
exitOnError: false,
|
|
115
|
+
// format,
|
|
116
|
+
transports: trans,
|
|
117
|
+
exceptionHandlers: [
|
|
118
|
+
new transports.Console(),
|
|
119
|
+
new transports.File({ filename: `${this.options.dirname}/${this.options.errorFilename}` })
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const redact$1 = (data, ...sensitiveKeysList) => {
|
|
125
|
+
var _a, _b;
|
|
126
|
+
if (typeof data === "object" && data !== null && !((_b = (_a = data == null ? void 0 : data.constructor) == null ? void 0 : _a.name) == null ? void 0 : _b.startsWith("model"))) {
|
|
127
|
+
if (Array.isArray(data)) {
|
|
128
|
+
return data.map((item) => redact$1(item, ...sensitiveKeysList));
|
|
129
|
+
}
|
|
130
|
+
const redactedData = {};
|
|
131
|
+
for (const key in data) {
|
|
132
|
+
if (data == null ? void 0 : data.hasOwnProperty(key)) {
|
|
133
|
+
if (sensitiveKeysList.includes(key)) {
|
|
134
|
+
redactedData[key] = "*****";
|
|
135
|
+
} else {
|
|
136
|
+
redactedData[key] = redact$1(data[key], ...sensitiveKeysList);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return redactedData;
|
|
141
|
+
} else {
|
|
142
|
+
return data;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const trim$1 = (data, length) => {
|
|
146
|
+
try {
|
|
147
|
+
let str = JSON.stringify(data);
|
|
148
|
+
if (str.length > length) {
|
|
149
|
+
return str.substring(0, length) + "... [TRIMMED]";
|
|
150
|
+
}
|
|
151
|
+
return data;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const http = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
157
|
+
__proto__: null,
|
|
158
|
+
redact: redact$1,
|
|
159
|
+
trim: trim$1
|
|
160
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
161
|
+
function toFixedNumber(num, digits, base) {
|
|
162
|
+
const pow = Math.pow(base ?? 10, digits);
|
|
163
|
+
return Math.round(num * pow) / pow;
|
|
164
|
+
}
|
|
165
|
+
function bytesToMB$1(b) {
|
|
166
|
+
return toFixedNumber(b / 1024 / 1024, 2, 10);
|
|
167
|
+
}
|
|
168
|
+
function formatBytes$1(bytes, decimals = 2) {
|
|
169
|
+
if (!+bytes)
|
|
170
|
+
return "0 Bytes";
|
|
171
|
+
const k = 1024;
|
|
172
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
173
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
174
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
175
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`;
|
|
176
|
+
}
|
|
177
|
+
const format = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
178
|
+
__proto__: null,
|
|
179
|
+
bytesToMB: bytesToMB$1,
|
|
180
|
+
formatBytes: formatBytes$1,
|
|
181
|
+
toFixedNumber
|
|
182
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
183
|
+
const pick = (obj, ...keys) => Object.fromEntries(
|
|
184
|
+
keys.filter((key) => key in obj).map((key) => [key, obj[key]])
|
|
185
|
+
);
|
|
186
|
+
const omit$1 = (obj, ...keys) => {
|
|
187
|
+
const result = { ...obj };
|
|
188
|
+
keys.forEach(function(prop) {
|
|
189
|
+
delete result[prop];
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
const object = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
194
|
+
__proto__: null,
|
|
195
|
+
omit: omit$1,
|
|
196
|
+
pick
|
|
197
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
198
|
+
const v8 = require("v8");
|
|
199
|
+
const { bytesToMB } = format;
|
|
200
|
+
function stats() {
|
|
201
|
+
try {
|
|
202
|
+
let h = v8.getHeapStatistics();
|
|
203
|
+
return {
|
|
204
|
+
"total_heap_size": bytesToMB(h.total_heap_size),
|
|
205
|
+
"total_heap_size_executable": bytesToMB(h.total_heap_size_executable),
|
|
206
|
+
"total_physical_size": bytesToMB(h.total_physical_size),
|
|
207
|
+
"total_available_size": bytesToMB(h.total_available_size),
|
|
208
|
+
"used_heap_size": bytesToMB(h.used_heap_size),
|
|
209
|
+
"heap_size_limit": bytesToMB(h.heap_size_limit),
|
|
210
|
+
"malloced_memory": bytesToMB(h.malloced_memory),
|
|
211
|
+
"peak_malloced_memory": bytesToMB(h.peak_malloced_memory),
|
|
212
|
+
"does_zap_garbage": h.does_zap_garbage,
|
|
213
|
+
"number_of_native_contexts": h.number_of_native_contexts,
|
|
214
|
+
"number_of_detached_contexts": h.number_of_detached_contexts
|
|
215
|
+
};
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
class BaseLogger {
|
|
221
|
+
constructor(component, options) {
|
|
222
|
+
this.logger = new Logger(options).logger.child({ component });
|
|
223
|
+
this.ctx = {};
|
|
224
|
+
this.startTime = Date.now();
|
|
225
|
+
this.absaluteStartTime = Date.now();
|
|
226
|
+
}
|
|
227
|
+
session(id) {
|
|
228
|
+
this.ctx.sessionID = id;
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
log() {
|
|
232
|
+
this.logger.info(...arguments, this.ctx);
|
|
233
|
+
this._stopMemProfile();
|
|
234
|
+
}
|
|
235
|
+
info() {
|
|
236
|
+
this.logger.info(...arguments, this.ctx);
|
|
237
|
+
this._stopMemProfile();
|
|
238
|
+
}
|
|
239
|
+
error() {
|
|
240
|
+
this.logger.error(...arguments, this.ctx);
|
|
241
|
+
this._stopMemProfile();
|
|
242
|
+
}
|
|
243
|
+
profile(action, options = {}) {
|
|
244
|
+
this.ctx.profiler = this.ctx.profiler || {};
|
|
245
|
+
if (action) {
|
|
246
|
+
let startTime = this.ctx.profiler[action] ? Date.now() - this.ctx.profiler[action] : this.startTime;
|
|
247
|
+
this.ctx.profiler[action] = Date.now() - startTime;
|
|
248
|
+
}
|
|
249
|
+
this.ctx.profiler.totalTime = Date.now() - this.absaluteStartTime;
|
|
250
|
+
if (!options.continue)
|
|
251
|
+
this.startTime = Date.now();
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
_stopMemProfile() {
|
|
255
|
+
if (this.memProfileInterval)
|
|
256
|
+
clearInterval(this.memProfileInterval);
|
|
257
|
+
}
|
|
258
|
+
profileMem(options = {}) {
|
|
259
|
+
this.ctx.maxMemory = this.ctx.maxMemory || 0;
|
|
260
|
+
this.ctx.memoryStats = this.ctx.memoryStats || [];
|
|
261
|
+
this.ctx.memoryUsage = this.ctx.memoryUsage || [];
|
|
262
|
+
this.ctx.memoryStatsIntervalMS = options.interval || 1e3;
|
|
263
|
+
this._stopMemProfile();
|
|
264
|
+
this.memProfileInterval = setInterval(() => {
|
|
265
|
+
let mem = stats();
|
|
266
|
+
this.ctx.memoryStats.push(mem);
|
|
267
|
+
this.ctx.memoryUsage.push(mem.used_heap_size);
|
|
268
|
+
if (mem.used_heap_size > this.ctx.maxMemory) {
|
|
269
|
+
this.ctx.maxMemory = mem.used_heap_size;
|
|
270
|
+
}
|
|
271
|
+
}, this.ctx.memoryStatsIntervalMS);
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
context(key, value) {
|
|
275
|
+
this.ctx[key] = value;
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const { redact, trim } = http;
|
|
280
|
+
const { formatBytes } = format;
|
|
281
|
+
const { omit } = object;
|
|
282
|
+
const MAX_BODY_LENGTH = 128;
|
|
283
|
+
class HTTPLogger extends BaseLogger {
|
|
284
|
+
constructor(startTime, options = {}) {
|
|
285
|
+
super("http", options);
|
|
286
|
+
this.startTime = startTime || Date.now();
|
|
287
|
+
}
|
|
288
|
+
request(req) {
|
|
289
|
+
this.session(req.sessionID);
|
|
290
|
+
this.req = req;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
response(res) {
|
|
294
|
+
this.res = res;
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
body(data) {
|
|
298
|
+
this.data = data;
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
_prepare() {
|
|
302
|
+
var _a, _b;
|
|
303
|
+
let req = this.req;
|
|
304
|
+
let res = this.res;
|
|
305
|
+
let body = this.data;
|
|
306
|
+
this.ctx.request = {
|
|
307
|
+
headers: redact(req.headers, "cookie"),
|
|
308
|
+
host: req.headers.host,
|
|
309
|
+
baseUrl: req.baseUrl,
|
|
310
|
+
url: req.originalUrl || req.url,
|
|
311
|
+
method: req.method,
|
|
312
|
+
body: redact(req.body, "password"),
|
|
313
|
+
params: req == null ? void 0 : req.params,
|
|
314
|
+
query: redact(req == null ? void 0 : req.query, "password"),
|
|
315
|
+
clientIP: ((_a = req == null ? void 0 : req.headers["x-forwarded-for"]) == null ? void 0 : _a.split(",")[0]) ?? (req == null ? void 0 : req.socket.remoteAddress)
|
|
316
|
+
};
|
|
317
|
+
this.ctx.response = {
|
|
318
|
+
headers: omit(res.getHeaders(), "set-cookie", "x-powered-by"),
|
|
319
|
+
statusCode: res.statusCode,
|
|
320
|
+
body: trim(body, MAX_BODY_LENGTH)
|
|
321
|
+
};
|
|
322
|
+
this.ctx.responseTimeMs = Date.now() - this.startTime;
|
|
323
|
+
this.ctx.responseSizeBytes = this.data ? JSON.stringify(this.data).length : 0;
|
|
324
|
+
this.ctx.user = (_b = req == null ? void 0 : req.user) == null ? void 0 : _b.id;
|
|
325
|
+
}
|
|
326
|
+
_message(msg) {
|
|
327
|
+
var _a;
|
|
328
|
+
let remoteAddress = this.req.ip || this.req._remoteAddress || this.req.connection && this.req.connection.remoteAddress || void 0;
|
|
329
|
+
let ip = this.ctx.request.clientIP;
|
|
330
|
+
let method = this.ctx.request.method;
|
|
331
|
+
let url = this.ctx.request.url;
|
|
332
|
+
let statusCode = this.ctx.response.statusCode;
|
|
333
|
+
let responseTimeMs = this.ctx.responseTimeMs + "ms";
|
|
334
|
+
let responseSize = formatBytes((_a = JSON.stringify(this.data)) == null ? void 0 : _a.length);
|
|
335
|
+
return `${method} ${url} ${statusCode} ${responseTimeMs} ${responseSize} ${ip} ${remoteAddress} ${msg || ""}`;
|
|
336
|
+
}
|
|
337
|
+
success(req, res, body) {
|
|
338
|
+
if (req)
|
|
339
|
+
this.request(req);
|
|
340
|
+
if (res)
|
|
341
|
+
this.response(res);
|
|
342
|
+
if (body)
|
|
343
|
+
this.body(body);
|
|
344
|
+
this._prepare();
|
|
345
|
+
super.info(this._message());
|
|
346
|
+
}
|
|
347
|
+
error(err) {
|
|
348
|
+
this._prepare();
|
|
349
|
+
super.error(this._message(err));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
export {
|
|
353
|
+
BaseLogger,
|
|
354
|
+
HTTPLogger as HttpLogger
|
|
355
|
+
};
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.index = {}));
|
|
3
|
+
})(this, function(exports2) {
|
|
4
|
+
"use strict";
|
|
5
|
+
require("winston-daily-rotate-file");
|
|
6
|
+
const { format: format$1, createLogger, transports, addColors } = require("winston");
|
|
7
|
+
const { inspect } = require("util");
|
|
8
|
+
const colors = {
|
|
9
|
+
error: "red",
|
|
10
|
+
warn: "yellow",
|
|
11
|
+
info: "green",
|
|
12
|
+
http: "magenta",
|
|
13
|
+
debug: "white"
|
|
14
|
+
};
|
|
15
|
+
addColors(colors);
|
|
16
|
+
const OPTIONS = {
|
|
17
|
+
dirname: "logs",
|
|
18
|
+
levels: {
|
|
19
|
+
error: 0,
|
|
20
|
+
warn: 1,
|
|
21
|
+
info: 2,
|
|
22
|
+
http: 3,
|
|
23
|
+
debug: 4
|
|
24
|
+
},
|
|
25
|
+
level: "info",
|
|
26
|
+
zippedArchive: false,
|
|
27
|
+
dateFormat: "YYYY-MM-DD HH:mm:ss:ms",
|
|
28
|
+
maxFiles: 2,
|
|
29
|
+
maxFileSize: "30m",
|
|
30
|
+
filename: "logs.log",
|
|
31
|
+
errorFilename: "error.log",
|
|
32
|
+
console: true,
|
|
33
|
+
exclude: [],
|
|
34
|
+
createSymlink: true,
|
|
35
|
+
symlinkName: "logs.log"
|
|
36
|
+
};
|
|
37
|
+
function errorToJSON(error) {
|
|
38
|
+
const { message, name, stack } = error;
|
|
39
|
+
return {
|
|
40
|
+
message,
|
|
41
|
+
name,
|
|
42
|
+
stack
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function fileFormatter(options) {
|
|
46
|
+
return format$1.combine(
|
|
47
|
+
// format.errors({stack: true}),
|
|
48
|
+
{
|
|
49
|
+
transform: (info) => {
|
|
50
|
+
const args = [info.message, ...info[Symbol.for("splat")] || []];
|
|
51
|
+
info.message = args.filter(Boolean).map((arg) => {
|
|
52
|
+
if (arg instanceof Error) {
|
|
53
|
+
return errorToJSON(arg);
|
|
54
|
+
}
|
|
55
|
+
return arg;
|
|
56
|
+
});
|
|
57
|
+
const msg = args.map((arg) => {
|
|
58
|
+
if (typeof arg == "object")
|
|
59
|
+
return inspect(arg, { compact: false, depth: Infinity });
|
|
60
|
+
return arg;
|
|
61
|
+
}).join(" ");
|
|
62
|
+
info[Symbol.for("message")] = `${info[Symbol.for("level")]}: ${msg}${info.stack ? " " + info.stack : ""}`;
|
|
63
|
+
if (options.exclude.some((string) => msg.includes(string)))
|
|
64
|
+
return null;
|
|
65
|
+
return info;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
format$1.timestamp({ format: options.dateFormat }),
|
|
69
|
+
format$1.json()
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
function consoleFormatter(options) {
|
|
73
|
+
return format$1.timestamp({ format: options.dateFormat }), format$1.colorize({ all: true });
|
|
74
|
+
}
|
|
75
|
+
function getFormatter(type, options) {
|
|
76
|
+
switch (type) {
|
|
77
|
+
case "file":
|
|
78
|
+
return fileFormatter(options);
|
|
79
|
+
case "access":
|
|
80
|
+
break;
|
|
81
|
+
case "console":
|
|
82
|
+
return consoleFormatter(options);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function createTransport(options) {
|
|
86
|
+
let formatter = getFormatter(options.format, options);
|
|
87
|
+
if (formatter) {
|
|
88
|
+
options.format = formatter;
|
|
89
|
+
} else {
|
|
90
|
+
delete options.format;
|
|
91
|
+
}
|
|
92
|
+
return new transports.DailyRotateFile(options);
|
|
93
|
+
}
|
|
94
|
+
class Logger {
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
this.options = { ...OPTIONS, ...options };
|
|
97
|
+
let trans = [
|
|
98
|
+
createTransport({
|
|
99
|
+
...this.options,
|
|
100
|
+
filename: this.options.filename,
|
|
101
|
+
symlinkName: this.options.filename
|
|
102
|
+
}),
|
|
103
|
+
createTransport({
|
|
104
|
+
...this.options,
|
|
105
|
+
filename: this.options.errorFilename,
|
|
106
|
+
symlinkName: this.options.errorFilename,
|
|
107
|
+
level: "error"
|
|
108
|
+
})
|
|
109
|
+
];
|
|
110
|
+
if (this.options.console) {
|
|
111
|
+
trans.push(new transports.Console({
|
|
112
|
+
format: getFormatter("console", this.options)
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
this.logger = createLogger({
|
|
116
|
+
level: this.options.level,
|
|
117
|
+
levels: this.options.levels,
|
|
118
|
+
exitOnError: false,
|
|
119
|
+
// format,
|
|
120
|
+
transports: trans,
|
|
121
|
+
exceptionHandlers: [
|
|
122
|
+
new transports.Console(),
|
|
123
|
+
new transports.File({ filename: `${this.options.dirname}/${this.options.errorFilename}` })
|
|
124
|
+
]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const redact$1 = (data, ...sensitiveKeysList) => {
|
|
129
|
+
var _a, _b;
|
|
130
|
+
if (typeof data === "object" && data !== null && !((_b = (_a = data == null ? void 0 : data.constructor) == null ? void 0 : _a.name) == null ? void 0 : _b.startsWith("model"))) {
|
|
131
|
+
if (Array.isArray(data)) {
|
|
132
|
+
return data.map((item) => redact$1(item, ...sensitiveKeysList));
|
|
133
|
+
}
|
|
134
|
+
const redactedData = {};
|
|
135
|
+
for (const key in data) {
|
|
136
|
+
if (data == null ? void 0 : data.hasOwnProperty(key)) {
|
|
137
|
+
if (sensitiveKeysList.includes(key)) {
|
|
138
|
+
redactedData[key] = "*****";
|
|
139
|
+
} else {
|
|
140
|
+
redactedData[key] = redact$1(data[key], ...sensitiveKeysList);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return redactedData;
|
|
145
|
+
} else {
|
|
146
|
+
return data;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const trim$1 = (data, length) => {
|
|
150
|
+
try {
|
|
151
|
+
let str = JSON.stringify(data);
|
|
152
|
+
if (str.length > length) {
|
|
153
|
+
return str.substring(0, length) + "... [TRIMMED]";
|
|
154
|
+
}
|
|
155
|
+
return data;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return "";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const http = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
161
|
+
__proto__: null,
|
|
162
|
+
redact: redact$1,
|
|
163
|
+
trim: trim$1
|
|
164
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
165
|
+
function toFixedNumber(num, digits, base) {
|
|
166
|
+
const pow = Math.pow(base ?? 10, digits);
|
|
167
|
+
return Math.round(num * pow) / pow;
|
|
168
|
+
}
|
|
169
|
+
function bytesToMB$1(b) {
|
|
170
|
+
return toFixedNumber(b / 1024 / 1024, 2, 10);
|
|
171
|
+
}
|
|
172
|
+
function formatBytes$1(bytes, decimals = 2) {
|
|
173
|
+
if (!+bytes)
|
|
174
|
+
return "0 Bytes";
|
|
175
|
+
const k = 1024;
|
|
176
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
177
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
178
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
179
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`;
|
|
180
|
+
}
|
|
181
|
+
const format = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
182
|
+
__proto__: null,
|
|
183
|
+
bytesToMB: bytesToMB$1,
|
|
184
|
+
formatBytes: formatBytes$1,
|
|
185
|
+
toFixedNumber
|
|
186
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
187
|
+
const pick = (obj, ...keys) => Object.fromEntries(
|
|
188
|
+
keys.filter((key) => key in obj).map((key) => [key, obj[key]])
|
|
189
|
+
);
|
|
190
|
+
const omit$1 = (obj, ...keys) => {
|
|
191
|
+
const result = { ...obj };
|
|
192
|
+
keys.forEach(function(prop) {
|
|
193
|
+
delete result[prop];
|
|
194
|
+
});
|
|
195
|
+
return result;
|
|
196
|
+
};
|
|
197
|
+
const object = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
198
|
+
__proto__: null,
|
|
199
|
+
omit: omit$1,
|
|
200
|
+
pick
|
|
201
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
202
|
+
const v8 = require("v8");
|
|
203
|
+
const { bytesToMB } = format;
|
|
204
|
+
function stats() {
|
|
205
|
+
try {
|
|
206
|
+
let h = v8.getHeapStatistics();
|
|
207
|
+
return {
|
|
208
|
+
"total_heap_size": bytesToMB(h.total_heap_size),
|
|
209
|
+
"total_heap_size_executable": bytesToMB(h.total_heap_size_executable),
|
|
210
|
+
"total_physical_size": bytesToMB(h.total_physical_size),
|
|
211
|
+
"total_available_size": bytesToMB(h.total_available_size),
|
|
212
|
+
"used_heap_size": bytesToMB(h.used_heap_size),
|
|
213
|
+
"heap_size_limit": bytesToMB(h.heap_size_limit),
|
|
214
|
+
"malloced_memory": bytesToMB(h.malloced_memory),
|
|
215
|
+
"peak_malloced_memory": bytesToMB(h.peak_malloced_memory),
|
|
216
|
+
"does_zap_garbage": h.does_zap_garbage,
|
|
217
|
+
"number_of_native_contexts": h.number_of_native_contexts,
|
|
218
|
+
"number_of_detached_contexts": h.number_of_detached_contexts
|
|
219
|
+
};
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return {};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
class BaseLogger {
|
|
225
|
+
constructor(component, options) {
|
|
226
|
+
this.logger = new Logger(options).logger.child({ component });
|
|
227
|
+
this.ctx = {};
|
|
228
|
+
this.startTime = Date.now();
|
|
229
|
+
this.absaluteStartTime = Date.now();
|
|
230
|
+
}
|
|
231
|
+
session(id) {
|
|
232
|
+
this.ctx.sessionID = id;
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
log() {
|
|
236
|
+
this.logger.info(...arguments, this.ctx);
|
|
237
|
+
this._stopMemProfile();
|
|
238
|
+
}
|
|
239
|
+
info() {
|
|
240
|
+
this.logger.info(...arguments, this.ctx);
|
|
241
|
+
this._stopMemProfile();
|
|
242
|
+
}
|
|
243
|
+
error() {
|
|
244
|
+
this.logger.error(...arguments, this.ctx);
|
|
245
|
+
this._stopMemProfile();
|
|
246
|
+
}
|
|
247
|
+
profile(action, options = {}) {
|
|
248
|
+
this.ctx.profiler = this.ctx.profiler || {};
|
|
249
|
+
if (action) {
|
|
250
|
+
let startTime = this.ctx.profiler[action] ? Date.now() - this.ctx.profiler[action] : this.startTime;
|
|
251
|
+
this.ctx.profiler[action] = Date.now() - startTime;
|
|
252
|
+
}
|
|
253
|
+
this.ctx.profiler.totalTime = Date.now() - this.absaluteStartTime;
|
|
254
|
+
if (!options.continue)
|
|
255
|
+
this.startTime = Date.now();
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
_stopMemProfile() {
|
|
259
|
+
if (this.memProfileInterval)
|
|
260
|
+
clearInterval(this.memProfileInterval);
|
|
261
|
+
}
|
|
262
|
+
profileMem(options = {}) {
|
|
263
|
+
this.ctx.maxMemory = this.ctx.maxMemory || 0;
|
|
264
|
+
this.ctx.memoryStats = this.ctx.memoryStats || [];
|
|
265
|
+
this.ctx.memoryUsage = this.ctx.memoryUsage || [];
|
|
266
|
+
this.ctx.memoryStatsIntervalMS = options.interval || 1e3;
|
|
267
|
+
this._stopMemProfile();
|
|
268
|
+
this.memProfileInterval = setInterval(() => {
|
|
269
|
+
let mem = stats();
|
|
270
|
+
this.ctx.memoryStats.push(mem);
|
|
271
|
+
this.ctx.memoryUsage.push(mem.used_heap_size);
|
|
272
|
+
if (mem.used_heap_size > this.ctx.maxMemory) {
|
|
273
|
+
this.ctx.maxMemory = mem.used_heap_size;
|
|
274
|
+
}
|
|
275
|
+
}, this.ctx.memoryStatsIntervalMS);
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
context(key, value) {
|
|
279
|
+
this.ctx[key] = value;
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const { redact, trim } = http;
|
|
284
|
+
const { formatBytes } = format;
|
|
285
|
+
const { omit } = object;
|
|
286
|
+
const MAX_BODY_LENGTH = 128;
|
|
287
|
+
class HTTPLogger extends BaseLogger {
|
|
288
|
+
constructor(startTime, options = {}) {
|
|
289
|
+
super("http", options);
|
|
290
|
+
this.startTime = startTime || Date.now();
|
|
291
|
+
}
|
|
292
|
+
request(req) {
|
|
293
|
+
this.session(req.sessionID);
|
|
294
|
+
this.req = req;
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
response(res) {
|
|
298
|
+
this.res = res;
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
body(data) {
|
|
302
|
+
this.data = data;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
_prepare() {
|
|
306
|
+
var _a, _b;
|
|
307
|
+
let req = this.req;
|
|
308
|
+
let res = this.res;
|
|
309
|
+
let body = this.data;
|
|
310
|
+
this.ctx.request = {
|
|
311
|
+
headers: redact(req.headers, "cookie"),
|
|
312
|
+
host: req.headers.host,
|
|
313
|
+
baseUrl: req.baseUrl,
|
|
314
|
+
url: req.originalUrl || req.url,
|
|
315
|
+
method: req.method,
|
|
316
|
+
body: redact(req.body, "password"),
|
|
317
|
+
params: req == null ? void 0 : req.params,
|
|
318
|
+
query: redact(req == null ? void 0 : req.query, "password"),
|
|
319
|
+
clientIP: ((_a = req == null ? void 0 : req.headers["x-forwarded-for"]) == null ? void 0 : _a.split(",")[0]) ?? (req == null ? void 0 : req.socket.remoteAddress)
|
|
320
|
+
};
|
|
321
|
+
this.ctx.response = {
|
|
322
|
+
headers: omit(res.getHeaders(), "set-cookie", "x-powered-by"),
|
|
323
|
+
statusCode: res.statusCode,
|
|
324
|
+
body: trim(body, MAX_BODY_LENGTH)
|
|
325
|
+
};
|
|
326
|
+
this.ctx.responseTimeMs = Date.now() - this.startTime;
|
|
327
|
+
this.ctx.responseSizeBytes = this.data ? JSON.stringify(this.data).length : 0;
|
|
328
|
+
this.ctx.user = (_b = req == null ? void 0 : req.user) == null ? void 0 : _b.id;
|
|
329
|
+
}
|
|
330
|
+
_message(msg) {
|
|
331
|
+
var _a;
|
|
332
|
+
let remoteAddress = this.req.ip || this.req._remoteAddress || this.req.connection && this.req.connection.remoteAddress || void 0;
|
|
333
|
+
let ip = this.ctx.request.clientIP;
|
|
334
|
+
let method = this.ctx.request.method;
|
|
335
|
+
let url = this.ctx.request.url;
|
|
336
|
+
let statusCode = this.ctx.response.statusCode;
|
|
337
|
+
let responseTimeMs = this.ctx.responseTimeMs + "ms";
|
|
338
|
+
let responseSize = formatBytes((_a = JSON.stringify(this.data)) == null ? void 0 : _a.length);
|
|
339
|
+
return `${method} ${url} ${statusCode} ${responseTimeMs} ${responseSize} ${ip} ${remoteAddress} ${msg || ""}`;
|
|
340
|
+
}
|
|
341
|
+
success(req, res, body) {
|
|
342
|
+
if (req)
|
|
343
|
+
this.request(req);
|
|
344
|
+
if (res)
|
|
345
|
+
this.response(res);
|
|
346
|
+
if (body)
|
|
347
|
+
this.body(body);
|
|
348
|
+
this._prepare();
|
|
349
|
+
super.info(this._message());
|
|
350
|
+
}
|
|
351
|
+
error(err) {
|
|
352
|
+
this._prepare();
|
|
353
|
+
super.error(this._message(err));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports2.BaseLogger = BaseLogger;
|
|
357
|
+
exports2.HttpLogger = HTTPLogger;
|
|
358
|
+
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
359
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "infront-logger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"main": "./dist/index.umd.js",
|
|
9
|
+
"module": "./dist/index.es.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.es.js",
|
|
13
|
+
"require": "./dist/index.umd.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
|
21
|
+
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
22
|
+
"vite": "^5.0.12"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"winston": "^3.11.0",
|
|
26
|
+
"winston-daily-rotate-file": "^4.7.1",
|
|
27
|
+
"infront-utils": "^1.0.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
31
|
+
"dev": "vite",
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"preview": "vite preview"
|
|
34
|
+
}
|
|
35
|
+
}
|