@webpieces/core-util 0.3.314 → 0.3.317
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/package.json +1 -1
- package/src/ContextKey.d.ts +8 -0
- package/src/ContextKey.js +22 -0
- package/src/ContextKey.js.map +1 -1
- package/src/http/ContextMgr.d.ts +24 -47
- package/src/http/ContextMgr.js +23 -47
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/ContextReader.d.ts +16 -8
- package/src/http/ContextReader.js.map +1 -1
- package/src/http/HeaderRegistry.d.ts +17 -1
- package/src/http/HeaderRegistry.js +40 -8
- package/src/http/HeaderRegistry.js.map +1 -1
- package/src/http/LogApiCall.d.ts +5 -3
- package/src/http/LogApiCall.js +6 -6
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +26 -27
- package/src/http/WebpiecesCoreHeaders.js +29 -31
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/index.d.ts +1 -2
- package/src/index.js +4 -9
- package/src/index.js.map +1 -1
- package/src/logging/ConsoleLogger.d.ts +10 -0
- package/src/logging/ConsoleLogger.js +25 -0
- package/src/logging/ConsoleLogger.js.map +1 -1
- package/src/logging/Logger.d.ts +1 -1
- package/src/logging/Logger.js.map +1 -1
- package/src/http/HeaderMethods.d.ts +0 -27
- package/src/http/HeaderMethods.js +0 -57
- package/src/http/HeaderMethods.js.map +0 -1
- package/src/http/RequestIdChainProcessor.d.ts +0 -20
- package/src/http/RequestIdChainProcessor.js +0 -36
- package/src/http/RequestIdChainProcessor.js.map +0 -1
package/package.json
CHANGED
package/src/ContextKey.d.ts
CHANGED
|
@@ -36,4 +36,12 @@ export declare class ContextKey {
|
|
|
36
36
|
constructor(name: string, httpHeader?: string, isSecured?: boolean, isLogged?: boolean);
|
|
37
37
|
/** True when this key is transferred over HTTP (has an httpHeader). */
|
|
38
38
|
isTransferred(): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The value as it should appear in a log line: returned as-is for a normal
|
|
41
|
+
* key, partially masked when this key is secured. Masking is length-based:
|
|
42
|
+
* - Length > 15: first 3 + "..." + last 3
|
|
43
|
+
* - Length 8-15: first 2 + "..."
|
|
44
|
+
* - Length < 8: "<secure key too short to log>"
|
|
45
|
+
*/
|
|
46
|
+
maskIfSecured(value: string): string;
|
|
39
47
|
}
|
package/src/ContextKey.js
CHANGED
|
@@ -46,6 +46,28 @@ class ContextKey {
|
|
|
46
46
|
isTransferred() {
|
|
47
47
|
return this.httpHeader !== undefined;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The value as it should appear in a log line: returned as-is for a normal
|
|
51
|
+
* key, partially masked when this key is secured. Masking is length-based:
|
|
52
|
+
* - Length > 15: first 3 + "..." + last 3
|
|
53
|
+
* - Length 8-15: first 2 + "..."
|
|
54
|
+
* - Length < 8: "<secure key too short to log>"
|
|
55
|
+
*/
|
|
56
|
+
maskIfSecured(value) {
|
|
57
|
+
if (!this.isSecured) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
const len = value.length;
|
|
61
|
+
if (len < 8) {
|
|
62
|
+
return '<secure key too short to log>';
|
|
63
|
+
}
|
|
64
|
+
else if (len <= 15) {
|
|
65
|
+
return `${value.substring(0, 2)}...`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return `${value.substring(0, 3)}...${value.substring(len - 3)}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
}
|
|
50
72
|
exports.ContextKey = ContextKey;
|
|
51
73
|
//# sourceMappingURL=ContextKey.js.map
|
package/src/ContextKey.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextKey.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/ContextKey.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,UAAU;IACnB,qEAAqE;IAC5D,IAAI,CAAS;IAEtB;;;OAGG;IACM,UAAU,CAAU;IAE7B,2CAA2C;IAClC,SAAS,CAAU;IAE5B,6EAA6E;IACpE,QAAQ,CAAU;IAE3B,YACI,IAAY,EACZ,UAAmB,EACnB,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,IAAI;QAEf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED,uEAAuE;IACvE,aAAa;QACT,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC;IACzC,CAAC;CACJ;
|
|
1
|
+
{"version":3,"file":"ContextKey.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/ContextKey.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,UAAU;IACnB,qEAAqE;IAC5D,IAAI,CAAS;IAEtB;;;OAGG;IACM,UAAU,CAAU;IAE7B,2CAA2C;IAClC,SAAS,CAAU;IAE5B,6EAA6E;IACpE,QAAQ,CAAU;IAE3B,YACI,IAAY,EACZ,UAAmB,EACnB,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,IAAI;QAEf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED,uEAAuE;IACvE,aAAa;QACT,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC;IACzC,CAAC;IAED;;;;;;OAMG;IACH,aAAa,CAAC,KAAa;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;QACzB,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACV,OAAO,+BAA+B,CAAC;QAC3C,CAAC;aAAM,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;YACnB,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;QACzC,CAAC;aAAM,CAAC;YACJ,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACpE,CAAC;IACL,CAAC;CACJ;AArDD,gCAqDC","sourcesContent":["/**\n * ContextKey - a single key that travels in the request's \"magic context\"\n * (RequestContext on the server, MutableContextStore in the browser).\n *\n * This ONE class replaces the old split of `Header` (interface) + `PlatformHeader`\n * (class) + `ContextKey` (class). Every context value — whether it rides over HTTP\n * (request-id, tenant, authorization) or stays in-process (method-meta, the\n * TestCaseRecorder) — is a `ContextKey`.\n *\n * The fields are named for what they DO (flipped from the old model):\n * - `name` ALWAYS set. The context storage key, the log/MDC key, and the\n * recorder name. e.g. 'requestId', 'tenantId', 'authorization'.\n * - `httpHeader` OPTIONAL. When set, this key is transferred over the wire under\n * this HTTP header name (inbound request -> context, and context ->\n * outbound request). e.g. 'x-request-id'. When UNSET, the key is\n * context-only and never leaves the process (method-meta, recorder).\n * - `isSecured` When true, the value is masked (partially) in logs.\n * - `isLogged` Defaults to true. When false, the value is NEVER logged (used for\n * object-valued/internal keys like the recorder or method-meta that\n * must not be serialized into log lines).\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\nexport class ContextKey {\n /** Context storage key + log/MDC key + recorder name. Always set. */\n readonly name: string;\n\n /**\n * HTTP header name when this key is transferred over the wire (e.g.\n * 'x-request-id'). Undefined = context-only, never transferred.\n */\n readonly httpHeader?: string;\n\n /** Mask this value (partially) in logs. */\n readonly isSecured: boolean;\n\n /** Whether this key is logged at all. Default true; false = never logged. */\n readonly isLogged: boolean;\n\n constructor(\n name: string,\n httpHeader?: string,\n isSecured = false,\n isLogged = true,\n ) {\n this.name = name;\n this.httpHeader = httpHeader;\n this.isSecured = isSecured;\n this.isLogged = isLogged;\n }\n\n /** True when this key is transferred over HTTP (has an httpHeader). */\n isTransferred(): boolean {\n return this.httpHeader !== undefined;\n }\n\n /**\n * The value as it should appear in a log line: returned as-is for a normal\n * key, partially masked when this key is secured. Masking is length-based:\n * - Length > 15: first 3 + \"...\" + last 3\n * - Length 8-15: first 2 + \"...\"\n * - Length < 8: \"<secure key too short to log>\"\n */\n maskIfSecured(value: string): string {\n if (!this.isSecured) {\n return value;\n }\n const len = value.length;\n if (len < 8) {\n return '<secure key too short to log>';\n } else if (len <= 15) {\n return `${value.substring(0, 2)}...`;\n } else {\n return `${value.substring(0, 3)}...${value.substring(len - 3)}`;\n }\n }\n}\n"]}
|
package/src/http/ContextMgr.d.ts
CHANGED
|
@@ -1,66 +1,43 @@
|
|
|
1
1
|
import { ContextReader } from './ContextReader';
|
|
2
2
|
/**
|
|
3
|
-
* ContextMgr - propagates the magic context onto outbound
|
|
3
|
+
* ContextMgr - propagates the magic context onto outbound BROWSER requests.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* like LogManager), so ContextMgr no longer takes a registry argument.
|
|
5
|
+
* BROWSER-ONLY. Only @webpieces/http-client-browser may name this class. The server reads
|
|
6
|
+
* `RequestContext` directly through `RequestContextHeaders` (in @webpieces/core-context) — a
|
|
7
|
+
* `ContextReader` indirection buys a server nothing, because there is exactly one right answer there.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @
|
|
9
|
+
* Browsers have no AsyncLocalStorage, so the app holds a `MutableContextStore` and sets values as
|
|
10
|
+
* they become known (login token, tenant). Every transferred key (httpHeader set) in the GLOBAL
|
|
11
|
+
* {@link HeaderRegistry} is read from it and added to outbound requests. The registry is a process
|
|
12
|
+
* global configured once at startup (like LogManager) and is browser-safe: it is the key SCHEMA,
|
|
13
|
+
* not the value store.
|
|
13
14
|
*
|
|
14
15
|
* Example usage:
|
|
15
16
|
* ```typescript
|
|
16
|
-
* //
|
|
17
|
-
*
|
|
17
|
+
* // startup, before bootstrap:
|
|
18
|
+
* HeaderRegistry.configure(AppHeaders.getAllHeaders(), CompanyHeaders.getAllHeaders(), true);
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
* const
|
|
21
|
-
*
|
|
22
|
-
* // Both cases — the ContextMgr is a factory dependency, the baseUrl is client state:
|
|
23
|
-
* const factory = new ClientHttpFactory(contextMgr);
|
|
20
|
+
* const store = new MutableContextStore();
|
|
21
|
+
* const factory = new ClientHttpBrowserFactory(store);
|
|
24
22
|
* const client = factory.createClient(SaveApi, new ClientConfig('http://api.example.com'));
|
|
25
23
|
* ```
|
|
26
24
|
*/
|
|
27
25
|
export declare class ContextMgr {
|
|
28
|
-
/**
|
|
29
|
-
* The context reader that provides context-key values.
|
|
30
|
-
* Different implementations for Node.js vs browser.
|
|
31
|
-
*/
|
|
26
|
+
/** The app-held store that provides context-key values. */
|
|
32
27
|
readonly contextReader: ContextReader;
|
|
33
|
-
/**
|
|
34
|
-
* When true (default), outbound calls send the current x-request-id as
|
|
35
|
-
* x-previous-request-id (and drop x-request-id) so each hop in a
|
|
36
|
-
* distributed trace gets its own id chained to its caller's.
|
|
37
|
-
*/
|
|
38
|
-
readonly chainRequestIds: boolean;
|
|
39
|
-
private chainProcessor;
|
|
40
|
-
private headerMethods;
|
|
41
28
|
constructor(
|
|
29
|
+
/** The app-held store that provides context-key values. */
|
|
30
|
+
contextReader: ContextReader);
|
|
42
31
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
contextReader: ContextReader,
|
|
47
|
-
/**
|
|
48
|
-
* When true (default), outbound calls send the current x-request-id as
|
|
49
|
-
* x-previous-request-id (and drop x-request-id) so each hop in a
|
|
50
|
-
* distributed trace gets its own id chained to its caller's.
|
|
51
|
-
*/
|
|
52
|
-
chainRequestIds?: boolean);
|
|
53
|
-
/**
|
|
54
|
-
* Build the headers to send on an outbound request: every transferred key
|
|
55
|
-
* (httpHeader set) with a non-empty value in the context, emitted under its
|
|
56
|
-
* `httpHeader` wire name, then request-id chaining applied (unless opted out).
|
|
32
|
+
* Build the headers to send on an outbound request: every transferred key (httpHeader set)
|
|
33
|
+
* with a non-empty value, emitted under its `httpHeader` wire name.
|
|
57
34
|
*
|
|
58
|
-
*
|
|
35
|
+
* NO request-id chaining. A browser ORIGINATES a trace — it has no inbound request to point
|
|
36
|
+
* back at. If the app puts an `x-request-id` on the store it goes out as-is, and the server's
|
|
37
|
+
* inbound transfer adopts it as hop 1's own id. Chaining is a server concern; see
|
|
38
|
+
* RequestContextHeaders.
|
|
39
|
+
*
|
|
40
|
+
* Values are RAW (unmasked) — this map goes on the wire, not in logs.
|
|
59
41
|
*/
|
|
60
42
|
buildOutboundHeaders(): Map<string, string>;
|
|
61
|
-
/**
|
|
62
|
-
* Build the header map for LOGGING: secured values masked, keyed by each key's
|
|
63
|
-
* `name`, only for keys with isLogged=true.
|
|
64
|
-
*/
|
|
65
|
-
buildHeadersForLogging(): Map<string, string>;
|
|
66
43
|
}
|
package/src/http/ContextMgr.js
CHANGED
|
@@ -1,61 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContextMgr = void 0;
|
|
4
|
-
const HeaderMethods_1 = require("./HeaderMethods");
|
|
5
4
|
const HeaderRegistry_1 = require("./HeaderRegistry");
|
|
6
|
-
const RequestIdChainProcessor_1 = require("./RequestIdChainProcessor");
|
|
7
5
|
/**
|
|
8
|
-
* ContextMgr - propagates the magic context onto outbound
|
|
6
|
+
* ContextMgr - propagates the magic context onto outbound BROWSER requests.
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* like LogManager), so ContextMgr no longer takes a registry argument.
|
|
8
|
+
* BROWSER-ONLY. Only @webpieces/http-client-browser may name this class. The server reads
|
|
9
|
+
* `RequestContext` directly through `RequestContextHeaders` (in @webpieces/core-context) — a
|
|
10
|
+
* `ContextReader` indirection buys a server nothing, because there is exactly one right answer there.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
12
|
+
* Browsers have no AsyncLocalStorage, so the app holds a `MutableContextStore` and sets values as
|
|
13
|
+
* they become known (login token, tenant). Every transferred key (httpHeader set) in the GLOBAL
|
|
14
|
+
* {@link HeaderRegistry} is read from it and added to outbound requests. The registry is a process
|
|
15
|
+
* global configured once at startup (like LogManager) and is browser-safe: it is the key SCHEMA,
|
|
16
|
+
* not the value store.
|
|
18
17
|
*
|
|
19
18
|
* Example usage:
|
|
20
19
|
* ```typescript
|
|
21
|
-
* //
|
|
22
|
-
*
|
|
20
|
+
* // startup, before bootstrap:
|
|
21
|
+
* HeaderRegistry.configure(AppHeaders.getAllHeaders(), CompanyHeaders.getAllHeaders(), true);
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
* const
|
|
26
|
-
*
|
|
27
|
-
* // Both cases — the ContextMgr is a factory dependency, the baseUrl is client state:
|
|
28
|
-
* const factory = new ClientHttpFactory(contextMgr);
|
|
23
|
+
* const store = new MutableContextStore();
|
|
24
|
+
* const factory = new ClientHttpBrowserFactory(store);
|
|
29
25
|
* const client = factory.createClient(SaveApi, new ClientConfig('http://api.example.com'));
|
|
30
26
|
* ```
|
|
31
27
|
*/
|
|
32
28
|
class ContextMgr {
|
|
33
29
|
contextReader;
|
|
34
|
-
chainRequestIds;
|
|
35
|
-
chainProcessor;
|
|
36
|
-
headerMethods = new HeaderMethods_1.HeaderMethods();
|
|
37
30
|
constructor(
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
* Different implementations for Node.js vs browser.
|
|
41
|
-
*/
|
|
42
|
-
contextReader,
|
|
43
|
-
/**
|
|
44
|
-
* When true (default), outbound calls send the current x-request-id as
|
|
45
|
-
* x-previous-request-id (and drop x-request-id) so each hop in a
|
|
46
|
-
* distributed trace gets its own id chained to its caller's.
|
|
47
|
-
*/
|
|
48
|
-
chainRequestIds = true) {
|
|
31
|
+
/** The app-held store that provides context-key values. */
|
|
32
|
+
contextReader) {
|
|
49
33
|
this.contextReader = contextReader;
|
|
50
|
-
this.chainRequestIds = chainRequestIds;
|
|
51
|
-
this.chainProcessor = new RequestIdChainProcessor_1.RequestIdChainProcessor();
|
|
52
34
|
}
|
|
53
35
|
/**
|
|
54
|
-
* Build the headers to send on an outbound request: every transferred key
|
|
55
|
-
*
|
|
56
|
-
* `httpHeader` wire name, then request-id chaining applied (unless opted out).
|
|
36
|
+
* Build the headers to send on an outbound request: every transferred key (httpHeader set)
|
|
37
|
+
* with a non-empty value, emitted under its `httpHeader` wire name.
|
|
57
38
|
*
|
|
58
|
-
*
|
|
39
|
+
* NO request-id chaining. A browser ORIGINATES a trace — it has no inbound request to point
|
|
40
|
+
* back at. If the app puts an `x-request-id` on the store it goes out as-is, and the server's
|
|
41
|
+
* inbound transfer adopts it as hop 1's own id. Chaining is a server concern; see
|
|
42
|
+
* RequestContextHeaders.
|
|
43
|
+
*
|
|
44
|
+
* Values are RAW (unmasked) — this map goes on the wire, not in logs.
|
|
59
45
|
*/
|
|
60
46
|
buildOutboundHeaders() {
|
|
61
47
|
const outbound = new Map();
|
|
@@ -65,18 +51,8 @@ class ContextMgr {
|
|
|
65
51
|
outbound.set(key.httpHeader, value);
|
|
66
52
|
}
|
|
67
53
|
}
|
|
68
|
-
if (this.chainRequestIds) {
|
|
69
|
-
this.chainProcessor.process(outbound);
|
|
70
|
-
}
|
|
71
54
|
return outbound;
|
|
72
55
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Build the header map for LOGGING: secured values masked, keyed by each key's
|
|
75
|
-
* `name`, only for keys with isLogged=true.
|
|
76
|
-
*/
|
|
77
|
-
buildHeadersForLogging() {
|
|
78
|
-
return this.headerMethods.buildSecureMapForLogs(HeaderRegistry_1.HeaderRegistry.get().getLoggedKeys(), this.contextReader);
|
|
79
|
-
}
|
|
80
56
|
}
|
|
81
57
|
exports.ContextMgr = ContextMgr;
|
|
82
58
|
//# sourceMappingURL=ContextMgr.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMgr.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextMgr.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"ContextMgr.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextMgr.ts"],"names":[],"mappings":";;;AAEA,qDAAkD;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,UAAU;IAIC;IAFpB;IACI,2DAA2D;IAC3C,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAC7C,CAAC;IAEJ;;;;;;;;;;OAUG;IACH,oBAAoB;QAChB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE3C,KAAK,MAAM,GAAG,IAAI,+BAAc,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACxD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,UAAW,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IACpB,CAAC;CACJ;AA9BD,gCA8BC","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { ContextReader } from './ContextReader';\nimport { HeaderRegistry } from './HeaderRegistry';\n\n/**\n * ContextMgr - propagates the magic context onto outbound BROWSER requests.\n *\n * BROWSER-ONLY. Only @webpieces/http-client-browser may name this class. The server reads\n * `RequestContext` directly through `RequestContextHeaders` (in @webpieces/core-context) — a\n * `ContextReader` indirection buys a server nothing, because there is exactly one right answer there.\n *\n * Browsers have no AsyncLocalStorage, so the app holds a `MutableContextStore` and sets values as\n * they become known (login token, tenant). Every transferred key (httpHeader set) in the GLOBAL\n * {@link HeaderRegistry} is read from it and added to outbound requests. The registry is a process\n * global configured once at startup (like LogManager) and is browser-safe: it is the key SCHEMA,\n * not the value store.\n *\n * Example usage:\n * ```typescript\n * // startup, before bootstrap:\n * HeaderRegistry.configure(AppHeaders.getAllHeaders(), CompanyHeaders.getAllHeaders(), true);\n *\n * const store = new MutableContextStore();\n * const factory = new ClientHttpBrowserFactory(store);\n * const client = factory.createClient(SaveApi, new ClientConfig('http://api.example.com'));\n * ```\n */\nexport class ContextMgr {\n\n constructor(\n /** The app-held store that provides context-key values. */\n public readonly contextReader: ContextReader,\n ) {}\n\n /**\n * Build the headers to send on an outbound request: every transferred key (httpHeader set)\n * with a non-empty value, emitted under its `httpHeader` wire name.\n *\n * NO request-id chaining. A browser ORIGINATES a trace — it has no inbound request to point\n * back at. If the app puts an `x-request-id` on the store it goes out as-is, and the server's\n * inbound transfer adopts it as hop 1's own id. Chaining is a server concern; see\n * RequestContextHeaders.\n *\n * Values are RAW (unmasked) — this map goes on the wire, not in logs.\n */\n buildOutboundHeaders(): Map<string, string> {\n const outbound = new Map<string, string>();\n\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const value = this.contextReader.read(key);\n if (value !== undefined && value !== null && value !== '') {\n outbound.set(key.httpHeader!, value);\n }\n }\n\n return outbound;\n }\n}\n"]}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { ContextKey } from '../ContextKey';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Reads one context key's string value. The ONE seam between the two environments:
|
|
4
|
+
* the server passes `RequestContext.getHeader`, a browser passes its store's read.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
6
|
+
* A lambda, not an object — nothing here needs an implementation to hold.
|
|
7
|
+
*/
|
|
8
|
+
export type ContextRead = (key: ContextKey) => string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* ContextReader - reads context-key values from an app-held store.
|
|
11
|
+
*
|
|
12
|
+
* BROWSER-ONLY. Browsers have no AsyncLocalStorage and therefore no ambient request scope, so the
|
|
13
|
+
* app holds a `MutableContextStore` (in @webpieces/http-client-browser) and sets values as they
|
|
14
|
+
* become known (login token, tenant, ...).
|
|
15
|
+
*
|
|
16
|
+
* The server has no use for this: there is exactly one right answer there, so `RequestContextHeaders`
|
|
17
|
+
* (in @webpieces/core-context) reads `RequestContext` directly rather than through a reader object.
|
|
10
18
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
19
|
+
* Note this is only about where VALUES live. The key SCHEMA — which keys exist, which transfer, which
|
|
20
|
+
* are secured — is the global {@link HeaderRegistry}, and that is browser-safe and shared by both.
|
|
13
21
|
*
|
|
14
22
|
* This is a business-logic interface (per CLAUDE.md: behavior = interface).
|
|
15
23
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextReader.ts"],"names":[],"mappings":"","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"ContextReader.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ContextReader.ts"],"names":[],"mappings":"","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n * Reads one context key's string value. The ONE seam between the two environments:\n * the server passes `RequestContext.getHeader`, a browser passes its store's read.\n *\n * A lambda, not an object — nothing here needs an implementation to hold.\n */\nexport type ContextRead = (key: ContextKey) => string | undefined;\n\n/**\n * ContextReader - reads context-key values from an app-held store.\n *\n * BROWSER-ONLY. Browsers have no AsyncLocalStorage and therefore no ambient request scope, so the\n * app holds a `MutableContextStore` (in @webpieces/http-client-browser) and sets values as they\n * become known (login token, tenant, ...).\n *\n * The server has no use for this: there is exactly one right answer there, so `RequestContextHeaders`\n * (in @webpieces/core-context) reads `RequestContext` directly rather than through a reader object.\n *\n * Note this is only about where VALUES live. The key SCHEMA — which keys exist, which transfer, which\n * are secured — is the global {@link HeaderRegistry}, and that is browser-safe and shared by both.\n *\n * This is a business-logic interface (per CLAUDE.md: behavior = interface).\n */\nexport interface ContextReader {\n /**\n * Read the string value of a context key. Returns undefined if not present.\n */\n read(key: ContextKey): string | undefined;\n\n /**\n * OPTIONAL: read a non-string context value (e.g. the active TestCaseRecorder\n * under RecorderKeys.RECORDER). Server-side readers implement this over the\n * RequestContext; browser readers may omit it (no server-side recording in\n * browsers — same as Java).\n */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (recorder, meta objects)\n readValue?(key: ContextKey): unknown;\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextKey } from '../ContextKey';
|
|
2
|
+
import { ContextRead } from './ContextReader';
|
|
2
3
|
/**
|
|
3
4
|
* HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the
|
|
4
5
|
* platform knows about. Port of Java webpieces' HeaderTranslation.
|
|
@@ -28,6 +29,10 @@ export declare class HeaderRegistry {
|
|
|
28
29
|
static readonly DEFAULT_HEADERS: ContextKey[];
|
|
29
30
|
private static instance;
|
|
30
31
|
private readonly keys;
|
|
32
|
+
private readonly transferredKeys;
|
|
33
|
+
private readonly securedNames;
|
|
34
|
+
private readonly loggedKeys;
|
|
35
|
+
private readonly byHttpHeader;
|
|
31
36
|
private constructor();
|
|
32
37
|
/**
|
|
33
38
|
* Install the process-wide registry. Call once at startup, BEFORE
|
|
@@ -49,7 +54,18 @@ export declare class HeaderRegistry {
|
|
|
49
54
|
getSecuredNames(): string[];
|
|
50
55
|
/** Keys that appear in logs. isLogged=true. */
|
|
51
56
|
getLoggedKeys(): ContextKey[];
|
|
52
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* The log/MDC field map: every logged key with a value, under its `name`, secured values masked.
|
|
59
|
+
*
|
|
60
|
+
* The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows
|
|
61
|
+
* how to mask its own value. Callers differ only in WHERE a value is read from:
|
|
62
|
+
* `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes
|
|
63
|
+
* the app-held store's read (browser).
|
|
64
|
+
*
|
|
65
|
+
* getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.
|
|
66
|
+
*/
|
|
67
|
+
buildLogFields(read: ContextRead): Map<string, string>;
|
|
68
|
+
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
53
69
|
findByHttpHeader(httpHeader: string): ContextKey | undefined;
|
|
54
70
|
/**
|
|
55
71
|
* Collapse exact duplicates, throw on conflicting definitions sharing a `name`
|
|
@@ -31,8 +31,23 @@ class HeaderRegistry {
|
|
|
31
31
|
static DEFAULT_HEADERS = WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.getAllHeaders();
|
|
32
32
|
static instance;
|
|
33
33
|
keys;
|
|
34
|
+
// Derived collections precomputed ONCE, here in the constructor (i.e. at
|
|
35
|
+
// configure() time). The hot path — every log line calls getLoggedKeys(), every
|
|
36
|
+
// outbound request calls getTransferredKeys() — then returns the cached array
|
|
37
|
+
// instead of re-filtering the full key list on each call. These are reachable
|
|
38
|
+
// only through HeaderRegistry.get(), which throws until configure() has run.
|
|
39
|
+
transferredKeys;
|
|
40
|
+
securedNames;
|
|
41
|
+
loggedKeys;
|
|
42
|
+
byHttpHeader;
|
|
34
43
|
constructor(keys) {
|
|
35
44
|
this.keys = this.checkForDuplicates(keys);
|
|
45
|
+
this.transferredKeys = this.keys.filter((k) => k.httpHeader !== undefined);
|
|
46
|
+
this.securedNames = this.keys
|
|
47
|
+
.filter((k) => k.isSecured)
|
|
48
|
+
.map((k) => k.name);
|
|
49
|
+
this.loggedKeys = this.keys.filter((k) => k.isLogged);
|
|
50
|
+
this.byHttpHeader = new Map(this.transferredKeys.map((k) => [k.httpHeader.toLowerCase(), k]));
|
|
36
51
|
}
|
|
37
52
|
/**
|
|
38
53
|
* Install the process-wide registry. Call once at startup, BEFORE
|
|
@@ -67,22 +82,39 @@ class HeaderRegistry {
|
|
|
67
82
|
* outbound request): those with an httpHeader set.
|
|
68
83
|
*/
|
|
69
84
|
getTransferredKeys() {
|
|
70
|
-
return this.
|
|
85
|
+
return this.transferredKeys;
|
|
71
86
|
}
|
|
72
87
|
/** Names (log keys) whose values must be masked in logs. isSecured=true. */
|
|
73
88
|
getSecuredNames() {
|
|
74
|
-
return this.
|
|
75
|
-
.filter((k) => k.isSecured)
|
|
76
|
-
.map((k) => k.name);
|
|
89
|
+
return this.securedNames;
|
|
77
90
|
}
|
|
78
91
|
/** Keys that appear in logs. isLogged=true. */
|
|
79
92
|
getLoggedKeys() {
|
|
80
|
-
return this.
|
|
93
|
+
return this.loggedKeys;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The log/MDC field map: every logged key with a value, under its `name`, secured values masked.
|
|
97
|
+
*
|
|
98
|
+
* The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows
|
|
99
|
+
* how to mask its own value. Callers differ only in WHERE a value is read from:
|
|
100
|
+
* `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes
|
|
101
|
+
* the app-held store's read (browser).
|
|
102
|
+
*
|
|
103
|
+
* getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.
|
|
104
|
+
*/
|
|
105
|
+
buildLogFields(read) {
|
|
106
|
+
const fields = new Map();
|
|
107
|
+
for (const key of this.getLoggedKeys()) {
|
|
108
|
+
const value = read(key);
|
|
109
|
+
if (value) {
|
|
110
|
+
fields.set(key.name, key.maskIfSecured(value));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return fields;
|
|
81
114
|
}
|
|
82
|
-
/** Look up a key by its HTTP header name (case-insensitive). */
|
|
115
|
+
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
83
116
|
findByHttpHeader(httpHeader) {
|
|
84
|
-
|
|
85
|
-
return this.keys.find((k) => k.httpHeader?.toLowerCase() === lower);
|
|
117
|
+
return this.byHttpHeader.get(httpHeader.toLowerCase());
|
|
86
118
|
}
|
|
87
119
|
/**
|
|
88
120
|
* Collapse exact duplicates, throw on conflicting definitions sharing a `name`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(AppHeaders.getAllHeaders(), CompanyHeaders.getAllHeaders(), true);\n * ```\n *\n * - `svrHeaders` this server's own keys.\n * - `companyHeaders` keys from a shared company lib all services use.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/isSecured/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: ContextKey[] = WebpiecesCoreHeaders.getAllHeaders();\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: ContextKey[];\n\n private constructor(keys: ContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n static configure(svrHeaders: ContextKey[], companyHeaders: ContextKey[], platformHeaders: boolean): void {\n const all: ContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...companyHeaders,\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): ContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): ContextKey[] {\n return this.keys.filter((k: ContextKey) => k.httpHeader !== undefined);\n }\n\n /** Names (log keys) whose values must be masked in logs. isSecured=true. */\n getSecuredNames(): string[] {\n return this.keys\n .filter((k: ContextKey) => k.isSecured)\n .map((k: ContextKey) => k.name);\n }\n\n /** Keys that appear in logs. isLogged=true. */\n getLoggedKeys(): ContextKey[] {\n return this.keys.filter((k: ContextKey) => k.isLogged);\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). */\n findByHttpHeader(httpHeader: string): ContextKey | undefined {\n const lower = httpHeader.toLowerCase();\n return this.keys.find((k: ContextKey) => k.httpHeader?.toLowerCase() === lower);\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: ContextKey[]): ContextKey[] {\n const byName = new Map<string, ContextKey>();\n const byHttpHeader = new Map<string, ContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/isSecured/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: ContextKey, duplicate: ContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { ContextRead } from './ContextReader';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(AppHeaders.getAllHeaders(), CompanyHeaders.getAllHeaders(), true);\n * ```\n *\n * - `svrHeaders` this server's own keys.\n * - `companyHeaders` keys from a shared company lib all services use.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/isSecured/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: ContextKey[] = WebpiecesCoreHeaders.getAllHeaders();\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: ContextKey[];\n\n // Derived collections precomputed ONCE, here in the constructor (i.e. at\n // configure() time). The hot path — every log line calls getLoggedKeys(), every\n // outbound request calls getTransferredKeys() — then returns the cached array\n // instead of re-filtering the full key list on each call. These are reachable\n // only through HeaderRegistry.get(), which throws until configure() has run.\n private readonly transferredKeys: ContextKey[];\n private readonly securedNames: string[];\n private readonly loggedKeys: ContextKey[];\n private readonly byHttpHeader: Map<string, ContextKey>;\n\n private constructor(keys: ContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n this.transferredKeys = this.keys.filter((k: ContextKey) => k.httpHeader !== undefined);\n this.securedNames = this.keys\n .filter((k: ContextKey) => k.isSecured)\n .map((k: ContextKey) => k.name);\n this.loggedKeys = this.keys.filter((k: ContextKey) => k.isLogged);\n this.byHttpHeader = new Map(\n this.transferredKeys.map((k: ContextKey): [string, ContextKey] => [k.httpHeader!.toLowerCase(), k]),\n );\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n static configure(svrHeaders: ContextKey[], companyHeaders: ContextKey[], platformHeaders: boolean): void {\n const all: ContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...companyHeaders,\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): ContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): ContextKey[] {\n return this.transferredKeys;\n }\n\n /** Names (log keys) whose values must be masked in logs. isSecured=true. */\n getSecuredNames(): string[] {\n return this.securedNames;\n }\n\n /** Keys that appear in logs. isLogged=true. */\n getLoggedKeys(): ContextKey[] {\n return this.loggedKeys;\n }\n\n /**\n * The log/MDC field map: every logged key with a value, under its `name`, secured values masked.\n *\n * The ONE implementation, because the registry owns the keys and each {@link ContextKey} knows\n * how to mask its own value. Callers differ only in WHERE a value is read from:\n * `RequestContext.buildLogFields()` passes its own getHeader (server), and `ContextMgr` passes\n * the app-held store's read (browser).\n *\n * getLoggedKeys() is precomputed at configure() time, so this is hot-path safe.\n */\n buildLogFields(read: ContextRead): Map<string, string> {\n const fields = new Map<string, string>();\n for (const key of this.getLoggedKeys()) {\n const value = read(key);\n if (value) {\n fields.set(key.name, key.maskIfSecured(value));\n }\n }\n return fields;\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */\n findByHttpHeader(httpHeader: string): ContextKey | undefined {\n return this.byHttpHeader.get(httpHeader.toLowerCase());\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: ContextKey[]): ContextKey[] {\n const byName = new Map<string, ContextKey>();\n const byHttpHeader = new Map<string, ContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/isSecured/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: ContextKey, duplicate: ContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
package/src/http/LogApiCall.d.ts
CHANGED
|
@@ -18,11 +18,13 @@ export declare class LogApiCall {
|
|
|
18
18
|
* @param type - 'SVR' or 'CLIENT'
|
|
19
19
|
* @param meta - Route metadata with controllerClassName and methodName
|
|
20
20
|
* @param requestDto - The request DTO
|
|
21
|
-
* @param headers - Map of header name -> values
|
|
22
|
-
* @param splitHeaders - SplitHeaders with secureHeaders and publicHeaders for masking
|
|
23
21
|
* @param method - The method to execute
|
|
22
|
+
*
|
|
23
|
+
* Context fields (requestId, tenantId, ...) are NOT stamped here. A logging BACKEND owns that:
|
|
24
|
+
* bunyan/winston read RequestContext.buildLogFields() on every record. The bootstrap
|
|
25
|
+
* ConsoleLogger deliberately carries no context.
|
|
24
26
|
*/
|
|
25
|
-
execute(type: string, meta: RouteMetadata, requestDto: any,
|
|
27
|
+
execute(type: string, meta: RouteMetadata, requestDto: any, method: (dto: any) => Promise<any>): Promise<any>;
|
|
26
28
|
/**
|
|
27
29
|
* Check if an error is a user error (expected behavior from server perspective).
|
|
28
30
|
* User errors are NOT failures - just users making mistakes or validation issues.
|
package/src/http/LogApiCall.js
CHANGED
|
@@ -24,14 +24,14 @@ class LogApiCall {
|
|
|
24
24
|
* @param type - 'SVR' or 'CLIENT'
|
|
25
25
|
* @param meta - Route metadata with controllerClassName and methodName
|
|
26
26
|
* @param requestDto - The request DTO
|
|
27
|
-
* @param headers - Map of header name -> values
|
|
28
|
-
* @param splitHeaders - SplitHeaders with secureHeaders and publicHeaders for masking
|
|
29
27
|
* @param method - The method to execute
|
|
28
|
+
*
|
|
29
|
+
* Context fields (requestId, tenantId, ...) are NOT stamped here. A logging BACKEND owns that:
|
|
30
|
+
* bunyan/winston read RequestContext.buildLogFields() on every record. The bootstrap
|
|
31
|
+
* ConsoleLogger deliberately carries no context.
|
|
30
32
|
*/
|
|
31
|
-
async execute(type, meta, requestDto,
|
|
32
|
-
|
|
33
|
-
const headersObj = Object.fromEntries(headers);
|
|
34
|
-
log.info(`[API-${type}-req] ${meta.controllerClassName}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)} headers=${JSON.stringify(headersObj)}`);
|
|
33
|
+
async execute(type, meta, requestDto, method) {
|
|
34
|
+
log.info(`[API-${type}-req] ${meta.controllerClassName}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)}`);
|
|
35
35
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller
|
|
36
36
|
try {
|
|
37
37
|
if (!requestDto)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":";;;AACA,qCAMkB;AAClB,kDAA0C;AAC1C,sDAAiD;AAEjD,MAAM,GAAG,GAAG,uBAAU,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;AAG/C;;;;;;;;;;;GAWG;AACH,MAAa,UAAU;IAEnB
|
|
1
|
+
{"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":";;;AACA,qCAMkB;AAClB,kDAA0C;AAC1C,sDAAiD;AAEjD,MAAM,GAAG,GAAG,uBAAU,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;AAG/C;;;;;;;;;;;GAWG;AACH,MAAa,UAAU;IAEnB;;;;;;;;;;;OAWG;IACI,KAAK,CAAC,OAAO,CAChB,IAAY,EACZ,IAAmB,EACnB,UAAe,EACf,MAAkC;QAElC,GAAG,CAAC,IAAI,CACJ,QAAQ,IAAI,SAAS,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CACxH,CAAC;QAEF,qHAAqH;QACrH,IAAI,CAAC;YACD,IAAG,CAAC,UAAU;gBACV,MAAM,IAAI,KAAK,CAAC,uCAAuC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAE1G,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YAE1C,IAAG,CAAC,QAAQ;gBACR,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAE3G,uBAAuB;YACvB,GAAG,CAAC,IAAI,CACJ,QAAQ,IAAI,kBAAkB,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,aAAa,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CACnH,CAAC;YAEF,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,oBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC;YACzC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC;YAEnC,uCAAuC;YACvC,IAAI,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,GAAG,CAAC,IAAI,CACJ,QAAQ,IAAI,gBAAgB,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,cAAc,SAAS,EAAE,CACnG,CAAC;YACN,CAAC;iBAAM,CAAC;gBACJ,GAAG,CAAC,KAAK,CACL,QAAQ,IAAI,eAAe,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,UAAU,cAAc,SAAS,UAAU,YAAY,EAAE,CACxH,CAAC;YACN,CAAC;YACD,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,WAAW,CAAC,KAAc;QAC7B,OAAO,CACH,KAAK,YAAY,4BAAmB;YACpC,KAAK,YAAY,8BAAqB;YACtC,KAAK,YAAY,2BAAkB;YACnC,KAAK,YAAY,0BAAiB;YAClC,KAAK,YAAY,sBAAa,CACjC,CAAC;IACN,CAAC;CACJ;AAlFD,gCAkFC","sourcesContent":["import {RouteMetadata} from \"./decorators\";\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from './errors';\nimport {toError} from \"../lib/errorUtils\";\nimport {LogManager} from \"../logging/LogManager\";\n\nconst log = LogManager.getLogger('LogApiCall');\n\n\n/**\n * LogApiCall - Generic API call logging utility.\n *\n * Used by both server-side (LogApiFilter) and client-side (ClientFactory) for\n * consistent logging patterns across the framework.\n *\n * Logging format patterns:\n * - [API-{type}-req] ClassName.methodName request={...} headers={...}\n * - [API-{type}-resp-SUCCESS] ClassName.methodName response={...}\n * - [API-{type}-resp-OTHER] ClassName.methodName errorType={...} (user errors)\n * - [API-{type}-resp-FAIL] ClassName.methodName error={...} (server errors)\n */\nexport class LogApiCall {\n\n /**\n * Execute an API call with logging around it.\n *\n * @param type - 'SVR' or 'CLIENT'\n * @param meta - Route metadata with controllerClassName and methodName\n * @param requestDto - The request DTO\n * @param method - The method to execute\n *\n * Context fields (requestId, tenantId, ...) are NOT stamped here. A logging BACKEND owns that:\n * bunyan/winston read RequestContext.buildLogFields() on every record. The bootstrap\n * ConsoleLogger deliberately carries no context.\n */\n public async execute(\n type: string,\n meta: RouteMetadata,\n requestDto: any,\n method: (dto: any) => Promise<any>\n ): Promise<any> {\n log.info(\n `[API-${type}-req] ${meta.controllerClassName}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)}`\n );\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller\n try {\n if(!requestDto)\n throw new Error(`Request cannot be null and was from ${meta.controllerClassName}.${meta.methodName}`);\n \n const response = await method(requestDto);\n\n if(!response)\n throw new Error(`Response cannot be null and was from ${meta.controllerClassName}.${meta.methodName}`);\n\n // Log success response\n log.info(\n `[API-${type}-resp-SUCCESS] ${meta.controllerClassName}.${meta.methodName} response=${JSON.stringify(response)}`\n );\n\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n const errorType = error.constructor.name;\n const errorMessage = error.message;\n\n // Log error based on type and re-throw\n if (LogApiCall.isUserError(error)) {\n log.warn(\n `[API-${type}-resp-OTHER] ${meta.controllerClassName}.${meta.methodName} errorType=${errorType}`\n );\n } else {\n log.error(\n `[API-${type}-resp-FAIL] ${meta.controllerClassName}.${meta.methodName} errorType=${errorType} error=${errorMessage}`\n );\n }\n throw error;\n }\n }\n\n /**\n * Check if an error is a user error (expected behavior from server perspective).\n * User errors are NOT failures - just users making mistakes or validation issues.\n *\n * User errors (logged as OTHER, no stack trace):\n * - HttpBadRequestError (400)\n * - HttpUnauthorizedError (401)\n * - HttpForbiddenError (403)\n * - HttpNotFoundError (404)\n * - HttpUserError (266)\n *\n * @param error - The error to check\n * @returns true if this is a user error, false for server errors\n */\n static isUserError(error: unknown): boolean {\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError ||\n error instanceof HttpUserError\n );\n }\n}\n"]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ContextKey } from '../ContextKey';
|
|
2
2
|
/**
|
|
3
|
-
* Core framework context keys
|
|
3
|
+
* Core framework context keys — the minimum the WebPieces framework needs to correlate one
|
|
4
|
+
* request across every service it touches, and across every log line each of them writes.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
6
|
+
* ONE id, propagated unchanged. The first service to see a request without an `x-request-id`
|
|
7
|
+
* generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you
|
|
8
|
+
* have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must
|
|
9
|
+
* stitch back together buys nothing a single shared id does not already give you.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* so BOTH the http-client (request-id chaining) and http-server can reference it.
|
|
11
|
+
* Lives in core-util (browser-safe) so both the http clients and http-server can reference it.
|
|
12
12
|
*
|
|
13
13
|
* Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by
|
|
14
14
|
* passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.
|
|
@@ -17,35 +17,34 @@ import { ContextKey } from '../ContextKey';
|
|
|
17
17
|
*/
|
|
18
18
|
export declare class WebpiecesCoreHeaders {
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* The id that correlates every hop of one request, and every log line of every hop.
|
|
21
|
+
* Generated by the first service to see a request without one; propagated unchanged after that.
|
|
22
22
|
*/
|
|
23
23
|
static readonly REQUEST_ID: ContextKey;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
static readonly PREVIOUS_REQUEST_ID: ContextKey;
|
|
29
|
-
/**
|
|
30
|
-
* Correlation ID spanning multiple related requests. Set by the gateway or
|
|
31
|
-
* first service; all hops in the chain share it.
|
|
32
|
-
*/
|
|
33
|
-
static readonly CORRELATION_ID: ContextKey;
|
|
24
|
+
static readonly ORG_ID: ContextKey;
|
|
25
|
+
static readonly USER_ID: ContextKey;
|
|
26
|
+
static readonly USER_ROLES: ContextKey;
|
|
34
27
|
/**
|
|
35
28
|
* Turns on test-case recording for this request (Java: x-webpieces-recording).
|
|
36
29
|
* Transferred so recording follows the request across service hops.
|
|
37
30
|
*/
|
|
38
31
|
static readonly RECORDING: ContextKey;
|
|
39
32
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
33
|
+
* NO CREDENTIAL KEYS LIVE HERE.
|
|
34
|
+
*
|
|
35
|
+
* `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them
|
|
36
|
+
* TRANSFERRED keys, so the inbound transfer copied them off the request into the
|
|
37
|
+
* RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the
|
|
38
|
+
* caller's credential onward — to services that had no business seeing it.
|
|
39
|
+
*
|
|
40
|
+
* A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}
|
|
41
|
+
* by the framework AuthFilter, and written straight onto the outbound request by the client
|
|
42
|
+
* that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the
|
|
43
|
+
* magic context, so nothing can propagate it by accident.
|
|
44
|
+
*
|
|
45
|
+
* An app that genuinely wants a credential to travel can still register its own ContextKey for
|
|
46
|
+
* it — but that is now an explicit, visible decision rather than the default.
|
|
47
47
|
*/
|
|
48
|
-
static readonly SHARED_SECRET: ContextKey;
|
|
49
48
|
/**
|
|
50
49
|
* Get all core context keys as an array (the platform DEFAULT_HEADERS set).
|
|
51
50
|
*/
|
|
@@ -3,15 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WebpiecesCoreHeaders = void 0;
|
|
4
4
|
const ContextKey_1 = require("../ContextKey");
|
|
5
5
|
/**
|
|
6
|
-
* Core framework context keys
|
|
6
|
+
* Core framework context keys — the minimum the WebPieces framework needs to correlate one
|
|
7
|
+
* request across every service it touches, and across every log line each of them writes.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
9
|
+
* ONE id, propagated unchanged. The first service to see a request without an `x-request-id`
|
|
10
|
+
* generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you
|
|
11
|
+
* have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must
|
|
12
|
+
* stitch back together buys nothing a single shared id does not already give you.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* so BOTH the http-client (request-id chaining) and http-server can reference it.
|
|
14
|
+
* Lives in core-util (browser-safe) so both the http clients and http-server can reference it.
|
|
15
15
|
*
|
|
16
16
|
* Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by
|
|
17
17
|
* passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.
|
|
@@ -20,46 +20,44 @@ const ContextKey_1 = require("../ContextKey");
|
|
|
20
20
|
*/
|
|
21
21
|
class WebpiecesCoreHeaders {
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* The id that correlates every hop of one request, and every log line of every hop.
|
|
24
|
+
* Generated by the first service to see a request without one; propagated unchanged after that.
|
|
25
25
|
*/
|
|
26
26
|
static REQUEST_ID = new ContextKey_1.ContextKey('requestId', 'x-request-id');
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
static PREVIOUS_REQUEST_ID = new ContextKey_1.ContextKey('previousId', 'x-previous-request-id');
|
|
32
|
-
/**
|
|
33
|
-
* Correlation ID spanning multiple related requests. Set by the gateway or
|
|
34
|
-
* first service; all hops in the chain share it.
|
|
35
|
-
*/
|
|
36
|
-
static CORRELATION_ID = new ContextKey_1.ContextKey('correlationId', 'x-correlation-id');
|
|
27
|
+
static ORG_ID = new ContextKey_1.ContextKey('orgId', 'x-org-id');
|
|
28
|
+
static USER_ID = new ContextKey_1.ContextKey('userId', 'x-user-id');
|
|
29
|
+
static USER_ROLES = new ContextKey_1.ContextKey('roles', 'x-webpieces-roles');
|
|
37
30
|
/**
|
|
38
31
|
* Turns on test-case recording for this request (Java: x-webpieces-recording).
|
|
39
32
|
* Transferred so recording follows the request across service hops.
|
|
40
33
|
*/
|
|
41
34
|
static RECORDING = new ContextKey_1.ContextKey('recording', 'x-webpieces-recording');
|
|
42
35
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
36
|
+
* NO CREDENTIAL KEYS LIVE HERE.
|
|
37
|
+
*
|
|
38
|
+
* `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them
|
|
39
|
+
* TRANSFERRED keys, so the inbound transfer copied them off the request into the
|
|
40
|
+
* RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the
|
|
41
|
+
* caller's credential onward — to services that had no business seeing it.
|
|
42
|
+
*
|
|
43
|
+
* A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}
|
|
44
|
+
* by the framework AuthFilter, and written straight onto the outbound request by the client
|
|
45
|
+
* that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the
|
|
46
|
+
* magic context, so nothing can propagate it by accident.
|
|
47
|
+
*
|
|
48
|
+
* An app that genuinely wants a credential to travel can still register its own ContextKey for
|
|
49
|
+
* it — but that is now an explicit, visible decision rather than the default.
|
|
50
50
|
*/
|
|
51
|
-
static SHARED_SECRET = new ContextKey_1.ContextKey('sharedSecret', 'x-webpieces-shared-secret', /*isSecured*/ true);
|
|
52
51
|
/**
|
|
53
52
|
* Get all core context keys as an array (the platform DEFAULT_HEADERS set).
|
|
54
53
|
*/
|
|
55
54
|
static getAllHeaders() {
|
|
56
55
|
return [
|
|
57
56
|
WebpiecesCoreHeaders.REQUEST_ID,
|
|
58
|
-
WebpiecesCoreHeaders.
|
|
59
|
-
WebpiecesCoreHeaders.
|
|
57
|
+
WebpiecesCoreHeaders.USER_ID,
|
|
58
|
+
WebpiecesCoreHeaders.ORG_ID,
|
|
59
|
+
WebpiecesCoreHeaders.USER_ROLES,
|
|
60
60
|
WebpiecesCoreHeaders.RECORDING,
|
|
61
|
-
WebpiecesCoreHeaders.AUTHORIZATION,
|
|
62
|
-
WebpiecesCoreHeaders.SHARED_SECRET,
|
|
63
61
|
];
|
|
64
62
|
}
|
|
65
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA2C;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzE
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA2C;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAEzE,MAAM,CAAU,MAAM,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE7D,MAAM,CAAU,OAAO,GAAG,IAAI,uBAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEhE,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAE1E;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,IAAI,uBAAU,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAEjF;;;;;;;;;;;;;;;OAeG;IAEH;;OAEG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO;YACH,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,OAAO;YAC5B,oBAAoB,CAAC,MAAM;YAC3B,oBAAoB,CAAC,UAAU;YAC/B,oBAAoB,CAAC,SAAS;SACjC,CAAC;IACN,CAAC;;AA/CL,oDAgDC","sourcesContent":["import { ContextKey } from '../ContextKey';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n */\n static readonly REQUEST_ID = new ContextKey('requestId', 'x-request-id');\n\n static readonly ORG_ID = new ContextKey('orgId', 'x-org-id');\n\n static readonly USER_ID = new ContextKey('userId', 'x-user-id');\n\n static readonly USER_ROLES = new ContextKey('roles', 'x-webpieces-roles');\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = new ContextKey('recording', 'x-webpieces-recording');\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * Get all core context keys as an array (the platform DEFAULT_HEADERS set).\n */\n static getAllHeaders(): ContextKey[] {\n return [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.RECORDING,\n ];\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -23,10 +23,9 @@ export { ProtocolError, HttpError, HttpNotFoundError, EndpointNotFoundError, Htt
|
|
|
23
23
|
export { InstantDto, DateDto, TimeDto, DateTimeDto, InstantUtil, DateUtil, TimeUtil, DateTimeUtil, } from './http/datetime';
|
|
24
24
|
export { HeaderRegistry } from './http/HeaderRegistry';
|
|
25
25
|
export { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';
|
|
26
|
-
export { HeaderMethods } from './http/HeaderMethods';
|
|
27
26
|
export { ContextReader } from './http/ContextReader';
|
|
27
|
+
export type { ContextRead } from './http/ContextReader';
|
|
28
28
|
export { ContextMgr } from './http/ContextMgr';
|
|
29
|
-
export { RequestIdChainProcessor } from './http/RequestIdChainProcessor';
|
|
30
29
|
export { LogApiCall } from './http/LogApiCall';
|
|
31
30
|
export { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';
|
|
32
31
|
export { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';
|
package/src/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.ENTITY_NOT_FOUND = exports.HttpUserError = exports.HttpVendorError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.Secrets = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.Auth = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = exports.LogManager = exports.ConsoleLoggerFactory = exports.ConsoleLogger = exports.DESIGN_METADATA_KEYS = exports.isDocumentDesign = exports.DocumentDesign = exports.ContextTuple = exports.ContextKey = exports.toError = void 0;
|
|
12
|
-
exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.LogApiCall = exports.
|
|
12
|
+
exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = void 0;
|
|
13
13
|
var errorUtils_1 = require("./lib/errorUtils");
|
|
14
14
|
Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
|
|
15
15
|
var ContextKey_1 = require("./ContextKey");
|
|
@@ -102,16 +102,11 @@ var HeaderRegistry_1 = require("./http/HeaderRegistry");
|
|
|
102
102
|
Object.defineProperty(exports, "HeaderRegistry", { enumerable: true, get: function () { return HeaderRegistry_1.HeaderRegistry; } });
|
|
103
103
|
var WebpiecesCoreHeaders_1 = require("./http/WebpiecesCoreHeaders");
|
|
104
104
|
Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return WebpiecesCoreHeaders_1.WebpiecesCoreHeaders; } });
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
// Browser-safe — the server-side reader (RequestContextReader) and browser store
|
|
109
|
-
// (MutableContextStore) both implement ContextReader, so this lives here rather
|
|
110
|
-
// than in the Node-only @webpieces/core-context (which re-exports for back-compat).
|
|
105
|
+
// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).
|
|
106
|
+
// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via
|
|
107
|
+
// RequestContextHeaders in the Node-only @webpieces/core-context.
|
|
111
108
|
var ContextMgr_1 = require("./http/ContextMgr");
|
|
112
109
|
Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function () { return ContextMgr_1.ContextMgr; } });
|
|
113
|
-
var RequestIdChainProcessor_1 = require("./http/RequestIdChainProcessor");
|
|
114
|
-
Object.defineProperty(exports, "RequestIdChainProcessor", { enumerable: true, get: function () { return RequestIdChainProcessor_1.RequestIdChainProcessor; } });
|
|
115
110
|
// API-call logging helper (uses LogManager above)
|
|
116
111
|
var LogApiCall_1 = require("./http/LogApiCall");
|
|
117
112
|
Object.defineProperty(exports, "LogApiCall", { enumerable: true, get: function () { return LogApiCall_1.LogApiCall; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;AAEH,+CAA2C;AAAlC,qGAAA,OAAO,OAAA;AAChB,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+EAA+E;AAC/E,kFAAkF;AAClF,yCAAyC;AACzC,mDAA0F;AAAjF,gHAAA,cAAc,OAAA;AAAE,kHAAA,gBAAgB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAO/D,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,mDAAkD;AAAzC,wGAAA,UAAU,OAAA;AAEnB,8DAA8D;AAC9D,sEAAsE;AACtE,sEAAsE;AACtE,uEAAuE;AACvE,kEAAkE;AAElE,4BAA4B;AAC5B,gDA6B2B;AA5BvB,qGAAA,OAAO,OAAA;AACP,sGAAA,QAAQ,OAAA;AACR,4GAAA,cAAc,OAAA;AACd,kHAAA,oBAAoB,OAAA;AACpB,mEAAmE;AACnE,oGAAA,MAAM,OAAA;AACN,qGAAA,OAAO,OAAA;AACP,kGAAA,IAAI,OAAA;AACJ,sGAAA,QAAQ,OAAA;AACR,8GAAA,gBAAgB,OAAA;AAChB,sDAAsD;AACtD,iGAAA,GAAG,OAAA;AACH,oGAAA,MAAM,OAAA;AACN,mGAAA,KAAK,OAAA;AACL,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,uGAAA,SAAS,OAAA;AACT,yGAAA,WAAW,OAAA;AACX,yGAAA,WAAW,OAAA;AACX,4HAAA,8BAA8B,OAAA;AAC9B,wGAAA,UAAU,OAAA;AACV,2GAAA,aAAa,OAAA;AACb,qHAAA,uBAAuB,OAAA;AACvB,0GAAA,YAAY,OAAA;AACZ,6HAAA,+BAA+B,OAAA;AAC/B,sGAAA,QAAQ,OAAA;AACR,2GAAA,aAAa,OAAA;AACb,2GAAA,aAAa,OAAA;AAGjB,4FAA4F;AAC5F,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAKhB,cAAc;AACd,wCAuBuB;AAtBnB,uGAAA,aAAa,OAAA;AACb,mGAAA,SAAS,OAAA;AACT,2GAAA,iBAAiB,OAAA;AACjB,+GAAA,qBAAqB,OAAA;AACrB,6GAAA,mBAAmB,OAAA;AACnB,+GAAA,qBAAqB,OAAA;AACrB,4GAAA,kBAAkB,OAAA;AAClB,0GAAA,gBAAgB,OAAA;AAChB,6GAAA,mBAAmB,OAAA;AACnB,iHAAA,uBAAuB,OAAA;AACvB,iHAAA,uBAAuB,OAAA;AACvB,yGAAA,eAAe,OAAA;AACf,uGAAA,aAAa,OAAA;AACb,0BAA0B;AAC1B,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,sGAAA,YAAY,OAAA;AACZ,6GAAA,mBAAmB,OAAA;AACnB,sGAAA,YAAY,OAAA;AACZ,uGAAA,aAAa,OAAA;AACb,qGAAA,WAAW,OAAA;AAGf,iEAAiE;AACjE,4CASyB;AAJrB,uGAAA,WAAW,OAAA;AACX,oGAAA,QAAQ,OAAA;AACR,oGAAA,QAAQ,OAAA;AACR,wGAAA,YAAY,OAAA;AAGhB,mEAAmE;AACnE,wDAAuD;AAA9C,gHAAA,cAAc,OAAA;AACvB,oEAAmE;AAA1D,4HAAA,oBAAoB,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the HttpError hierarchy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, JwtRequirement } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpVendorError,\n HttpUserError,\n // Error subtype constants\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './http/errors';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\nexport type { ContextRead } from './http/ContextReader';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above)\nexport { LogApiCall } from './http/LogApiCall';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n"]}
|
|
@@ -7,6 +7,10 @@ import { Logger } from './Logger';
|
|
|
7
7
|
* name so multi-source logs stay greppable. An optional `Error` is forwarded to
|
|
8
8
|
* `console.*` as a second argument so its stack trace is rendered.
|
|
9
9
|
*
|
|
10
|
+
* It carries NO context fields (requestId, tenantId, …) — deliberately. A real logging backend
|
|
11
|
+
* (bunyan/winston) reads them off the RequestContext on every record; the bootstrap console
|
|
12
|
+
* cannot, because core-util is browser-safe and has no AsyncLocalStorage.
|
|
13
|
+
*
|
|
10
14
|
* Level → console method mapping:
|
|
11
15
|
* - trace/debug → console.debug
|
|
12
16
|
* - info → console.log (stdout, matching conventional server logging)
|
|
@@ -22,6 +26,12 @@ export declare class ConsoleLogger implements Logger {
|
|
|
22
26
|
* been installed (see {@link ConsoleLoggerFactory}); empty once one is.
|
|
23
27
|
*/
|
|
24
28
|
constructor(name: string, bootstrapPrefix?: string);
|
|
29
|
+
/**
|
|
30
|
+
* Complain ONCE, at error level, that no logging backend was installed — a banner on every
|
|
31
|
+
* line is easy to scroll past, and an app running on the bootstrap console silently loses
|
|
32
|
+
* structured context fields and its GCP log payload. Every subsequent line keeps the banner.
|
|
33
|
+
*/
|
|
34
|
+
private reportMissingFactoryOnce;
|
|
25
35
|
private prefix;
|
|
26
36
|
private errArg;
|
|
27
37
|
trace(message: string, err?: Error): void;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConsoleLogger = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Latch so the "you never called setFactory" complaint is emitted exactly ONCE per process,
|
|
6
|
+
* no matter how many bootstrap loggers or lines there are. Mirrors RequestContext's
|
|
7
|
+
* reportedMissingContext latch.
|
|
8
|
+
*/
|
|
9
|
+
let reportedMissingFactory = false;
|
|
4
10
|
/**
|
|
5
11
|
* ConsoleLogger - the default, browser-safe {@link Logger} implementation.
|
|
6
12
|
*
|
|
@@ -9,6 +15,10 @@ exports.ConsoleLogger = void 0;
|
|
|
9
15
|
* name so multi-source logs stay greppable. An optional `Error` is forwarded to
|
|
10
16
|
* `console.*` as a second argument so its stack trace is rendered.
|
|
11
17
|
*
|
|
18
|
+
* It carries NO context fields (requestId, tenantId, …) — deliberately. A real logging backend
|
|
19
|
+
* (bunyan/winston) reads them off the RequestContext on every record; the bootstrap console
|
|
20
|
+
* cannot, because core-util is browser-safe and has no AsyncLocalStorage.
|
|
21
|
+
*
|
|
12
22
|
* Level → console method mapping:
|
|
13
23
|
* - trace/debug → console.debug
|
|
14
24
|
* - info → console.log (stdout, matching conventional server logging)
|
|
@@ -27,7 +37,22 @@ class ConsoleLogger {
|
|
|
27
37
|
this.name = name;
|
|
28
38
|
this.bootstrapPrefix = bootstrapPrefix;
|
|
29
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Complain ONCE, at error level, that no logging backend was installed — a banner on every
|
|
42
|
+
* line is easy to scroll past, and an app running on the bootstrap console silently loses
|
|
43
|
+
* structured context fields and its GCP log payload. Every subsequent line keeps the banner.
|
|
44
|
+
*/
|
|
45
|
+
reportMissingFactoryOnce() {
|
|
46
|
+
if (!this.bootstrapPrefix || reportedMissingFactory) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
reportedMissingFactory = true; // set BEFORE logging: console.error must not re-enter
|
|
50
|
+
console.error('No logging backend installed. Call LogManager.setFactory(...) at startup — ' +
|
|
51
|
+
'see .webpieces/instruct-ai/webpieces.logging.md. Until then every line goes to the ' +
|
|
52
|
+
'bootstrap console with NO context fields (requestId, tenantId, …) and no structured payload.');
|
|
53
|
+
}
|
|
30
54
|
prefix() {
|
|
55
|
+
this.reportMissingFactoryOnce();
|
|
31
56
|
return `${this.bootstrapPrefix}[${this.name}]`;
|
|
32
57
|
}
|
|
33
58
|
errArg(err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsoleLogger.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/logging/ConsoleLogger.ts"],"names":[],"mappings":";;;AAEA
|
|
1
|
+
{"version":3,"file":"ConsoleLogger.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/logging/ConsoleLogger.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,IAAI,sBAAsB,GAAG,KAAK,CAAC;AAEnC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,aAAa;IACL,IAAI,CAAS;IACb,eAAe,CAAS;IAEzC;;;;OAIG;IACH,YAAY,IAAY,EAAE,eAAe,GAAG,EAAE;QAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACK,wBAAwB;QAC5B,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,sBAAsB,EAAE,CAAC;YAClD,OAAO;QACX,CAAC;QACD,sBAAsB,GAAG,IAAI,CAAC,CAAC,sDAAsD;QACrF,OAAO,CAAC,KAAK,CACT,6EAA6E;YAC7E,qFAAqF;YACrF,8FAA8F,CACjG,CAAC;IACN,CAAC;IAEO,MAAM;QACV,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAChC,OAAO,GAAG,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC;IACnD,CAAC;IAEO,MAAM,CAAC,GAAW;QACtB,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAW;QAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,CAAC;CACJ;AA3DD,sCA2DC","sourcesContent":["import { Logger } from './Logger';\n\n/**\n * Latch so the \"you never called setFactory\" complaint is emitted exactly ONCE per process,\n * no matter how many bootstrap loggers or lines there are. Mirrors RequestContext's\n * reportedMissingContext latch.\n */\nlet reportedMissingFactory = false;\n\n/**\n * ConsoleLogger - the default, browser-safe {@link Logger} implementation.\n *\n * Backed purely by `console.*` (no Node imports), so it works unchanged in the\n * browser (Angular/React) and in Node. Each line is prefixed with the logger\n * name so multi-source logs stay greppable. An optional `Error` is forwarded to\n * `console.*` as a second argument so its stack trace is rendered.\n *\n * It carries NO context fields (requestId, tenantId, …) — deliberately. A real logging backend\n * (bunyan/winston) reads them off the RequestContext on every record; the bootstrap console\n * cannot, because core-util is browser-safe and has no AsyncLocalStorage.\n *\n * Level → console method mapping:\n * - trace/debug → console.debug\n * - info → console.log (stdout, matching conventional server logging)\n * - warn → console.warn\n * - error → console.error\n */\nexport class ConsoleLogger implements Logger {\n private readonly name: string;\n private readonly bootstrapPrefix: string;\n\n /**\n * @param name logger name (slf4j-style; usually the class/module)\n * @param bootstrapPrefix prepended to every line while no real backend has\n * been installed (see {@link ConsoleLoggerFactory}); empty once one is.\n */\n constructor(name: string, bootstrapPrefix = '') {\n this.name = name;\n this.bootstrapPrefix = bootstrapPrefix;\n }\n\n /**\n * Complain ONCE, at error level, that no logging backend was installed — a banner on every\n * line is easy to scroll past, and an app running on the bootstrap console silently loses\n * structured context fields and its GCP log payload. Every subsequent line keeps the banner.\n */\n private reportMissingFactoryOnce(): void {\n if (!this.bootstrapPrefix || reportedMissingFactory) {\n return;\n }\n reportedMissingFactory = true; // set BEFORE logging: console.error must not re-enter\n console.error(\n 'No logging backend installed. Call LogManager.setFactory(...) at startup — ' +\n 'see .webpieces/instruct-ai/webpieces.logging.md. Until then every line goes to the ' +\n 'bootstrap console with NO context fields (requestId, tenantId, …) and no structured payload.',\n );\n }\n\n private prefix(): string {\n this.reportMissingFactoryOnce();\n return `${this.bootstrapPrefix}[${this.name}]`;\n }\n\n private errArg(err?: Error): Error[] {\n return err ? [err] : [];\n }\n\n trace(message: string, err?: Error): void {\n console.debug(`${this.prefix()} ${message}`, ...this.errArg(err));\n }\n\n debug(message: string, err?: Error): void {\n console.debug(`${this.prefix()} ${message}`, ...this.errArg(err));\n }\n\n info(message: string, err?: Error): void {\n console.log(`${this.prefix()} ${message}`, ...this.errArg(err));\n }\n\n warn(message: string, err?: Error): void {\n console.warn(`${this.prefix()} ${message}`, ...this.errArg(err));\n }\n\n error(message: string, err?: Error): void {\n console.error(`${this.prefix()} ${message}`, ...this.errArg(err));\n }\n}\n"]}
|
package/src/logging/Logger.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
|
21
21
|
* There is deliberately no structured-fields / varargs argument — so nobody can
|
|
22
22
|
* pass request ids, tenant ids, or other platform-header values into a log line.
|
|
23
23
|
* Those are already emitted automatically by the framework (see
|
|
24
|
-
* `
|
|
24
|
+
* `HeaderRegistry.buildLogFields`); duplicating them here is impossible.
|
|
25
25
|
* See `.webpieces/instruct-ai/webpieces.logging.md`.
|
|
26
26
|
*/
|
|
27
27
|
export interface Logger {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/logging/Logger.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Log severity levels, ordered lowest → highest.\n *\n * Mirrors the common slf4j/bunyan/winston vocabulary so any of those backends\n * can be plugged in behind the {@link Logger} interface.\n */\nexport type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Logger - the pluggable logging contract for WebPieces.\n *\n * This is a BUSINESS-LOGIC interface (methods with behavior), so per the\n * webpieces guidelines it is an `interface`, not a class. Different projects\n * plug in different backends (bunyan, winston, pino, browser console, a\n * file writer, ...) by supplying an implementation via a {@link LoggerFactory}.\n *\n * Implementations MUST stay browser-safe if they are to be used from Angular /\n * React. Node-only backends (bunyan, file writers, ...) are wired in by\n * `framework:express` apps at startup, never in browser-safe libraries.\n *\n * KISS by design: every method takes ONLY a message plus an OPTIONAL `Error`.\n * There is deliberately no structured-fields / varargs argument — so nobody can\n * pass request ids, tenant ids, or other platform-header values into a log line.\n * Those are already emitted automatically by the framework (see\n * `
|
|
1
|
+
{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/logging/Logger.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Log severity levels, ordered lowest → highest.\n *\n * Mirrors the common slf4j/bunyan/winston vocabulary so any of those backends\n * can be plugged in behind the {@link Logger} interface.\n */\nexport type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Logger - the pluggable logging contract for WebPieces.\n *\n * This is a BUSINESS-LOGIC interface (methods with behavior), so per the\n * webpieces guidelines it is an `interface`, not a class. Different projects\n * plug in different backends (bunyan, winston, pino, browser console, a\n * file writer, ...) by supplying an implementation via a {@link LoggerFactory}.\n *\n * Implementations MUST stay browser-safe if they are to be used from Angular /\n * React. Node-only backends (bunyan, file writers, ...) are wired in by\n * `framework:express` apps at startup, never in browser-safe libraries.\n *\n * KISS by design: every method takes ONLY a message plus an OPTIONAL `Error`.\n * There is deliberately no structured-fields / varargs argument — so nobody can\n * pass request ids, tenant ids, or other platform-header values into a log line.\n * Those are already emitted automatically by the framework (see\n * `HeaderRegistry.buildLogFields`); duplicating them here is impossible.\n * See `.webpieces/instruct-ai/webpieces.logging.md`.\n */\nexport interface Logger {\n trace(message: string, err?: Error): void;\n debug(message: string, err?: Error): void;\n info(message: string, err?: Error): void;\n warn(message: string, err?: Error): void;\n error(message: string, err?: Error): void;\n}\n"]}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { ContextKey } from '../ContextKey';
|
|
2
|
-
import { ContextReader } from './ContextReader';
|
|
3
|
-
/**
|
|
4
|
-
* HeaderMethods - stateless utility for turning context keys + a ContextReader into
|
|
5
|
-
* the maps the framework needs (outbound transfer, masked log map).
|
|
6
|
-
*
|
|
7
|
-
* Works in both server (Node) and browser environments; a plain `new HeaderMethods()`
|
|
8
|
-
* (no DI). The set of keys is supplied by the caller (from HeaderRegistry).
|
|
9
|
-
*/
|
|
10
|
-
export declare class HeaderMethods {
|
|
11
|
-
/** Keys that transfer over the wire (httpHeader set). */
|
|
12
|
-
findTransferKeys(keys: ContextKey[]): ContextKey[];
|
|
13
|
-
/** Keys whose values are masked in logs (isSecured=true). */
|
|
14
|
-
securedKeys(keys: ContextKey[]): ContextKey[];
|
|
15
|
-
/**
|
|
16
|
-
* Build the map for LOGGING from the given keys: each logged key (isLogged=true)
|
|
17
|
-
* with a value present is added under its `name`, masked when isSecured.
|
|
18
|
-
*/
|
|
19
|
-
buildSecureMapForLogs(keys: ContextKey[], contextReader: ContextReader): Map<string, string>;
|
|
20
|
-
/**
|
|
21
|
-
* Mask a secure value based on its length.
|
|
22
|
-
* - Length > 15: first 3 + "..." + last 3
|
|
23
|
-
* - Length 8-15: first 2 + "..."
|
|
24
|
-
* - Length < 8: "<secure key too short to log>"
|
|
25
|
-
*/
|
|
26
|
-
private maskSecureValue;
|
|
27
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HeaderMethods = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* HeaderMethods - stateless utility for turning context keys + a ContextReader into
|
|
6
|
-
* the maps the framework needs (outbound transfer, masked log map).
|
|
7
|
-
*
|
|
8
|
-
* Works in both server (Node) and browser environments; a plain `new HeaderMethods()`
|
|
9
|
-
* (no DI). The set of keys is supplied by the caller (from HeaderRegistry).
|
|
10
|
-
*/
|
|
11
|
-
class HeaderMethods {
|
|
12
|
-
/** Keys that transfer over the wire (httpHeader set). */
|
|
13
|
-
findTransferKeys(keys) {
|
|
14
|
-
return keys.filter(k => k.httpHeader !== undefined);
|
|
15
|
-
}
|
|
16
|
-
/** Keys whose values are masked in logs (isSecured=true). */
|
|
17
|
-
securedKeys(keys) {
|
|
18
|
-
return keys.filter(k => k.isSecured);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Build the map for LOGGING from the given keys: each logged key (isLogged=true)
|
|
22
|
-
* with a value present is added under its `name`, masked when isSecured.
|
|
23
|
-
*/
|
|
24
|
-
buildSecureMapForLogs(keys, contextReader) {
|
|
25
|
-
const logMap = new Map();
|
|
26
|
-
for (const key of keys) {
|
|
27
|
-
if (!key.isLogged) {
|
|
28
|
-
continue; // never logged (e.g. recorder, method-meta)
|
|
29
|
-
}
|
|
30
|
-
const value = contextReader.read(key);
|
|
31
|
-
if (value) {
|
|
32
|
-
logMap.set(key.name, key.isSecured ? this.maskSecureValue(value) : value);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return logMap;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Mask a secure value based on its length.
|
|
39
|
-
* - Length > 15: first 3 + "..." + last 3
|
|
40
|
-
* - Length 8-15: first 2 + "..."
|
|
41
|
-
* - Length < 8: "<secure key too short to log>"
|
|
42
|
-
*/
|
|
43
|
-
maskSecureValue(value) {
|
|
44
|
-
const len = value.length;
|
|
45
|
-
if (len < 8) {
|
|
46
|
-
return '<secure key too short to log>';
|
|
47
|
-
}
|
|
48
|
-
else if (len <= 15) {
|
|
49
|
-
return `${value.substring(0, 2)}...`;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
return `${value.substring(0, 3)}...${value.substring(len - 3)}`;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
exports.HeaderMethods = HeaderMethods;
|
|
57
|
-
//# sourceMappingURL=HeaderMethods.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderMethods.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderMethods.ts"],"names":[],"mappings":";;;AAGA;;;;;;GAMG;AACH,MAAa,aAAa;IACtB,yDAAyD;IACzD,gBAAgB,CAAC,IAAkB;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC;IACxD,CAAC;IAED,6DAA6D;IAC7D,WAAW,CAAC,IAAkB;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,qBAAqB,CAAC,IAAkB,EAAE,aAA4B;QAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QAEzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAChB,SAAS,CAAC,4CAA4C;YAC1D,CAAC;YACD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,KAAK,EAAE,CAAC;gBACR,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC9E,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;;;OAKG;IACK,eAAe,CAAC,KAAa;QACjC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;QAEzB,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACV,OAAO,+BAA+B,CAAC;QAC3C,CAAC;aAAM,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;YACnB,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;QACzC,CAAC;aAAM,CAAC;YACJ,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACpE,CAAC;IACL,CAAC;CACJ;AAhDD,sCAgDC","sourcesContent":["import { ContextKey } from '../ContextKey';\nimport { ContextReader } from './ContextReader';\n\n/**\n * HeaderMethods - stateless utility for turning context keys + a ContextReader into\n * the maps the framework needs (outbound transfer, masked log map).\n *\n * Works in both server (Node) and browser environments; a plain `new HeaderMethods()`\n * (no DI). The set of keys is supplied by the caller (from HeaderRegistry).\n */\nexport class HeaderMethods {\n /** Keys that transfer over the wire (httpHeader set). */\n findTransferKeys(keys: ContextKey[]): ContextKey[] {\n return keys.filter(k => k.httpHeader !== undefined);\n }\n\n /** Keys whose values are masked in logs (isSecured=true). */\n securedKeys(keys: ContextKey[]): ContextKey[] {\n return keys.filter(k => k.isSecured);\n }\n\n /**\n * Build the map for LOGGING from the given keys: each logged key (isLogged=true)\n * with a value present is added under its `name`, masked when isSecured.\n */\n buildSecureMapForLogs(keys: ContextKey[], contextReader: ContextReader): Map<string, string> {\n const logMap = new Map<string, string>();\n\n for (const key of keys) {\n if (!key.isLogged) {\n continue; // never logged (e.g. recorder, method-meta)\n }\n const value = contextReader.read(key);\n if (value) {\n logMap.set(key.name, key.isSecured ? this.maskSecureValue(value) : value);\n }\n }\n\n return logMap;\n }\n\n /**\n * Mask a secure value based on its length.\n * - Length > 15: first 3 + \"...\" + last 3\n * - Length 8-15: first 2 + \"...\"\n * - Length < 8: \"<secure key too short to log>\"\n */\n private maskSecureValue(value: string): string {\n const len = value.length;\n\n if (len < 8) {\n return '<secure key too short to log>';\n } else if (len <= 15) {\n return `${value.substring(0, 2)}...`;\n } else {\n return `${value.substring(0, 3)}...${value.substring(len - 3)}`;\n }\n }\n}\n"]}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RequestIdChainProcessor - Builds the per-hop distributed-trace chain.
|
|
3
|
-
*
|
|
4
|
-
* TS equivalent of the Java MicroSvcHeader REQUEST_ID/PREVIOUS_REQUEST_ID flow:
|
|
5
|
-
* when a server makes an outbound call, its CURRENT request id is sent to the
|
|
6
|
-
* downstream service as x-previous-request-id, and x-request-id is NOT sent -
|
|
7
|
-
* the downstream ContextFilter then generates a fresh id for its own hop.
|
|
8
|
-
* Result: every hop has its own id plus a pointer to its caller's id, forming
|
|
9
|
-
* a trace tree.
|
|
10
|
-
*
|
|
11
|
-
* Invoked by ContextMgr.buildOutboundHeaders() after the transferred headers
|
|
12
|
-
* are collected. Opt out via `new ContextMgr(reader, registry, false)` if you
|
|
13
|
-
* want raw pass-through of x-request-id instead.
|
|
14
|
-
*/
|
|
15
|
-
export declare class RequestIdChainProcessor {
|
|
16
|
-
/**
|
|
17
|
-
* Rewrite the outbound header map in place: x-request-id -> x-previous-request-id.
|
|
18
|
-
*/
|
|
19
|
-
process(outboundHeaders: Map<string, string>): void;
|
|
20
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RequestIdChainProcessor = void 0;
|
|
4
|
-
const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
|
|
5
|
-
/**
|
|
6
|
-
* RequestIdChainProcessor - Builds the per-hop distributed-trace chain.
|
|
7
|
-
*
|
|
8
|
-
* TS equivalent of the Java MicroSvcHeader REQUEST_ID/PREVIOUS_REQUEST_ID flow:
|
|
9
|
-
* when a server makes an outbound call, its CURRENT request id is sent to the
|
|
10
|
-
* downstream service as x-previous-request-id, and x-request-id is NOT sent -
|
|
11
|
-
* the downstream ContextFilter then generates a fresh id for its own hop.
|
|
12
|
-
* Result: every hop has its own id plus a pointer to its caller's id, forming
|
|
13
|
-
* a trace tree.
|
|
14
|
-
*
|
|
15
|
-
* Invoked by ContextMgr.buildOutboundHeaders() after the transferred headers
|
|
16
|
-
* are collected. Opt out via `new ContextMgr(reader, registry, false)` if you
|
|
17
|
-
* want raw pass-through of x-request-id instead.
|
|
18
|
-
*/
|
|
19
|
-
class RequestIdChainProcessor {
|
|
20
|
-
/**
|
|
21
|
-
* Rewrite the outbound header map in place: x-request-id -> x-previous-request-id.
|
|
22
|
-
*/
|
|
23
|
-
process(outboundHeaders) {
|
|
24
|
-
// The outbound map is keyed by wire (HTTP header) name.
|
|
25
|
-
const requestIdName = WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.REQUEST_ID.httpHeader;
|
|
26
|
-
const previousIdName = WebpiecesCoreHeaders_1.WebpiecesCoreHeaders.PREVIOUS_REQUEST_ID.httpHeader;
|
|
27
|
-
const currentRequestId = outboundHeaders.get(requestIdName);
|
|
28
|
-
if (currentRequestId === undefined) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
outboundHeaders.set(previousIdName, currentRequestId);
|
|
32
|
-
outboundHeaders.delete(requestIdName);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
exports.RequestIdChainProcessor = RequestIdChainProcessor;
|
|
36
|
-
//# sourceMappingURL=RequestIdChainProcessor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RequestIdChainProcessor.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RequestIdChainProcessor.ts"],"names":[],"mappings":";;;AAAA,iEAA8D;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAa,uBAAuB;IAChC;;OAEG;IACH,OAAO,CAAC,eAAoC;QACxC,wDAAwD;QACxD,MAAM,aAAa,GAAG,2CAAoB,CAAC,UAAU,CAAC,UAAW,CAAC;QAClE,MAAM,cAAc,GAAG,2CAAoB,CAAC,mBAAmB,CAAC,UAAW,CAAC;QAE5E,MAAM,gBAAgB,GAAG,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC5D,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO;QACX,CAAC;QAED,eAAe,CAAC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACtD,eAAe,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC;CACJ;AAjBD,0DAiBC","sourcesContent":["import { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * RequestIdChainProcessor - Builds the per-hop distributed-trace chain.\n *\n * TS equivalent of the Java MicroSvcHeader REQUEST_ID/PREVIOUS_REQUEST_ID flow:\n * when a server makes an outbound call, its CURRENT request id is sent to the\n * downstream service as x-previous-request-id, and x-request-id is NOT sent -\n * the downstream ContextFilter then generates a fresh id for its own hop.\n * Result: every hop has its own id plus a pointer to its caller's id, forming\n * a trace tree.\n *\n * Invoked by ContextMgr.buildOutboundHeaders() after the transferred headers\n * are collected. Opt out via `new ContextMgr(reader, registry, false)` if you\n * want raw pass-through of x-request-id instead.\n */\nexport class RequestIdChainProcessor {\n /**\n * Rewrite the outbound header map in place: x-request-id -> x-previous-request-id.\n */\n process(outboundHeaders: Map<string, string>): void {\n // The outbound map is keyed by wire (HTTP header) name.\n const requestIdName = WebpiecesCoreHeaders.REQUEST_ID.httpHeader!;\n const previousIdName = WebpiecesCoreHeaders.PREVIOUS_REQUEST_ID.httpHeader!;\n\n const currentRequestId = outboundHeaders.get(requestIdName);\n if (currentRequestId === undefined) {\n return;\n }\n\n outboundHeaders.set(previousIdName, currentRequestId);\n outboundHeaders.delete(requestIdName);\n }\n}\n"]}
|