@naturalcycles/backend-lib 9.76.0 → 9.77.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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/logMiddleware.js +6 -5
- package/dist/server/trace.util.d.ts +16 -0
- package/dist/server/trace.util.js +35 -0
- package/dist/upload/getUploadedFiles.js +3 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/server/logMiddleware.ts +6 -5
- package/src/server/trace.util.ts +54 -0
- package/src/upload/getUploadedFiles.ts +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,5 @@ export type * from './server/server.model.js';
|
|
|
18
18
|
export * from './server/serverStatsMiddleware.js';
|
|
19
19
|
export * from './server/serverStatusMiddleware.js';
|
|
20
20
|
export * from './server/simpleRequestLoggerMiddleware.js';
|
|
21
|
+
export * from './server/trace.util.js';
|
|
21
22
|
export * from './util.js';
|
package/dist/index.js
CHANGED
|
@@ -17,4 +17,5 @@ export * from './server/safeJsonMiddleware.js';
|
|
|
17
17
|
export * from './server/serverStatsMiddleware.js';
|
|
18
18
|
export * from './server/serverStatusMiddleware.js';
|
|
19
19
|
export * from './server/simpleRequestLoggerMiddleware.js';
|
|
20
|
+
export * from './server/trace.util.js';
|
|
20
21
|
export * from './util.js';
|
|
@@ -5,6 +5,7 @@ import { _safeJsonStringify } from '@naturalcycles/js-lib/string/safeJsonStringi
|
|
|
5
5
|
import { _objectAssign } from '@naturalcycles/js-lib/types';
|
|
6
6
|
import { logEntryToString } from '@naturalcycles/nodejs-lib';
|
|
7
7
|
import { dimGrey } from '@naturalcycles/nodejs-lib/colors';
|
|
8
|
+
import { getRequestTraceId } from './trace.util.js';
|
|
8
9
|
const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, K_SERVICE, APP_ENV } = process.env;
|
|
9
10
|
const isGAE = !!GAE_INSTANCE;
|
|
10
11
|
const isCloudRun = !!K_SERVICE;
|
|
@@ -75,11 +76,11 @@ export function logMiddleware() {
|
|
|
75
76
|
// gcloud run deploy my-service \
|
|
76
77
|
// --update-env-vars=GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
|
|
77
78
|
if (GOOGLE_CLOUD_PROJECT) {
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
req.requestId =
|
|
79
|
+
const traceId = getRequestTraceId(req);
|
|
80
|
+
if (traceId) {
|
|
81
|
+
meta['logging.googleapis.com/trace'] =
|
|
82
|
+
`projects/${GOOGLE_CLOUD_PROJECT}/traces/${traceId}`;
|
|
83
|
+
req.requestId = traceId;
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
if (isGAE) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BackendRequest } from './server.model.js';
|
|
2
|
+
/**
|
|
3
|
+
* The trace a request belongs to, as carried by the W3C `traceparent` header.
|
|
4
|
+
*/
|
|
5
|
+
export interface TraceContext {
|
|
6
|
+
traceId: string;
|
|
7
|
+
spanId: string;
|
|
8
|
+
sampled: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The trace id of the request, from `traceparent`. Cached per request, so it is cheap to call on
|
|
12
|
+
* every log line.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getRequestTraceId(req: BackendRequest): string | undefined;
|
|
15
|
+
export declare function getRequestTraceContext(req: BackendRequest): TraceContext | undefined;
|
|
16
|
+
export declare function parseTraceparent(header: string | undefined): TraceContext | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// https://www.w3.org/TR/trace-context/#traceparent-header
|
|
2
|
+
// Version `ff` is invalid. Higher versions may append `-` fields, which are ignored (forward
|
|
3
|
+
// compatibility), while version `00` must not have any.
|
|
4
|
+
const TRACEPARENT_REGEX = /^(?!ff)([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})(-.*)?$/;
|
|
5
|
+
const INVALID_TRACE_ID = '0'.repeat(32);
|
|
6
|
+
const INVALID_SPAN_ID = '0'.repeat(16);
|
|
7
|
+
const traceIdByRequest = new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* The trace id of the request, from `traceparent`. Cached per request, so it is cheap to call on
|
|
10
|
+
* every log line.
|
|
11
|
+
*/
|
|
12
|
+
export function getRequestTraceId(req) {
|
|
13
|
+
let traceId = traceIdByRequest.get(req);
|
|
14
|
+
if (traceId === undefined) {
|
|
15
|
+
traceId = getRequestTraceContext(req)?.traceId ?? null;
|
|
16
|
+
traceIdByRequest.set(req, traceId);
|
|
17
|
+
}
|
|
18
|
+
return traceId ?? undefined;
|
|
19
|
+
}
|
|
20
|
+
export function getRequestTraceContext(req) {
|
|
21
|
+
// Reads from `req.headers` and not `req.header()`, so a bare request object (as in tests) works too
|
|
22
|
+
const header = req.headers?.['traceparent'];
|
|
23
|
+
return parseTraceparent(typeof header === 'string' ? header : undefined);
|
|
24
|
+
}
|
|
25
|
+
export function parseTraceparent(header) {
|
|
26
|
+
const match = header ? TRACEPARENT_REGEX.exec(header) : null;
|
|
27
|
+
if (!match)
|
|
28
|
+
return;
|
|
29
|
+
const [, version, traceId, spanId, flags, extension] = match;
|
|
30
|
+
if (version === '00' && extension !== undefined)
|
|
31
|
+
return;
|
|
32
|
+
if (traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID)
|
|
33
|
+
return;
|
|
34
|
+
return { traceId, spanId, sampled: Number.parseInt(flags, 16) % 2 === 1 };
|
|
35
|
+
}
|
|
@@ -75,7 +75,9 @@ async function parseMultipart(req, maxFileSize) {
|
|
|
75
75
|
stream.on('close', () => {
|
|
76
76
|
const data = Buffer.concat(chunks);
|
|
77
77
|
files[name] = {
|
|
78
|
-
|
|
78
|
+
// busboy reports `filename` as undefined for a bare `application/octet-stream` part with
|
|
79
|
+
// no filename; normalize to `""`, same as a blank file input (`filename=""`) yields.
|
|
80
|
+
name: info.filename ?? '',
|
|
79
81
|
mimeType: info.mimeType,
|
|
80
82
|
encoding: info.encoding,
|
|
81
83
|
data,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,4 +18,5 @@ export type * from './server/server.model.js'
|
|
|
18
18
|
export * from './server/serverStatsMiddleware.js'
|
|
19
19
|
export * from './server/serverStatusMiddleware.js'
|
|
20
20
|
export * from './server/simpleRequestLoggerMiddleware.js'
|
|
21
|
+
export * from './server/trace.util.js'
|
|
21
22
|
export * from './util.js'
|
|
@@ -8,6 +8,7 @@ import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
|
8
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
|
+
import { getRequestTraceId } from './trace.util.js'
|
|
11
12
|
|
|
12
13
|
const { GOOGLE_CLOUD_PROJECT, GAE_INSTANCE, K_SERVICE, APP_ENV } = process.env
|
|
13
14
|
const isGAE = !!GAE_INSTANCE
|
|
@@ -88,11 +89,11 @@ export function logMiddleware(): BackendRequestHandler {
|
|
|
88
89
|
// gcloud run deploy my-service \
|
|
89
90
|
// --update-env-vars=GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
|
|
90
91
|
if (GOOGLE_CLOUD_PROJECT) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
req.requestId =
|
|
92
|
+
const traceId = getRequestTraceId(req)
|
|
93
|
+
if (traceId) {
|
|
94
|
+
meta['logging.googleapis.com/trace'] =
|
|
95
|
+
`projects/${GOOGLE_CLOUD_PROJECT}/traces/${traceId}`
|
|
96
|
+
req.requestId = traceId
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
if (isGAE) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BackendRequest } from './server.model.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The trace a request belongs to, as carried by the W3C `traceparent` header.
|
|
5
|
+
*/
|
|
6
|
+
export interface TraceContext {
|
|
7
|
+
traceId: string
|
|
8
|
+
spanId: string
|
|
9
|
+
sampled: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// https://www.w3.org/TR/trace-context/#traceparent-header
|
|
13
|
+
// Version `ff` is invalid. Higher versions may append `-` fields, which are ignored (forward
|
|
14
|
+
// compatibility), while version `00` must not have any.
|
|
15
|
+
const TRACEPARENT_REGEX = /^(?!ff)([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})(-.*)?$/
|
|
16
|
+
const INVALID_TRACE_ID = '0'.repeat(32)
|
|
17
|
+
const INVALID_SPAN_ID = '0'.repeat(16)
|
|
18
|
+
|
|
19
|
+
const traceIdByRequest = new WeakMap<BackendRequest, string | null>()
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The trace id of the request, from `traceparent`. Cached per request, so it is cheap to call on
|
|
23
|
+
* every log line.
|
|
24
|
+
*/
|
|
25
|
+
export function getRequestTraceId(req: BackendRequest): string | undefined {
|
|
26
|
+
let traceId = traceIdByRequest.get(req)
|
|
27
|
+
if (traceId === undefined) {
|
|
28
|
+
traceId = getRequestTraceContext(req)?.traceId ?? null
|
|
29
|
+
traceIdByRequest.set(req, traceId)
|
|
30
|
+
}
|
|
31
|
+
return traceId ?? undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getRequestTraceContext(req: BackendRequest): TraceContext | undefined {
|
|
35
|
+
// Reads from `req.headers` and not `req.header()`, so a bare request object (as in tests) works too
|
|
36
|
+
const header = req.headers?.['traceparent']
|
|
37
|
+
return parseTraceparent(typeof header === 'string' ? header : undefined)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseTraceparent(header: string | undefined): TraceContext | undefined {
|
|
41
|
+
const match = header ? TRACEPARENT_REGEX.exec(header) : null
|
|
42
|
+
if (!match) return
|
|
43
|
+
const [, version, traceId, spanId, flags, extension] = match as unknown as [
|
|
44
|
+
string,
|
|
45
|
+
string,
|
|
46
|
+
string,
|
|
47
|
+
string,
|
|
48
|
+
string,
|
|
49
|
+
string?,
|
|
50
|
+
]
|
|
51
|
+
if (version === '00' && extension !== undefined) return
|
|
52
|
+
if (traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID) return
|
|
53
|
+
return { traceId, spanId, sampled: Number.parseInt(flags, 16) % 2 === 1 }
|
|
54
|
+
}
|
|
@@ -102,7 +102,9 @@ async function parseMultipart(
|
|
|
102
102
|
stream.on('close', () => {
|
|
103
103
|
const data = Buffer.concat(chunks)
|
|
104
104
|
files[name] = {
|
|
105
|
-
|
|
105
|
+
// busboy reports `filename` as undefined for a bare `application/octet-stream` part with
|
|
106
|
+
// no filename; normalize to `""`, same as a blank file input (`filename=""`) yields.
|
|
107
|
+
name: info.filename ?? '',
|
|
106
108
|
mimeType: info.mimeType,
|
|
107
109
|
encoding: info.encoding,
|
|
108
110
|
data,
|