@naturalcycles/backend-lib 9.2.0 → 9.3.1

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.
@@ -1,10 +1,10 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { _lazyValue } from '@naturalcycles/js-lib';
3
- import { ciLogger, devLogger, gaeLogger } from './logMiddleware.js';
4
- const { GAE_INSTANCE, CI } = process.env;
3
+ import { ciLogger, devLogger, gcpStructuredLogger } from './logMiddleware.js';
4
+ const { GAE_INSTANCE, K_SERVICE, CI } = process.env;
5
5
  const isGAE = !!GAE_INSTANCE;
6
6
  const isCI = !!CI;
7
- const isCloudRun = !!CI;
7
+ const isCloudRun = !!K_SERVICE;
8
8
  // Singleton, for simplicity
9
9
  // Create it lazily (on demand)
10
10
  const storage = _lazyValue(() => new AsyncLocalStorage());
@@ -25,7 +25,8 @@ export function getRequest() {
25
25
  * @experimental
26
26
  */
27
27
  export function getRequestLogger() {
28
- return (storage().getStore()?.req || (isGAE ? gaeLogger : isCI || isCloudRun ? ciLogger : devLogger));
28
+ return (storage().getStore()?.req ||
29
+ (isGAE || isCloudRun ? gcpStructuredLogger : isCI ? ciLogger : devLogger));
29
30
  }
30
31
  /**
31
32
  * CommonLogger implementation that is Request-bound.
@@ -1,10 +1,10 @@
1
1
  import type { CommonLogger } from '@naturalcycles/js-lib';
2
2
  import type { BackendRequestHandler } from './server.model.js';
3
3
  /**
4
- * Logger that logs in AppEngine format.
4
+ * Logger that logs in "GCP structured log" format.
5
5
  * To be used in outside-of-request situations (otherwise req.log should be used).
6
6
  */
7
- export declare const gaeLogger: CommonLogger;
7
+ export declare const gcpStructuredLogger: CommonLogger;
8
8
  /**
9
9
  * Fancy development logger, to be used in outside-of-request situations
10
10
  * (otherwise req.log should be used).
@@ -1,20 +1,20 @@
1
1
  import { inspect } from 'node:util';
2
2
  import { _inspect, dimGrey } from '@naturalcycles/nodejs-lib';
3
- const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, APP_ENV } = process.env;
3
+ const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, K_SERVICE, APP_ENV } = process.env;
4
4
  const isGAE = !!GAE_INSTANCE;
5
- // const isCloudRun = !!K_SERVICE
5
+ const isCloudRun = !!K_SERVICE;
6
6
  // const isTest = APP_ENV === 'test'
7
7
  const isDev = APP_ENV === 'dev';
8
8
  // Simple "request counter" (poor man's "correlation id") counter, to use on dev machine (not in the cloud)
9
9
  let reqCounter = 0;
10
10
  /**
11
- * Logger that logs in AppEngine format.
11
+ * Logger that logs in "GCP structured log" format.
12
12
  * To be used in outside-of-request situations (otherwise req.log should be used).
13
13
  */
14
- export const gaeLogger = {
15
- log: (...args) => logToAppEngine({}, args),
16
- warn: (...args) => logToAppEngine({ severity: 'WARNING' }, args),
17
- error: (...args) => logToAppEngine({ severity: 'ERROR' }, args),
14
+ export const gcpStructuredLogger = {
15
+ log: (...args) => writeGCPStructuredLog({}, args),
16
+ warn: (...args) => writeGCPStructuredLog({ severity: 'WARNING' }, args),
17
+ error: (...args) => writeGCPStructuredLog({ severity: 'ERROR' }, args),
18
18
  };
19
19
  /**
20
20
  * Fancy development logger, to be used in outside-of-request situations
@@ -34,7 +34,8 @@ export const ciLogger = {
34
34
  error: (...args) => logToCI(args),
35
35
  };
36
36
  // Documented here: https://cloud.google.com/logging/docs/structured-logging
37
- function logToAppEngine(meta, args) {
37
+ // Cloud Run logging: https://cloud.google.com/run/docs/logging
38
+ function writeGCPStructuredLog(meta, args) {
38
39
  console.log(JSON.stringify({
39
40
  message: args.map(a => (typeof a === 'string' ? a : inspect(a))).join(' '),
40
41
  ...meta,
@@ -55,31 +56,29 @@ function logToCI(args) {
55
56
  console.log(args.map(a => _inspect(a, { includeErrorStack: true, colors: false })).join(' '));
56
57
  }
57
58
  export function logMiddleware() {
58
- if (isGAE) {
59
- if (GOOGLE_CLOUD_PROJECT) {
60
- return function appEngineLogHandler(req, _res, next) {
59
+ if (isGAE || isCloudRun) {
60
+ return function gcpStructuredLogHandler(req, _res, next) {
61
+ const meta = {};
62
+ // CloudRun does NOT have this env variable set,
63
+ // so you have to set it manually on deployment, like this:
64
+ // gcloud run deploy my-service \
65
+ // --update-env-vars=GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
66
+ if (GOOGLE_CLOUD_PROJECT) {
61
67
  const traceHeader = req.header('x-cloud-trace-context');
62
68
  if (traceHeader) {
63
69
  const [trace] = traceHeader.split('/');
64
- const meta = {
65
- 'logging.googleapis.com/trace': `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`,
66
- 'appengine.googleapis.com/request_id': req.header('x-appengine-request-log-id'),
67
- };
68
- Object.assign(req, {
69
- log: (...args) => logToAppEngine({ ...meta, severity: 'INFO' }, args),
70
- warn: (...args) => logToAppEngine({ ...meta, severity: 'WARNING' }, args),
71
- error: (...args) => logToAppEngine({ ...meta, severity: 'ERROR' }, args),
72
- });
70
+ meta['logging.googleapis.com/trace'] = `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`;
73
71
  req.requestId = trace;
74
72
  }
75
- else {
76
- Object.assign(req, gaeLogger);
77
- }
78
- next();
79
- };
80
- }
81
- return function appEngineLogHandler(req, _res, next) {
82
- Object.assign(req, gaeLogger);
73
+ }
74
+ if (isGAE) {
75
+ meta['appengine.googleapis.com/request_id'] = req.header('x-appengine-request-log-id');
76
+ }
77
+ Object.assign(req, {
78
+ log: (...args) => writeGCPStructuredLog({ ...meta, severity: 'INFO' }, args),
79
+ warn: (...args) => writeGCPStructuredLog({ ...meta, severity: 'WARNING' }, args),
80
+ error: (...args) => writeGCPStructuredLog({ ...meta, severity: 'ERROR' }, args),
81
+ });
83
82
  next();
84
83
  };
85
84
  }
@@ -93,7 +92,7 @@ export function logMiddleware() {
93
92
  };
94
93
  }
95
94
  // Otherwise, return "simple" logger
96
- // This includes: unit tests, CloudRun, CI environments
95
+ // This includes: unit tests, CI environments
97
96
  return function simpleLogHandler(req, _res, next) {
98
97
  req.log = req.warn = req.error = (...args) => logToCI(args);
99
98
  next();
@@ -2,7 +2,7 @@ import { _filterNullishValues, localTime } from '@naturalcycles/js-lib';
2
2
  import { memoryUsageFull, processSharedUtil } from '@naturalcycles/nodejs-lib';
3
3
  import { getDeployInfo } from './deployInfo.util.js';
4
4
  const { versions, arch, platform } = process;
5
- const { GAE_APPLICATION, GAE_SERVICE, GAE_VERSION, K_SERVICE, K_REVISION, APP_ENV, NODE_OPTIONS } = process.env;
5
+ const { GAE_APPLICATION, GAE_SERVICE, GAE_VERSION, GOOGLE_CLOUD_PROJECT, K_SERVICE, K_REVISION, APP_ENV, NODE_OPTIONS, } = process.env;
6
6
  export function serverStatusMiddleware(projectDir, extra) {
7
7
  return async (_req, res) => {
8
8
  res.json(getServerStatusData(projectDir, extra));
@@ -18,6 +18,7 @@ export function getServerStatusData(projectDir = process.cwd(), extra) {
18
18
  deployBuildTime,
19
19
  APP_ENV,
20
20
  buildInfo,
21
+ GOOGLE_CLOUD_PROJECT,
21
22
  GAE_APPLICATION,
22
23
  GAE_SERVICE,
23
24
  GAE_VERSION,
@@ -9,4 +9,4 @@ export interface SimpleRequestLoggerMiddlewareCfg {
9
9
  */
10
10
  logFinish: boolean;
11
11
  }
12
- export declare function simpleRequestLoggerMiddleware(_cfg?: Partial<SimpleRequestLoggerMiddlewareCfg>): BackendRequestHandler;
12
+ export declare function simpleRequestLoggerMiddleware(cfg?: Partial<SimpleRequestLoggerMiddlewareCfg>): BackendRequestHandler;
@@ -3,17 +3,12 @@ import { boldGrey, dimGrey } from '@naturalcycles/nodejs-lib';
3
3
  import { onFinished } from '../index.js';
4
4
  import { logRequest } from './request.log.util.js';
5
5
  const { APP_ENV } = process.env;
6
- export function simpleRequestLoggerMiddleware(_cfg = {}) {
6
+ export function simpleRequestLoggerMiddleware(cfg = {}) {
7
7
  // Disable logger in AppEngine, as it doesn't make sense there
8
8
  // UPD: Only log in dev environment
9
9
  if (APP_ENV !== 'dev')
10
10
  return (_req, _res, next) => next();
11
- const cfg = {
12
- logStart: false,
13
- logFinish: true,
14
- ..._cfg,
15
- };
16
- const { logStart, logFinish } = cfg;
11
+ const { logStart = false, logFinish = true } = cfg;
17
12
  return (req, res, next) => {
18
13
  const started = Date.now();
19
14
  if (logStart) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.2.0",
4
+ "version": "9.3.1",
5
5
  "peerDependencies": {
6
6
  "@sentry/node": "^9"
7
7
  },
@@ -69,6 +69,7 @@
69
69
  "lint": "dev-lib lint",
70
70
  "bt": "dev-lib bt",
71
71
  "clean": "dev-lib clean",
72
+ "check": "dev-lib check",
72
73
  "typecheck": "dev-lib typecheck",
73
74
  "dev": "APP_ENV=dev tsx watch src/test/server/server.ts",
74
75
  "deploy-gae": "tsx src/bin/deploy-gae.ts",
@@ -1,13 +1,13 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
2
  import type { CommonLogger } from '@naturalcycles/js-lib'
3
3
  import { _lazyValue } from '@naturalcycles/js-lib'
4
- import { ciLogger, devLogger, gaeLogger } from './logMiddleware.js'
4
+ import { ciLogger, devLogger, gcpStructuredLogger } from './logMiddleware.js'
5
5
  import type { BackendRequest, BackendRequestHandler } from './server.model.js'
6
6
 
7
- const { GAE_INSTANCE, CI } = process.env
7
+ const { GAE_INSTANCE, K_SERVICE, CI } = process.env
8
8
  const isGAE = !!GAE_INSTANCE
9
9
  const isCI = !!CI
10
- const isCloudRun = !!CI
10
+ const isCloudRun = !!K_SERVICE
11
11
 
12
12
  export interface RequestLocalStorage {
13
13
  req: BackendRequest
@@ -38,7 +38,8 @@ export function getRequest(): BackendRequest | undefined {
38
38
  */
39
39
  export function getRequestLogger(): CommonLogger {
40
40
  return (
41
- storage().getStore()?.req || (isGAE ? gaeLogger : isCI || isCloudRun ? ciLogger : devLogger)
41
+ storage().getStore()?.req ||
42
+ (isGAE || isCloudRun ? gcpStructuredLogger : isCI ? ciLogger : devLogger)
42
43
  )
43
44
  }
44
45
 
@@ -1,11 +1,11 @@
1
1
  import { inspect } from 'node:util'
2
- import type { AnyObject, CommonLogger } from '@naturalcycles/js-lib'
2
+ import type { AnyObject, CommonLogger, StringMap } from '@naturalcycles/js-lib'
3
3
  import { _inspect, dimGrey } from '@naturalcycles/nodejs-lib'
4
4
  import type { BackendRequestHandler } from './server.model.js'
5
5
 
6
- const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, APP_ENV } = process.env
6
+ const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, K_SERVICE, APP_ENV } = process.env
7
7
  const isGAE = !!GAE_INSTANCE
8
- // const isCloudRun = !!K_SERVICE
8
+ const isCloudRun = !!K_SERVICE
9
9
  // const isTest = APP_ENV === 'test'
10
10
  const isDev = APP_ENV === 'dev'
11
11
 
@@ -13,13 +13,13 @@ const isDev = APP_ENV === 'dev'
13
13
  let reqCounter = 0
14
14
 
15
15
  /**
16
- * Logger that logs in AppEngine format.
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 gaeLogger: CommonLogger = {
20
- log: (...args) => logToAppEngine({}, args),
21
- warn: (...args) => logToAppEngine({ severity: 'WARNING' }, args),
22
- error: (...args) => logToAppEngine({ severity: 'ERROR' }, args),
19
+ export const gcpStructuredLogger: CommonLogger = {
20
+ log: (...args) => writeGCPStructuredLog({}, args),
21
+ warn: (...args) => writeGCPStructuredLog({ severity: 'WARNING' }, args),
22
+ error: (...args) => writeGCPStructuredLog({ severity: 'ERROR' }, args),
23
23
  }
24
24
 
25
25
  /**
@@ -42,7 +42,8 @@ export const ciLogger: CommonLogger = {
42
42
  }
43
43
 
44
44
  // Documented here: https://cloud.google.com/logging/docs/structured-logging
45
- function logToAppEngine(meta: AnyObject, args: any[]): void {
45
+ // Cloud Run logging: https://cloud.google.com/run/docs/logging
46
+ function writeGCPStructuredLog(meta: AnyObject, args: any[]): void {
46
47
  console.log(
47
48
  JSON.stringify({
48
49
  message: args.map(a => (typeof a === 'string' ? a : inspect(a))).join(' '),
@@ -70,32 +71,32 @@ function logToCI(args: any[]): void {
70
71
  }
71
72
 
72
73
  export function logMiddleware(): BackendRequestHandler {
73
- if (isGAE) {
74
- if (GOOGLE_CLOUD_PROJECT) {
75
- return function appEngineLogHandler(req, _res, next) {
74
+ if (isGAE || isCloudRun) {
75
+ return function gcpStructuredLogHandler(req, _res, next) {
76
+ const meta: StringMap = {}
77
+
78
+ // CloudRun does NOT have this env variable set,
79
+ // so you have to set it manually on deployment, like this:
80
+ // gcloud run deploy my-service \
81
+ // --update-env-vars=GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
82
+ if (GOOGLE_CLOUD_PROJECT) {
76
83
  const traceHeader = req.header('x-cloud-trace-context')
77
84
  if (traceHeader) {
78
85
  const [trace] = traceHeader.split('/')
79
- const meta = {
80
- 'logging.googleapis.com/trace': `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`,
81
- 'appengine.googleapis.com/request_id': req.header('x-appengine-request-log-id'),
82
- }
83
- Object.assign(req, {
84
- log: (...args: any[]) => logToAppEngine({ ...meta, severity: 'INFO' }, args),
85
- warn: (...args: any[]) => logToAppEngine({ ...meta, severity: 'WARNING' }, args),
86
- error: (...args: any[]) => logToAppEngine({ ...meta, severity: 'ERROR' }, args),
87
- })
86
+ meta['logging.googleapis.com/trace'] = `projects/${GOOGLE_CLOUD_PROJECT}/traces/${trace}`
88
87
  req.requestId = trace
89
- } else {
90
- Object.assign(req, gaeLogger)
91
88
  }
92
-
93
- next()
94
89
  }
95
- }
90
+ if (isGAE) {
91
+ meta['appengine.googleapis.com/request_id'] = req.header('x-appengine-request-log-id')
92
+ }
93
+
94
+ Object.assign(req, {
95
+ log: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'INFO' }, args),
96
+ warn: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'WARNING' }, args),
97
+ error: (...args: any[]) => writeGCPStructuredLog({ ...meta, severity: 'ERROR' }, args),
98
+ })
96
99
 
97
- return function appEngineLogHandler(req, _res, next) {
98
- Object.assign(req, gaeLogger)
99
100
  next()
100
101
  }
101
102
  }
@@ -111,7 +112,7 @@ export function logMiddleware(): BackendRequestHandler {
111
112
  }
112
113
 
113
114
  // Otherwise, return "simple" logger
114
- // This includes: unit tests, CloudRun, CI environments
115
+ // This includes: unit tests, CI environments
115
116
  return function simpleLogHandler(req, _res, next) {
116
117
  req.log = req.warn = req.error = (...args: any[]) => logToCI(args)
117
118
  next()
@@ -4,8 +4,16 @@ import { getDeployInfo } from './deployInfo.util.js'
4
4
  import type { BackendRequestHandler } from './server.model.js'
5
5
 
6
6
  const { versions, arch, platform } = process
7
- const { GAE_APPLICATION, GAE_SERVICE, GAE_VERSION, K_SERVICE, K_REVISION, APP_ENV, NODE_OPTIONS } =
8
- process.env
7
+ const {
8
+ GAE_APPLICATION,
9
+ GAE_SERVICE,
10
+ GAE_VERSION,
11
+ GOOGLE_CLOUD_PROJECT,
12
+ K_SERVICE,
13
+ K_REVISION,
14
+ APP_ENV,
15
+ NODE_OPTIONS,
16
+ } = process.env
9
17
 
10
18
  export function serverStatusMiddleware(projectDir?: string, extra?: any): BackendRequestHandler {
11
19
  return async (_req, res) => {
@@ -27,6 +35,7 @@ export function getServerStatusData(
27
35
  deployBuildTime,
28
36
  APP_ENV,
29
37
  buildInfo,
38
+ GOOGLE_CLOUD_PROJECT,
30
39
  GAE_APPLICATION,
31
40
  GAE_SERVICE,
32
41
  GAE_VERSION,
@@ -20,18 +20,13 @@ export interface SimpleRequestLoggerMiddlewareCfg {
20
20
  }
21
21
 
22
22
  export function simpleRequestLoggerMiddleware(
23
- _cfg: Partial<SimpleRequestLoggerMiddlewareCfg> = {},
23
+ cfg: Partial<SimpleRequestLoggerMiddlewareCfg> = {},
24
24
  ): BackendRequestHandler {
25
25
  // Disable logger in AppEngine, as it doesn't make sense there
26
26
  // UPD: Only log in dev environment
27
27
  if (APP_ENV !== 'dev') return (_req, _res, next) => next()
28
28
 
29
- const cfg: SimpleRequestLoggerMiddlewareCfg = {
30
- logStart: false,
31
- logFinish: true,
32
- ..._cfg,
33
- }
34
- const { logStart, logFinish } = cfg
29
+ const { logStart = false, logFinish = true } = cfg
35
30
 
36
31
  return (req, res, next) => {
37
32
  const started = Date.now() as UnixTimestampMillis