zeddemore-logger 1.0.6 → 1.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/lib/color.js +100 -25
- package/lib/enums.js +67 -0
- package/lib/mappings/color_mappings.js +33 -21
- package/lib/mappings/http_mappings.js +18 -0
- package/lib/{http.js → request.js} +23 -3
- package/lib/response.js +36 -0
- package/lib/zeddemore-logger.js +148 -23
- package/package.json +1 -1
package/lib/color.js
CHANGED
|
@@ -1,43 +1,119 @@
|
|
|
1
|
-
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
1
|
+
// Copyright (c) 2024-2025 by Beardon Services, Inc.
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
|
|
6
6
|
const colorMappings = require('./mappings/color_mappings');
|
|
7
|
+
const enums = require('./enums');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const { chalkLayers: cl, chalkFormats: cs } = enums;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param ansiColor {number}
|
|
13
|
+
* @param layer {string}
|
|
14
|
+
* @param string {string}
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function chalkAnsi(ansiColor, layer = cl.FOREGROUND, string) {
|
|
18
|
+
if (!string) return '';
|
|
19
|
+
if (!ansiColor) return string;
|
|
20
|
+
switch (layer) {
|
|
21
|
+
case cl.BACKGROUND: return `\x1B[${ ansiColor }m${ string }\x1B[49m`;
|
|
22
|
+
case cl.FOREGROUND: return `\x1B[${ ansiColor }m${ string }\x1B[39m`;
|
|
23
|
+
default: return string;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param ansiColor {number}
|
|
29
|
+
* @param layer {string}
|
|
30
|
+
* @param string {string}
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function chalkAnsi256(ansiColor, layer = cl.FOREGROUND, string) {
|
|
34
|
+
if (!string) return '';
|
|
35
|
+
if (!ansiColor) return string;
|
|
36
|
+
switch (layer) {
|
|
37
|
+
case cl.BACKGROUND: return chalk.bgAnsi256(ansiColor)(string);
|
|
38
|
+
case cl.FOREGROUND: return chalk.ansi256(ansiColor)(string);
|
|
39
|
+
default: return string;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param hexColor {string}
|
|
45
|
+
* @param layer {string}
|
|
46
|
+
* @param string {string}
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function chalkHex(hexColor, layer = cl.FOREGROUND, string) {
|
|
50
|
+
if (!string) return '';
|
|
51
|
+
if (!hexColor) return string;
|
|
52
|
+
hexColor = (hexColor[ 0 ] === '#') ? hexColor : `#${ hexColor }`;
|
|
53
|
+
switch (layer) {
|
|
54
|
+
case cl.BACKGROUND: return chalk.bgHex(hexColor)(string);
|
|
55
|
+
case cl.FOREGROUND: return chalk.hex(hexColor)(string);
|
|
56
|
+
default: return string;
|
|
57
|
+
}
|
|
11
58
|
}
|
|
12
59
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
60
|
+
function chalkHttpStatuses(statusCode, string = null) {
|
|
61
|
+
if (!string) string = statusCode;
|
|
62
|
+
const colorMap = _.find(colorMappings.httpStatusColors, (httpStatusColor) => httpStatusColor.range.includes(string));
|
|
63
|
+
if (!colorMap || !colorMap.style) return string;
|
|
64
|
+
let chalkedMatch = string;
|
|
65
|
+
if (colorMap.style.fore) chalkedMatch = `\x1B[${ colorMap.style.fore }m${ string }\x1B[39m`;
|
|
18
66
|
return chalkedMatch;
|
|
19
67
|
}
|
|
20
68
|
|
|
21
|
-
function
|
|
69
|
+
function chalkHttpVerbs(verb, string = null, replaceGlobal = false) {
|
|
22
70
|
if (!string) string = verb;
|
|
23
|
-
return chalkViaColorMap(verb, string, colorMappings.httpVerbColors,
|
|
71
|
+
return chalkViaColorMap(verb, string, colorMappings.httpVerbColors, replaceGlobal);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param redColor {number}
|
|
76
|
+
* @param greenColor {number}
|
|
77
|
+
* @param blueColor {number}
|
|
78
|
+
* @param layer {string}
|
|
79
|
+
* @param string {string}
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function chalkRgb(redColor, greenColor, blueColor, layer = cl.FOREGROUND, string) {
|
|
83
|
+
if (!string) return '';
|
|
84
|
+
if (!redColor || !greenColor || !blueColor) return string;
|
|
85
|
+
switch (layer) {
|
|
86
|
+
case cl.BACKGROUND: return chalk.bgRgb(redColor, greenColor, blueColor)(string);
|
|
87
|
+
case cl.FOREGROUND: return chalk.rgb(redColor, greenColor, blueColor)(string);
|
|
88
|
+
default: return string;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function chalkTarget(target, style) {
|
|
93
|
+
if (!style) return target;
|
|
94
|
+
const fore = style.fore;
|
|
95
|
+
const back = style.back;
|
|
96
|
+
switch (style.format) {
|
|
97
|
+
case cs.ANSI: return chalkAnsi(back, cl.BACKGROUND, chalkAnsi(fore, cl.FOREGROUND, target));
|
|
98
|
+
case cs.ANSI256: return chalkAnsi256(back, cl.BACKGROUND, chalkAnsi256(fore, cl.FOREGROUND, target));
|
|
99
|
+
case cs.HEX: return chalkHex(back, cl.BACKGROUND, chalkHex(fore, cl.FOREGROUND, target));
|
|
100
|
+
case cs.RGB: return chalkRgb(back.r, back.g, back.b, cl.BACKGROUND, chalkRgb(fore.r, fore.g, fore.b, cl.FOREGROUND, target));
|
|
101
|
+
default: return target;
|
|
102
|
+
}
|
|
24
103
|
}
|
|
25
104
|
|
|
26
|
-
function chalkViaColorMap(match,
|
|
105
|
+
function chalkViaColorMap(match, target, colorsMap, replaceGlobal = true) {
|
|
27
106
|
function fixPattern(pattern) {
|
|
28
|
-
return pattern.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
|
|
107
|
+
return _.isString(pattern) ? pattern.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1') : pattern;
|
|
29
108
|
}
|
|
30
|
-
if (!match || !_.isString(match) || !
|
|
109
|
+
if (!match || !_.isString(match) || !target || !_.isString(target) || !_.isObject(colorsMap)) return target;
|
|
31
110
|
const cleanMatch = match.replace(/[^A-Z]/g, '');
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
34
|
-
const flags =
|
|
111
|
+
const colorMap = _.find(colorsMap, { match: cleanMatch });
|
|
112
|
+
if (!colorMap || !colorMap.style) return target;
|
|
113
|
+
const flags = replaceGlobal ? 'g' : '';
|
|
35
114
|
const re = new RegExp(fixPattern(match), flags);
|
|
36
|
-
if (!
|
|
37
|
-
|
|
38
|
-
if (mapping.fore) chalkedMatch = chalk.hex(mapping.fore)(chalkedMatch);
|
|
39
|
-
if (mapping.back) chalkedMatch = chalk.bgHex(mapping.back)(chalkedMatch);
|
|
40
|
-
return string.replace(re, chalkedMatch);
|
|
115
|
+
if (!target.match(re)) return target;
|
|
116
|
+
return target.replace(re, chalkTarget(cleanMatch, colorMap.style));
|
|
41
117
|
}
|
|
42
118
|
|
|
43
119
|
// adapted from https://stackoverflow.com/a/44134328
|
|
@@ -100,8 +176,7 @@ function stringToIdempotentHslValues(str, hslOptions) {
|
|
|
100
176
|
}
|
|
101
177
|
|
|
102
178
|
module.exports = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
chalkHttpVerb,
|
|
179
|
+
chalkHttpStatuses,
|
|
180
|
+
chalkHttpVerbs,
|
|
106
181
|
stringToIdempotentHexColor,
|
|
107
182
|
};
|
package/lib/enums.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
2
|
|
|
3
|
+
const ansi256Colors = {
|
|
4
|
+
LOG_LEVEL_DEBUG: 34, // blue
|
|
5
|
+
LOG_LEVEL_ERROR: 31, // red
|
|
6
|
+
LOG_LEVEL_HTTP: 32, // green
|
|
7
|
+
LOG_LEVEL_INFO: 32, // green
|
|
8
|
+
LOG_LEVEL_SILLY: 35, // magenta
|
|
9
|
+
LOG_LEVEL_VERBOSE: 36, // cyan
|
|
10
|
+
LOG_LEVEL_WARNING: 33, // yellow
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ansiColors = {
|
|
14
|
+
HTTP_CLIENT_ERROR: 33, // red
|
|
15
|
+
HTTP_INFORMATIONAL: 0,
|
|
16
|
+
HTTP_REDIRECTION: 36, // blue
|
|
17
|
+
HTTP_SERVER_ERROR: 31, // red
|
|
18
|
+
HTTP_SUCCESSFUL: 32, // green
|
|
19
|
+
};
|
|
20
|
+
|
|
3
21
|
const apiLogLevelCaptions = {
|
|
4
22
|
DEFAULT: 'Default',
|
|
5
23
|
ERROR: 'Error',
|
|
@@ -22,6 +40,18 @@ const apiLogLevels = {
|
|
|
22
40
|
SILLY: 6,
|
|
23
41
|
};
|
|
24
42
|
|
|
43
|
+
const chalkFormats = {
|
|
44
|
+
ANSI: 'ansi',
|
|
45
|
+
ANSI256: 'ansi256',
|
|
46
|
+
HEX: 'hex',
|
|
47
|
+
RGB: 'rgb',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const chalkLayers = {
|
|
51
|
+
BACKGROUND: 'bg',
|
|
52
|
+
FOREGROUND: 'fg',
|
|
53
|
+
};
|
|
54
|
+
|
|
25
55
|
const databaseOperations = {
|
|
26
56
|
DELETE: 'DELETE',
|
|
27
57
|
INSERT: 'INSERT',
|
|
@@ -30,6 +60,34 @@ const databaseOperations = {
|
|
|
30
60
|
UPDATE: 'UPDATE',
|
|
31
61
|
};
|
|
32
62
|
|
|
63
|
+
const hexColors = {
|
|
64
|
+
BLACK: '#000000',
|
|
65
|
+
CONSOLE_WHITE: '#ECECEC',
|
|
66
|
+
DB_DELETE: '#F22613',
|
|
67
|
+
DB_INSERT: '#00B16A',
|
|
68
|
+
DB_SELECT: '#1E90FF',
|
|
69
|
+
DB_TRUNCATE: '#750505',
|
|
70
|
+
DB_UPDATE: '#F9690E',
|
|
71
|
+
HTTP_CONNECT: '#FFFFFF',
|
|
72
|
+
HTTP_DELETE: '#EF968A',
|
|
73
|
+
HTTP_GET: '#67D193',
|
|
74
|
+
HTTP_HEAD: '#68D696',
|
|
75
|
+
HTTP_OPTIONS: '#E55AA8',
|
|
76
|
+
HTTP_PATCH: '#C0A8E1',
|
|
77
|
+
HTTP_POST: '#F4DA7A',
|
|
78
|
+
HTTP_PUT: '#74AEF6',
|
|
79
|
+
HTTP_TRACE: '#FFFFFF',
|
|
80
|
+
OSU_ORANGE: '#FF6600',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const httpStatusCodeGroups = {
|
|
84
|
+
INFORMATIONAL: 1,
|
|
85
|
+
SUCCESSFUL: 2,
|
|
86
|
+
REDIRECTION: 3,
|
|
87
|
+
CLIENT_ERROR: 4,
|
|
88
|
+
SERVER_ERROR: 5,
|
|
89
|
+
};
|
|
90
|
+
|
|
33
91
|
const httpMethods = {
|
|
34
92
|
GET: 'GET',
|
|
35
93
|
HEAD: 'HEAD',
|
|
@@ -44,9 +102,12 @@ const httpMethods = {
|
|
|
44
102
|
|
|
45
103
|
const httpRequestHeaders = {
|
|
46
104
|
ALLOW: 'Allow',
|
|
105
|
+
APP_VERSION: 'App-Version',
|
|
47
106
|
AUTHORIZATION: 'Authorization',
|
|
48
107
|
BEARER_PREFIX: 'Bearer',
|
|
108
|
+
FORWARDED_FOR: 'X-Forwarded-For',
|
|
49
109
|
REQUEST_ID: 'X-Request-Id',
|
|
110
|
+
USER_AGENT: 'User-Agent',
|
|
50
111
|
};
|
|
51
112
|
|
|
52
113
|
const httpResponseHeaders = {
|
|
@@ -84,9 +145,15 @@ const winstonLogLevels = {
|
|
|
84
145
|
};
|
|
85
146
|
|
|
86
147
|
module.exports = {
|
|
148
|
+
ansi256Colors,
|
|
149
|
+
ansiColors,
|
|
87
150
|
apiLogLevelCaptions,
|
|
88
151
|
apiLogLevels,
|
|
152
|
+
chalkFormats,
|
|
153
|
+
chalkLayers,
|
|
89
154
|
databaseOperations,
|
|
155
|
+
hexColors,
|
|
156
|
+
httpStatusCodeGroups,
|
|
90
157
|
httpMethods,
|
|
91
158
|
httpRequestHeaders,
|
|
92
159
|
httpResponseHeaders,
|
|
@@ -1,40 +1,52 @@
|
|
|
1
|
-
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
1
|
+
// Copyright (c) 2024-2025 by Beardon Services, Inc.
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const enums = require('../enums');
|
|
5
5
|
|
|
6
|
-
const { databaseOperations: dbo,
|
|
6
|
+
const { ansi256Colors: a256c, ansiColors: ac, databaseOperations: dbo, chalkFormats: cs, hexColors: hc,
|
|
7
|
+
httpMethods: http, winstonLogLevels: wll } = enums;
|
|
7
8
|
|
|
8
9
|
const databaseOperationColors = [
|
|
9
|
-
{ match: dbo.DELETE, fore:
|
|
10
|
-
{ match: dbo.INSERT, fore:
|
|
11
|
-
{ match: dbo.SELECT, fore:
|
|
12
|
-
{ match: dbo.TRUNCATE, fore:
|
|
13
|
-
{ match: dbo.UPDATE, fore:
|
|
10
|
+
{ match: dbo.DELETE, style: { format: cs.HEX, fore: hc.DB_DELETE, back: null } },
|
|
11
|
+
{ match: dbo.INSERT, style: { format: cs.HEX, fore: hc.DB_INSERT, back: null } },
|
|
12
|
+
{ match: dbo.SELECT, style: { format: cs.HEX, fore: hc.DB_SELECT, back: null } },
|
|
13
|
+
{ match: dbo.TRUNCATE, style: { format: cs.HEX, fore: hc.DB_TRUNCATE, back: null } },
|
|
14
|
+
{ match: dbo.UPDATE, style: { format: cs.HEX, fore: hc.DB_UPDATE, back: null } },
|
|
14
15
|
];
|
|
15
16
|
|
|
16
17
|
const httpStatusColors = [
|
|
17
|
-
{ range: _.range(500, 600),
|
|
18
|
-
{ range: _.range(400, 500),
|
|
19
|
-
{ range: _.range(300, 400),
|
|
20
|
-
{ range: _.range(200, 300),
|
|
21
|
-
{ range: _.range(100, 200),
|
|
18
|
+
{ range: _.range(500, 600), style: { format: cs.ANSI, fore: ac.HTTP_SERVER_ERROR, back: null } },
|
|
19
|
+
{ range: _.range(400, 500), style: { format: cs.ANSI, fore: ac.HTTP_CLIENT_ERROR, back: null } },
|
|
20
|
+
{ range: _.range(300, 400), style: { format: cs.ANSI, fore: ac.HTTP_REDIRECTION, back: null } },
|
|
21
|
+
{ range: _.range(200, 300), style: { format: cs.ANSI, fore: ac.HTTP_SUCCESSFUL, back: null } },
|
|
22
|
+
{ range: _.range(100, 200), style: { format: cs.ANSI, fore: ac.HTTP_INFORMATIONAL, back: null } },
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
const httpVerbColors = [
|
|
25
|
-
{ match: http.
|
|
26
|
-
{ match: http.
|
|
27
|
-
{ match: http.
|
|
28
|
-
{ match: http.
|
|
29
|
-
{ match: http.
|
|
30
|
-
{ match: http.
|
|
31
|
-
{ match: http.
|
|
32
|
-
{ match: http.
|
|
33
|
-
{ match: http.
|
|
26
|
+
{ match: http.CONNECT, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_CONNECT } },
|
|
27
|
+
{ match: http.DELETE, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_DELETE } },
|
|
28
|
+
{ match: http.GET, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_GET } },
|
|
29
|
+
{ match: http.HEAD, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_HEAD } },
|
|
30
|
+
{ match: http.OPTIONS, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_OPTIONS } },
|
|
31
|
+
{ match: http.PATCH, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_PATCH } },
|
|
32
|
+
{ match: http.POST, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_POST } },
|
|
33
|
+
{ match: http.PUT, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_PUT } },
|
|
34
|
+
{ match: http.TRACE, style: { format: cs.HEX, fore: hc.BLACK, back: hc.HTTP_TRACE } },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const logLevelColors = [
|
|
38
|
+
{ match: wll.ERROR, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_ERROR, back: null } },
|
|
39
|
+
{ match: wll.WARNING, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_WARNING, back: null } },
|
|
40
|
+
{ match: wll.INFO, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_INFO, back: null } },
|
|
41
|
+
{ match: wll.HTTP, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_HTTP, back: null } },
|
|
42
|
+
{ match: wll.VERBOSE, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_VERBOSE, back: null } },
|
|
43
|
+
{ match: wll.DEBUG, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_DEBUG, back: null } },
|
|
44
|
+
{ match: wll.SILLY, style: { format: cs.ANSI256, fore: a256c.LOG_LEVEL_SILLY, back: null } },
|
|
34
45
|
];
|
|
35
46
|
|
|
36
47
|
module.exports = {
|
|
37
48
|
databaseOperationColors,
|
|
38
49
|
httpStatusColors,
|
|
39
50
|
httpVerbColors,
|
|
51
|
+
logLevelColors,
|
|
40
52
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const enums = require('../enums');
|
|
5
|
+
|
|
6
|
+
const { httpStatusCodeGroups: hscg } = enums;
|
|
7
|
+
|
|
8
|
+
const httpStatusCodeGroupMap = [
|
|
9
|
+
{ group: hscg.INFORMATIONAL, range: _.range(100, 200) },
|
|
10
|
+
{ group: hscg.SUCCESSFUL, range: _.range(200, 300) },
|
|
11
|
+
{ group: hscg.REDIRECTION, range: _.range(300, 400) },
|
|
12
|
+
{ group: hscg.CLIENT_ERROR, range: _.range(400, 500) },
|
|
13
|
+
{ group: hscg.SERVER_ERROR, range: _.range(500, 600) },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
httpStatusCodeGroupMap,
|
|
18
|
+
};
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const enums = require('./enums');
|
|
6
|
+
|
|
7
|
+
const { httpRequestHeaders: rqh } = enums;
|
|
8
|
+
|
|
9
|
+
function extractToken(req) {
|
|
10
|
+
let token;
|
|
11
|
+
const authHeader = req.header(rqh.AUTHORIZATION);
|
|
12
|
+
if (authHeader && authHeader.startsWith(`${ rqh.BEARER_PREFIX } `)) {
|
|
13
|
+
token = req.header(rqh.AUTHORIZATION).split(' ')[ 1 ];
|
|
14
|
+
} else if (req.query && req.query.token) {
|
|
15
|
+
token = req.query.token;
|
|
16
|
+
}
|
|
17
|
+
return token;
|
|
5
18
|
}
|
|
6
19
|
|
|
7
20
|
function getRequestResponseTime(req, res) {
|
|
@@ -9,6 +22,12 @@ function getRequestResponseTime(req, res) {
|
|
|
9
22
|
return (res._startAt[ 0 ] - req._startAt[ 0 ]) * 1e3 + (res._startAt[ 1 ] - req._startAt[ 1 ]) * 1e-6;
|
|
10
23
|
}
|
|
11
24
|
|
|
25
|
+
function isRequestOfMethod(req, method) {
|
|
26
|
+
const methods = Array.isArray(method) ? method : [ method ];
|
|
27
|
+
if (!req || !req.method || _.isEmpty(methods)) return false;
|
|
28
|
+
return methods.includes(req.method);
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
function parseRequest(req) {
|
|
13
32
|
if (!req) return { };
|
|
14
33
|
const body = req.body || { };
|
|
@@ -41,7 +60,8 @@ function parseRequestUserAgent(req) {
|
|
|
41
60
|
}
|
|
42
61
|
|
|
43
62
|
module.exports = {
|
|
44
|
-
|
|
63
|
+
extractToken,
|
|
45
64
|
getRequestResponseTime,
|
|
65
|
+
isRequestOfMethod,
|
|
46
66
|
parseRequest,
|
|
47
67
|
};
|
package/lib/response.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const enums = require('./enums');
|
|
6
|
+
const { httpStatusCodeGroupMap } = require('./mappings/http_mappings');
|
|
7
|
+
|
|
8
|
+
const { httpStatusCodeGroups: hscg } = enums;
|
|
9
|
+
|
|
10
|
+
function areHeadersSent(res) {
|
|
11
|
+
return typeof (res.headersSent !== 'boolean') ? Boolean(res._header) : res.headersSent;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isResponseStatusCodeInGroup(statusCode, group) {
|
|
15
|
+
if (!statusCode || !group) return false;
|
|
16
|
+
const httpStatusCodeGroupMapEntry = _.find(httpStatusCodeGroupMap, { group });
|
|
17
|
+
if (!httpStatusCodeGroupMapEntry) return false;
|
|
18
|
+
return httpStatusCodeGroupMapEntry.range.includes(+statusCode);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isResponseStatusCodeRedirection(statusCode) {
|
|
22
|
+
return isResponseStatusCodeInGroup(statusCode, hscg.REDIRECTION);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isResponseStatusCodeSuccessful(statusCode) {
|
|
26
|
+
return isResponseStatusCodeInGroup(statusCode, hscg.SUCCESSFUL);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isResponseSuccessful(statusCode) {
|
|
30
|
+
return isResponseStatusCodeSuccessful(statusCode) || isResponseStatusCodeRedirection(statusCode);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
areHeadersSent,
|
|
35
|
+
isResponseSuccessful,
|
|
36
|
+
};
|
package/lib/zeddemore-logger.js
CHANGED
|
@@ -7,15 +7,17 @@ const morgan = require('morgan');
|
|
|
7
7
|
const sizeof = require('object-sizeof');
|
|
8
8
|
const { v4: uuid } = require('uuid');
|
|
9
9
|
|
|
10
|
-
const { areHeadersSent,
|
|
11
|
-
const {
|
|
10
|
+
const { areHeadersSent, isResponseSuccessful } = require('./response');
|
|
11
|
+
const { chalkHttpStatuses, chalkHttpVerbs, stringToIdempotentHexColor } = require('./color');
|
|
12
12
|
const { convertBytes } = require('./math');
|
|
13
13
|
const { convertMilliseconds } = require('./date');
|
|
14
14
|
const enums = require('./enums');
|
|
15
|
+
const { extractToken, getRequestResponseTime, isRequestOfMethod, parseRequest } = require('./request');
|
|
15
16
|
const { logLevelMap } = require('./mappings/log_mappings');
|
|
16
17
|
|
|
17
18
|
const { colorize, combine, label, metadata, printf, timestamp } = format;
|
|
18
|
-
const { morganFormats: mf, httpRequestHeaders: rqh, httpResponseHeaders: rsh, winstonLogLevelNames: wlln,
|
|
19
|
+
const { morganFormats: mf, httpMethods: http, httpRequestHeaders: rqh, httpResponseHeaders: rsh, winstonLogLevelNames: wlln,
|
|
20
|
+
winstonLogLevels: wll } = enums;
|
|
19
21
|
|
|
20
22
|
const COLOR_CONSOLE_WHITE_HEX = '#ECECEC';
|
|
21
23
|
const DEFAULT_REQUEST_ID_ATTRIBUTE = 'requestId';
|
|
@@ -35,27 +37,64 @@ function winstonLogLevelIdToName(levelId) {
|
|
|
35
37
|
|
|
36
38
|
class ZeddemoreLogger {
|
|
37
39
|
|
|
40
|
+
#forceLogLevel = false;
|
|
41
|
+
#logLevel = wll.INFO;
|
|
42
|
+
|
|
38
43
|
constructor(options) {
|
|
39
44
|
options = options || { };
|
|
40
|
-
this.
|
|
45
|
+
this.apiCallModel = options.apiCallModel || null;
|
|
46
|
+
this.apiRoutes = options.apiRoutes || [ ];
|
|
47
|
+
this.apiVersion = options.apiVersion || null;
|
|
41
48
|
this.defaultLabel = options.defaultLabel || 'zeddemore-logger';
|
|
49
|
+
this.disableApiCallLogging = options.hasOwnProperty('disableApiCallLogging') ? options.disableApiCallLogging : false;
|
|
50
|
+
this.disableRouteLogging = options.hasOwnProperty('disableRouteLogging') ? options.disableRouteLogging : false;
|
|
42
51
|
this.disableMorganLogging = options.hasOwnProperty('disableMorganLogging') ? options.disableMorganLogging : false;
|
|
43
|
-
this.displayCaller = options.hasOwnProperty('displayCaller') ? options.
|
|
52
|
+
this.displayCaller = options.hasOwnProperty('displayCaller') ? options.displayCaller : true;
|
|
44
53
|
this.displayIpAddress = options.hasOwnProperty('displayIpAddress') ? options.displayIpAddress : true;
|
|
45
54
|
this.displayRequestId = options.hasOwnProperty('displayRequestId') ? options.displayRequestId : true;
|
|
46
55
|
this.customCallerSettings = options.customCallerSettings || [ ];
|
|
47
56
|
this.defaultUserLogLevel = options.defaultUserLogLevel || DEFAULT_USER_LOG_LEVEL;
|
|
48
|
-
this
|
|
49
|
-
this
|
|
57
|
+
this.#forceLogLevel = options.hasOwnProperty('forceLogLevel') ? options.forceLogLevel : false;
|
|
58
|
+
this.#logLevel = options.hasOwnProperty('logLevel') ? options.logLevel : wll.INFO;
|
|
50
59
|
this.morganFormat = options.morganFormat || mf.DEV_ENHANCED;
|
|
60
|
+
this.routeModel = options.routeModel || null;
|
|
61
|
+
this.suppressNonMutatingRequestApiCallLogging = options.hasOwnProperty('suppressNonMutatingRequestApiCallLogging') ? options.suppressNonMutatingRequestApiCallLogging : false;
|
|
62
|
+
this.suppressSuccessfulRequestApiCallLogging = options.hasOwnProperty('suppressSuccessfulRequestApiCallLogging') ? options.suppressSuccessfulRequestApiCallLogging : false;
|
|
51
63
|
this.timestampFormat = options.timestampFormat || 'YYYY-MM-DD HH:mm:ss';
|
|
52
64
|
this.requestIdAttribute = options.requestIdAttribute || DEFAULT_REQUEST_ID_ATTRIBUTE;
|
|
53
65
|
this.userGetFn = options.userGetFn || this.#userGetFn;
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
get apiCallLoggingEnabled() {
|
|
69
|
+
return !this.disableApiCallLogging && !!this.apiCallModel;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get forceLogLevel() {
|
|
73
|
+
return this.#forceLogLevel;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get logLevel() {
|
|
77
|
+
return this.#logLevel;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get logLevelName() {
|
|
81
|
+
return winstonLogLevelIdToName(this.#logLevel);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get routeLoggingEnabled() {
|
|
85
|
+
return !this.disableRouteLogging && !!this.routeModel;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
set forceLogLevel(doForce) {
|
|
89
|
+
this.#forceLogLevel = !!doForce;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
set logLevel(level) {
|
|
93
|
+
this.#logLevel = massageLogLevel(level);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#buildWinstonFormat = (options, additionalFormats = [ ]) => {
|
|
57
97
|
options = options || { };
|
|
58
|
-
const colorizedTemplate = this.#colorizedTemplate.bind(this);
|
|
59
98
|
const formats = [
|
|
60
99
|
format((info) => {
|
|
61
100
|
info.id = info.id || options.id || null;
|
|
@@ -72,13 +111,13 @@ class ZeddemoreLogger {
|
|
|
72
111
|
return combine(...additionalFormats.concat(formats));
|
|
73
112
|
}
|
|
74
113
|
|
|
75
|
-
#buildWinstonTransport = (logLevel = this
|
|
76
|
-
logLevel = logLevel || this
|
|
114
|
+
#buildWinstonTransport = (logLevel = this.#logLevel) => {
|
|
115
|
+
logLevel = logLevel || this.#logLevel;
|
|
77
116
|
const level = winstonLogLevelIdToName(logLevel);
|
|
78
117
|
return new transports.Console({ level, name: WINSTON_CONSOLE_TRANSPORT_NAME });
|
|
79
118
|
}
|
|
80
119
|
|
|
81
|
-
#buildWinstonTransports = (logLevel = this
|
|
120
|
+
#buildWinstonTransports = (logLevel = this.#logLevel, additionalTransports = [ ]) => {
|
|
82
121
|
return additionalTransports.concat([ this.#buildWinstonTransport(logLevel) ]);
|
|
83
122
|
}
|
|
84
123
|
|
|
@@ -91,7 +130,7 @@ class ZeddemoreLogger {
|
|
|
91
130
|
|
|
92
131
|
#colorizeResponse(message) {
|
|
93
132
|
const verb = message.split(' ')[ 0 ];
|
|
94
|
-
return
|
|
133
|
+
return chalkHttpVerbs(verb, message);
|
|
95
134
|
}
|
|
96
135
|
|
|
97
136
|
#colorizedTemplate = (info) => {
|
|
@@ -177,7 +216,7 @@ class ZeddemoreLogger {
|
|
|
177
216
|
function buildStatusColoredToken(res) {
|
|
178
217
|
const statusCode = areHeadersSent(res) ? res.statusCode : null;
|
|
179
218
|
if (!statusCode) return '-';
|
|
180
|
-
return
|
|
219
|
+
return chalkHttpStatuses(statusCode);
|
|
181
220
|
}
|
|
182
221
|
morgan.token('content-length-format', (req, res) => { return buildContentLengthFormatToken(res); });
|
|
183
222
|
morgan.token('response-time-format', (req, res) => { return buildResponseTimeFormatToken(req, res); });
|
|
@@ -192,9 +231,20 @@ class ZeddemoreLogger {
|
|
|
192
231
|
return zeddemoreLogger.createLogger(options);
|
|
193
232
|
}
|
|
194
233
|
|
|
234
|
+
#matchPathToRoute = (path) => {
|
|
235
|
+
if (!path) return null;
|
|
236
|
+
let matchedRoute = null;
|
|
237
|
+
for (const route of this.apiRoutes) {
|
|
238
|
+
if (path.match(route.regexp)) {
|
|
239
|
+
matchedRoute = route.path;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return matchedRoute;
|
|
244
|
+
}
|
|
245
|
+
|
|
195
246
|
morganMiddleware = (options) => {
|
|
196
247
|
options = options || { };
|
|
197
|
-
const additionalWriteFn = (_.isFunction(options.additionalWriteFn) ? options.additionalWriteFn : null) || this.additionalWriteFn;
|
|
198
248
|
const disableMorganLogging = options.hasOwnProperty('disableMorganLogging') ? options.disableMorganLogging : this.disableMorganLogging;
|
|
199
249
|
const morganFormat = options.morganFormat || this.morganFormat;
|
|
200
250
|
const userGetFn = (_.isFunction(options.userGetFn) ? options.userGetFn : null) || this.userGetFn;
|
|
@@ -204,7 +254,7 @@ class ZeddemoreLogger {
|
|
|
204
254
|
skip: () => disableMorganLogging,
|
|
205
255
|
stream: {
|
|
206
256
|
write: (message) => {
|
|
207
|
-
if (
|
|
257
|
+
if (this.apiCallLoggingEnabled) this.#writeApiCallToDatabase(req, res, options);
|
|
208
258
|
const logOptions = { };
|
|
209
259
|
const user = userGetFn ? userGetFn(req, res) : null;
|
|
210
260
|
if (user) logOptions.user = user;
|
|
@@ -227,6 +277,21 @@ class ZeddemoreLogger {
|
|
|
227
277
|
return next();
|
|
228
278
|
}
|
|
229
279
|
|
|
280
|
+
routeLoggingMiddleware = (req, res, next) => {
|
|
281
|
+
if (!this.routeLoggingEnabled) return next();
|
|
282
|
+
try {
|
|
283
|
+
const routeValues = {
|
|
284
|
+
route: req.path,
|
|
285
|
+
method: req.method,
|
|
286
|
+
lastCalled: new Date().toISOString(),
|
|
287
|
+
};
|
|
288
|
+
this.#writeRouteToDatabase(routeValues);
|
|
289
|
+
} catch (e) {
|
|
290
|
+
} finally {
|
|
291
|
+
next();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
230
295
|
#userGetFn(req, res) {
|
|
231
296
|
return ((res && res.locals) ? res.locals.user : null)
|
|
232
297
|
|| ((req && req.locals) ? req.locals.user : null)
|
|
@@ -237,21 +302,81 @@ class ZeddemoreLogger {
|
|
|
237
302
|
const requestId = this.#getRequestId(req);
|
|
238
303
|
const user = this.userGetFn(req, res) || { };
|
|
239
304
|
if (!requestId) return next();
|
|
240
|
-
const username = user.login || null;
|
|
305
|
+
const username = user.login || user.username || null;
|
|
241
306
|
let ipAddress = user.ipAddress || null;
|
|
242
307
|
if (!ipAddress) {
|
|
243
308
|
const requestValues = parseRequest(req);
|
|
244
309
|
ipAddress = requestValues.ipAddress;
|
|
245
310
|
}
|
|
246
311
|
let userLogLevel = !_.isNil(user.logLevel) ? user.logLevel : this.defaultUserLogLevel;
|
|
247
|
-
if (userLogLevel < 0) userLogLevel = this
|
|
248
|
-
const level = this
|
|
312
|
+
if (userLogLevel < 0) userLogLevel = this.#logLevel;
|
|
313
|
+
const level = this.#forceLogLevel ? this.#logLevel : userLogLevel;
|
|
249
314
|
res.locals = res.locals || { };
|
|
250
315
|
res.locals.user = res.locals.user || user;
|
|
251
316
|
res.locals.user.logger = this.createUserLogger(requestId, username, level, ipAddress);
|
|
252
317
|
return next();
|
|
253
318
|
}
|
|
254
319
|
|
|
320
|
+
#writeApiCallToDatabase = async (req, res, options) => {
|
|
321
|
+
options = options || { };
|
|
322
|
+
if (!req) return;
|
|
323
|
+
const user = this.userGetFn ? this.userGetFn(req, res) : null;
|
|
324
|
+
const statusCode = options.statusCode || (res ? res.statusCode : null);
|
|
325
|
+
if (isResponseSuccessful(statusCode) && this.suppressSuccessfulRequestApiCallLogging) return;
|
|
326
|
+
if (!isRequestOfMethod(req, [ http.DELETE, http.PUT ]) && this.suppressNonMutatingRequestApiCallLogging) return;
|
|
327
|
+
const matchedRoute = this.#matchPathToRoute(req.path);
|
|
328
|
+
let metadataDefaults = Object.assign({ }, user);
|
|
329
|
+
delete(metadataDefaults.id);
|
|
330
|
+
delete(metadataDefaults.logger);
|
|
331
|
+
const _metadata = _.defaults({ }, (options.metadata || { }), metadataDefaults);
|
|
332
|
+
if (matchedRoute) {
|
|
333
|
+
try {
|
|
334
|
+
const appCallValues = {
|
|
335
|
+
apiVersion: this.apiVersion,
|
|
336
|
+
client: user ? user.clientId : null,
|
|
337
|
+
clientVersion: req.header(rqh.APP_VERSION),
|
|
338
|
+
httpStatusCode: statusCode,
|
|
339
|
+
ipAddress: req.header(rqh.FORWARDED_FOR) || req.socket.remoteAddress,
|
|
340
|
+
metadata: _metadata,
|
|
341
|
+
method: req.method,
|
|
342
|
+
route: matchedRoute,
|
|
343
|
+
token: extractToken(req),
|
|
344
|
+
userAgent: req.header(rqh.USER_AGENT),
|
|
345
|
+
userId: user ? user.id : null,
|
|
346
|
+
};
|
|
347
|
+
await this.apiCallModel.create(appCallValues);
|
|
348
|
+
} catch (e) {
|
|
349
|
+
console.error(e);
|
|
350
|
+
ZeddemoreLogger.log({ user }).error('Api call logging failed, moving on');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
#writeRouteToDatabase = async (routeValues) => {
|
|
356
|
+
if (!this.routeLoggingEnabled || !routeValues) return;
|
|
357
|
+
const matchedRoute = this.#matchPathToRoute(routeValues.route);
|
|
358
|
+
if (matchedRoute) {
|
|
359
|
+
try {
|
|
360
|
+
const route = await this.routeModel.findOne({ where: { route: matchedRoute }, logging: false });
|
|
361
|
+
if (route) {
|
|
362
|
+
route.count++;
|
|
363
|
+
route.lastCalledAt = new Date();
|
|
364
|
+
route.save();
|
|
365
|
+
} else {
|
|
366
|
+
this.routeModel.create({
|
|
367
|
+
count: 1,
|
|
368
|
+
lastCalledAt: new Date(),
|
|
369
|
+
method: routeValues.method,
|
|
370
|
+
route: matchedRoute,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch (e) {
|
|
374
|
+
console.error(e);
|
|
375
|
+
console.error('Route logging failed, moving on');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
255
380
|
static writeAxiosErrorLog(axiosError, logOptions, options) {
|
|
256
381
|
logOptions = logOptions || { };
|
|
257
382
|
options = options || { };
|
|
@@ -262,13 +387,13 @@ class ZeddemoreLogger {
|
|
|
262
387
|
const logger = options.logger || ZeddemoreLogger.log(options);
|
|
263
388
|
if (!logger) return;
|
|
264
389
|
const _method = _request.method || null;
|
|
265
|
-
const method = _method ?
|
|
390
|
+
const method = _method ? chalkHttpVerbs(_method) : null;
|
|
266
391
|
const _config = _response.config || null;
|
|
267
392
|
const _headers = _response.headers || null;
|
|
268
393
|
const _data = _response.data || null;
|
|
269
394
|
const path = _config ? _config.url : '';
|
|
270
395
|
const prefix = logOptions.prefix ? `[${ logOptions.prefix }]` : null;
|
|
271
|
-
const status =
|
|
396
|
+
const status = chalkHttpStatuses(_response.status);
|
|
272
397
|
const _duration = axiosError.duration || _response.duration;
|
|
273
398
|
const duration = _duration ? convertMilliseconds(_duration) : '-';
|
|
274
399
|
let _contentLength = _headers ? _headers[ rsh.CONTENT_LENGTH ] : null;
|
|
@@ -291,13 +416,13 @@ class ZeddemoreLogger {
|
|
|
291
416
|
const logger = options.logger || ZeddemoreLogger.log(options);
|
|
292
417
|
if (!logger) return;
|
|
293
418
|
const _method = _request.method || null;
|
|
294
|
-
const method = _method ?
|
|
419
|
+
const method = _method ? chalkHttpVerbs(_method) : null;
|
|
295
420
|
const _headers = axiosResponse.headers || null;
|
|
296
421
|
const _data = axiosResponse.data || null;
|
|
297
422
|
const path = _request.path || '';
|
|
298
423
|
const prefix = logOptions.prefix ? `[${ logOptions.prefix }]` : null;
|
|
299
424
|
const responseData = (logOptions.response && _.isObject(_data)) ? JSON.stringify(_data) : null;
|
|
300
|
-
const status =
|
|
425
|
+
const status = chalkHttpStatuses(axiosResponse.status);
|
|
301
426
|
const _duration = axiosResponse.duration;
|
|
302
427
|
const duration = _duration ? convertMilliseconds(_duration) : '-';
|
|
303
428
|
let _contentLength = _headers ? _headers[ rsh.CONTENT_LENGTH ] : null;
|