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 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
- function chalkDatabaseOperation(operation, string = null) {
9
- if (!string) string = operation;
10
- return chalkViaColorMap(operation, string, colorMappings.databaseOperationColors, true);
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 chalkHttpStatus(statusCode) {
14
- const colorMap = _.find(colorMappings.httpStatusColors, (httpStatusColor) => httpStatusColor.range.includes(statusCode));
15
- if (!colorMap) return statusCode;
16
- let chalkedMatch = statusCode;
17
- if (colorMap.ansi) chalkedMatch = `\x1B[${ colorMap.ansi }m${ statusCode }\x1B[39m`;
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 chalkHttpVerb(verb, string = null) {
69
+ function chalkHttpVerbs(verb, string = null, replaceGlobal = false) {
22
70
  if (!string) string = verb;
23
- return chalkViaColorMap(verb, string, colorMappings.httpVerbColors, false);
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, string, colorMap, useGlobal = true) {
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) || !string || !_.isString(string) || !_.isObject(colorMap)) return string;
109
+ if (!match || !_.isString(match) || !target || !_.isString(target) || !_.isObject(colorsMap)) return target;
31
110
  const cleanMatch = match.replace(/[^A-Z]/g, '');
32
- const mapping = _.find(colorMap, { match: cleanMatch });
33
- if (!mapping) return string;
34
- const flags = useGlobal ? 'g' : '';
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 (!string.match(re)) return string;
37
- let chalkedMatch = cleanMatch;
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
- chalkDatabaseOperation,
104
- chalkHttpStatus,
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, httpMethods: http } = enums;
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: '#F22613', back: null },
10
- { match: dbo.INSERT, fore: '#00B16A', back: null },
11
- { match: dbo.SELECT, fore: '#1E90FF', back: null },
12
- { match: dbo.TRUNCATE, fore: '#750505', back: null },
13
- { match: dbo.UPDATE, fore: '#F9690E', back: null },
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), ansi: 31 },
18
- { range: _.range(400, 500), ansi: 33 },
19
- { range: _.range(300, 400), ansi: 36 },
20
- { range: _.range(200, 300), ansi: 32 },
21
- { range: _.range(100, 200), ansi: 0 },
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.GET, fore: '#000000', back: '#67D193' },
26
- { match: http.HEAD, fore: '#000000', back: '#68D696' },
27
- { match: http.POST, fore: '#000000', back: '#F4DA7A' },
28
- { match: http.PUT, fore: '#000000', back: '#74AEF6' },
29
- { match: http.DELETE, fore: '#000000', back: '#EF968A' },
30
- { match: http.CONNECT, fore: '#000000', back: '#FFFFFF' },
31
- { match: http.OPTIONS, fore: '#000000', back: '#E55AA8' },
32
- { match: http.TRACE, fore: '#000000', back: '#FFFFFF' },
33
- { match: http.PATCH, fore: '#000000', back: '#C0A8E1' },
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
- function areHeadersSent(res) {
4
- return typeof (res.headersSent !== 'boolean') ? Boolean(res._header) : res.headersSent;
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
- areHeadersSent,
63
+ extractToken,
45
64
  getRequestResponseTime,
65
+ isRequestOfMethod,
46
66
  parseRequest,
47
67
  };
@@ -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
+ };
@@ -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, getRequestResponseTime, parseRequest } = require('./http');
11
- const { chalkHttpStatus, chalkHttpVerb, stringToIdempotentHexColor } = require('./color');
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, winstonLogLevels: wll } = enums;
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.additionalWriteFn = options.additionalWriteFn || null;
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.displayUsername : true;
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.forceLogLevel = options.hasOwnProperty('forceLogLevel') ? options.forceLogLevel : false;
49
- this.logLevel = options.hasOwnProperty('logLevel') ? options.logLevel : wll.INFO;
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
- #buildWinstonFormat = (options, additionalFormats = [ ]) =>{
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.logLevel) => {
76
- logLevel = logLevel || this.logLevel;
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.logLevel, additionalTransports = [ ]) => {
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 chalkHttpVerb(verb, message);
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 chalkHttpStatus(statusCode);
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 (additionalWriteFn) additionalWriteFn(req, res);
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.logLevel;
248
- const level = this.forceLogLevel ? this.logLevel : userLogLevel;
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 ? chalkHttpVerb(_method) : null;
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 = chalkHttpStatus(_response.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 ? chalkHttpVerb(_method) : null;
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 = chalkHttpStatus(axiosResponse.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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeddemore-logger",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Node.js Express logger using Morgan and Winston for thread and caller info tracking",
5
5
  "repository": {
6
6
  "type": "git",