@naturalcycles/backend-lib 9.4.0 → 9.5.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.
@@ -33,7 +33,7 @@ export declare class SentrySharedService {
33
33
  * Does console.log(err)
34
34
  * Returns "eventId" or undefined (if error was not reported).
35
35
  */
36
- captureException(err_: any, logError?: boolean): string | undefined;
36
+ captureException(err_: any): string | undefined;
37
37
  /**
38
38
  * Returns "eventId"
39
39
  */
@@ -54,15 +54,13 @@ export class SentrySharedService {
54
54
  * Does console.log(err)
55
55
  * Returns "eventId" or undefined (if error was not reported).
56
56
  */
57
- captureException(err_, logError = true) {
57
+ captureException(err_) {
58
58
  // normalize the error
59
59
  const err = _anyToError(err_);
60
60
  const data = _isErrorObject(err) ? err.data : undefined;
61
61
  // Using request-aware logger here
62
- if (logError) {
63
- // Log both the error and attached ErrorData (if any)
64
- getRequestLogger().error('captureException:', ...[err_, data].filter(Boolean));
65
- }
62
+ // Log both the error and attached ErrorData (if any)
63
+ getRequestLogger().error('captureException:', ...[err_, data].filter(Boolean));
66
64
  if (data?.report === false) {
67
65
  // Skip reporting the error
68
66
  return;
@@ -30,16 +30,24 @@ export function genericErrorMiddleware(cfg = {}) {
30
30
  }
31
31
  export function respondWithError(req, res, err) {
32
32
  const { headersSent } = res;
33
- // todo: add endpoint to the log
34
- // todo: add userId from the "Context" (or, just req.userId?) to the log
35
- if (headersSent) {
36
- req.error(`error after headersSent:`, err);
33
+ const originalError = _anyToError(err);
34
+ let errorId;
35
+ if (errorService) {
36
+ // captureException logs the error,
37
+ // so we don't need to log it here
38
+ errorId = errorService.captureException(originalError);
37
39
  }
38
40
  else {
39
- req.error(err);
41
+ // because errorService was not provided - we are going to log the error here
42
+ if (headersSent) {
43
+ req.error(`error after headersSent:`, err);
44
+ }
45
+ else {
46
+ req.error(err);
47
+ }
48
+ // todo: add endpoint to the log
49
+ // todo: add userId from the "Context" (or, just req.userId?) to the log
40
50
  }
41
- const originalError = _anyToError(err);
42
- const errorId = errorService?.captureException(originalError);
43
51
  if (headersSent)
44
52
  return;
45
53
  const httpError = _errorLikeToErrorObject(originalError);
@@ -61,7 +61,9 @@ export function logMiddleware() {
61
61
  const meta = {
62
62
  // Experimental!
63
63
  // Testing to include userId in metadata (not message payload) to see if it's searchable
64
- userId: req.userId,
64
+ labels: {
65
+ userId: req.userId,
66
+ },
65
67
  };
66
68
  // CloudRun does NOT have this env variable set,
67
69
  // so you have to set it manually on deployment, like this:
@@ -1,7 +1,13 @@
1
1
  import type { BackendRequestHandler } from '../index.js';
2
+ export interface RequestLoggerMiddlewareCfg {
3
+ /**
4
+ * If set - this prefix will be removed from the request url before logging.
5
+ */
6
+ removeUrlPrefix?: string;
7
+ }
2
8
  /**
3
9
  * Experimental request logger for Cloud Run.
4
10
  *
5
11
  * @experimental
6
12
  */
7
- export declare function requestLoggerMiddleware(): BackendRequestHandler;
13
+ export declare function requestLoggerMiddleware(cfg?: RequestLoggerMiddlewareCfg): BackendRequestHandler;
@@ -5,14 +5,27 @@ import { onFinished } from '../index.js';
5
5
  *
6
6
  * @experimental
7
7
  */
8
- export function requestLoggerMiddleware() {
8
+ export function requestLoggerMiddleware(cfg = {}) {
9
+ const { removeUrlPrefix } = cfg;
10
+ const removeUrlPrefixLength = removeUrlPrefix?.length;
9
11
  return (req, res, next) => {
10
12
  const started = Date.now();
11
- req.log([req.method, req.originalUrl, req.userId].filter(Boolean).join(' '));
13
+ let url = req.originalUrl.split('?')[0];
14
+ if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
15
+ url = url.slice(removeUrlPrefixLength);
16
+ }
17
+ // todo: include requestId (3-character hash of it?)
18
+ req.log(['>>', req.method, url, req.userId].filter(Boolean).join(' '));
12
19
  onFinished(res, () => {
13
- req.log([res.statusCode || '0', _since(started), req.method, req.originalUrl, req.userId]
20
+ const str = ['<<', res.statusCode, _since(started), req.method, url, req.userId]
14
21
  .filter(Boolean)
15
- .join(' '));
22
+ .join(' ');
23
+ if (res.statusCode && res.statusCode >= 400) {
24
+ req.error(str);
25
+ }
26
+ else {
27
+ req.log(str);
28
+ }
16
29
  });
17
30
  next();
18
31
  };
@@ -16,12 +16,20 @@ export class BackendServer {
16
16
  // 1. Register error handlers, etc.
17
17
  if (registerUncaughtExceptionHandlers) {
18
18
  process.on('uncaughtException', err => {
19
- console.error('BackendServer uncaughtException:', err);
20
- this.cfg.sentryService?.captureException(err, false);
19
+ if (this.cfg.sentryService) {
20
+ this.cfg.sentryService.captureException(err);
21
+ }
22
+ else {
23
+ console.error('BackendServer uncaughtException:', err);
24
+ }
21
25
  });
22
26
  process.on('unhandledRejection', err => {
23
- console.error('BackendServer unhandledRejection:', err);
24
- this.cfg.sentryService?.captureException(err, false);
27
+ if (this.cfg.sentryService) {
28
+ this.cfg.sentryService.captureException(err);
29
+ }
30
+ else {
31
+ console.error('BackendServer unhandledRejection:', err);
32
+ }
25
33
  });
26
34
  }
27
35
  process.once('SIGINT', () => this.stop('SIGINT'));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.4.0",
4
+ "version": "9.5.0",
5
5
  "peerDependencies": {
6
6
  "@sentry/node": "^9"
7
7
  },
@@ -70,16 +70,14 @@ export class SentrySharedService {
70
70
  * Does console.log(err)
71
71
  * Returns "eventId" or undefined (if error was not reported).
72
72
  */
73
- captureException(err_: any, logError = true): string | undefined {
73
+ captureException(err_: any): string | undefined {
74
74
  // normalize the error
75
75
  const err = _anyToError(err_)
76
76
  const data = _isErrorObject(err) ? err.data : undefined
77
77
 
78
78
  // Using request-aware logger here
79
- if (logError) {
80
- // Log both the error and attached ErrorData (if any)
81
- getRequestLogger().error('captureException:', ...[err_, data].filter(Boolean))
82
- }
79
+ // Log both the error and attached ErrorData (if any)
80
+ getRequestLogger().error('captureException:', ...[err_, data].filter(Boolean))
83
81
 
84
82
  if (data?.report === false) {
85
83
  // Skip reporting the error
@@ -67,17 +67,25 @@ export function genericErrorMiddleware(
67
67
  export function respondWithError(req: BackendRequest, res: BackendResponse, err: any): void {
68
68
  const { headersSent } = res
69
69
 
70
- // todo: add endpoint to the log
71
- // todo: add userId from the "Context" (or, just req.userId?) to the log
72
- if (headersSent) {
73
- req.error(`error after headersSent:`, err)
70
+ const originalError = _anyToError(err)
71
+
72
+ let errorId: string | undefined
73
+ if (errorService) {
74
+ // captureException logs the error,
75
+ // so we don't need to log it here
76
+ errorId = errorService.captureException(originalError)
74
77
  } else {
75
- req.error(err)
76
- }
78
+ // because errorService was not provided - we are going to log the error here
77
79
 
78
- const originalError = _anyToError(err)
80
+ if (headersSent) {
81
+ req.error(`error after headersSent:`, err)
82
+ } else {
83
+ req.error(err)
84
+ }
79
85
 
80
- const errorId = errorService?.captureException(originalError)
86
+ // todo: add endpoint to the log
87
+ // todo: add userId from the "Context" (or, just req.userId?) to the log
88
+ }
81
89
 
82
90
  if (headersSent) return
83
91
 
@@ -1,5 +1,5 @@
1
1
  import { inspect } from 'node:util'
2
- import type { AnyObject, CommonLogger, StringMap } from '@naturalcycles/js-lib'
2
+ import type { AnyObject, CommonLogger } from '@naturalcycles/js-lib'
3
3
  import { _inspect, dimGrey } from '@naturalcycles/nodejs-lib'
4
4
  import type { BackendRequestHandler } from './server.model.js'
5
5
 
@@ -73,10 +73,12 @@ function logToCI(args: any[]): void {
73
73
  export function logMiddleware(): BackendRequestHandler {
74
74
  if (isGAE || isCloudRun) {
75
75
  return function gcpStructuredLogHandler(req, _res, next) {
76
- const meta: StringMap = {
76
+ const meta: AnyObject = {
77
77
  // Experimental!
78
78
  // Testing to include userId in metadata (not message payload) to see if it's searchable
79
- userId: req.userId,
79
+ labels: {
80
+ userId: req.userId,
81
+ },
80
82
  }
81
83
 
82
84
  // CloudRun does NOT have this env variable set,
@@ -3,23 +3,45 @@ import { _since } from '@naturalcycles/js-lib'
3
3
  import type { BackendRequestHandler } from '../index.js'
4
4
  import { onFinished } from '../index.js'
5
5
 
6
+ export interface RequestLoggerMiddlewareCfg {
7
+ /**
8
+ * If set - this prefix will be removed from the request url before logging.
9
+ */
10
+ removeUrlPrefix?: string
11
+ }
12
+
6
13
  /**
7
14
  * Experimental request logger for Cloud Run.
8
15
  *
9
16
  * @experimental
10
17
  */
11
- export function requestLoggerMiddleware(): BackendRequestHandler {
18
+ export function requestLoggerMiddleware(
19
+ cfg: RequestLoggerMiddlewareCfg = {},
20
+ ): BackendRequestHandler {
21
+ const { removeUrlPrefix } = cfg
22
+ const removeUrlPrefixLength = removeUrlPrefix?.length
23
+
12
24
  return (req, res, next) => {
13
25
  const started = Date.now() as UnixTimestampMillis
14
26
 
15
- req.log([req.method, req.originalUrl, req.userId].filter(Boolean).join(' '))
27
+ let url = req.originalUrl.split('?')[0]!
28
+ if (removeUrlPrefix && url.startsWith(removeUrlPrefix)) {
29
+ url = url.slice(removeUrlPrefixLength)
30
+ }
31
+
32
+ // todo: include requestId (3-character hash of it?)
33
+ req.log(['>>', req.method, url, req.userId].filter(Boolean).join(' '))
16
34
 
17
35
  onFinished(res, () => {
18
- req.log(
19
- [res.statusCode || '0', _since(started), req.method, req.originalUrl, req.userId]
20
- .filter(Boolean)
21
- .join(' '),
22
- )
36
+ const str = ['<<', res.statusCode, _since(started), req.method, url, req.userId]
37
+ .filter(Boolean)
38
+ .join(' ')
39
+
40
+ if (res.statusCode && res.statusCode >= 400) {
41
+ req.error(str)
42
+ } else {
43
+ req.log(str)
44
+ }
23
45
  })
24
46
 
25
47
  next()
@@ -19,13 +19,19 @@ export class BackendServer {
19
19
  // 1. Register error handlers, etc.
20
20
  if (registerUncaughtExceptionHandlers) {
21
21
  process.on('uncaughtException', err => {
22
- console.error('BackendServer uncaughtException:', err)
23
- this.cfg.sentryService?.captureException(err, false)
22
+ if (this.cfg.sentryService) {
23
+ this.cfg.sentryService.captureException(err)
24
+ } else {
25
+ console.error('BackendServer uncaughtException:', err)
26
+ }
24
27
  })
25
28
 
26
29
  process.on('unhandledRejection', err => {
27
- console.error('BackendServer unhandledRejection:', err)
28
- this.cfg.sentryService?.captureException(err, false)
30
+ if (this.cfg.sentryService) {
31
+ this.cfg.sentryService.captureException(err)
32
+ } else {
33
+ console.error('BackendServer unhandledRejection:', err)
34
+ }
29
35
  })
30
36
  }
31
37