@naturalcycles/backend-lib 9.73.0 → 9.74.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.
@@ -168,7 +168,7 @@ export class BaseAdminService {
168
168
  err?.code?.includes('id-token-expired')) {
169
169
  return; // skip logging, expected error
170
170
  }
171
- req.error(`getEmailByToken error:`, err);
171
+ req.error({ msg: `getEmailByToken error:`, err });
172
172
  }
173
173
  }
174
174
  async getFirebaseAuth() {
@@ -191,7 +191,10 @@ export class BaseAdminService {
191
191
  */
192
192
  // oxlint-disable-next-line max-params
193
193
  async onPermissionCheck(req, email, reqPermissions, required, granted, meta = {}) {
194
- req.log(`${dimGrey(email)} ${required ? 'required' : 'optional'} permissions check [${dimGrey(reqPermissions.join(', '))}]: ${granted ? green('GRANTED') : red('DENIED')}`, meta);
194
+ req.log({
195
+ ...meta,
196
+ msg: `${dimGrey(email)} ${required ? 'required' : 'optional'} permissions check [${dimGrey(reqPermissions.join(', '))}]: ${granted ? green('GRANTED') : red('DENIED')}`,
197
+ });
195
198
  }
196
199
  /**
197
200
  * Install it on POST /admin/login url
@@ -2,7 +2,6 @@ import { asyncLocalStorageMiddleware } from '../server/asyncLocalStorageMiddlewa
2
2
  import { compressionMiddleware } from '../server/compressionMiddleware.js';
3
3
  import { genericErrorMiddleware } from '../server/genericErrorMiddleware.js';
4
4
  import { logMiddleware } from '../server/logMiddleware.js';
5
- import { methodOverrideMiddleware } from '../server/methodOverrideMiddleware.js';
6
5
  import { notFoundMiddleware } from '../server/notFoundMiddleware.js';
7
6
  import { requestTimeoutMiddleware } from '../server/requestTimeoutMiddleware.js';
8
7
  import { simpleRequestLoggerMiddleware } from '../server/simpleRequestLoggerMiddleware.js';
@@ -24,7 +23,6 @@ export async function createDefaultApp(cfg) {
24
23
  if (!isTest) {
25
24
  app.use(asyncLocalStorageMiddleware());
26
25
  }
27
- app.use(methodOverrideMiddleware());
28
26
  app.use(requestTimeoutMiddleware());
29
27
  // app.use(serverStatsMiddleware()) // disabled by default
30
28
  // app.use(bodyParserTimeout()) // removed by default
package/dist/index.d.ts CHANGED
@@ -7,7 +7,6 @@ export * from './server/compressionMiddleware.js';
7
7
  export * from './server/eventLoop.util.js';
8
8
  export * from './server/genericErrorMiddleware.js';
9
9
  export * from './server/logMiddleware.js';
10
- export * from './server/methodOverrideMiddleware.js';
11
10
  export * from './server/notFoundMiddleware.js';
12
11
  export * from './server/okMiddleware.js';
13
12
  export * from './server/request.log.util.js';
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ export * from './server/compressionMiddleware.js';
7
7
  export * from './server/eventLoop.util.js';
8
8
  export * from './server/genericErrorMiddleware.js';
9
9
  export * from './server/logMiddleware.js';
10
- export * from './server/methodOverrideMiddleware.js';
11
10
  export * from './server/notFoundMiddleware.js';
12
11
  export * from './server/okMiddleware.js';
13
12
  export * from './server/request.log.util.js';
@@ -1,5 +1,7 @@
1
1
  import { _anyToError, _isErrorObject } from '@naturalcycles/js-lib/error';
2
- import { _inspect } from '@naturalcycles/nodejs-lib';
2
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log';
3
+ import { _omit } from '@naturalcycles/js-lib/object/object.util.js';
4
+ import { _inspect, logEntryToString } from '@naturalcycles/nodejs-lib';
3
5
  import { getRequestLogger } from '../server/asyncLocalStorageMiddleware.js';
4
6
  const sentrySeverityMap = {
5
7
  debug: 'log',
@@ -60,7 +62,7 @@ export class SentrySharedService {
60
62
  const data = _isErrorObject(err) ? err.data : undefined;
61
63
  // Using request-aware logger here
62
64
  // Log both the error and attached ErrorData (if any)
63
- getRequestLogger().error(...[err_, data].filter(Boolean));
65
+ getRequestLogger().error({ err: err_, ...data });
64
66
  if (data?.report === false) {
65
67
  // Skip reporting the error
66
68
  return;
@@ -81,7 +83,7 @@ export class SentrySharedService {
81
83
  * Returns "eventId"
82
84
  */
83
85
  captureMessage(msg, level) {
84
- getRequestLogger()[sentrySeverityMap[level] || 'log']('captureMessage:', msg);
86
+ getRequestLogger()[sentrySeverityMap[level] || 'log'](`captureMessage: ${msg}`);
85
87
  return this.sentry.captureMessage(msg, level);
86
88
  }
87
89
  addBreadcrumb(breadcrumb) {
@@ -95,17 +97,14 @@ export class SentrySharedService {
95
97
  * @experimental
96
98
  */
97
99
  getCommonLogger() {
98
- return {
99
- debug: () => { }, // noop
100
- log: () => { }, // noop
101
- warn: () => { }, // noop
102
- error: (...args) => {
103
- const message = args.map(arg => _inspect(arg, INSPECT_OPT)).join(' ');
104
- this.sentry.addBreadcrumb({
105
- message,
106
- });
107
- this.sentry.captureException(_anyToError(args.length === 1 ? args[0] : args));
108
- },
109
- };
100
+ return commonLoggerCreate(entry => {
101
+ if (entry.level !== 'error')
102
+ return; // noop
103
+ const { msg, err } = entry;
104
+ this.sentry.addBreadcrumb({
105
+ message: logEntryToString(entry, INSPECT_OPT),
106
+ });
107
+ this.sentry.captureException(_anyToError(err ?? msg ?? _omit(entry, ['level'])));
108
+ });
110
109
  }
111
110
  }
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { _lazyValue } from '@naturalcycles/js-lib';
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log';
3
4
  import { ciLogger, devLogger, gcpStructuredLogger } from './logMiddleware.js';
4
5
  const { GAE_INSTANCE, K_SERVICE, CI } = process.env;
5
6
  const isGAE = !!GAE_INSTANCE;
@@ -43,9 +44,4 @@ export function getRequestLogger() {
43
44
  *
44
45
  * @experimental
45
46
  */
46
- export const requestLogger = {
47
- debug: (...args) => getRequestLogger().debug(...args),
48
- log: (...args) => getRequestLogger().log(...args),
49
- warn: (...args) => getRequestLogger().warn(...args),
50
- error: (...args) => getRequestLogger().error(...args),
51
- };
47
+ export const requestLogger = commonLoggerCreate(({ level, ...data }) => getRequestLogger()[level](data));
@@ -40,10 +40,10 @@ export function respondWithError(req, res, err) {
40
40
  else {
41
41
  // because errorService was not provided - we are going to log the error here
42
42
  if (headersSent) {
43
- req.error(`error after headersSent:`, err);
43
+ req.error({ msg: `error after headersSent:`, err });
44
44
  }
45
45
  else {
46
- req.error(err);
46
+ req.error({ err });
47
47
  }
48
48
  // todo: add endpoint to the log
49
49
  // todo: add userId from the "Context" (or, just req.userId?) to the log
@@ -1,9 +1,9 @@
1
1
  import { inspect } from 'node:util';
2
- import { _isPlainObject } from '@naturalcycles/js-lib';
3
2
  import { _anyToErrorObject } from '@naturalcycles/js-lib/error';
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log';
4
4
  import { _safeJsonStringify } from '@naturalcycles/js-lib/string/safeJsonStringify.js';
5
5
  import { _objectAssign } from '@naturalcycles/js-lib/types';
6
- import { _inspect } from '@naturalcycles/nodejs-lib';
6
+ import { logEntryToString } from '@naturalcycles/nodejs-lib';
7
7
  import { dimGrey } from '@naturalcycles/nodejs-lib/colors';
8
8
  const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, K_SERVICE, APP_ENV } = process.env;
9
9
  const isGAE = !!GAE_INSTANCE;
@@ -16,81 +16,49 @@ let reqCounter = 0;
16
16
  * Logger that logs in "GCP structured log" format.
17
17
  * To be used in outside-of-request situations (otherwise req.log should be used).
18
18
  */
19
- export const gcpStructuredLogger = {
20
- debug: (...args) => writeGCPStructuredLog({ severity: 'DEBUG' }, args),
21
- log: (...args) => writeGCPStructuredLog({}, args),
22
- warn: (...args) => writeGCPStructuredLog({ severity: 'WARNING' }, args),
23
- error: (...args) => writeGCPStructuredLog({ severity: 'ERROR' }, args),
24
- };
19
+ export const gcpStructuredLogger = commonLoggerCreate(entry => writeGCPStructuredLog({}, entry));
25
20
  /**
26
21
  * Fancy development logger, to be used in outside-of-request situations
27
22
  * (otherwise req.log should be used).
28
23
  */
29
- export const devLogger = {
30
- debug: (...args) => logToDev(null, args),
31
- log: (...args) => logToDev(null, args),
32
- warn: (...args) => logToDev(null, args),
33
- error: (...args) => logToDev(null, args),
34
- };
24
+ export const devLogger = commonLoggerCreate(entry => logToDev(null, entry));
35
25
  /**
36
26
  * Same as devLogger, but without colors (e.g to not confuse Sentry).
37
27
  */
38
- export const ciLogger = {
39
- debug: (...args) => logToCI(args),
40
- log: (...args) => logToCI(args),
41
- warn: (...args) => logToCI(args),
42
- error: (...args) => logToCI(args),
28
+ export const ciLogger = commonLoggerCreate(logToCI);
29
+ const gcpSeverityByLevel = {
30
+ debug: 'DEBUG',
31
+ log: 'INFO',
32
+ warn: 'WARNING',
33
+ error: 'ERROR',
43
34
  };
44
35
  // Documented here: https://cloud.google.com/logging/docs/structured-logging
45
36
  // Cloud Run logging: https://cloud.google.com/run/docs/logging
46
- function writeGCPStructuredLog(meta, args) {
47
- if (args.length !== 1 || !_isPlainObject(args[0])) {
48
- console.log(JSON.stringify({
49
- message: args.map(a => (typeof a === 'string' ? a : inspect(a))).join(' '),
50
- ...meta,
51
- }));
52
- return;
53
- }
54
- const { msg, err, ...entry } = args[0];
37
+ function writeGCPStructuredLog(meta, entry) {
38
+ const { level, msg, err, ...fields } = entry;
55
39
  if (err instanceof Error) {
56
- entry['err'] = _anyToErrorObject(err);
57
- entry['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err);
40
+ fields['err'] = _anyToErrorObject(err);
41
+ fields['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err);
58
42
  }
59
43
  else {
60
44
  if (err !== undefined)
61
- entry['err'] = err;
45
+ fields['err'] = err;
62
46
  if (msg !== undefined)
63
- entry['message'] = msg;
47
+ fields['message'] = msg;
64
48
  }
65
- console.log(_safeJsonStringify({ ...entry, ...meta }));
49
+ console.log(_safeJsonStringify({ ...fields, ...meta, severity: gcpSeverityByLevel[level] }));
66
50
  }
67
- function logToDev(requestId, args) {
51
+ function logToDev(requestId, entry) {
68
52
  // Run on local machine
69
- if (args.length !== 1 || !_isPlainObject(args[0])) {
70
- console.log([
71
- requestId ? [dimGrey(`[${requestId}]`)] : [],
72
- ...args.map(a => _inspect(a, { includeErrorStack: true, colors: true })),
73
- ].join(' '));
74
- return;
75
- }
76
- const { msg, err, ...rest } = args[0];
77
- const parts = [];
78
- if (requestId)
79
- parts.push(dimGrey(`[${requestId}]`));
80
- if (msg !== undefined)
81
- parts.push(String(msg));
82
- if (err !== undefined)
83
- parts.push(_inspect(err, { includeErrorStack: true, colors: true }));
84
- if (Object.keys(rest).length)
85
- parts.push(dimGrey(_inspect(rest, { colors: false })));
86
- console.log(parts.join(' '));
53
+ const line = logEntryToString(entry, { colors: true });
54
+ console.log(requestId ? `${dimGrey(`[${requestId}]`)} ${line}` : line);
87
55
  }
88
56
  /**
89
57
  * Same as logToDev, but without request and without colors.
90
58
  * This is to not confuse e.g Sentry when it picks up messages with colors
91
59
  */
92
- function logToCI(args) {
93
- console.log(args.map(a => _inspect(a, { includeErrorStack: true, colors: false })).join(' '));
60
+ function logToCI(entry) {
61
+ console.log(logEntryToString(entry, { colors: false }));
94
62
  }
95
63
  export function logMiddleware() {
96
64
  if (isGAE || isCloudRun) {
@@ -117,12 +85,7 @@ export function logMiddleware() {
117
85
  if (isGAE) {
118
86
  meta['appengine.googleapis.com/request_id'] = req.header('x-appengine-request-log-id');
119
87
  }
120
- _objectAssign(req, {
121
- debug: (...args) => writeGCPStructuredLog({ ...meta, severity: 'DEBUG' }, args),
122
- log: (...args) => writeGCPStructuredLog({ ...meta, severity: 'INFO' }, args),
123
- warn: (...args) => writeGCPStructuredLog({ ...meta, severity: 'WARNING' }, args),
124
- error: (...args) => writeGCPStructuredLog({ ...meta, severity: 'ERROR' }, args),
125
- });
88
+ _objectAssign(req, commonLoggerCreate(entry => writeGCPStructuredLog(meta, entry)));
126
89
  next();
127
90
  };
128
91
  }
@@ -131,18 +94,14 @@ export function logMiddleware() {
131
94
  return function devLogHandler(req, _res, next) {
132
95
  // Local machine
133
96
  req.requestId = String(++reqCounter);
134
- req.debug =
135
- req.log =
136
- req.warn =
137
- req.error =
138
- (...args) => logToDev(req.requestId, args);
97
+ _objectAssign(req, commonLoggerCreate(entry => logToDev(req.requestId, entry)));
139
98
  next();
140
99
  };
141
100
  }
142
101
  // Otherwise, return "simple" logger
143
102
  // This includes: unit tests, CI environments
144
103
  return function simpleLogHandler(req, _res, next) {
145
- req.debug = req.log = req.warn = req.error = (...args) => logToCI(args);
104
+ _objectAssign(req, ciLogger);
146
105
  next();
147
106
  };
148
107
  }
@@ -1,6 +1,9 @@
1
1
  import { getRequestEndpoint } from './request.util.js';
2
2
  export function notFoundMiddleware() {
3
3
  return (req, res) => {
4
- res.status(404).send(`404 Not Found: ${getRequestEndpoint(req)}`);
4
+ res
5
+ .status(404)
6
+ .type('text/plain')
7
+ .send(`404 Not Found: ${getRequestEndpoint(req)}`);
5
8
  };
6
9
  }
@@ -1,4 +1,4 @@
1
- import type { CommonLogFunction } from '@naturalcycles/js-lib/log';
1
+ import type { CommonLogger } from '@naturalcycles/js-lib/log';
2
2
  import type { Promisable } from '@naturalcycles/js-lib/types';
3
3
  import type { Application, IRouter, NextFunction, Request, Response } from 'express';
4
4
  /**
@@ -8,11 +8,7 @@ import type { Application, IRouter, NextFunction, Request, Response } from 'expr
8
8
  * `BackendRequest` seems to not conflict with anything right now.
9
9
  * Previous name `ExpressRequest` was clashing with Sentry.
10
10
  */
11
- export interface BackendRequest<BODY = unknown> extends Request {
12
- debug: CommonLogFunction;
13
- log: CommonLogFunction;
14
- warn: CommonLogFunction;
15
- error: CommonLogFunction;
11
+ export interface BackendRequest<BODY = unknown> extends Request, CommonLogger {
16
12
  requestId?: string;
17
13
  /**
18
14
  * Only used for request logging purposes.
@@ -50,11 +46,7 @@ export type BackendErrorRequestHandler = (err: any, req: BackendRequest, res: Ba
50
46
  export type BackendRouter = IRouter;
51
47
  export type BackendApplication = Application;
52
48
  declare module 'http' {
53
- interface IncomingMessage {
54
- debug: CommonLogFunction;
55
- log: CommonLogFunction;
56
- warn: CommonLogFunction;
57
- error: CommonLogFunction;
49
+ interface IncomingMessage extends CommonLogger {
58
50
  requestId?: string;
59
51
  requestTimeout?: NodeJS.Timeout;
60
52
  bodyParserTimeout?: NodeJS.Timeout;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.73.0",
4
+ "version": "9.74.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/db-lib": "^10",
7
7
  "@naturalcycles/js-lib": "^15",
@@ -220,7 +220,7 @@ export class BaseAdminService {
220
220
  return // skip logging, expected error
221
221
  }
222
222
 
223
- req.error(`getEmailByToken error:`, err)
223
+ req.error({ msg: `getEmailByToken error:`, err })
224
224
  }
225
225
  }
226
226
 
@@ -257,12 +257,12 @@ export class BaseAdminService {
257
257
  granted: boolean,
258
258
  meta: AnyObject = {},
259
259
  ): Promise<void> {
260
- req.log(
261
- `${dimGrey(email)} ${required ? 'required' : 'optional'} permissions check [${dimGrey(
260
+ req.log({
261
+ ...meta,
262
+ msg: `${dimGrey(email)} ${required ? 'required' : 'optional'} permissions check [${dimGrey(
262
263
  reqPermissions.join(', '),
263
264
  )}]: ${granted ? green('GRANTED') : red('DENIED')}`,
264
- meta,
265
- )
265
+ })
266
266
  }
267
267
 
268
268
  /**
@@ -6,7 +6,6 @@ import { compressionMiddleware } from '../server/compressionMiddleware.js'
6
6
  import { genericErrorMiddleware } from '../server/genericErrorMiddleware.js'
7
7
  import type { GenericErrorMiddlewareCfg } from '../server/genericErrorMiddleware.js'
8
8
  import { logMiddleware } from '../server/logMiddleware.js'
9
- import { methodOverrideMiddleware } from '../server/methodOverrideMiddleware.js'
10
9
  import { notFoundMiddleware } from '../server/notFoundMiddleware.js'
11
10
  import { requestTimeoutMiddleware } from '../server/requestTimeoutMiddleware.js'
12
11
  import type {
@@ -42,7 +41,6 @@ export async function createDefaultApp(cfg: DefaultAppCfg): Promise<BackendAppli
42
41
  app.use(asyncLocalStorageMiddleware())
43
42
  }
44
43
 
45
- app.use(methodOverrideMiddleware())
46
44
  app.use(requestTimeoutMiddleware())
47
45
  // app.use(serverStatsMiddleware()) // disabled by default
48
46
  // app.use(bodyParserTimeout()) // removed by default
package/src/index.ts CHANGED
@@ -7,7 +7,6 @@ export * from './server/compressionMiddleware.js'
7
7
  export * from './server/eventLoop.util.js'
8
8
  export * from './server/genericErrorMiddleware.js'
9
9
  export * from './server/logMiddleware.js'
10
- export * from './server/methodOverrideMiddleware.js'
11
10
  export * from './server/notFoundMiddleware.js'
12
11
  export * from './server/okMiddleware.js'
13
12
  export * from './server/request.log.util.js'
@@ -1,8 +1,10 @@
1
1
  import { _anyToError, _isErrorObject } from '@naturalcycles/js-lib/error'
2
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
2
3
  import type { CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib/log'
4
+ import { _omit } from '@naturalcycles/js-lib/object/object.util.js'
3
5
  import type { Primitive, StringMap } from '@naturalcycles/js-lib/types'
4
6
  import type { InspectAnyOptions } from '@naturalcycles/nodejs-lib'
5
- import { _inspect } from '@naturalcycles/nodejs-lib'
7
+ import { _inspect, logEntryToString } from '@naturalcycles/nodejs-lib'
6
8
  import type { Breadcrumb, SeverityLevel } from '@sentry/node-core/light'
7
9
  import type * as SentryLib from '@sentry/node-core/light'
8
10
  import { getRequestLogger } from '../server/asyncLocalStorageMiddleware.js'
@@ -78,7 +80,7 @@ export class SentrySharedService {
78
80
 
79
81
  // Using request-aware logger here
80
82
  // Log both the error and attached ErrorData (if any)
81
- getRequestLogger().error(...[err_, data].filter(Boolean))
83
+ getRequestLogger().error({ err: err_, ...data })
82
84
 
83
85
  if (data?.report === false) {
84
86
  // Skip reporting the error
@@ -106,7 +108,7 @@ export class SentrySharedService {
106
108
  * Returns "eventId"
107
109
  */
108
110
  captureMessage(msg: string, level?: SeverityLevel): string {
109
- getRequestLogger()[sentrySeverityMap[level!] || 'log']('captureMessage:', msg)
111
+ getRequestLogger()[sentrySeverityMap[level!] || 'log'](`captureMessage: ${msg}`)
110
112
  return this.sentry.captureMessage(msg, level)
111
113
  }
112
114
 
@@ -122,19 +124,15 @@ export class SentrySharedService {
122
124
  * @experimental
123
125
  */
124
126
  getCommonLogger(): CommonLogger {
125
- return {
126
- debug: () => {}, // noop
127
- log: () => {}, // noop
128
- warn: () => {}, // noop
129
- error: (...args) => {
130
- const message = args.map(arg => _inspect(arg, INSPECT_OPT)).join(' ')
131
-
132
- this.sentry.addBreadcrumb({
133
- message,
134
- })
135
-
136
- this.sentry.captureException(_anyToError(args.length === 1 ? args[0] : args))
137
- },
138
- }
127
+ return commonLoggerCreate(entry => {
128
+ if (entry.level !== 'error') return // noop
129
+ const { msg, err } = entry
130
+
131
+ this.sentry.addBreadcrumb({
132
+ message: logEntryToString(entry, INSPECT_OPT),
133
+ })
134
+
135
+ this.sentry.captureException(_anyToError(err ?? msg ?? _omit(entry, ['level'])))
136
+ })
139
137
  }
140
138
  }
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
2
  import { _lazyValue } from '@naturalcycles/js-lib'
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
3
4
  import type { CommonLogger } from '@naturalcycles/js-lib/log'
4
5
  import { ciLogger, devLogger, gcpStructuredLogger } from './logMiddleware.js'
5
6
  import type { BackendRequest, BackendRequestHandler } from './server.model.js'
@@ -53,9 +54,6 @@ export function getRequestLogger(): CommonLogger {
53
54
  *
54
55
  * @experimental
55
56
  */
56
- export const requestLogger: CommonLogger = {
57
- debug: (...args) => getRequestLogger().debug(...args),
58
- log: (...args) => getRequestLogger().log(...args),
59
- warn: (...args) => getRequestLogger().warn(...args),
60
- error: (...args) => getRequestLogger().error(...args),
61
- }
57
+ export const requestLogger: CommonLogger = commonLoggerCreate(({ level, ...data }) =>
58
+ getRequestLogger()[level](data),
59
+ )
@@ -75,9 +75,9 @@ export function respondWithError(req: BackendRequest, res: BackendResponse, err:
75
75
  // because errorService was not provided - we are going to log the error here
76
76
 
77
77
  if (headersSent) {
78
- req.error(`error after headersSent:`, err)
78
+ req.error({ msg: `error after headersSent:`, err })
79
79
  } else {
80
- req.error(err)
80
+ req.error({ err })
81
81
  }
82
82
 
83
83
  // todo: add endpoint to the log
@@ -1,11 +1,11 @@
1
1
  import { inspect } from 'node:util'
2
- import { _isPlainObject } from '@naturalcycles/js-lib'
3
2
  import { _anyToErrorObject } from '@naturalcycles/js-lib/error'
4
- import type { CommonLogger } from '@naturalcycles/js-lib/log'
3
+ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
4
+ import type { CommonLogger, CommonLogLevel, LogEntry } from '@naturalcycles/js-lib/log'
5
5
  import { _safeJsonStringify } from '@naturalcycles/js-lib/string/safeJsonStringify.js'
6
6
  import { _objectAssign } from '@naturalcycles/js-lib/types'
7
7
  import type { AnyObject } from '@naturalcycles/js-lib/types'
8
- import { _inspect } from '@naturalcycles/nodejs-lib'
8
+ import { logEntryToString } from '@naturalcycles/nodejs-lib'
9
9
  import { dimGrey } from '@naturalcycles/nodejs-lib/colors'
10
10
  import type { BackendRequestHandler } from './server.model.js'
11
11
 
@@ -22,85 +22,54 @@ let reqCounter = 0
22
22
  * Logger that logs in "GCP structured log" format.
23
23
  * To be used in outside-of-request situations (otherwise req.log should be used).
24
24
  */
25
- export const gcpStructuredLogger: CommonLogger = {
26
- debug: (...args) => writeGCPStructuredLog({ severity: 'DEBUG' }, args),
27
- log: (...args) => writeGCPStructuredLog({}, args),
28
- warn: (...args) => writeGCPStructuredLog({ severity: 'WARNING' }, args),
29
- error: (...args) => writeGCPStructuredLog({ severity: 'ERROR' }, args),
30
- }
25
+ export const gcpStructuredLogger: CommonLogger = commonLoggerCreate(entry =>
26
+ writeGCPStructuredLog({}, entry),
27
+ )
31
28
 
32
29
  /**
33
30
  * Fancy development logger, to be used in outside-of-request situations
34
31
  * (otherwise req.log should be used).
35
32
  */
36
- export const devLogger: CommonLogger = {
37
- debug: (...args) => logToDev(null, args),
38
- log: (...args) => logToDev(null, args),
39
- warn: (...args) => logToDev(null, args),
40
- error: (...args) => logToDev(null, args),
41
- }
33
+ export const devLogger: CommonLogger = commonLoggerCreate(entry => logToDev(null, entry))
42
34
 
43
35
  /**
44
36
  * Same as devLogger, but without colors (e.g to not confuse Sentry).
45
37
  */
46
- export const ciLogger: CommonLogger = {
47
- debug: (...args) => logToCI(args),
48
- log: (...args) => logToCI(args),
49
- warn: (...args) => logToCI(args),
50
- error: (...args) => logToCI(args),
38
+ export const ciLogger: CommonLogger = commonLoggerCreate(logToCI)
39
+
40
+ const gcpSeverityByLevel: Record<CommonLogLevel, string> = {
41
+ debug: 'DEBUG',
42
+ log: 'INFO',
43
+ warn: 'WARNING',
44
+ error: 'ERROR',
51
45
  }
52
46
 
53
47
  // Documented here: https://cloud.google.com/logging/docs/structured-logging
54
48
  // Cloud Run logging: https://cloud.google.com/run/docs/logging
55
- function writeGCPStructuredLog(meta: AnyObject, args: any[]): void {
56
- if (args.length !== 1 || !_isPlainObject(args[0])) {
57
- console.log(
58
- JSON.stringify({
59
- message: args.map(a => (typeof a === 'string' ? a : inspect(a))).join(' '),
60
- ...meta,
61
- }),
62
- )
63
- return
64
- }
65
-
66
- const { msg, err, ...entry } = args[0]
49
+ function writeGCPStructuredLog(meta: AnyObject, entry: LogEntry): void {
50
+ const { level, msg, err, ...fields } = entry
67
51
  if (err instanceof Error) {
68
- entry['err'] = _anyToErrorObject(err)
69
- entry['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err)
52
+ fields['err'] = _anyToErrorObject(err)
53
+ fields['message'] = msg ? `${msg}\n${inspect(err)}` : inspect(err)
70
54
  } else {
71
- if (err !== undefined) entry['err'] = err
72
- if (msg !== undefined) entry['message'] = msg
55
+ if (err !== undefined) fields['err'] = err
56
+ if (msg !== undefined) fields['message'] = msg
73
57
  }
74
- console.log(_safeJsonStringify({ ...entry, ...meta }))
58
+ console.log(_safeJsonStringify({ ...fields, ...meta, severity: gcpSeverityByLevel[level] }))
75
59
  }
76
60
 
77
- function logToDev(requestId: string | null, args: any[]): void {
61
+ function logToDev(requestId: string | null, entry: LogEntry): void {
78
62
  // Run on local machine
79
- if (args.length !== 1 || !_isPlainObject(args[0])) {
80
- console.log(
81
- [
82
- requestId ? [dimGrey(`[${requestId}]`)] : [],
83
- ...args.map(a => _inspect(a, { includeErrorStack: true, colors: true })),
84
- ].join(' '),
85
- )
86
- return
87
- }
88
-
89
- const { msg, err, ...rest } = args[0]
90
- const parts: string[] = []
91
- if (requestId) parts.push(dimGrey(`[${requestId}]`))
92
- if (msg !== undefined) parts.push(String(msg))
93
- if (err !== undefined) parts.push(_inspect(err, { includeErrorStack: true, colors: true }))
94
- if (Object.keys(rest).length) parts.push(dimGrey(_inspect(rest, { colors: false })))
95
- console.log(parts.join(' '))
63
+ const line = logEntryToString(entry, { colors: true })
64
+ console.log(requestId ? `${dimGrey(`[${requestId}]`)} ${line}` : line)
96
65
  }
97
66
 
98
67
  /**
99
68
  * Same as logToDev, but without request and without colors.
100
69
  * This is to not confuse e.g Sentry when it picks up messages with colors
101
70
  */
102
- function logToCI(args: any[]): void {
103
- console.log(args.map(a => _inspect(a, { includeErrorStack: true, colors: false })).join(' '))
71
+ function logToCI(entry: LogEntry): void {
72
+ console.log(logEntryToString(entry, { colors: false }))
104
73
  }
105
74
 
106
75
  export function logMiddleware(): BackendRequestHandler {
@@ -130,12 +99,10 @@ export function logMiddleware(): BackendRequestHandler {
130
99
  meta['appengine.googleapis.com/request_id'] = req.header('x-appengine-request-log-id')
131
100
  }
132
101
 
133
- _objectAssign(req, {
134
- debug: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'DEBUG' }, args),
135
- log: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'INFO' }, args),
136
- warn: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'WARNING' }, args),
137
- error: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'ERROR' }, args),
138
- } satisfies CommonLogger)
102
+ _objectAssign(
103
+ req,
104
+ commonLoggerCreate(entry => writeGCPStructuredLog(meta, entry)),
105
+ )
139
106
 
140
107
  next()
141
108
  }
@@ -146,11 +113,10 @@ export function logMiddleware(): BackendRequestHandler {
146
113
  return function devLogHandler(req, _res, next) {
147
114
  // Local machine
148
115
  req.requestId = String(++reqCounter)
149
- req.debug =
150
- req.log =
151
- req.warn =
152
- req.error =
153
- (...args: any[]) => logToDev(req.requestId!, args)
116
+ _objectAssign(
117
+ req,
118
+ commonLoggerCreate(entry => logToDev(req.requestId!, entry)),
119
+ )
154
120
  next()
155
121
  }
156
122
  }
@@ -158,7 +124,7 @@ export function logMiddleware(): BackendRequestHandler {
158
124
  // Otherwise, return "simple" logger
159
125
  // This includes: unit tests, CI environments
160
126
  return function simpleLogHandler(req, _res, next) {
161
- req.debug = req.log = req.warn = req.error = (...args: any[]) => logToCI(args)
127
+ _objectAssign(req, ciLogger)
162
128
  next()
163
129
  }
164
130
  }
@@ -3,6 +3,9 @@ import type { BackendRequestHandler } from './server.model.js'
3
3
 
4
4
  export function notFoundMiddleware(): BackendRequestHandler {
5
5
  return (req, res) => {
6
- res.status(404).send(`404 Not Found: ${getRequestEndpoint(req)}`)
6
+ res
7
+ .status(404)
8
+ .type('text/plain')
9
+ .send(`404 Not Found: ${getRequestEndpoint(req)}`)
7
10
  }
8
11
  }
@@ -1,4 +1,4 @@
1
- import type { CommonLogFunction } from '@naturalcycles/js-lib/log'
1
+ import type { CommonLogger } from '@naturalcycles/js-lib/log'
2
2
  import type { Promisable } from '@naturalcycles/js-lib/types'
3
3
  import type { Application, IRouter, NextFunction, Request, Response } from 'express'
4
4
 
@@ -9,12 +9,7 @@ import type { Application, IRouter, NextFunction, Request, Response } from 'expr
9
9
  * `BackendRequest` seems to not conflict with anything right now.
10
10
  * Previous name `ExpressRequest` was clashing with Sentry.
11
11
  */
12
- export interface BackendRequest<BODY = unknown> extends Request {
13
- debug: CommonLogFunction
14
- log: CommonLogFunction
15
- warn: CommonLogFunction
16
- error: CommonLogFunction
17
-
12
+ export interface BackendRequest<BODY = unknown> extends Request, CommonLogger {
18
13
  requestId?: string
19
14
  /**
20
15
  * Only used for request logging purposes.
@@ -71,12 +66,7 @@ export type BackendRouter = IRouter
71
66
  export type BackendApplication = Application
72
67
 
73
68
  declare module 'http' {
74
- interface IncomingMessage {
75
- debug: CommonLogFunction
76
- log: CommonLogFunction
77
- warn: CommonLogFunction
78
- error: CommonLogFunction
79
-
69
+ interface IncomingMessage extends CommonLogger {
80
70
  requestId?: string
81
71
 
82
72
  requestTimeout?: NodeJS.Timeout
@@ -1,8 +0,0 @@
1
- import type { BackendRequestHandler } from './server.model.js';
2
- export interface MethodOverrideMiddlewareCfg {
3
- /**
4
- * @default _method
5
- */
6
- methodKey?: string;
7
- }
8
- export declare function methodOverrideMiddleware(cfg?: MethodOverrideMiddlewareCfg): BackendRequestHandler;
@@ -1,13 +0,0 @@
1
- export function methodOverrideMiddleware(cfg = {}) {
2
- const { methodKey } = {
3
- methodKey: '_method',
4
- ...cfg,
5
- };
6
- return (req, _res, next) => {
7
- if (req.query[methodKey]) {
8
- req.method = req.query[methodKey];
9
- // delete req.query[methodKey]
10
- }
11
- next();
12
- };
13
- }
@@ -1,26 +0,0 @@
1
- import type { BackendRequestHandler } from './server.model.js'
2
-
3
- export interface MethodOverrideMiddlewareCfg {
4
- /**
5
- * @default _method
6
- */
7
- methodKey?: string
8
- }
9
-
10
- export function methodOverrideMiddleware(
11
- cfg: MethodOverrideMiddlewareCfg = {},
12
- ): BackendRequestHandler {
13
- const { methodKey } = {
14
- methodKey: '_method',
15
- ...cfg,
16
- }
17
-
18
- return (req, _res, next) => {
19
- if (req.query[methodKey]) {
20
- req.method = req.query[methodKey] as string
21
- // delete req.query[methodKey]
22
- }
23
-
24
- next()
25
- }
26
- }