@naturalcycles/backend-lib 2.72.0 → 2.73.3

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/dist/index.d.ts CHANGED
@@ -27,7 +27,9 @@ import { coloredHttpCode, logRequest } from './server/request.log.util';
27
27
  import { BackendServer, startServer } from './server/startServer';
28
28
  import { StartServerCfg, StartServerData } from './server/startServer.model';
29
29
  import { createAsyncLocalStorage, getRequest, getRequestLogger, requestLogger } from './server/handlers/asyncLocalStorage.mw';
30
- import { createGAELogMiddleware, RequestWithLog } from './server/handlers/createGaeLogMiddleware';
30
+ import type { RequestWithLog } from './server/handlers/createGaeLogMiddleware';
31
+ export * from './server/handlers/createGaeLogMiddleware';
31
32
  import { safeJsonMiddleware } from './server/handlers/safeJsonMiddleware';
33
+ export * from './server/request.util';
32
34
  export type { MethodOverrideCfg, SentrySharedServiceCfg, RequestHandlerWithPath, RequestHandlerCfg, DefaultAppCfg, StartServerCfg, StartServerData, EnvSharedServiceCfg, BaseEnv, AdminMiddleware, AdminServiceCfg, AdminInfo, RequireAdminCfg, SecureHeaderMiddlewareCfg, BodyParserTimeoutCfg, RequestTimeoutCfg, SimpleRequestLoggerCfg, ReqValidationOptions, RequestWithLog, };
33
- export { BackendServer, SentrySharedService, EnvSharedService, reqValidation, notFoundHandler, genericErrorHandler, methodOverride, createDefaultApp, startServer, catchWrapper, getDefaultRouter, isGAE, statusHandler, statusHandlerData, okHandler, getDeployInfo, onFinished, respondWithError, logRequest, FirebaseSharedService, createAdminMiddleware, BaseAdminService, loginHtml, createSecureHeaderMiddleware, bodyParserTimeout, clearBodyParserTimeout, requestTimeout, simpleRequestLogger, coloredHttpCode, validateBody, validateParams, validateQuery, createAsyncLocalStorage, createGAELogMiddleware, getRequest, getRequestLogger, requestLogger, serverStatsHTMLHandler, serverStatsMiddleware, safeJsonMiddleware, };
35
+ export { BackendServer, SentrySharedService, EnvSharedService, reqValidation, notFoundHandler, genericErrorHandler, methodOverride, createDefaultApp, startServer, catchWrapper, getDefaultRouter, isGAE, statusHandler, statusHandlerData, okHandler, getDeployInfo, onFinished, respondWithError, logRequest, FirebaseSharedService, createAdminMiddleware, BaseAdminService, loginHtml, createSecureHeaderMiddleware, bodyParserTimeout, clearBodyParserTimeout, requestTimeout, simpleRequestLogger, coloredHttpCode, validateBody, validateParams, validateQuery, createAsyncLocalStorage, getRequest, getRequestLogger, requestLogger, serverStatsHTMLHandler, serverStatsMiddleware, safeJsonMiddleware, };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.safeJsonMiddleware = exports.serverStatsMiddleware = exports.serverStatsHTMLHandler = exports.requestLogger = exports.getRequestLogger = exports.getRequest = exports.createGAELogMiddleware = exports.createAsyncLocalStorage = exports.validateQuery = exports.validateParams = exports.validateBody = exports.coloredHttpCode = exports.simpleRequestLogger = exports.requestTimeout = exports.clearBodyParserTimeout = exports.bodyParserTimeout = exports.createSecureHeaderMiddleware = exports.loginHtml = exports.BaseAdminService = exports.createAdminMiddleware = exports.FirebaseSharedService = exports.logRequest = exports.respondWithError = exports.onFinished = exports.getDeployInfo = exports.okHandler = exports.statusHandlerData = exports.statusHandler = exports.isGAE = exports.getDefaultRouter = exports.catchWrapper = exports.startServer = exports.createDefaultApp = exports.methodOverride = exports.genericErrorHandler = exports.notFoundHandler = exports.reqValidation = exports.EnvSharedService = exports.SentrySharedService = exports.BackendServer = void 0;
3
+ exports.safeJsonMiddleware = exports.serverStatsMiddleware = exports.serverStatsHTMLHandler = exports.requestLogger = exports.getRequestLogger = exports.getRequest = exports.createAsyncLocalStorage = exports.validateQuery = exports.validateParams = exports.validateBody = exports.coloredHttpCode = exports.simpleRequestLogger = exports.requestTimeout = exports.clearBodyParserTimeout = exports.bodyParserTimeout = exports.createSecureHeaderMiddleware = exports.loginHtml = exports.BaseAdminService = exports.createAdminMiddleware = exports.FirebaseSharedService = exports.logRequest = exports.respondWithError = exports.onFinished = exports.getDeployInfo = exports.okHandler = exports.statusHandlerData = exports.statusHandler = exports.isGAE = exports.getDefaultRouter = exports.catchWrapper = exports.startServer = exports.createDefaultApp = exports.methodOverride = exports.genericErrorHandler = exports.notFoundHandler = exports.reqValidation = exports.EnvSharedService = exports.SentrySharedService = exports.BackendServer = void 0;
4
+ const tslib_1 = require("tslib");
4
5
  const onFinished = require("on-finished");
