@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.
- package/dist/server/asyncLocalStorageMiddleware.js +5 -4
- package/dist/server/logMiddleware.d.ts +2 -2
- package/dist/server/logMiddleware.js +28 -29
- package/dist/server/serverStatusMiddleware.js +2 -1
- package/dist/server/simpleRequestLoggerMiddleware.d.ts +1 -1
- package/dist/server/simpleRequestLoggerMiddleware.js +2 -7
- package/package.json +2 -1
- package/src/server/asyncLocalStorageMiddleware.ts +5 -4
- package/src/server/logMiddleware.ts +30 -29
- package/src/server/serverStatusMiddleware.ts +11 -2
- package/src/server/simpleRequestLoggerMiddleware.ts +2 -7
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { _lazyValue } from '@naturalcycles/js-lib';
|
|
3
|
-
import { ciLogger, devLogger,
|
|
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 = !!
|
|
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 ||
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
15
|
-
log: (...args) =>
|
|
16
|
-
warn: (...args) =>
|
|
17
|
-
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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,
|
|
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 = !!
|
|
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 ||
|
|
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
|
-
|
|
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
|
|
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
|
|
20
|
-
log: (...args) =>
|
|
21
|
-
warn: (...args) =>
|
|
22
|
-
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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
|