zeddemore-logger 1.0.7 → 1.2.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 +25 -21
- package/lib/enums.js +54 -1
- package/lib/mappings/color_mappings.js +36 -27
- 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 +201 -15
- package/package.json +1 -1
package/lib/color.js
CHANGED
|
@@ -6,7 +6,7 @@ const chalk = require('chalk');
|
|
|
6
6
|
const colorMappings = require('./mappings/color_mappings');
|
|
7
7
|
const enums = require('./enums');
|
|
8
8
|
|
|
9
|
-
const { chalkLayers: cl, chalkFormats:
|
|
9
|
+
const { chalkLayers: cl, chalkFormats: cf, hexColors: hc } = enums;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param ansiColor {number}
|
|
@@ -24,22 +24,6 @@ function chalkAnsi(ansiColor, layer = cl.FOREGROUND, string) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
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
27
|
/**
|
|
44
28
|
* @param hexColor {string}
|
|
45
29
|
* @param layer {string}
|
|
@@ -66,11 +50,29 @@ function chalkHttpStatuses(statusCode, string = null) {
|
|
|
66
50
|
return chalkedMatch;
|
|
67
51
|
}
|
|
68
52
|
|
|
53
|
+
function chalkLogLevel(logLevel, string = null) {
|
|
54
|
+
return chalkMatch(logLevel, string, colorMappings.logLevelColors);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function chalkMatch(match, string, colorsMap) {
|
|
58
|
+
if (!string) string = match;
|
|
59
|
+
const colorMap = _.find(colorsMap, { match });
|
|
60
|
+
if (!colorMap || !colorMap.style) return string;
|
|
61
|
+
return chalkTarget(string, colorMap.style);
|
|
62
|
+
}
|
|
63
|
+
|
|
69
64
|
function chalkHttpVerbs(verb, string = null, replaceGlobal = false) {
|
|
70
65
|
if (!string) string = verb;
|
|
71
66
|
return chalkViaColorMap(verb, string, colorMappings.httpVerbColors, replaceGlobal);
|
|
72
67
|
}
|
|
73
68
|
|
|
69
|
+
function chalkPackage(packageName, string = null) {
|
|
70
|
+
if (!string) string = packageName;
|
|
71
|
+
const colorMap = _.find(colorMappings.packageColors, { match: packageName });
|
|
72
|
+
if (!colorMap || !colorMap.style) return chalkTarget(string, { format: cf.HEX, fore: hc.BEARDON_RED, back: null });
|
|
73
|
+
return chalkTarget(string, colorMap.style);
|
|
74
|
+
}
|
|
75
|
+
|
|
74
76
|
/**
|
|
75
77
|
* @param redColor {number}
|
|
76
78
|
* @param greenColor {number}
|
|
@@ -94,10 +96,9 @@ function chalkTarget(target, style) {
|
|
|
94
96
|
const fore = style.fore;
|
|
95
97
|
const back = style.back;
|
|
96
98
|
switch (style.format) {
|
|
97
|
-
case
|
|
98
|
-
case
|
|
99
|
-
case
|
|
100
|
-
case cs.RGB: return chalkRgb(back.r, back.g, back.b, cl.BACKGROUND, chalkRgb(fore.r, fore.g, fore.b, cl.FOREGROUND, target));
|
|
99
|
+
case cf.ANSI: return chalkAnsi(back, cl.BACKGROUND, chalkAnsi(fore, cl.FOREGROUND, target));
|
|
100
|
+
case cf.HEX: return chalkHex(back, cl.BACKGROUND, chalkHex(fore, cl.FOREGROUND, target));
|
|
101
|
+
case cf.RGB: return chalkRgb(back.r, back.g, back.b, cl.BACKGROUND, chalkRgb(fore.r, fore.g, fore.b, cl.FOREGROUND, target));
|
|
101
102
|
default: return target;
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -178,5 +179,8 @@ function stringToIdempotentHslValues(str, hslOptions) {
|
|
|
178
179
|
module.exports = {
|
|
179
180
|
chalkHttpStatuses,
|
|
180
181
|
chalkHttpVerbs,
|
|
182
|
+
chalkLogLevel,
|
|
183
|
+
chalkPackage,
|
|
184
|
+
chalkTarget,
|
|
181
185
|
stringToIdempotentHexColor,
|
|
182
186
|
};
|
package/lib/enums.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
// Copyright (c) 2025 by Beardon Services, Inc.
|
|
2
2
|
|
|
3
|
+
const ansiColors = {
|
|
4
|
+
HTTP_CLIENT_ERROR: 33, // red
|
|
5
|
+
HTTP_INFORMATIONAL: 0, // white
|
|
6
|
+
HTTP_REDIRECTION: 36, // blue
|
|
7
|
+
HTTP_SERVER_ERROR: 31, // red
|
|
8
|
+
HTTP_SUCCESSFUL: 32, // green
|
|
9
|
+
LOG_LEVEL_DEBUG: 34, // blue
|
|
10
|
+
LOG_LEVEL_ERROR: 31, // red
|
|
11
|
+
LOG_LEVEL_HTTP: 32, // green
|
|
12
|
+
LOG_LEVEL_INFO: 32, // green
|
|
13
|
+
LOG_LEVEL_SILLY: 35, // magenta
|
|
14
|
+
LOG_LEVEL_VERBOSE: 36, // cyan
|
|
15
|
+
LOG_LEVEL_WARNING: 33, // yellow
|
|
16
|
+
};
|
|
17
|
+
|
|
3
18
|
const apiLogLevelCaptions = {
|
|
4
19
|
DEFAULT: 'Default',
|
|
5
20
|
ERROR: 'Error',
|
|
@@ -24,7 +39,6 @@ const apiLogLevels = {
|
|
|
24
39
|
|
|
25
40
|
const chalkFormats = {
|
|
26
41
|
ANSI: 'ansi',
|
|
27
|
-
ANSI256: 'ansi256',
|
|
28
42
|
HEX: 'hex',
|
|
29
43
|
RGB: 'rgb',
|
|
30
44
|
};
|
|
@@ -42,6 +56,39 @@ const databaseOperations = {
|
|
|
42
56
|
UPDATE: 'UPDATE',
|
|
43
57
|
};
|
|
44
58
|
|
|
59
|
+
const hexColors = {
|
|
60
|
+
BEARDON_RED: '#951A1D',
|
|
61
|
+
BLACK: '#000000',
|
|
62
|
+
CONSOLE_WHITE: '#ECECEC',
|
|
63
|
+
DB_DELETE: '#F22613',
|
|
64
|
+
DB_INSERT: '#00B16A',
|
|
65
|
+
DB_SELECT: '#1E90FF',
|
|
66
|
+
DB_TRUNCATE: '#750505',
|
|
67
|
+
DB_UPDATE: '#F9690E',
|
|
68
|
+
HTTP_CONNECT: '#FFFFFF',
|
|
69
|
+
HTTP_DELETE: '#EF968A',
|
|
70
|
+
HTTP_GET: '#67D193',
|
|
71
|
+
HTTP_HEAD: '#68D696',
|
|
72
|
+
HTTP_OPTIONS: '#E55AA8',
|
|
73
|
+
HTTP_PATCH: '#C0A8E1',
|
|
74
|
+
HTTP_POST: '#F4DA7A',
|
|
75
|
+
HTTP_PUT: '#74AEF6',
|
|
76
|
+
HTTP_TRACE: '#FFFFFF',
|
|
77
|
+
PACKAGE_EXPRESS: '#F7DF1E',
|
|
78
|
+
PACKAGE_MYSQL: '#00618A',
|
|
79
|
+
PACKAGE_NODE_JS: '#689F63',
|
|
80
|
+
PACKAGE_POSTGRESQL: '#336791',
|
|
81
|
+
PACKAGE_SEQUELIZE: '#00AFEF',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const httpStatusCodeGroups = {
|
|
85
|
+
INFORMATIONAL: 1,
|
|
86
|
+
SUCCESSFUL: 2,
|
|
87
|
+
REDIRECTION: 3,
|
|
88
|
+
CLIENT_ERROR: 4,
|
|
89
|
+
SERVER_ERROR: 5,
|
|
90
|
+
};
|
|
91
|
+
|
|
45
92
|
const httpMethods = {
|
|
46
93
|
GET: 'GET',
|
|
47
94
|
HEAD: 'HEAD',
|
|
@@ -56,9 +103,12 @@ const httpMethods = {
|
|
|
56
103
|
|
|
57
104
|
const httpRequestHeaders = {
|
|
58
105
|
ALLOW: 'Allow',
|
|
106
|
+
APP_VERSION: 'App-Version',
|
|
59
107
|
AUTHORIZATION: 'Authorization',
|
|
60
108
|
BEARER_PREFIX: 'Bearer',
|
|
109
|
+
FORWARDED_FOR: 'X-Forwarded-For',
|
|
61
110
|
REQUEST_ID: 'X-Request-Id',
|
|
111
|
+
USER_AGENT: 'User-Agent',
|
|
62
112
|
};
|
|
63
113
|
|
|
64
114
|
const httpResponseHeaders = {
|
|
@@ -96,11 +146,14 @@ const winstonLogLevels = {
|
|
|
96
146
|
};
|
|
97
147
|
|
|
98
148
|
module.exports = {
|
|
149
|
+
ansiColors,
|
|
99
150
|
apiLogLevelCaptions,
|
|
100
151
|
apiLogLevels,
|
|
101
152
|
chalkFormats,
|
|
102
153
|
chalkLayers,
|
|
103
154
|
databaseOperations,
|
|
155
|
+
hexColors,
|
|
156
|
+
httpStatusCodeGroups,
|
|
104
157
|
httpMethods,
|
|
105
158
|
httpRequestHeaders,
|
|
106
159
|
httpResponseHeaders,
|
|
@@ -3,44 +3,52 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const enums = require('../enums');
|
|
5
5
|
|
|
6
|
-
const { databaseOperations: dbo, chalkFormats:
|
|
6
|
+
const { ansiColors: ac, databaseOperations: dbo, chalkFormats: cf, hexColors: hc, httpMethods: http, winstonLogLevels: wll } = enums;
|
|
7
7
|
|
|
8
8
|
const databaseOperationColors = [
|
|
9
|
-
{ match: dbo.DELETE, style: { format:
|
|
10
|
-
{ match: dbo.INSERT, style: { format:
|
|
11
|
-
{ match: dbo.SELECT, style: { format:
|
|
12
|
-
{ match: dbo.TRUNCATE, style: { format:
|
|
13
|
-
{ match: dbo.UPDATE, style: { format:
|
|
9
|
+
{ match: dbo.DELETE, style: { format: cf.HEX, fore: hc.DB_DELETE, back: null } },
|
|
10
|
+
{ match: dbo.INSERT, style: { format: cf.HEX, fore: hc.DB_INSERT, back: null } },
|
|
11
|
+
{ match: dbo.SELECT, style: { format: cf.HEX, fore: hc.DB_SELECT, back: null } },
|
|
12
|
+
{ match: dbo.TRUNCATE, style: { format: cf.HEX, fore: hc.DB_TRUNCATE, back: null } },
|
|
13
|
+
{ match: dbo.UPDATE, style: { format: cf.HEX, fore: hc.DB_UPDATE, back: null } },
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
const httpStatusColors = [
|
|
17
|
-
{ range: _.range(500, 600), style: { format:
|
|
18
|
-
{ range: _.range(400, 500), style: { format:
|
|
19
|
-
{ range: _.range(300, 400), style: { format:
|
|
20
|
-
{ range: _.range(200, 300), style: { format:
|
|
21
|
-
{ range: _.range(100, 200), style: { format:
|
|
17
|
+
{ range: _.range(500, 600), style: { format: cf.ANSI, fore: ac.HTTP_SERVER_ERROR, back: null } },
|
|
18
|
+
{ range: _.range(400, 500), style: { format: cf.ANSI, fore: ac.HTTP_CLIENT_ERROR, back: null } },
|
|
19
|
+
{ range: _.range(300, 400), style: { format: cf.ANSI, fore: ac.HTTP_REDIRECTION, back: null } },
|
|
20
|
+
{ range: _.range(200, 300), style: { format: cf.ANSI, fore: ac.HTTP_SUCCESSFUL, back: null } },
|
|
21
|
+
{ range: _.range(100, 200), style: { format: cf.ANSI, fore: ac.HTTP_INFORMATIONAL, back: null } },
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
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.
|
|
25
|
+
{ match: http.CONNECT, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_CONNECT } },
|
|
26
|
+
{ match: http.DELETE, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_DELETE } },
|
|
27
|
+
{ match: http.GET, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_GET } },
|
|
28
|
+
{ match: http.HEAD, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_HEAD } },
|
|
29
|
+
{ match: http.OPTIONS, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_OPTIONS } },
|
|
30
|
+
{ match: http.PATCH, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_PATCH } },
|
|
31
|
+
{ match: http.POST, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_POST } },
|
|
32
|
+
{ match: http.PUT, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_PUT } },
|
|
33
|
+
{ match: http.TRACE, style: { format: cf.HEX, fore: hc.BLACK, back: hc.HTTP_TRACE } },
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
const logLevelColors = [
|
|
37
|
-
{ match: wll.ERROR, style: { format:
|
|
38
|
-
{ match: wll.WARNING, style: { format:
|
|
39
|
-
{ match: wll.INFO, style: { format:
|
|
40
|
-
{ match: wll.HTTP, style: { format:
|
|
41
|
-
{ match: wll.VERBOSE, style: { format:
|
|
42
|
-
{ match: wll.DEBUG, style: { format:
|
|
43
|
-
{ match: wll.SILLY, style: { format:
|
|
37
|
+
{ match: wll.ERROR, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_ERROR, back: null } },
|
|
38
|
+
{ match: wll.WARNING, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_WARNING, back: null } },
|
|
39
|
+
{ match: wll.INFO, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_INFO, back: null } },
|
|
40
|
+
{ match: wll.HTTP, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_HTTP, back: null } },
|
|
41
|
+
{ match: wll.VERBOSE, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_VERBOSE, back: null } },
|
|
42
|
+
{ match: wll.DEBUG, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_DEBUG, back: null } },
|
|
43
|
+
{ match: wll.SILLY, style: { format: cf.ANSI, fore: ac.LOG_LEVEL_SILLY, back: null } },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const packageColors = [
|
|
47
|
+
{ match: 'express', style: { format: cf.HEX, fore: hc.PACKAGE_EXPRESS, back: null } },
|
|
48
|
+
{ match: 'MySQL', style: { format: cf.HEX, fore: hc.PACKAGE_MYSQL, back: null } },
|
|
49
|
+
{ match: 'Node.js', style: { format: cf.HEX, fore: hc.PACKAGE_NODE_JS, back: null } },
|
|
50
|
+
{ match: 'PostgreSQL', style: { format: cf.HEX, fore: hc.PACKAGE_POSTGRESQL, back: null } },
|
|
51
|
+
{ match: 'sequelize', style: { format: cf.HEX, fore: hc.PACKAGE_SEQUELIZE, back: null } },
|
|
44
52
|
];
|
|
45
53
|
|
|
46
54
|
module.exports = {
|
|
@@ -48,4 +56,5 @@ module.exports = {
|
|
|
48
56
|
httpStatusColors,
|
|
49
57
|
httpVerbColors,
|
|
50
58
|
logLevelColors,
|
|
59
|
+
packageColors,
|
|
51
60
|
};
|
|
@@ -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
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
|
-
const { createLogger: createWinstonLogger, format, transports } = require('winston');
|
|
5
|
+
const { createLogger: createWinstonLogger, format, Logger, transports } = require('winston');
|
|
6
6
|
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 { chalkHttpStatuses, chalkHttpVerbs, stringToIdempotentHexColor } = require('./color');
|
|
10
|
+
const { areHeadersSent, isResponseSuccessful } = require('./response');
|
|
11
|
+
const { chalkHttpStatuses, chalkHttpVerbs, chalkLogLevel, chalkPackage, chalkTarget, 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,
|
|
19
20
|
winstonLogLevels: wll } = enums;
|
|
20
21
|
|
|
21
22
|
const COLOR_CONSOLE_WHITE_HEX = '#ECECEC';
|
|
@@ -23,12 +24,22 @@ const DEFAULT_REQUEST_ID_ATTRIBUTE = 'requestId';
|
|
|
23
24
|
const DEFAULT_USER_LOG_LEVEL = -1;
|
|
24
25
|
const WINSTON_CONSOLE_TRANSPORT_NAME = 'console';
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Convert a log level to a Winston log level name
|
|
29
|
+
* @param logLevel {number|string}
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
26
32
|
function massageLogLevel(logLevel) {
|
|
27
33
|
if (!logLevel) return wlln.VERBOSE;
|
|
28
34
|
if (!_.isInteger(logLevel)) return logLevel;
|
|
29
35
|
return winstonLogLevelIdToName(logLevel);
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Convert a log level ID to a Winston log level name
|
|
40
|
+
* @param levelId {number}
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
32
43
|
function winstonLogLevelIdToName(levelId) {
|
|
33
44
|
const logLevelEntry = _.find(logLevelMap, { winstonLevel: levelId });
|
|
34
45
|
return logLevelEntry ? logLevelEntry.winstonName : wlln.HTTP;
|
|
@@ -37,14 +48,21 @@ function winstonLogLevelIdToName(levelId) {
|
|
|
37
48
|
class ZeddemoreLogger {
|
|
38
49
|
|
|
39
50
|
#forceLogLevel = false;
|
|
51
|
+
/** @type {Logger} */
|
|
52
|
+
#logger = null;
|
|
40
53
|
#logLevel = wll.INFO;
|
|
41
54
|
|
|
42
55
|
constructor(options) {
|
|
43
56
|
options = options || { };
|
|
44
|
-
this.
|
|
57
|
+
this.apiCallModel = options.apiCallModel || null;
|
|
58
|
+
this.apiRouteCategories = options.apiRouteCategories || [ ];
|
|
59
|
+
this.apiRoutes = options.apiRoutes || [ ];
|
|
60
|
+
this.apiVersion = options.apiVersion || null;
|
|
45
61
|
this.defaultLabel = options.defaultLabel || 'zeddemore-logger';
|
|
62
|
+
this.disableApiCallLogging = options.hasOwnProperty('disableApiCallLogging') ? options.disableApiCallLogging : false;
|
|
63
|
+
this.disableRouteLogging = options.hasOwnProperty('disableRouteLogging') ? options.disableRouteLogging : false;
|
|
46
64
|
this.disableMorganLogging = options.hasOwnProperty('disableMorganLogging') ? options.disableMorganLogging : false;
|
|
47
|
-
this.displayCaller = options.hasOwnProperty('displayCaller') ? options.
|
|
65
|
+
this.displayCaller = options.hasOwnProperty('displayCaller') ? options.displayCaller : true;
|
|
48
66
|
this.displayIpAddress = options.hasOwnProperty('displayIpAddress') ? options.displayIpAddress : true;
|
|
49
67
|
this.displayRequestId = options.hasOwnProperty('displayRequestId') ? options.displayRequestId : true;
|
|
50
68
|
this.customCallerSettings = options.customCallerSettings || [ ];
|
|
@@ -52,15 +70,27 @@ class ZeddemoreLogger {
|
|
|
52
70
|
this.#forceLogLevel = options.hasOwnProperty('forceLogLevel') ? options.forceLogLevel : false;
|
|
53
71
|
this.#logLevel = options.hasOwnProperty('logLevel') ? options.logLevel : wll.INFO;
|
|
54
72
|
this.morganFormat = options.morganFormat || mf.DEV_ENHANCED;
|
|
73
|
+
this.routeModel = options.routeModel || null;
|
|
74
|
+
this.suppressNonMutatingRequestApiCallLogging = options.hasOwnProperty('suppressNonMutatingRequestApiCallLogging') ? options.suppressNonMutatingRequestApiCallLogging : false;
|
|
75
|
+
this.suppressSuccessfulRequestApiCallLogging = options.hasOwnProperty('suppressSuccessfulRequestApiCallLogging') ? options.suppressSuccessfulRequestApiCallLogging : false;
|
|
55
76
|
this.timestampFormat = options.timestampFormat || 'YYYY-MM-DD HH:mm:ss';
|
|
56
77
|
this.requestIdAttribute = options.requestIdAttribute || DEFAULT_REQUEST_ID_ATTRIBUTE;
|
|
57
78
|
this.userGetFn = options.userGetFn || this.#userGetFn;
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
get apiCallLoggingEnabled() {
|
|
82
|
+
return !this.disableApiCallLogging && !!this.apiCallModel;
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
get forceLogLevel() {
|
|
61
86
|
return this.#forceLogLevel;
|
|
62
87
|
}
|
|
63
88
|
|
|
89
|
+
get logger() {
|
|
90
|
+
if (!this.#logger) this.#logger = this.createLogger();
|
|
91
|
+
return this.#logger;
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
get logLevel() {
|
|
65
95
|
return this.#logLevel;
|
|
66
96
|
}
|
|
@@ -69,6 +99,10 @@ class ZeddemoreLogger {
|
|
|
69
99
|
return winstonLogLevelIdToName(this.#logLevel);
|
|
70
100
|
}
|
|
71
101
|
|
|
102
|
+
get routeLoggingEnabled() {
|
|
103
|
+
return !this.disableRouteLogging && !!this.routeModel;
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
set forceLogLevel(doForce) {
|
|
73
107
|
this.#forceLogLevel = !!doForce;
|
|
74
108
|
}
|
|
@@ -183,6 +217,14 @@ class ZeddemoreLogger {
|
|
|
183
217
|
return uuid(null, null, null);
|
|
184
218
|
}
|
|
185
219
|
|
|
220
|
+
static getLogger(options) {
|
|
221
|
+
options = options || { };
|
|
222
|
+
const logger = options.logger || (options.user ? options.user.logger : null) || ((app && app.locals) ? app.locals.logger : null);
|
|
223
|
+
if (logger) return logger;
|
|
224
|
+
const zeddemoreLogger = new ZeddemoreLogger();
|
|
225
|
+
return zeddemoreLogger.createLogger(options);
|
|
226
|
+
}
|
|
227
|
+
|
|
186
228
|
#getRequestId = (req) => {
|
|
187
229
|
if (!req) return null;
|
|
188
230
|
return req[ this.requestIdAttribute ];
|
|
@@ -207,17 +249,86 @@ class ZeddemoreLogger {
|
|
|
207
249
|
morgan.token('status-colored', (req, res) => { return buildStatusColoredToken(res); });
|
|
208
250
|
}
|
|
209
251
|
|
|
210
|
-
|
|
252
|
+
log = (logLevel) => {
|
|
253
|
+
logLevel = logLevel || this.#logLevel;
|
|
254
|
+
const logLevelName = massageLogLevel(logLevel);
|
|
255
|
+
return this.logger[ logLevelName ];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#logServerInfo = (options, logLevel = wll.INFO) => {
|
|
211
259
|
options = options || { };
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
260
|
+
const showLogLevel = options.hasOwnProperty('showLogLevel') ? options.showLogLevel : true;
|
|
261
|
+
const logLevelLabel = chalkLogLevel(this.#logLevel, this.logLevelName.toUpperCase());
|
|
262
|
+
const msgParts = [ `Server is ${ chalk.bold(chalk.yellowBright('⚡live⚡')) }` ];
|
|
263
|
+
if (showLogLevel) msgParts.push(`with log level ${ logLevelLabel }${ this.#forceLogLevel ? ` (${ chalk.italic('forced') })` : '' }`);
|
|
264
|
+
if (options.url) msgParts.push(`at ${ chalk.underline(options.url) }`);
|
|
265
|
+
this.log(logLevel)(msgParts.join(' '));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
#logServerListening = (options, logLevel = wll.INFO) => {
|
|
269
|
+
const serverEnvironment = options.environment || process.env.NODE_ENV || 'development';
|
|
270
|
+
const serverInstance = options.instance || (process.env.NODE_APP_INSTANCE && (process.env.NODE_APP_INSTANCE.length > 1)) ? process.env.NODE_APP_INSTANCE : null; // handles goofy PM2 NODE_APP_INSTANCE of '0'
|
|
271
|
+
const serverName = options.name || this.defaultLabel;
|
|
272
|
+
const serverPort = options.port || process.env.PORT || 3000;
|
|
273
|
+
const msgParts = [ ];
|
|
274
|
+
const serverParts = [ serverName ];
|
|
275
|
+
if (serverEnvironment) serverParts.push(serverEnvironment);
|
|
276
|
+
if (serverInstance) serverParts.push(serverInstance);
|
|
277
|
+
msgParts.push(`[${ serverParts.join(':') }]`);
|
|
278
|
+
msgParts.push(`Node.js Express server listening on port ${ chalk.bold(serverPort) }`);
|
|
279
|
+
this.log(logLevel)(msgParts.join(' '));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#logServerPackagesInfo = (options, packages, logLevel = wll.INFO) => {
|
|
283
|
+
function buildVersionLabel(_package) {
|
|
284
|
+
if (!_package || !_package.name || !_package.version) return null;
|
|
285
|
+
const packageName = _package.style ? chalkTarget(_package.name, _package.style) : chalkPackage(_package.name);
|
|
286
|
+
return `${ packageName }@${ _package.version }`;
|
|
287
|
+
}
|
|
288
|
+
const versionParts = _.compact(_.sortBy(packages, (_package) => _package.name.toUpperCase()).map(buildVersionLabel));
|
|
289
|
+
this.log(logLevel)(`Packages: ${ versionParts.join(' | ') }`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#logServerRoutesAdded = (options, logLevel = wll.INFO) => {
|
|
293
|
+
options = options || { };
|
|
294
|
+
const showCategories = options.hasOwnProperty('showCategories') ? options.showCategories : true;
|
|
295
|
+
const routes = options.routes || this.apiRoutes || [ ];
|
|
296
|
+
const routesLabel = (routes.length === 1) ? 'route' : 'routes';
|
|
297
|
+
const addedLabel = chalk.green('added');
|
|
298
|
+
const msgParts = [ `${ routes.length } ${ routesLabel } ${ addedLabel }` ];
|
|
299
|
+
if (showCategories) {
|
|
300
|
+
const categories = options.categories || this.apiRouteCategories || [];
|
|
301
|
+
msgParts.push(`in: ${ categories.sort().join('/') }`);
|
|
302
|
+
}
|
|
303
|
+
this.log(logLevel)(msgParts.join(', '));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
logServerStartup = (options, logLevel = wll.INFO) => {
|
|
307
|
+
options = options || { };
|
|
308
|
+
const showListening = options.hasOwnProperty('showListening') ? options.showListening : true;
|
|
309
|
+
const showPackages = options.hasOwnProperty('showPackages') ? options.showPackages : true;
|
|
310
|
+
const showRoutes = options.hasOwnProperty('showRoutes') ? options.showRoutes : true;
|
|
311
|
+
const showInfo = options.hasOwnProperty('showInfo') ? options.showInfo : true;
|
|
312
|
+
if (showListening) this.#logServerListening(options, logLevel);
|
|
313
|
+
if (showPackages && options.packages) this.#logServerPackagesInfo(options, options.packages, logLevel);
|
|
314
|
+
if (showRoutes) this.#logServerRoutesAdded(options, logLevel);
|
|
315
|
+
if (showInfo) this.#logServerInfo(options, logLevel);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#matchPathToRoute = (path) => {
|
|
319
|
+
if (!path) return null;
|
|
320
|
+
let matchedRoute = null;
|
|
321
|
+
for (const route of this.apiRoutes) {
|
|
322
|
+
if (path.match(route.regexp)) {
|
|
323
|
+
matchedRoute = route.path;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return matchedRoute;
|
|
216
328
|
}
|
|
217
329
|
|
|
218
330
|
morganMiddleware = (options) => {
|
|
219
331
|
options = options || { };
|
|
220
|
-
const additionalWriteFn = (_.isFunction(options.additionalWriteFn) ? options.additionalWriteFn : null) || this.additionalWriteFn;
|
|
221
332
|
const disableMorganLogging = options.hasOwnProperty('disableMorganLogging') ? options.disableMorganLogging : this.disableMorganLogging;
|
|
222
333
|
const morganFormat = options.morganFormat || this.morganFormat;
|
|
223
334
|
const userGetFn = (_.isFunction(options.userGetFn) ? options.userGetFn : null) || this.userGetFn;
|
|
@@ -227,7 +338,7 @@ class ZeddemoreLogger {
|
|
|
227
338
|
skip: () => disableMorganLogging,
|
|
228
339
|
stream: {
|
|
229
340
|
write: (message) => {
|
|
230
|
-
if (
|
|
341
|
+
if (this.apiCallLoggingEnabled) this.#writeApiCallToDatabase(req, res, options);
|
|
231
342
|
const logOptions = { };
|
|
232
343
|
const user = userGetFn ? userGetFn(req, res) : null;
|
|
233
344
|
if (user) logOptions.user = user;
|
|
@@ -250,6 +361,21 @@ class ZeddemoreLogger {
|
|
|
250
361
|
return next();
|
|
251
362
|
}
|
|
252
363
|
|
|
364
|
+
routeLoggingMiddleware = (req, res, next) => {
|
|
365
|
+
if (!this.routeLoggingEnabled) return next();
|
|
366
|
+
try {
|
|
367
|
+
const routeValues = {
|
|
368
|
+
route: req.path,
|
|
369
|
+
method: req.method,
|
|
370
|
+
lastCalled: new Date().toISOString(),
|
|
371
|
+
};
|
|
372
|
+
this.#writeRouteToDatabase(routeValues);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
} finally {
|
|
375
|
+
next();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
253
379
|
#userGetFn(req, res) {
|
|
254
380
|
return ((res && res.locals) ? res.locals.user : null)
|
|
255
381
|
|| ((req && req.locals) ? req.locals.user : null)
|
|
@@ -260,7 +386,7 @@ class ZeddemoreLogger {
|
|
|
260
386
|
const requestId = this.#getRequestId(req);
|
|
261
387
|
const user = this.userGetFn(req, res) || { };
|
|
262
388
|
if (!requestId) return next();
|
|
263
|
-
const username = user.login || null;
|
|
389
|
+
const username = user.login || user.username || null;
|
|
264
390
|
let ipAddress = user.ipAddress || null;
|
|
265
391
|
if (!ipAddress) {
|
|
266
392
|
const requestValues = parseRequest(req);
|
|
@@ -275,6 +401,66 @@ class ZeddemoreLogger {
|
|
|
275
401
|
return next();
|
|
276
402
|
}
|
|
277
403
|
|
|
404
|
+
#writeApiCallToDatabase = async (req, res, options) => {
|
|
405
|
+
options = options || { };
|
|
406
|
+
if (!req) return;
|
|
407
|
+
const user = this.userGetFn ? this.userGetFn(req, res) : null;
|
|
408
|
+
const statusCode = options.statusCode || (res ? res.statusCode : null);
|
|
409
|
+
if (isResponseSuccessful(statusCode) && this.suppressSuccessfulRequestApiCallLogging) return;
|
|
410
|
+
if (!isRequestOfMethod(req, [ http.DELETE, http.PUT ]) && this.suppressNonMutatingRequestApiCallLogging) return;
|
|
411
|
+
const matchedRoute = this.#matchPathToRoute(req.path);
|
|
412
|
+
let metadataDefaults = Object.assign({ }, user);
|
|
413
|
+
delete(metadataDefaults.id);
|
|
414
|
+
delete(metadataDefaults.logger);
|
|
415
|
+
const _metadata = _.defaults({ }, (options.metadata || { }), metadataDefaults);
|
|
416
|
+
if (matchedRoute) {
|
|
417
|
+
try {
|
|
418
|
+
const appCallValues = {
|
|
419
|
+
apiVersion: this.apiVersion,
|
|
420
|
+
client: user ? user.clientId : null,
|
|
421
|
+
clientVersion: req.header(rqh.APP_VERSION),
|
|
422
|
+
httpStatusCode: statusCode,
|
|
423
|
+
ipAddress: req.header(rqh.FORWARDED_FOR) || req.socket.remoteAddress,
|
|
424
|
+
metadata: _metadata,
|
|
425
|
+
method: req.method,
|
|
426
|
+
route: matchedRoute,
|
|
427
|
+
token: extractToken(req),
|
|
428
|
+
userAgent: req.header(rqh.USER_AGENT),
|
|
429
|
+
userId: user ? user.id : null,
|
|
430
|
+
};
|
|
431
|
+
await this.apiCallModel.create(appCallValues);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
console.error(e);
|
|
434
|
+
ZeddemoreLogger.log({ user }).error('Api call logging failed, moving on');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
#writeRouteToDatabase = async (routeValues) => {
|
|
440
|
+
if (!this.routeLoggingEnabled || !routeValues) return;
|
|
441
|
+
const matchedRoute = this.#matchPathToRoute(routeValues.route);
|
|
442
|
+
if (matchedRoute) {
|
|
443
|
+
try {
|
|
444
|
+
const route = await this.routeModel.findOne({ where: { route: matchedRoute }, logging: false });
|
|
445
|
+
if (route) {
|
|
446
|
+
route.count++;
|
|
447
|
+
route.lastCalledAt = new Date();
|
|
448
|
+
route.save();
|
|
449
|
+
} else {
|
|
450
|
+
this.routeModel.create({
|
|
451
|
+
count: 1,
|
|
452
|
+
lastCalledAt: new Date(),
|
|
453
|
+
method: routeValues.method,
|
|
454
|
+
route: matchedRoute,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
} catch (e) {
|
|
458
|
+
console.error(e);
|
|
459
|
+
console.error('Route logging failed, moving on');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
278
464
|
static writeAxiosErrorLog(axiosError, logOptions, options) {
|
|
279
465
|
logOptions = logOptions || { };
|
|
280
466
|
options = options || { };
|
|
@@ -334,7 +520,7 @@ class ZeddemoreLogger {
|
|
|
334
520
|
}
|
|
335
521
|
|
|
336
522
|
module.exports = {
|
|
337
|
-
log: ZeddemoreLogger.
|
|
523
|
+
log: ZeddemoreLogger.getLogger,
|
|
338
524
|
winstonLogLevelIdToName,
|
|
339
525
|
writeAxiosErrorLog: ZeddemoreLogger.writeAxiosErrorLog,
|
|
340
526
|
writeAxiosLog: ZeddemoreLogger.writeAxiosResponseLog, // deprecated
|