5
6
  exports.onFinished = onFinished;
6
7
  const admin_mw_1 = require("./admin/admin.mw");
@@ -65,7 +66,7 @@ Object.defineProperty(exports, "createAsyncLocalStorage", { enumerable: true, ge
65
66
  Object.defineProperty(exports, "getRequest", { enumerable: true, get: function () { return asyncLocalStorage_mw_1.getRequest; } });
66
67
  Object.defineProperty(exports, "getRequestLogger", { enumerable: true, get: function () { return asyncLocalStorage_mw_1.getRequestLogger; } });
67
68
  Object.defineProperty(exports, "requestLogger", { enumerable: true, get: function () { return asyncLocalStorage_mw_1.requestLogger; } });
68
- const createGaeLogMiddleware_1 = require("./server/handlers/createGaeLogMiddleware");
69
- Object.defineProperty(exports, "createGAELogMiddleware", { enumerable: true, get: function () { return createGaeLogMiddleware_1.createGAELogMiddleware; } });
69
+ (0, tslib_1.__exportStar)(require("./server/handlers/createGaeLogMiddleware"), exports);
70
70
  const safeJsonMiddleware_1 = require("./server/handlers/safeJsonMiddleware");
71
71
  Object.defineProperty(exports, "safeJsonMiddleware", { enumerable: true, get: function () { return safeJsonMiddleware_1.safeJsonMiddleware; } });
72
+ (0, tslib_1.__exportStar)(require("./server/request.util"), exports);
@@ -1,3 +1,4 @@
1
+ import { CommonLogger } from '@naturalcycles/js-lib';
1
2
  import { Severity } from '@sentry/node';
2
3
  import type { Breadcrumb, NodeOptions } from '@sentry/node';
3
4
  import type * as SentryLib from '@sentry/node';
@@ -35,4 +36,12 @@ export declare class SentrySharedService {
35
36
  */
36
37
  captureMessage(msg: string, level?: Severity): string;
37
38
  addBreadcrumb(breadcrumb: Breadcrumb): void;
39
+ /**
40
+ * Currently it will only use `logger.error` ("error" level) and ignore `log` and `warn`.
41
+ *
42
+ * For each `logger.error` - it'll do a captureException.
43
+ *
44
+ * @experimental
45
+ */
46
+ getCommonLogger(): CommonLogger;
38
47
  }
@@ -84,7 +84,7 @@ class SentrySharedService {
84
84
  }),
85
85
  // data: (err as AppError).data, // included in message
86
86
  });
