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 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: cs } = enums;
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 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));
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: cs, httpMethods: http, winstonLogLevels: wll } = enums;
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: cs.HEX, fore: '#F22613', back: null } },
10
- { match: dbo.INSERT, style: { format: cs.HEX, fore: '#00B16A', back: null } },
11
- { match: dbo.SELECT, style: { format: cs.HEX, fore: '#1E90FF', back: null } },
12
- { match: dbo.TRUNCATE, style: { format: cs.HEX, fore: '#750505', back: null } },
13
- { match: dbo.UPDATE, style: { format: cs.HEX, fore: '#F9690E', back: null } },
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: cs.ANSI, fore: 31, back: null } },
18
- { range: _.range(400, 500), style: { format: cs.ANSI, fore: 33, back: null } },
19
- { range: _.range(300, 400), style: { format: cs.ANSI, fore: 36, back: null } },
20
- { range: _.range(200, 300), style: { format: cs.ANSI, fore: 32, back: null } },
21
- { range: _.range(100, 200), style: { format: cs.ANSI, fore: 0, back: null } },
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.GET, style: { format: cs.HEX, fore: '#000000', back: '#67D193' } },
26
- { match: http.HEAD, style: { format: cs.HEX, fore: '#000000', back: '#68D696' } },
27
- { match: http.POST, style: { format: cs.HEX, fore: '#000000', back: '#F4DA7A' } },
28
- { match: http.PUT, style: { format: cs.HEX, fore: '#000000', back: '#74AEF6' } },
29
- { match: http.DELETE, style: { format: cs.HEX, fore: '#000000', back: '#EF968A' } },
30
- { match: http.CONNECT, style: { format: cs.HEX, fore: '#000000', back: '#FFFFFF' } },
31
- { match: http.OPTIONS, style: { format: cs.HEX, fore: '#000000', back: '#E55AA8' } },
32
- { match: http.TRACE, style: { format: cs.HEX, fore: '#000000', back: '#FFFFFF' } },
33
- { match: http.PATCH, style: { format: cs.HEX, fore: '#000000', back: '#C0A8E1' } },
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: cs.ANSI256, fore: 31, back: null } }, // red
38
- { match: wll.WARNING, style: { format: cs.ANSI256, fore: 33, back: null } }, // yellow
39
- { match: wll.INFO, style: { format: cs.ANSI256, fore: 32, back: null } }, // green
40
- { match: wll.HTTP, style: { format: cs.ANSI256, fore: 32, back: null } },
41
- { match: wll.VERBOSE, style: { format: cs.ANSI256, fore: 36, back: null } }, // cyan
42
- { match: wll.DEBUG, style: { format: cs.ANSI256, fore: 34, back: null } }, // blue
43
- { match: wll.SILLY, style: { format: cs.ANSI256, fore: 35, back: null } }, // magenta
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
- 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
+ };
@@ -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, getRequestResponseTime, parseRequest } = require('./http');
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.additionalWriteFn = options.additionalWriteFn || null;
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.displayUsername : true;
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
- static log(options) {
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 logger = options.logger || (options.user ? options.user.logger : null) || ((app && app.locals) ? app.locals.logger : null);
213
- if (logger) return logger;
214
- const zeddemoreLogger = new ZeddemoreLogger();
215
- return zeddemoreLogger.createLogger(options);
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 (additionalWriteFn) additionalWriteFn(req, res);
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.log,
523
+ log: ZeddemoreLogger.getLogger,
338
524
  winstonLogLevelIdToName,
339
525
  writeAxiosErrorLog: ZeddemoreLogger.writeAxiosErrorLog,
340
526
  writeAxiosLog: ZeddemoreLogger.writeAxiosResponseLog, // deprecated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeddemore-logger",
3
- "version": "1.0.7",
3
+ "version": "1.2.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",