@webpieces/core-util 0.4.709 → 0.4.711
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/http/ApiCallContext.d.ts +17 -29
- package/src/http/ApiCallContext.js +0 -36
- package/src/http/ApiCallContext.js.map +1 -1
- package/src/http/ApiCallInfo.d.ts +4 -4
- package/src/http/ApiCallInfo.js +1 -1
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ApiCallLogName.d.ts +4 -4
- package/src/http/ApiCallLogName.js +4 -4
- package/src/http/ApiCallLogName.js.map +1 -1
- package/src/http/ApiMethodInfo.d.ts +3 -3
- package/src/http/ApiMethodInfo.js +2 -2
- package/src/http/ApiMethodInfo.js.map +1 -1
- package/src/http/LogApiCall.d.ts +23 -13
- package/src/http/LogApiCall.js +30 -18
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/LogFieldMask.d.ts +1 -1
- package/src/http/LogFieldMask.js +1 -1
- package/src/http/LogFieldMask.js.map +1 -1
- package/src/http/RouteMetadata.d.ts +1 -1
- package/src/http/RouteMetadata.js +1 -1
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RuntimeLocality.d.ts +3 -2
- package/src/http/RuntimeLocality.js +3 -2
- package/src/http/RuntimeLocality.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +1 -1
- package/src/http/WebpiecesCoreHeaders.js +1 -1
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/http/decorators.d.ts +1 -1
- package/src/http/decorators.js +1 -1
- package/src/http/decorators.js.map +1 -1
- package/src/index.d.ts +1 -2
- package/src/index.js +6 -6
- package/src/index.js.map +1 -1
- package/src/logging/LogChunker.d.ts +2 -2
- package/src/logging/LogChunker.js +2 -2
- package/src/logging/LogChunker.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { AnyUntrustedContextKey } from '../ContextKey';
|
|
2
2
|
/**
|
|
3
|
-
* ApiCallContext - the tiny seam that lets {@link
|
|
3
|
+
* ApiCallContext - the tiny seam that lets {@link LogApiCallImpl} (browser-safe, core-util) stamp a
|
|
4
4
|
* ContextKey (the `api` tag) into the ambient request context WITHOUT importing it.
|
|
5
5
|
*
|
|
6
6
|
* WHY a seam instead of a direct call: the ambient context is `RequestContext` in
|
|
7
7
|
* `@webpieces/core-context`, which is built on Node `async_hooks` (AsyncLocalStorage). core-util —
|
|
8
8
|
* and `ProxyClient`, which runs in a BROWSER bundle — must never import that (it would be a circular
|
|
9
9
|
* dependency, and it would drag Node vocabulary into a browser build). So core-util owns only this
|
|
10
|
-
* interface
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
10
|
+
* interface, and each environment-specific package CONSTRUCTS its own impl and hands it to
|
|
11
|
+
* {@link LogApiCallImpl}'s constructor:
|
|
12
|
+
* - Node server inbound: `LogApiFilter` (http-routing) → `RequestContextApiCallContext`.
|
|
13
|
+
* - Node client outbound: `NodeProxyClient` (http-client-node) and `TaskProxyClient`
|
|
14
|
+
* (cloudtasks-client) → `RequestContextApiCallContext`.
|
|
15
|
+
* - Browser: `BrowserProxyClient` (http-client-browser) → `BrowserApiCallContext`.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* There is deliberately NO process-global holder and no startup install: the context is a REQUIRED
|
|
18
|
+
* constructor argument, so "nobody set it up" is a compile error rather than a runtime throw on the
|
|
19
|
+
* first real call. That is what lets a plain NestJS/Express host use `@webpieces/http-client-node` or
|
|
20
|
+
* `@webpieces/cloudtasks-client` with no webpieces STARTUP INSTALL — it only has to run the call
|
|
21
|
+
* inside a `RequestContext.run(...)` scope, which such a host already opens per request. (The other
|
|
22
|
+
* process-globals those clients read — HeaderRegistry, LogManager, a ClientRegistry mapping or
|
|
23
|
+
* deriver — are unchanged by this and still apply.)
|
|
16
24
|
*
|
|
17
|
-
*
|
|
18
|
-
* HeaderRegistry): behavior is an interface (per CLAUDE.md), the holder is the global seam.
|
|
25
|
+
* Per CLAUDE.md this is behavior, hence an interface; the impls are ordinary classes.
|
|
19
26
|
*/
|
|
20
27
|
export interface ApiCallContext {
|
|
21
28
|
/**
|
|
22
29
|
* True when there is a context to stamp into (a live Node RequestContext scope; a browser is always
|
|
23
|
-
* active). {@link
|
|
30
|
+
* active). {@link LogApiCallImpl} throws if this is false — an api call with nowhere to tag is a bug.
|
|
24
31
|
*/
|
|
25
32
|
isActive(): boolean;
|
|
26
33
|
/**
|
|
@@ -33,28 +40,9 @@ export interface ApiCallContext {
|
|
|
33
40
|
*/
|
|
34
41
|
set(contextKey: AnyUntrustedContextKey, value: unknown): void;
|
|
35
42
|
/**
|
|
36
|
-
* Clear one ContextKey. {@link
|
|
43
|
+
* Clear one ContextKey. {@link LogApiCallImpl} calls set → log → remove as one SYNCHRONOUS span, so the
|
|
37
44
|
* tag is never held across `await`. That is what makes a single browser global safe: single-threaded,
|
|
38
45
|
* nothing can interleave between set and remove, so a concurrent call can never clobber the slot.
|
|
39
46
|
*/
|
|
40
47
|
remove(contextKey: AnyUntrustedContextKey): void;
|
|
41
48
|
}
|
|
42
|
-
/**
|
|
43
|
-
* ApiCallContextHolder - the process-wide holder for the active {@link ApiCallContext}.
|
|
44
|
-
*
|
|
45
|
-
* Configured exactly like {@link LogManager}: the environment calls {@link ApiCallContextHolder.install}
|
|
46
|
-
* at startup. Until then {@link get} THROWS, so a forgotten setup fails loudly rather than silently
|
|
47
|
-
* dropping the `api` tag off every log line.
|
|
48
|
-
*/
|
|
49
|
-
export declare class ApiCallContextHolder {
|
|
50
|
-
private static current;
|
|
51
|
-
/** Install the environment's ApiCallContext (Node: RequestContext-backed; browser: module-global). */
|
|
52
|
-
static install(ctx: ApiCallContext): void;
|
|
53
|
-
/** True once an ApiCallContext has been installed (used by tests to probe the unset state). */
|
|
54
|
-
static isInstalled(): boolean;
|
|
55
|
-
/**
|
|
56
|
-
* The active ApiCallContext. Throws if nothing was installed — a one-time setup call is required:
|
|
57
|
-
* `setupRuntime()` does it on a Node server; building `ClientHttpBrowserFactory` does it in a browser.
|
|
58
|
-
*/
|
|
59
|
-
static get(): ApiCallContext;
|
|
60
|
-
}
|
|
@@ -1,39 +1,3 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ApiCallContextHolder = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* ApiCallContextHolder - the process-wide holder for the active {@link ApiCallContext}.
|
|
6
|
-
*
|
|
7
|
-
* Configured exactly like {@link LogManager}: the environment calls {@link ApiCallContextHolder.install}
|
|
8
|
-
* at startup. Until then {@link get} THROWS, so a forgotten setup fails loudly rather than silently
|
|
9
|
-
* dropping the `api` tag off every log line.
|
|
10
|
-
*/
|
|
11
|
-
class ApiCallContextHolder {
|
|
12
|
-
static current;
|
|
13
|
-
/** Install the environment's ApiCallContext (Node: RequestContext-backed; browser: module-global). */
|
|
14
|
-
// webpieces-disable no-function-outside-class -- static global seam, configured once at startup (like LogManager.setFactory)
|
|
15
|
-
static install(ctx) {
|
|
16
|
-
ApiCallContextHolder.current = ctx;
|
|
17
|
-
}
|
|
18
|
-
/** True once an ApiCallContext has been installed (used by tests to probe the unset state). */
|
|
19
|
-
// webpieces-disable no-function-outside-class -- static global seam accessor (like HeaderRegistry.isConfigured)
|
|
20
|
-
static isInstalled() {
|
|
21
|
-
return ApiCallContextHolder.current !== undefined;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* The active ApiCallContext. Throws if nothing was installed — a one-time setup call is required:
|
|
25
|
-
* `setupRuntime()` does it on a Node server; building `ClientHttpBrowserFactory` does it in a browser.
|
|
26
|
-
*/
|
|
27
|
-
// webpieces-disable no-function-outside-class -- static global seam accessor (like LogManager/HeaderRegistry.get), not DI-injected
|
|
28
|
-
static get() {
|
|
29
|
-
if (!ApiCallContextHolder.current) {
|
|
30
|
-
throw new Error('ApiCallContext is not installed — LogApiCall cannot tag API-call logs. Set it up ONCE ' +
|
|
31
|
-
'at startup: on a Node server, setupRuntime() installs it for you; in a browser, construct ' +
|
|
32
|
-
'ClientHttpBrowserFactory once at startup. (This is the same one-time setup as ' +
|
|
33
|
-
'HeaderRegistry.configure / LogManager.setFactory.)');
|
|
34
|
-
}
|
|
35
|
-
return ApiCallContextHolder.current;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
exports.ApiCallContextHolder = ApiCallContextHolder;
|
|
39
3
|
//# sourceMappingURL=ApiCallContext.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallContext.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallContext.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ApiCallContext.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallContext.ts"],"names":[],"mappings":"","sourcesContent":["import { AnyUntrustedContextKey } from '../ContextKey';\n\n/**\n * ApiCallContext - the tiny seam that lets {@link LogApiCallImpl} (browser-safe, core-util) stamp a\n * ContextKey (the `api` tag) into the ambient request context WITHOUT importing it.\n *\n * WHY a seam instead of a direct call: the ambient context is `RequestContext` in\n * `@webpieces/core-context`, which is built on Node `async_hooks` (AsyncLocalStorage). core-util —\n * and `ProxyClient`, which runs in a BROWSER bundle — must never import that (it would be a circular\n * dependency, and it would drag Node vocabulary into a browser build). So core-util owns only this\n * interface, and each environment-specific package CONSTRUCTS its own impl and hands it to\n * {@link LogApiCallImpl}'s constructor:\n * - Node server inbound: `LogApiFilter` (http-routing) → `RequestContextApiCallContext`.\n * - Node client outbound: `NodeProxyClient` (http-client-node) and `TaskProxyClient`\n * (cloudtasks-client) → `RequestContextApiCallContext`.\n * - Browser: `BrowserProxyClient` (http-client-browser) → `BrowserApiCallContext`.\n *\n * There is deliberately NO process-global holder and no startup install: the context is a REQUIRED\n * constructor argument, so \"nobody set it up\" is a compile error rather than a runtime throw on the\n * first real call. That is what lets a plain NestJS/Express host use `@webpieces/http-client-node` or\n * `@webpieces/cloudtasks-client` with no webpieces STARTUP INSTALL — it only has to run the call\n * inside a `RequestContext.run(...)` scope, which such a host already opens per request. (The other\n * process-globals those clients read — HeaderRegistry, LogManager, a ClientRegistry mapping or\n * deriver — are unchanged by this and still apply.)\n *\n * Per CLAUDE.md this is behavior, hence an interface; the impls are ordinary classes.\n */\nexport interface ApiCallContext {\n /**\n * True when there is a context to stamp into (a live Node RequestContext scope; a browser is always\n * active). {@link LogApiCallImpl} throws if this is false — an api call with nowhere to tag is a bug.\n */\n isActive(): boolean;\n\n /**\n * Stamp one UNTRUSTED ContextKey → value into the ambient context. Untrusted by type on purpose:\n * this seam is reachable from browser-side client code, so if it accepted a trusted key it would\n * be a side door for forging a proven identity. The one key it actually stamps\n * ({@link WebpiecesCoreHeaders.API_CALL_INFO}) is a log tag, which is untrusted by nature.\n * The logger reads it back off the context\n * (server: RequestContext.buildStructuredLogFields; browser: its own store) during the log emit.\n */\n // webpieces-disable no-any-unknown -- a context value is heterogeneous (the api struct here; strings elsewhere)\n set(contextKey: AnyUntrustedContextKey, value: unknown): void;\n\n /**\n * Clear one ContextKey. {@link LogApiCallImpl} calls set → log → remove as one SYNCHRONOUS span, so the\n * tag is never held across `await`. That is what makes a single browser global safe: single-threaded,\n * nothing can interleave between set and remove, so a concurrent call can never clobber the slot.\n */\n remove(contextKey: AnyUntrustedContextKey): void;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiMethodInfo } from './ApiMethodInfo';
|
|
2
2
|
/**
|
|
3
3
|
* ApiCallInfo - the structured tag stamped into RequestContext around every API call
|
|
4
|
-
* (by {@link
|
|
4
|
+
* (by {@link LogApiCallImpl}), so ANY log line emitted during the call inherits a filterable
|
|
5
5
|
* `api` object rather than only the req/resp text lines.
|
|
6
6
|
*
|
|
7
7
|
* The node logging backends (winston/bunyan) read this struct out of context via
|
|
@@ -33,7 +33,7 @@ import { ApiMethodInfo } from './ApiMethodInfo';
|
|
|
33
33
|
export type ApiType = 'request' | 'response';
|
|
34
34
|
/**
|
|
35
35
|
* Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully
|
|
36
|
-
* handled "you made a mistake"); 'failure' is a genuine server error. See {@link
|
|
36
|
+
* handled "you made a mistake"); 'failure' is a genuine server error. See {@link LogApiCallImpl.isUserError}.
|
|
37
37
|
*/
|
|
38
38
|
export type ApiResult = 'success' | 'failure';
|
|
39
39
|
/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */
|
|
@@ -53,7 +53,7 @@ export declare class ApiCallInfo {
|
|
|
53
53
|
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
|
|
54
54
|
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
|
|
55
55
|
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
|
|
56
|
-
* the transport-neutral outcome, exactly as {@link
|
|
56
|
+
* the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
|
|
57
57
|
* portable Error TYPE rather than by status code.
|
|
58
58
|
*/
|
|
59
59
|
readonly durationMs?: number | undefined;
|
|
@@ -84,7 +84,7 @@ export declare class ApiCallInfo {
|
|
|
84
84
|
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
|
|
85
85
|
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
|
|
86
86
|
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
|
|
87
|
-
* the transport-neutral outcome, exactly as {@link
|
|
87
|
+
* the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
|
|
88
88
|
* portable Error TYPE rather than by status code.
|
|
89
89
|
*/
|
|
90
90
|
durationMs?: number | undefined,
|
package/src/http/ApiCallInfo.js
CHANGED
|
@@ -22,7 +22,7 @@ class ApiCallInfo {
|
|
|
22
22
|
* There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over
|
|
23
23
|
* in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP
|
|
24
24
|
* status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is
|
|
25
|
-
* the transport-neutral outcome, exactly as {@link
|
|
25
|
+
* the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by
|
|
26
26
|
* portable Error TYPE rather than by status code.
|
|
27
27
|
*/
|
|
28
28
|
durationMs,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AA6CA,MAAa,WAAW;IAIP;IACA;IAEA;IAYA;IAUA;IAGA;IA/Bb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAa;IACtB,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;OAUG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;QA5BrB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAS;QAEb,WAAM,GAAN,MAAM,CAAY;QAYlB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;IAC/B,CAAC;CACP;AAlCD,kCAkCC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link
|
|
1
|
+
{"version":3,"file":"ApiCallInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallInfo.ts"],"names":[],"mappings":";;;AA6CA,MAAa,WAAW;IAIP;IACA;IAEA;IAYA;IAUA;IAGA;IA/Bb;IACI;oCACgC;IACvB,MAAqB,EACrB,IAAa;IACtB,sDAAsD;IAC7C,MAAkB;IAC3B;;;;;;;;;;OAUG;IACM,UAAmB;IAC5B;;;;;;;;OAQG;IACM,WAAoB;IAC7B;yFACqF;IAC5E,YAAqB;QA5BrB,WAAM,GAAN,MAAM,CAAe;QACrB,SAAI,GAAJ,IAAI,CAAS;QAEb,WAAM,GAAN,MAAM,CAAY;QAYlB,eAAU,GAAV,UAAU,CAAS;QAUnB,gBAAW,GAAX,WAAW,CAAS;QAGpB,iBAAY,GAAZ,YAAY,CAAS;IAC/B,CAAC;CACP;AAlCD,kCAkCC","sourcesContent":["import { ApiMethodInfo } from './ApiMethodInfo';\n\n/**\n * ApiCallInfo - the structured tag stamped into RequestContext around every API call\n * (by {@link LogApiCallImpl}), so ANY log line emitted during the call inherits a filterable\n * `api` object rather than only the req/resp text lines.\n *\n * The node logging backends (winston/bunyan) read this struct out of context via\n * `RequestContext.buildStructuredLogFields()` and emit it AS AN OBJECT under `jsonPayload.api`,\n * which unlocks GCP Cloud Logging filters like:\n * - `jsonPayload.api.method.side=\"client\"` — every outbound call this process made\n * - `jsonPayload.api.method.side=\"server\"` — every inbound call it handled\n * - `jsonPayload.api.method.apiClass=\"SaveApi\"` — one logical method, BOTH sides (client + server)\n * - `jsonPayload.api.result=\"failure\"` — failed exchanges only\n * - `jsonPayload.api.durationMs>1000` — slow calls, either side\n * - `jsonPayload.api.responseSize>100000` — the fat responses (the ones that get chunked)\n * - `jsonPayload.api:*` — \"API traffic only\" (tracing + the recorder)\n *\n * IMPORTANT: the field names here (and on the nested {@link ApiMethodInfo}) ARE the GCP field names —\n * rename a field and the filter renames with it. The identity lives NESTED under `api.method`\n * (`api.method.{side,apiClass,methodName,controllerName}`); `api.type` and `api.result` sit at the top.\n *\n * NOTE: the request `httpMethod`/`path` are NOT here — an inbound request stamps them as the separate\n * top-level logged keys `jsonPayload.httpMethod` / `jsonPayload.requestPath` (see\n * {@link WebpiecesCoreHeaders} + `RequestContextHeaders.fillFromRequest`). Outbound client calls have\n * no inbound path, so they carry only the `api` identity.\n *\n * Per-hop only: the underlying `API_CALL_INFO` ContextKey is NOT transferred over the wire, so a\n * downstream server stamps its own `side:'server'` rather than inheriting the caller's `side:'client'`.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\n/** Which half of the exchange this tag describes: the outgoing 'request' or the returning 'response'. */\nexport type ApiType = 'request' | 'response';\n\n/**\n * Response outcome. 'success' covers 2xx AND user errors (400/401/403/404/266 — a successfully\n * handled \"you made a mistake\"); 'failure' is a genuine server error. See {@link LogApiCallImpl.isUserError}.\n */\nexport type ApiResult = 'success' | 'failure';\n\n/** Re-exported from {@link ApiMethodInfo} (its true home) so existing `ApiSide` imports keep working. */\nexport type { ApiSide } from './ApiMethodInfo';\n\nexport class ApiCallInfo {\n constructor(\n /** The call identity (side, apiClass, methodName, controllerName) — surfaces nested under\n * `jsonPayload.api.method`. */\n readonly method: ApiMethodInfo,\n readonly type: ApiType,\n /** Response only — undefined on the 'request' tag. */\n readonly result?: ApiResult,\n /**\n * Wall-clock milliseconds the call took. RESPONSE tag only (undefined on 'request') — a\n * request has no duration yet. Present on BOTH the success and failure paths, so\n * `jsonPayload.api.durationMs>1000 AND api.result=\"failure\"` finds slow failures.\n *\n * There is deliberately no `statusCode` beside this. LogApiCall runs deep in the stack over\n * in-process calls, pubsub handlers, and cloud-task enqueues — none of which have an HTTP\n * status — and business logic must not know about HTTP. `result` (see {@link ApiResult}) is\n * the transport-neutral outcome, exactly as {@link LogApiCallImpl.isUserError} classifies by\n * portable Error TYPE rather than by status code.\n */\n readonly durationMs?: number,\n /**\n * Bytes of the serialized request DTO. Stamped on BOTH tags: the 'request' tag reports it as\n * soon as it is known, and the 'response' tag repeats it so one record shows the whole\n * exchange (`api.requestSize` + `api.responseSize` without a join).\n *\n * This is the TOTAL size of the body, measured BEFORE any log chunking — chunking is a\n * transport concern handled by the GCP backends, and a body split across 3 records still\n * reports its one true size here.\n */\n readonly requestSize?: number,\n /** Bytes of the serialized response. RESPONSE tag only, and only when the call succeeded —\n * a thrown error produced no response body to measure. Total size, pre-chunking. */\n readonly responseSize?: number,\n ) {}\n}\n"]}
|
|
@@ -3,10 +3,10 @@ export declare const LOG_API_CALL_LOGGER_NAME = "LogApiCall";
|
|
|
3
3
|
/** A value read off a parsed/structured log record — the widest thing a record field can hold. */
|
|
4
4
|
type LogFieldValue = string | number | boolean | object | null | undefined;
|
|
5
5
|
/**
|
|
6
|
-
* ApiCallLogName - the console-render bridge that turns a {@link
|
|
6
|
+
* ApiCallLogName - the console-render bridge that turns a {@link LogApiCallImpl} line's plain
|
|
7
7
|
* `[LogApiCall]` logger bracket into a self-describing `[API.{side}.{phase}]` bracket.
|
|
8
8
|
*
|
|
9
|
-
* WHY: {@link
|
|
9
|
+
* WHY: {@link LogApiCallImpl} emits EVERY api req/resp line under the single logger name `LogApiCall`, so
|
|
10
10
|
* the local console showed the unhelpful `[LogApiCall]` on all of them. But each of those lines already
|
|
11
11
|
* carries the structured {@link ApiCallInfo} `api` tag in context, which knows the `side` (client/server)
|
|
12
12
|
* and whether this is the request or a success/failure response. The console backends (winston
|
|
@@ -14,7 +14,7 @@ type LogFieldValue = string | number | boolean | object | null | undefined;
|
|
|
14
14
|
* lines and render that richer bracket instead — e.g. `[API.client.request]`, `[API.server.success]`,
|
|
15
15
|
* `[API.client.failure]`. GCP is unaffected (it filters on `jsonPayload.api.*`, not the logger name).
|
|
16
16
|
*
|
|
17
|
-
* Singleton, mirroring {@link
|
|
17
|
+
* Singleton, mirroring {@link LogApiCallImpl}: use the exported {@link ApiCallLogName} constant, not `new`.
|
|
18
18
|
* Kept in one place so the two duplicated console formats stay byte-identical, and so the special-cased
|
|
19
19
|
* name matches the logger name LogApiCall actually uses ({@link LOG_API_CALL_LOGGER_NAME}).
|
|
20
20
|
*/
|
|
@@ -42,7 +42,7 @@ export declare class ApiCallLogNameImpl {
|
|
|
42
42
|
describe(loggerName: LogFieldValue, api: LogFieldValue): string | undefined;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* The process-wide {@link ApiCallLogNameImpl} singleton — mirrors the {@link
|
|
45
|
+
* The process-wide {@link ApiCallLogNameImpl} singleton — mirrors the {@link LogApiCallImpl} export pattern.
|
|
46
46
|
* Callers use `ApiCallLogName.describe(...)`, never `new`.
|
|
47
47
|
*/
|
|
48
48
|
export declare const ApiCallLogName: ApiCallLogNameImpl;
|
|
@@ -4,10 +4,10 @@ exports.ApiCallLogName = exports.ApiCallLogNameImpl = exports.LOG_API_CALL_LOGGE
|
|
|
4
4
|
/** The console-render logger name LogApiCall logs under; the backends special-case exactly this name. */
|
|
5
5
|
exports.LOG_API_CALL_LOGGER_NAME = 'LogApiCall';
|
|
6
6
|
/**
|
|
7
|
-
* ApiCallLogName - the console-render bridge that turns a {@link
|
|
7
|
+
* ApiCallLogName - the console-render bridge that turns a {@link LogApiCallImpl} line's plain
|
|
8
8
|
* `[LogApiCall]` logger bracket into a self-describing `[API.{side}.{phase}]` bracket.
|
|
9
9
|
*
|
|
10
|
-
* WHY: {@link
|
|
10
|
+
* WHY: {@link LogApiCallImpl} emits EVERY api req/resp line under the single logger name `LogApiCall`, so
|
|
11
11
|
* the local console showed the unhelpful `[LogApiCall]` on all of them. But each of those lines already
|
|
12
12
|
* carries the structured {@link ApiCallInfo} `api` tag in context, which knows the `side` (client/server)
|
|
13
13
|
* and whether this is the request or a success/failure response. The console backends (winston
|
|
@@ -15,7 +15,7 @@ exports.LOG_API_CALL_LOGGER_NAME = 'LogApiCall';
|
|
|
15
15
|
* lines and render that richer bracket instead — e.g. `[API.client.request]`, `[API.server.success]`,
|
|
16
16
|
* `[API.client.failure]`. GCP is unaffected (it filters on `jsonPayload.api.*`, not the logger name).
|
|
17
17
|
*
|
|
18
|
-
* Singleton, mirroring {@link
|
|
18
|
+
* Singleton, mirroring {@link LogApiCallImpl}: use the exported {@link ApiCallLogName} constant, not `new`.
|
|
19
19
|
* Kept in one place so the two duplicated console formats stay byte-identical, and so the special-cased
|
|
20
20
|
* name matches the logger name LogApiCall actually uses ({@link LOG_API_CALL_LOGGER_NAME}).
|
|
21
21
|
*/
|
|
@@ -63,7 +63,7 @@ class ApiCallLogNameImpl {
|
|
|
63
63
|
}
|
|
64
64
|
exports.ApiCallLogNameImpl = ApiCallLogNameImpl;
|
|
65
65
|
/**
|
|
66
|
-
* The process-wide {@link ApiCallLogNameImpl} singleton — mirrors the {@link
|
|
66
|
+
* The process-wide {@link ApiCallLogNameImpl} singleton — mirrors the {@link LogApiCallImpl} export pattern.
|
|
67
67
|
* Callers use `ApiCallLogName.describe(...)`, never `new`.
|
|
68
68
|
*/
|
|
69
69
|
exports.ApiCallLogName = new ApiCallLogNameImpl();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallLogName.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallLogName.ts"],"names":[],"mappings":";;;AAEA,yGAAyG;AAC5F,QAAA,wBAAwB,GAAG,YAAY,CAAC;AAKrD;;;;;;;;;;;;;;;GAeG;AACH,MAAa,kBAAkB;IAE3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAyB,EAAE,GAAkB;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,OAAO,GAAG,CAAC;QAC1B,CAAC;QACD,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,UAAyB,EAAE,GAAkB;QAClD,IAAI,UAAU,KAAK,gCAAwB,EAAE,CAAC;YAC1C,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,CAAC,GAAG,YAAY,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,2FAA2F;QAC3F,oFAAoF;QACpF,MAAM,IAAI,GAAG,GAA2B,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAClF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACtG,OAAO,OAAO,IAAI,IAAI,KAAK,EAAE,CAAC;IAClC,CAAC;CACJ;AA3CD,gDA2CC;AAED;;;GAGG;AACU,QAAA,cAAc,GAAG,IAAI,kBAAkB,EAAE,CAAC","sourcesContent":["import { ApiCallInfo } from './ApiCallInfo';\n\n/** The console-render logger name LogApiCall logs under; the backends special-case exactly this name. */\nexport const LOG_API_CALL_LOGGER_NAME = 'LogApiCall';\n\n/** A value read off a parsed/structured log record — the widest thing a record field can hold. */\ntype LogFieldValue = string | number | boolean | object | null | undefined;\n\n/**\n * ApiCallLogName - the console-render bridge that turns a {@link
|
|
1
|
+
{"version":3,"file":"ApiCallLogName.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallLogName.ts"],"names":[],"mappings":";;;AAEA,yGAAyG;AAC5F,QAAA,wBAAwB,GAAG,YAAY,CAAC;AAKrD;;;;;;;;;;;;;;;GAeG;AACH,MAAa,kBAAkB;IAE3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAyB,EAAE,GAAkB;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,OAAO,GAAG,CAAC;QAC1B,CAAC;QACD,OAAO,UAAU,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,UAAyB,EAAE,GAAkB;QAClD,IAAI,UAAU,KAAK,gCAAwB,EAAE,CAAC;YAC1C,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,CAAC,GAAG,YAAY,MAAM,CAAC,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,2FAA2F;QAC3F,oFAAoF;QACpF,MAAM,IAAI,GAAG,GAA2B,CAAC;QACzC,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAClF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACtG,OAAO,OAAO,IAAI,IAAI,KAAK,EAAE,CAAC;IAClC,CAAC;CACJ;AA3CD,gDA2CC;AAED;;;GAGG;AACU,QAAA,cAAc,GAAG,IAAI,kBAAkB,EAAE,CAAC","sourcesContent":["import { ApiCallInfo } from './ApiCallInfo';\n\n/** The console-render logger name LogApiCall logs under; the backends special-case exactly this name. */\nexport const LOG_API_CALL_LOGGER_NAME = 'LogApiCall';\n\n/** A value read off a parsed/structured log record — the widest thing a record field can hold. */\ntype LogFieldValue = string | number | boolean | object | null | undefined;\n\n/**\n * ApiCallLogName - the console-render bridge that turns a {@link LogApiCallImpl} line's plain\n * `[LogApiCall]` logger bracket into a self-describing `[API.{side}.{phase}]` bracket.\n *\n * WHY: {@link LogApiCallImpl} emits EVERY api req/resp line under the single logger name `LogApiCall`, so\n * the local console showed the unhelpful `[LogApiCall]` on all of them. But each of those lines already\n * carries the structured {@link ApiCallInfo} `api` tag in context, which knows the `side` (client/server)\n * and whether this is the request or a success/failure response. The console backends (winston\n * `localPrettyFormat` + bunyan `writeConsole`) call {@link describe} to special-case JUST the LogApiCall\n * lines and render that richer bracket instead — e.g. `[API.client.request]`, `[API.server.success]`,\n * `[API.client.failure]`. GCP is unaffected (it filters on `jsonPayload.api.*`, not the logger name).\n *\n * Singleton, mirroring {@link LogApiCallImpl}: use the exported {@link ApiCallLogName} constant, not `new`.\n * Kept in one place so the two duplicated console formats stay byte-identical, and so the special-cased\n * name matches the logger name LogApiCall actually uses ({@link LOG_API_CALL_LOGGER_NAME}).\n */\nexport class ApiCallLogNameImpl {\n\n /**\n * The complete logger bracket for one console line — the single seam both console backends call so\n * they render byte-identically. A LogApiCall line becomes a self-describing `[API.{side}.{phase}]`\n * bracket derived from its `api` tag; every other line keeps its plain `[loggerName]` bracket; a line\n * with no logger name at all (startup / pre-route) renders `''`.\n *\n * Phase mapping mirrors {@link ApiCallInfo}: a `request` tag → `request`; a `response` tag →\n * `failure` when `result:'failure'`, else `success` (which correctly folds handled user errors,\n * whose result is `success`, into `success`).\n *\n * @param loggerName - the record's `loggerName` field (off a parsed/structured log record)\n * @param api - the record's `api` field (the stamped {@link ApiCallInfo}, or undefined/other)\n * @returns e.g. `\"[API.client.request]\"`, `\"[MyClass]\"`, or `\"\"`\n */\n bracket(loggerName: LogFieldValue, api: LogFieldValue): string {\n const apiName = this.describe(loggerName, api);\n if (apiName !== undefined) {\n return `[${apiName}]`;\n }\n return loggerName ? `[${String(loggerName)}]` : '';\n }\n\n /**\n * The self-describing name (no brackets) for a LogApiCall line, e.g. `\"API.client.request\"`, or\n * `undefined` when this is not a LogApiCall line or its `api` tag is missing/misshapen. Callers use\n * {@link bracket}; this is factored out for direct testing of the phase mapping.\n */\n describe(loggerName: LogFieldValue, api: LogFieldValue): string | undefined {\n if (loggerName !== LOG_API_CALL_LOGGER_NAME) {\n return undefined;\n }\n if (!(api instanceof Object)) {\n return undefined;\n }\n // The record may be a plain JSON.parse output (bunyan) rather than an ApiCallInfo instance\n // (winston reads the live object), so read structurally rather than via instanceof.\n const info = api as Partial<ApiCallInfo>;\n const side = typeof info.method?.side === 'string' ? info.method.side : 'unknown';\n const phase = info.type === 'request' ? 'request' : info.result === 'failure' ? 'failure' : 'success';\n return `API.${side}.${phase}`;\n }\n}\n\n/**\n * The process-wide {@link ApiCallLogNameImpl} singleton — mirrors the {@link LogApiCallImpl} export pattern.\n * Callers use `ApiCallLogName.describe(...)`, never `new`.\n */\nexport const ApiCallLogName = new ApiCallLogNameImpl();\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ApiMethodInfo - the transport-neutral identity of a single API method call, handed to
|
|
3
|
-
* {@link
|
|
3
|
+
* {@link LogApiCallImpl.execute} by every caller (server inbound, http/in-process clients, cloud tasks,
|
|
4
4
|
* and external wrapped clients like the firestore admin client).
|
|
5
5
|
*
|
|
6
6
|
* WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,
|
|
@@ -30,7 +30,7 @@ export declare class ApiMethodInfo {
|
|
|
30
30
|
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
31
31
|
readonly controllerName?: string | undefined;
|
|
32
32
|
/** OPTIONAL — which DTO fields to mask in the log lines, and how. Absent = log the DTO
|
|
33
|
-
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link
|
|
33
|
+
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link LogApiCallImpl}
|
|
34
34
|
* runs {@link maskedStringify} so a declared-sensitive field (an OAuth refresh token, an
|
|
35
35
|
* id-token JWT) is masked in the log while the REAL value still travels on the wire untouched.
|
|
36
36
|
* Rides the call identity so the spec travels with the api definition, not a global app config. */
|
|
@@ -45,7 +45,7 @@ export declare class ApiMethodInfo {
|
|
|
45
45
|
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
46
46
|
controllerName?: string | undefined,
|
|
47
47
|
/** OPTIONAL — which DTO fields to mask in the log lines, and how. Absent = log the DTO
|
|
48
|
-
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link
|
|
48
|
+
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link LogApiCallImpl}
|
|
49
49
|
* runs {@link maskedStringify} so a declared-sensitive field (an OAuth refresh token, an
|
|
50
50
|
* id-token JWT) is masked in the log while the REAL value still travels on the wire untouched.
|
|
51
51
|
* Rides the call identity so the spec travels with the api definition, not a global app config. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* ApiMethodInfo - the transport-neutral identity of a single API method call, handed to
|
|
4
|
-
* {@link
|
|
4
|
+
* {@link LogApiCallImpl.execute} by every caller (server inbound, http/in-process clients, cloud tasks,
|
|
5
5
|
* and external wrapped clients like the firestore admin client).
|
|
6
6
|
*
|
|
7
7
|
* WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,
|
|
@@ -35,7 +35,7 @@ class ApiMethodInfo {
|
|
|
35
35
|
* impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */
|
|
36
36
|
controllerName,
|
|
37
37
|
/** OPTIONAL — which DTO fields to mask in the log lines, and how. Absent = log the DTO
|
|
38
|
-
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link
|
|
38
|
+
* verbatim exactly as before (the fast path, no per-field walk). Present = {@link LogApiCallImpl}
|
|
39
39
|
* runs {@link maskedStringify} so a declared-sensitive field (an OAuth refresh token, an
|
|
40
40
|
* id-token JWT) is masked in the log while the REAL value still travels on the wire untouched.
|
|
41
41
|
* Rides the call identity so the spec travels with the api definition, not a global app config. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAOH,MAAa,aAAa;IAET;IAGA;IAEA;IAGA;IAMA;IAfb,YACa,IAAa;IACtB;2FACuF;IAC9E,QAAgB;IACzB,2DAA2D;IAClD,UAAkB;IAC3B;gGAC4F;IACnF,cAAuB;IAChC;;;;wGAIoG;IAC3F,IAAe;QAdf,SAAI,GAAJ,IAAI,CAAS;QAGb,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,eAAU,GAAV,UAAU,CAAQ;QAGlB,mBAAc,GAAd,cAAc,CAAS;QAMvB,SAAI,GAAJ,IAAI,CAAW;IACzB,CAAC;CACP;AAlBD,sCAkBC","sourcesContent":["/**\n * ApiMethodInfo - the transport-neutral identity of a single API method call, handed to\n * {@link
|
|
1
|
+
{"version":3,"file":"ApiMethodInfo.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiMethodInfo.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAOH,MAAa,aAAa;IAET;IAGA;IAEA;IAGA;IAMA;IAfb,YACa,IAAa;IACtB;2FACuF;IAC9E,QAAgB;IACzB,2DAA2D;IAClD,UAAkB;IAC3B;gGAC4F;IACnF,cAAuB;IAChC;;;;wGAIoG;IAC3F,IAAe;QAdf,SAAI,GAAJ,IAAI,CAAS;QAGb,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,eAAU,GAAV,UAAU,CAAQ;QAGlB,mBAAc,GAAd,cAAc,CAAS;QAMvB,SAAI,GAAJ,IAAI,CAAW;IACzB,CAAC;CACP;AAlBD,sCAkBC","sourcesContent":["/**\n * ApiMethodInfo - the transport-neutral identity of a single API method call, handed to\n * {@link LogApiCallImpl.execute} by every caller (server inbound, http/in-process clients, cloud tasks,\n * and external wrapped clients like the firestore admin client).\n *\n * WHY generic (not RouteMetadata): LogApiCall runs deep in the stack over MANY shapes — HTTP routes,\n * pubsub/queue enqueues, and multi-param external clients returning Promise<void>/Promise<unknown>.\n * None of those own an `httpMethod`/`path`, so LogApiCall must not depend on the HTTP-shaped\n * `RouteMetadata`. This carries only what identifies the call.\n *\n * MATCHING is the point of {@link apiClass}: a CLIENT call and the SERVER handler for the same logical\n * method must log the SAME identity so `jsonPayload.api.method.apiClass=\"SaveApi\"` filters both sides\n * together. The API CONTRACT class name (e.g. 'SaveApi') is available on both sides — the client only\n * ever knows it, and the server carries it alongside its impl name — so it, not the server's impl\n * class, is the required key.\n *\n * Per CLAUDE.md: data-only structures are classes, not interfaces.\n */\n\nimport {MaskSpec} from \"./LogFieldMask\";\n\n/** Which end of the exchange this process is: the caller ('client') or the handler ('server'). */\nexport type ApiSide = 'client' | 'server';\n\nexport class ApiMethodInfo {\n constructor(\n readonly side: ApiSide,\n /** REQUIRED — the API CONTRACT class name (e.g. 'SaveApi'). Matches client + server so both\n * sides of one logical call filter together via `jsonPayload.api.method.apiClass`. */\n readonly apiClass: string,\n /** REQUIRED — the method on the contract (e.g. 'save'). */\n readonly methodName: string,\n /** OPTIONAL — server-side impl class (e.g. 'SaveController'). Absent on clients, which have no\n * impl. Surfaces as `jsonPayload.api.method.controllerName` for server-only drill-down. */\n readonly controllerName?: string,\n /** OPTIONAL — which DTO fields to mask in the log lines, and how. Absent = log the DTO\n * verbatim exactly as before (the fast path, no per-field walk). Present = {@link LogApiCallImpl}\n * runs {@link maskedStringify} so a declared-sensitive field (an OAuth refresh token, an\n * id-token JWT) is masked in the log while the REAL value still travels on the wire untouched.\n * Rides the call identity so the spec travels with the api definition, not a global app config. */\n readonly mask?: MaskSpec,\n ) {}\n}\n"]}
|
package/src/http/LogApiCall.d.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { ApiMethodInfo } from "./ApiMethodInfo";
|
|
2
|
+
import { ApiCallContext } from "./ApiCallContext";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* LogApiCallImpl - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
|
|
4
5
|
* client-side (ProxyClient) for one consistent logging shape across the framework.
|
|
5
6
|
*
|
|
6
7
|
* TWO things happen around each call:
|
|
7
8
|
* 1. Text lines are emitted (the human-readable `[API-...]` patterns below).
|
|
8
9
|
* 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the
|
|
9
|
-
* {@link
|
|
10
|
+
* {@link ApiCallContext} seam, so EVERY log line emitted during the call (not just the
|
|
10
11
|
* req/resp lines) inherits a filterable `api` object — surfacing in GCP as
|
|
11
12
|
* `jsonPayload.api.{method.{side,apiClass,methodName,controllerName},type,result}`.
|
|
12
13
|
*
|
|
13
14
|
* BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →
|
|
14
15
|
* BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).
|
|
15
|
-
* It stamps through the {@link ApiCallContext} seam instead
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* It stamps through the {@link ApiCallContext} seam instead, and takes that seam as a REQUIRED
|
|
17
|
+
* CONSTRUCTOR ARGUMENT — there is no process-global holder to install and none to forget. Each
|
|
18
|
+
* environment-specific package constructs its own:
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
+
* LogApiFilter (@webpieces/http-routing) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
21
|
+
* NodeProxyClient (@webpieces/http-client-node) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
22
|
+
* TaskProxyClient (@webpieces/cloudtasks-client) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
23
|
+
* BrowserProxyClient (@webpieces/http-client-browser) -> new LogApiCallImpl(new BrowserApiCallContext())
|
|
24
|
+
*
|
|
25
|
+
* NOT a singleton, deliberately: a shared instance would need a shared context, which is the global
|
|
26
|
+
* this constructor replaced. Construct one where you know which environment you are in.
|
|
20
27
|
*
|
|
21
28
|
* Logging format patterns:
|
|
22
29
|
* - [API-{side}-req] ClassName.methodName request={...}
|
|
@@ -25,6 +32,13 @@ import { ApiMethodInfo } from "./ApiMethodInfo";
|
|
|
25
32
|
* - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)
|
|
26
33
|
*/
|
|
27
34
|
export declare class LogApiCallImpl {
|
|
35
|
+
private readonly ctx;
|
|
36
|
+
/**
|
|
37
|
+
* @param ctx - the environment's {@link ApiCallContext}. REQUIRED, with no default: that is what
|
|
38
|
+
* turns "nobody bootstrapped the context" into a compile error instead of a throw on the first
|
|
39
|
+
* real call in production.
|
|
40
|
+
*/
|
|
41
|
+
constructor(ctx: ApiCallContext);
|
|
28
42
|
/**
|
|
29
43
|
* Execute an API call with logging + `api` context-tagging around it.
|
|
30
44
|
*
|
|
@@ -49,8 +63,9 @@ export declare class LogApiCallImpl {
|
|
|
49
63
|
*/
|
|
50
64
|
private serialize;
|
|
51
65
|
/**
|
|
52
|
-
* The ApiCallContext to stamp into.
|
|
53
|
-
*
|
|
66
|
+
* The ApiCallContext to stamp into. It cannot be MISSING (it is a constructor argument), but it
|
|
67
|
+
* can be INACTIVE — a Node context used outside any `RequestContext.run(...)` scope. That throws:
|
|
68
|
+
* an api call with nowhere to tag is a bug.
|
|
54
69
|
*/
|
|
55
70
|
private activeContext;
|
|
56
71
|
/**
|
|
@@ -80,8 +95,3 @@ export declare class LogApiCallImpl {
|
|
|
80
95
|
*/
|
|
81
96
|
isUserError(error: Error, server: boolean): boolean;
|
|
82
97
|
}
|
|
83
|
-
/**
|
|
84
|
-
* The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.
|
|
85
|
-
* Callers use `LogApiCall.execute(...)`, never `new`.
|
|
86
|
-
*/
|
|
87
|
-
export declare const LogApiCall: LogApiCallImpl;
|
package/src/http/LogApiCall.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.LogApiCallImpl = void 0;
|
|
4
4
|
const errorUtils_1 = require("../lib/errorUtils");
|
|
5
5
|
const LogManager_1 = require("../logging/LogManager");
|
|
6
6
|
const ApiCallInfo_1 = require("./ApiCallInfo");
|
|
7
7
|
const ApiMethodInfo_1 = require("./ApiMethodInfo");
|
|
8
|
-
const ApiCallContext_1 = require("./ApiCallContext");
|
|
9
8
|
const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
|
|
10
9
|
const ApiCallLogName_1 = require("./ApiCallLogName");
|
|
11
10
|
const ClientRegistry_1 = require("./ClientRegistry");
|
|
@@ -14,23 +13,29 @@ const WebpiecesDefaultFailureClassifier_1 = require("./WebpiecesDefaultFailureCl
|
|
|
14
13
|
// bracket (see ApiCallLogName) — so the name here and the name they match are the one constant.
|
|
15
14
|
const log = LogManager_1.LogManager.getLogger(ApiCallLogName_1.LOG_API_CALL_LOGGER_NAME);
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* LogApiCallImpl - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
|
|
18
17
|
* client-side (ProxyClient) for one consistent logging shape across the framework.
|
|
19
18
|
*
|
|
20
19
|
* TWO things happen around each call:
|
|
21
20
|
* 1. Text lines are emitted (the human-readable `[API-...]` patterns below).
|
|
22
21
|
* 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the
|
|
23
|
-
* {@link
|
|
22
|
+
* {@link ApiCallContext} seam, so EVERY log line emitted during the call (not just the
|
|
24
23
|
* req/resp lines) inherits a filterable `api` object — surfacing in GCP as
|
|
25
24
|
* `jsonPayload.api.{method.{side,apiClass,methodName,controllerName},type,result}`.
|
|
26
25
|
*
|
|
27
26
|
* BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →
|
|
28
27
|
* BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).
|
|
29
|
-
* It stamps through the {@link ApiCallContext} seam instead
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* It stamps through the {@link ApiCallContext} seam instead, and takes that seam as a REQUIRED
|
|
29
|
+
* CONSTRUCTOR ARGUMENT — there is no process-global holder to install and none to forget. Each
|
|
30
|
+
* environment-specific package constructs its own:
|
|
32
31
|
*
|
|
33
|
-
*
|
|
32
|
+
* LogApiFilter (@webpieces/http-routing) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
33
|
+
* NodeProxyClient (@webpieces/http-client-node) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
34
|
+
* TaskProxyClient (@webpieces/cloudtasks-client) -> new LogApiCallImpl(new RequestContextApiCallContext())
|
|
35
|
+
* BrowserProxyClient (@webpieces/http-client-browser) -> new LogApiCallImpl(new BrowserApiCallContext())
|
|
36
|
+
*
|
|
37
|
+
* NOT a singleton, deliberately: a shared instance would need a shared context, which is the global
|
|
38
|
+
* this constructor replaced. Construct one where you know which environment you are in.
|
|
34
39
|
*
|
|
35
40
|
* Logging format patterns:
|
|
36
41
|
* - [API-{side}-req] ClassName.methodName request={...}
|
|
@@ -39,6 +44,15 @@ const log = LogManager_1.LogManager.getLogger(ApiCallLogName_1.LOG_API_CALL_LOGG
|
|
|
39
44
|
* - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)
|
|
40
45
|
*/
|
|
41
46
|
class LogApiCallImpl {
|
|
47
|
+
ctx;
|
|
48
|
+
/**
|
|
49
|
+
* @param ctx - the environment's {@link ApiCallContext}. REQUIRED, with no default: that is what
|
|
50
|
+
* turns "nobody bootstrapped the context" into a compile error instead of a throw on the first
|
|
51
|
+
* real call in production.
|
|
52
|
+
*/
|
|
53
|
+
constructor(ctx) {
|
|
54
|
+
this.ctx = ctx;
|
|
55
|
+
}
|
|
42
56
|
/**
|
|
43
57
|
* Execute an API call with logging + `api` context-tagging around it.
|
|
44
58
|
*
|
|
@@ -111,14 +125,17 @@ class LogApiCallImpl {
|
|
|
111
125
|
return methodInfo.mask ? methodInfo.mask.stringify(dto) : JSON.stringify(dto);
|
|
112
126
|
}
|
|
113
127
|
/**
|
|
114
|
-
* The ApiCallContext to stamp into.
|
|
115
|
-
*
|
|
128
|
+
* The ApiCallContext to stamp into. It cannot be MISSING (it is a constructor argument), but it
|
|
129
|
+
* can be INACTIVE — a Node context used outside any `RequestContext.run(...)` scope. That throws:
|
|
130
|
+
* an api call with nowhere to tag is a bug.
|
|
116
131
|
*/
|
|
117
132
|
activeContext() {
|
|
118
|
-
const ctx =
|
|
133
|
+
const ctx = this.ctx;
|
|
119
134
|
if (!ctx.isActive()) {
|
|
120
|
-
throw new Error('LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside
|
|
121
|
-
'RequestContext.run(...) a server filter opens
|
|
135
|
+
throw new Error('LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside a ' +
|
|
136
|
+
'RequestContext.run(...) scope — a server filter opens one per request, and a ' +
|
|
137
|
+
'non-webpieces host must open one around the work that calls a webpieces client. ' +
|
|
138
|
+
'(A BrowserApiCallContext is always active, so this can only be the Node side.)');
|
|
122
139
|
}
|
|
123
140
|
return ctx;
|
|
124
141
|
}
|
|
@@ -169,9 +186,4 @@ class LogApiCallImpl {
|
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
exports.LogApiCallImpl = LogApiCallImpl;
|
|
172
|
-
/**
|
|
173
|
-
* The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.
|
|
174
|
-
* Callers use `LogApiCall.execute(...)`, never `new`.
|
|
175
|
-
*/
|
|
176
|
-
exports.LogApiCall = new LogApiCallImpl();
|
|
177
189
|
//# sourceMappingURL=LogApiCall.js.map
|