87
- return this.sentry().captureException((0, js_lib_1._anyToError)(err, {
87
+ return this.sentry().captureException((0, js_lib_1._anyToError)(err, Error, {
88
88
  stringifyFn: nodejs_lib_1.inspectAnyStringifyFn,
89
89
  }));
90
90
  }
@@ -98,6 +98,33 @@ class SentrySharedService {
98
98
  addBreadcrumb(breadcrumb) {
99
99
  this.sentry().addBreadcrumb(breadcrumb);
100
100
  }
101
+ /**
102
+ * Currently it will only use `logger.error` ("error" level) and ignore `log` and `warn`.
103
+ *
104
+ * For each `logger.error` - it'll do a captureException.
105
+ *
106
+ * @experimental
107
+ */
108
+ getCommonLogger() {
109
+ return {
110
+ log: () => { },
111
+ warn: () => { },
112
+ error: (...args) => {
113
+ const message = args
114
+ .map(arg => (0, nodejs_lib_1.inspectAny)(arg, {
115
+ includeErrorData: true,
116
+ colors: false,
117
+ }))
118
+ .join(' ');
119
+ this.sentry().addBreadcrumb({
120
+ message,
121
+ });
122
+ this.sentry().captureException((0, js_lib_1._anyToError)(args.length === 1 ? args[0] : args, Error, {
123
+ stringifyFn: nodejs_lib_1.inspectAnyStringifyFn,
124
+ }));
125
+ },
126
+ };
127
+ }
101
128
  }
102
129
  (0, tslib_1.__decorate)([
103
130
  (0, js_lib_1._Memo)()
@@ -4,7 +4,7 @@ export interface RequestLocalStorage {
4
4
  req: Request;
5
5
  }
6
6
  export declare function createAsyncLocalStorage(): RequestHandler;
7
- export declare function getRequest(): Request;
7
+ export declare function getRequest(): Request | undefined;
8
8
  /**
9
9
  * It requires both `createAsyncLocalStorage` and `createGAELogMiddleware` to be in use to work.
10
10
  *
@@ -19,7 +19,7 @@ function createAsyncLocalStorage() {
19
19
  }
20
20
  exports.createAsyncLocalStorage = createAsyncLocalStorage;
21
21
  function getRequest() {
22
- return storage().getStore().req;
22
+ return storage().getStore()?.req;
23
23
  }
24
24
  exports.getRequest = getRequest;
25
25
  /**
@@ -28,7 +28,7 @@ exports.getRequest = getRequest;
28
28
  * @experimental
29
29
  */
30
30
  function getRequestLogger() {
31
- return storage().getStore()?.req || (isGAE ? createGaeLogMiddleware_1.defaultAppEngineLogger : createGaeLogMiddleware_1.defaultDevLogger);
31
+ return storage().getStore()?.req || (isGAE ? createGaeLogMiddleware_1.gaeLogger : createGaeLogMiddleware_1.devLogger);
32
32
  }
33
33
  exports.getRequestLogger = getRequestLogger;
34
34
  /**
@@ -18,11 +18,13 @@ declare module 'http' {
18
18
  }
19
19
  }
20
20
  /**
21
- * Outside-of-request logger.
21
+ * Logger that logs in AppEngine format.
22
+ * To be used in outside-of-request situations (otherwise req.log should be used).
22
23
  */
23
- export declare const defaultAppEngineLogger: CommonLogger;
24
+ export declare const gaeLogger: CommonLogger;
24
25
  /**
25
- * Outside-of-request local logger
26
+ * Fancy development logger, to be used in outside-of-request situations
27
+ * (otherwise req.log should be used).
26
28
  */
27
- export declare const defaultDevLogger: CommonLogger;
29
+ export declare const devLogger: CommonLogger;
28
30
  export declare function createGAELogMiddleware(): RequestHandler;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createGAELogMiddleware = exports.defaultDevLogger = exports.defaultAppEngineLogger = void 0;
3
+ exports.createGAELogMiddleware = exports.devLogger = exports.gaeLogger = void 0;
4
4
  const util_1 = require("util");
5
5
  const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
6
6
  const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
@@ -9,17 +9,19 @@ const isGAE = !!GAE_INSTANCE;
9
9
  // Simple "request counter" (poor man's "correlation id") counter, to use on dev machine (not in the cloud)
10
10
  let reqCounter = 0;
11
11
  /**
12
- * Outside-of-request logger.
12
+ * Logger that logs in AppEngine format.
13
+ * To be used in outside-of-request situations (otherwise req.log should be used).
13
14
  */
14
- exports.defaultAppEngineLogger = {
15
+ exports.gaeLogger = {
15
16
  log: (...args) => logToAppEngine({}, args),
16
17
  warn: (...args) => logToAppEngine({ severity: 'WARNING' }, args),
17
18
  error: (...args) => logToAppEngine({ severity: 'ERROR' }, args),
18
19
  };
19
20
  /**
20
- * Outside-of-request local logger
21
+ * Fancy development logger, to be used in outside-of-request situations
22
+ * (otherwise req.log should be used).
21
23
  */
22
- exports.defaultDevLogger = {
24
+ exports.devLogger = {
23
25
  log: (...args) => logToDev(null, args),
24
26
  warn: (...args) => logToDev(null, args),
25
27
  error: (...args) => logToDev(null, args),
@@ -50,14 +52,13 @@ function createGAELogMiddleware() {
50
52
  }
51
53
  // Otherwise, we're in AppEngine
52
54
  return function gaeLogMiddleware(req, res, next) {
53
- const meta = {};
54
55
  const traceHeader = req.header('x-cloud-trace-context');
55
56
  if (traceHeader) {
56
57
  const [trace] = traceHeader.split('/');
57
- Object.assign(meta, {
58
+ const meta = {
58
59
  'logging.googleapis.com/trace': `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`,
59
60
  'appengine.googleapis.com/request_id': req.header('x-appengine-request-log-id'),
60
- });
61
+ };
61
62
  Object.assign(req, {
62
63
  log: (...args) => logToAppEngine({ ...meta, severity: 'INFO' }, args),
63
64
  warn: (...args) => logToAppEngine({ ...meta, severity: 'WARNING' }, args),
@@ -66,7 +67,7 @@ function createGAELogMiddleware() {
66
67
  req.requestId = trace;
67
68
  }
68
69
  else {
69
- Object.assign(req, exports.defaultAppEngineLogger);
70
+ Object.assign(req, exports.gaeLogger);
70
71
  }
71
72
  next();
72
73
  };
@@ -37,26 +37,29 @@ function respondWithError(req, res, err) {
37
37
  var _a, _b;
38
38
  const { headersSent } = res;
39
39
  req.error(`genericErrorHandler${headersSent ? ' after headersSent' : ''}:\n`, err);
40
- const error = (0, js_lib_1._anyToErrorObject)(err, {
40
+ const originalError = (0, js_lib_1._anyToError)(err, Error, {
41
41
  stringifyFn: nodejs_lib_1.inspectAnyStringifyFn,
42
- includeErrorStack,
43
- includeErrorData: true,
44
42
  });
45
- (_a = error.data).httpStatusCode || (_a.httpStatusCode = 500); // default to 500
46
- error.data.headersSent = headersSent || undefined;
47
- (_b = error.data).report || (_b.report = undefined); // set to undefined if false
48
- (0, js_lib_1._filterUndefinedValues)(error.data, true);
49
- if (sentryService && shouldReportToSentry(error)) {
50
- error.data.errorId = sentryService.captureException(error, false);
43
+ let errorId;
44
+ if (sentryService && shouldReportToSentry(originalError)) {
45
+ errorId = sentryService.captureException(originalError, false);
51
46
  }
52
47
  if (res.headersSent)
53
48
  return;
54
- res.status(error.data.httpStatusCode).json({
55
- error,
49
+ const httpError = (0, js_lib_1._errorToErrorObject)(originalError, includeErrorStack);
50
+ httpError.data.errorId = errorId;
51
+ (_a = httpError.data).httpStatusCode || (_a.httpStatusCode = 500); // default to 500
52
+ httpError.data.headersSent = headersSent || undefined;
53
+ (_b = httpError.data).report || (_b.report = undefined); // set to undefined if false
54
+ (0, js_lib_1._filterUndefinedValues)(httpError.data, true);
55
+ res.status(httpError.data.httpStatusCode).json({
56
+ error: httpError,
56
57
  });
57
58
  }
58
59
  exports.respondWithError = respondWithError;
59
60
  function shouldReportToSentry(err) {
60
61
  // Only report 5xx
61
- return err.data.report || err.data.httpStatusCode >= 500;
62
+ return (err?.data?.report ||
63
+ !err?.data ||
64
+ err.data.httpStatusCode >= 500);
62
65
  }
@@ -23,8 +23,7 @@ const serverStatsHTMLHandler = (req, res) => {
23
23
  // calc things
24
24
  (0, js_lib_1._stringMapValues)(serverStatsMap).forEach(s => {
25
25
  s.total = s['2xx'] + s['4xx'] + s['5xx'];
26
- s.pc = {};
27
- percentiles.forEach(pc => (s.pc[pc] = Math.round((0, js_lib_1._percentile)(s.stack.items, pc))));
26
+ s.pc = (0, js_lib_1._mapValues)(s.stack.percentiles(percentiles), (_k, v) => Math.round(v), true);
28
27
  });
29
28
  const allLatencies = (0, js_lib_1._stringMapValues)(serverStatsMap).flatMap(s => s.stack.items);
30
29
  const all2xx = (0, js_lib_1._sum)((0, js_lib_1._stringMapValues)(serverStatsMap).flatMap(s => s['2xx']));
@@ -80,7 +79,7 @@ function serverStatsMiddleware() {
80
79
  const latency = now - started;
81
80
  const endpoint = (0, request_util_1.getRequestEndpoint)(req);
82
81
  serverStatsMap[endpoint] || (serverStatsMap[endpoint] = {
83
- stack: new SizeLimitedStack(SIZE),
82
+ stack: new js_lib_1.NumberStack(SIZE),
84
83
  '2xx': 0,
85
84
  '4xx': 0,
86
85
  '5xx': 0,
@@ -112,14 +111,3 @@ function getStatusFamily(statusCode) {
112
111
  return '4xx';
113
112
  return '5xx';
114
113
  }
115
- class SizeLimitedStack {
116
- constructor(size) {
117
- this.size = size;
118
- this.index = 0;
119
- this.items = [];
120
- }
121
- push(item) {
122
- this.items[this.index] = item;
123
- this.index = this.index === this.size ? 0 : this.index + 1;
124
- }
125
- }
@@ -5,9 +5,11 @@ const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const colors_1 = require("@naturalcycles/nodejs-lib/dist/colors");
6
6
  const index_1 = require("../../index");
7
7
  const request_log_util_1 = require("../request.log.util");
8
+ const { APP_ENV } = process.env;
8
9
  function simpleRequestLogger(_cfg = {}) {
9
10
  // Disable logger in AppEngine, as it doesn't make sense there
10
- if ((0, index_1.isGAE)())
11
+ // UPD: Only log in dev environment
12
+ if (APP_ENV !== 'dev')
11
13
  return (req, res, next) => next();
12
14
  const cfg = {
13
15
  logStart: false,
@@ -18,7 +20,6 @@ function simpleRequestLogger(_cfg = {}) {
18
20
  return (req, res, next) => {
19
21
  const started = Date.now();
20
22
  if (logStart) {
21
- ;
22
23
  req.log(['>>', req.method, (0, colors_1.boldGrey)(req.url)].join(' '));
23
24
  }
24
25
  if (logFinish) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
- "version": "2.72.0",
3
+ "version": "2.73.3",
4
4
  "scripts": {
5
- "prepare": "husky install",
5
+ "prepare": "husky install && patch-package",
6
6
  "serve": "APP_ENV=dev nodemon",
7
7
  "docs-serve": "vuepress dev docs",
8
8
  "docs-build": "vuepress build docs",
@@ -45,6 +45,8 @@
45
45
  "@types/ejs": "^3.0.0",
46
46
  "@types/js-yaml": "^4.0.0",
47
47
  "@types/node": "^16.4.1",
48
+ "esbuild": "^0.13.13",
49
+ "esbuild-register": "^3.1.2",
48
50
  "fastify": "^3.20.1",
49
51
  "jest": "^27.0.1",
50
52
  "nodemon": "^2.0.14",
package/src/index.ts CHANGED
@@ -51,8 +51,10 @@ import {
51
51
  getRequestLogger,
52
52
  requestLogger,
53
53
  } from './server/handlers/asyncLocalStorage.mw'
54
- import { createGAELogMiddleware, RequestWithLog } from './server/handlers/createGaeLogMiddleware'
54
+ import type { RequestWithLog } from './server/handlers/createGaeLogMiddleware'
55
+ export * from './server/handlers/createGaeLogMiddleware'
55
56
  import { safeJsonMiddleware } from './server/handlers/safeJsonMiddleware'
57
+ export * from './server/request.util'
56
58
 
57
59
  export type {
58
60
  MethodOverrideCfg,
@@ -110,7 +112,6 @@ export {
110
112
  validateParams,
111
113
  validateQuery,
112
114
  createAsyncLocalStorage,
113
- createGAELogMiddleware,
114
115
  getRequest,
115
116
  getRequestLogger,
116
117
  requestLogger,
@@ -1,4 +1,4 @@
1
- import { _anyToError, _Memo, CommonLogLevel } from '@naturalcycles/js-lib'
1
+ import { _anyToError, _Memo, CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib'
2
2
  import { inspectAny, inspectAnyStringifyFn } from '@naturalcycles/nodejs-lib'
3
3
  import { Severity } from '@sentry/node'
4
4
  import type { Breadcrumb, NodeOptions } from '@sentry/node'
@@ -98,7 +98,7 @@ export class SentrySharedService {
98
98
  })
99
99
 
100
100
  return this.sentry().captureException(
101
- _anyToError(err, {
101
+ _anyToError(err, Error, {
102
102
  stringifyFn: inspectAnyStringifyFn,
103
103
  }),
104
104
  )
@@ -115,4 +115,38 @@ export class SentrySharedService {
115
115
  addBreadcrumb(breadcrumb: Breadcrumb): void {
116
116
  this.sentry().addBreadcrumb(breadcrumb)
117
117
  }
118
+
119
+ /**
120
+ * Currently it will only use `logger.error` ("error" level) and ignore `log` and `warn`.
121
+ *
122
+ * For each `logger.error` - it'll do a captureException.
123
+ *
124
+ * @experimental
125
+ */
126
+ getCommonLogger(): CommonLogger {
127
+ return {
128
+ log: () => {}, // noop
129
+ warn: () => {}, // noop
130
+ error: (...args) => {
131
+ const message = args
132
+ .map(arg =>
133
+ inspectAny(arg, {
134
+ includeErrorData: true,
135
+ colors: false,
136
+ }),
137
+ )
138
+ .join(' ')
139
+
140
+ this.sentry().addBreadcrumb({
141
+ message,
142
+ })
143
+
144
+ this.sentry().captureException(
145
+ _anyToError(args.length === 1 ? args[0] : args, Error, {
146
+ stringifyFn: inspectAnyStringifyFn,
147
+ }),
148
+ )
149
+ },
150
+ }
151
+ }
118
152
  }
@@ -1,7 +1,7 @@
1
1
  import { AsyncLocalStorage } from 'async_hooks'
2
2
  import { _lazyValue, CommonLogger } from '@naturalcycles/js-lib'
3
3
  import { Request, RequestHandler } from 'express'
4
- import { defaultAppEngineLogger, defaultDevLogger } from './createGaeLogMiddleware'
4
+ import { gaeLogger, devLogger } from './createGaeLogMiddleware'
5
5
 
6
6
  const { GAE_INSTANCE } = process.env
7
7
  const isGAE = !!GAE_INSTANCE
@@ -24,8 +24,8 @@ export function createAsyncLocalStorage(): RequestHandler {
24
24
  }
25
25
  }
26
26
 
27
- export function getRequest(): Request {
28
- return storage().getStore()!.req
27
+ export function getRequest(): Request | undefined {
28
+ return storage().getStore()?.req
29
29
  }
30
30
 
31
31
  /**
@@ -34,7 +34,7 @@ export function getRequest(): Request {
34
34
  * @experimental
35
35
  */
36
36
  export function getRequestLogger(): CommonLogger {
37
- return storage().getStore()?.req || (isGAE ? defaultAppEngineLogger : defaultDevLogger)
37
+ return storage().getStore()?.req || (isGAE ? gaeLogger : devLogger)
38
38
  }
39
39
 
40
40
  /**
@@ -32,18 +32,20 @@ const isGAE = !!GAE_INSTANCE
32
32
  let reqCounter = 0
33
33
 
34
34
  /**
35
- * Outside-of-request logger.
35
+ * Logger that logs in AppEngine format.
36
+ * To be used in outside-of-request situations (otherwise req.log should be used).
36
37
  */
37
- export const defaultAppEngineLogger: CommonLogger = {
38
+ export const gaeLogger: CommonLogger = {
38
39
  log: (...args) => logToAppEngine({}, args),
39
40
  warn: (...args) => logToAppEngine({ severity: 'WARNING' }, args),
40
41
  error: (...args) => logToAppEngine({ severity: 'ERROR' }, args),
41
42
  }
42
43
 
43
44
  /**
44
- * Outside-of-request local logger
45
+ * Fancy development logger, to be used in outside-of-request situations
46
+ * (otherwise req.log should be used).
45
47
  */
46
- export const defaultDevLogger: CommonLogger = {
48
+ export const devLogger: CommonLogger = {
47
49
  log: (...args) => logToDev(null, args),
48
50
  warn: (...args) => logToDev(null, args),
49
51
  error: (...args) => logToDev(null, args),
@@ -83,15 +85,13 @@ export function createGAELogMiddleware(): RequestHandler {
83
85
  // Otherwise, we're in AppEngine
84
86
 
85
87
  return function gaeLogMiddleware(req, res, next) {
86
- const meta: AnyObject = {}
87
-
88
88
  const traceHeader = req.header('x-cloud-trace-context')
89
89
  if (traceHeader) {
90
90
  const [trace] = traceHeader.split('/')
91
- Object.assign(meta, {
91
+ const meta = {
92
92
  'logging.googleapis.com/trace': `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`,
93
93
  'appengine.googleapis.com/request_id': req.header('x-appengine-request-log-id'),
94
- })
94
+ }
95
95
  Object.assign(req, {
96
96
  log: (...args: any[]) => logToAppEngine({ ...meta, severity: 'INFO' }, args),
97
97
  warn: (...args: any[]) => logToAppEngine({ ...meta, severity: 'WARNING' }, args),
@@ -99,7 +99,7 @@ export function createGAELogMiddleware(): RequestHandler {
99
99
  })
100
100
  req.requestId = trace
101
101
  } else {
102
- Object.assign(req, defaultAppEngineLogger)
102
+ Object.assign(req, gaeLogger)
103
103
  }
104
104
 
105
105
  next()
@@ -1,5 +1,6 @@
1
1
  import {
2
- _anyToErrorObject,
2
+ _anyToError,
3
+ _errorToErrorObject,
3
4
  _filterUndefinedValues,
4
5
  HttpError,
5
6
  HttpErrorData,
@@ -53,29 +54,36 @@ export function respondWithError(req: RequestWithLog, res: Response, err: any):
53
54
 
54
55
  req.error(`genericErrorHandler${headersSent ? ' after headersSent' : ''}:\n`, err)
55
56
 
56
- const error = _anyToErrorObject<HttpErrorData>(err, {
57
+ const originalError = _anyToError(err, Error, {
57
58
  stringifyFn: inspectAnyStringifyFn,
58
- includeErrorStack,
59
- includeErrorData: true,
60
- }) as HttpError
59
+ })
61
60
 
62
- error.data.httpStatusCode ||= 500 // default to 500
63
- error.data.headersSent = headersSent || undefined
64
- error.data.report ||= undefined // set to undefined if false
65
- _filterUndefinedValues(error.data, true)
61
+ let errorId: string | undefined
66
62
 
67
- if (sentryService && shouldReportToSentry(error)) {
68
- error.data.errorId = sentryService.captureException(error, false)
63
+ if (sentryService && shouldReportToSentry(originalError)) {
64
+ errorId = sentryService.captureException(originalError, false)
69
65
  }
70
66
 
71
67
  if (res.headersSent) return
72
68
 
73
- res.status(error.data.httpStatusCode).json({
74
- error,
69
+ const httpError = _errorToErrorObject<HttpErrorData>(originalError, includeErrorStack)
70
+
71
+ httpError.data.errorId = errorId
72
+ httpError.data.httpStatusCode ||= 500 // default to 500
73
+ httpError.data.headersSent = headersSent || undefined
74
+ httpError.data.report ||= undefined // set to undefined if false
75
+ _filterUndefinedValues(httpError.data, true)
76
+
77
+ res.status(httpError.data.httpStatusCode).json({
78
+ error: httpError,
75
79
  } as HttpErrorResponse)
76
80
  }
77
81
 
78
- function shouldReportToSentry(err: HttpError): boolean {
82
+ function shouldReportToSentry(err: Error): boolean {
79
83
  // Only report 5xx
80
- return err.data.report || err.data.httpStatusCode >= 500
84
+ return (
85
+ (err as HttpError)?.data?.report ||
86
+ !(err as HttpError)?.data ||
87
+ (err as HttpError).data.httpStatusCode >= 500
88
+ )
81
89
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  _get,
3
+ _mapValues,
3
4
  _mb,
4
5
  _ms,
5
6
  _percentile,
@@ -7,6 +8,7 @@ import {
7
8
  _stringMapEntries,
8
9
  _stringMapValues,
9
10
  _sum,
11
+ NumberStack,
10
12
  StringMap,
11
13
  } from '@naturalcycles/js-lib'
12
14
  import { RequestHandler } from 'express'
@@ -17,14 +19,14 @@ const { GAE_INSTANCE } = process.env
17
19
 
18
20
  // Map from "endpoint" to latency
19
21
  interface Stat {
20
- stack: SizeLimitedStack<number>
22
+ stack: NumberStack
21
23
  '2xx': number
22
24
  '4xx': number
23
25
  '5xx': number
24
26
 
25
27
  // calculated on the fly
26
28
  total?: number
27
- pc?: StringMap<number> // e.g 50 => 123
29
+ pc?: Record<number, number> // e.g 50 => 123
28
30
  }
29
31
 
30
32
  const serverStatsMap: StringMap<Stat> = {}
@@ -49,8 +51,7 @@ export const serverStatsHTMLHandler: RequestHandler = (req, res) => {
49
51
  // calc things
50
52
  _stringMapValues(serverStatsMap).forEach(s => {
51
53
  s.total = s['2xx'] + s['4xx'] + s['5xx']
52
- s.pc = {}
53
- percentiles.forEach(pc => (s.pc![pc] = Math.round(_percentile(s.stack.items, pc))))
54
+ s.pc = _mapValues(s.stack.percentiles(percentiles), (_k, v) => Math.round(v), true)
54
55
  })
55
56
  const allLatencies = _stringMapValues(serverStatsMap).flatMap(s => s.stack.items)
56
57
  const all2xx = _sum(_stringMapValues(serverStatsMap).flatMap(s => s['2xx']))
@@ -121,7 +122,7 @@ export function serverStatsMiddleware(): RequestHandler {
121
122
  const endpoint = getRequestEndpoint(req)
122
123
 
123
124
  serverStatsMap[endpoint] ||= {
124
- stack: new SizeLimitedStack<number>(SIZE),
125
+ stack: new NumberStack(SIZE),
125
126
  '2xx': 0,
126
127
  '4xx': 0,
127
128
  '5xx': 0,
@@ -155,15 +156,3 @@ function getStatusFamily(statusCode: number): '2xx' | '4xx' | '5xx' {
155
156
  if (statusCode < 500) return '4xx'
156
157
  return '5xx'
157
158
  }
158
-
159
- class SizeLimitedStack<T> {
160
- constructor(public size: number) {}
161
-
162
- index = 0
163
- items: T[] = []
164
-
165
- push(item: T): void {
166
- this.items[this.index] = item
167
- this.index = this.index === this.size ? 0 : this.index + 1
168
- }
169
- }
@@ -1,9 +1,11 @@
1
1
  import { _since } from '@naturalcycles/js-lib'
2
2
  import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib/dist/colors'
3
3
  import { RequestHandler, Response } from 'express'
4
- import { isGAE, onFinished, RequestWithLog } from '../../index'
4
+ import { onFinished } from '../../index'
5
5
  import { logRequest } from '../request.log.util'
6
6
 
7
+ const { APP_ENV } = process.env
8
+
7
9
  export interface SimpleRequestLoggerCfg {
8
10
  /**
9
11
  * @default false
@@ -18,7 +20,8 @@ export interface SimpleRequestLoggerCfg {
18
20
 
19
21
  export function simpleRequestLogger(_cfg: Partial<SimpleRequestLoggerCfg> = {}): RequestHandler {
20
22
  // Disable logger in AppEngine, as it doesn't make sense there
21
- if (isGAE()) return (req, res, next) => next()
23
+ // UPD: Only log in dev environment
24
+ if (APP_ENV !== 'dev') return (req, res, next) => next()
22
25
 
23
26
  const cfg: SimpleRequestLoggerCfg = {
24
27
  logStart: false,
@@ -31,7 +34,7 @@ export function simpleRequestLogger(_cfg: Partial<SimpleRequestLoggerCfg> = {}):
31
34
  const started = Date.now()
32
35
 
33
36
  if (logStart) {
34
- ;(req as RequestWithLog).log(['>>', req.method, boldGrey(req.url)].join(' '))
37
+ req.log(['>>', req.method, boldGrey(req.url)].join(' '))
35
38
  }
36
39
 
37
40
  if (logFinish) {