@apifuse/provider-sdk 2.2.0-beta.55 → 2.2.0-beta.56
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/CHANGELOG.md +4 -0
- package/bin/apifuse-pack-types.ts +121 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/runtime/proxy-telemetry.d.ts +53 -2
- package/dist/runtime/proxy-telemetry.js +106 -65
- package/dist/runtime/request-telemetry.d.ts +70 -0
- package/dist/runtime/request-telemetry.js +228 -0
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +1 -0
- package/dist/server/serve-implementation.js +58 -20
- package/package.json +1 -1
- package/src/index.ts +13 -0
- package/src/runtime/proxy-telemetry.ts +160 -74
- package/src/runtime/request-telemetry.ts +376 -0
- package/src/server/index.ts +14 -0
- package/src/server/serve-implementation.ts +93 -21
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { PROVIDER_OBSERVABILITY_TAXONOMY_VERSION } from "../observability.js";
|
|
2
|
+
export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
|
|
3
|
+
/** Brands a value from an SDK-declared closed string union without changing it. */
|
|
4
|
+
export function closedEnum(value) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
/** Names the deliberate tenant cache-key exemption without changing its wire value. */
|
|
8
|
+
export function tenantOpaqueKeys(value) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
const MAX_HEADER_BYTES = 4_096;
|
|
12
|
+
const MAX_INGESTIBLE_ARRAY_LENGTH = 64;
|
|
13
|
+
const MAX_INGESTIBLE_OBJECT_KEYS = 32;
|
|
14
|
+
const MAX_INGESTIBLE_DEPTH = 4;
|
|
15
|
+
const INGESTIBLE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/;
|
|
16
|
+
const HEADER_PRIORITY = [
|
|
17
|
+
"proxy",
|
|
18
|
+
"resolver",
|
|
19
|
+
"native",
|
|
20
|
+
"http",
|
|
21
|
+
"stealth",
|
|
22
|
+
"browser",
|
|
23
|
+
"ocr",
|
|
24
|
+
"stt",
|
|
25
|
+
"cache",
|
|
26
|
+
"state",
|
|
27
|
+
];
|
|
28
|
+
const warnedInvalidKeys = new Set();
|
|
29
|
+
function createSpanIndex(trace) {
|
|
30
|
+
const spans = trace.getSpans().slice();
|
|
31
|
+
const mutableByName = new Map();
|
|
32
|
+
for (const span of spans) {
|
|
33
|
+
const named = mutableByName.get(span.name);
|
|
34
|
+
if (named)
|
|
35
|
+
named.push(span);
|
|
36
|
+
else
|
|
37
|
+
mutableByName.set(span.name, [span]);
|
|
38
|
+
}
|
|
39
|
+
const byName = new Map(mutableByName);
|
|
40
|
+
return {
|
|
41
|
+
spans,
|
|
42
|
+
byName,
|
|
43
|
+
count(name) {
|
|
44
|
+
return byName.get(name)?.length ?? 0;
|
|
45
|
+
},
|
|
46
|
+
durationMs(name) {
|
|
47
|
+
return (byName.get(name) ?? []).reduce((total, span) => total + span.duration_ms, 0);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function isPlainRecord(value) {
|
|
52
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
53
|
+
return false;
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === Object.prototype || prototype === null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Bounded structural defence in depth against casts and accidental free text.
|
|
59
|
+
* Closed-enum brands are erased at runtime; the type-level guard is the contract,
|
|
60
|
+
* while this check only enforces safe token shapes and collection bounds.
|
|
61
|
+
*/
|
|
62
|
+
export function isGatewayIngestible(value) {
|
|
63
|
+
const ancestors = new Set();
|
|
64
|
+
const visit = (candidate, depth) => {
|
|
65
|
+
if (typeof candidate === "number")
|
|
66
|
+
return Number.isFinite(candidate);
|
|
67
|
+
if (typeof candidate === "boolean")
|
|
68
|
+
return true;
|
|
69
|
+
if (typeof candidate === "string")
|
|
70
|
+
return INGESTIBLE_TOKEN.test(candidate);
|
|
71
|
+
if (candidate === null || typeof candidate !== "object")
|
|
72
|
+
return false;
|
|
73
|
+
if (depth >= MAX_INGESTIBLE_DEPTH || ancestors.has(candidate))
|
|
74
|
+
return false;
|
|
75
|
+
if (Array.isArray(candidate)) {
|
|
76
|
+
if (candidate.length > MAX_INGESTIBLE_ARRAY_LENGTH)
|
|
77
|
+
return false;
|
|
78
|
+
ancestors.add(candidate);
|
|
79
|
+
const valid = candidate.every((item, index) => Object.hasOwn(candidate, index) && visit(item, depth + 1));
|
|
80
|
+
ancestors.delete(candidate);
|
|
81
|
+
return valid;
|
|
82
|
+
}
|
|
83
|
+
if (!isPlainRecord(candidate))
|
|
84
|
+
return false;
|
|
85
|
+
const keys = Object.keys(candidate);
|
|
86
|
+
if (keys.length > MAX_INGESTIBLE_OBJECT_KEYS)
|
|
87
|
+
return false;
|
|
88
|
+
ancestors.add(candidate);
|
|
89
|
+
const valid = keys.every((key) => visit(candidate[key], depth + 1));
|
|
90
|
+
ancestors.delete(candidate);
|
|
91
|
+
return valid;
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
return visit(value, 0);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function encodeEnvelope(envelope) {
|
|
101
|
+
return Buffer.from(JSON.stringify(envelope), "utf8").toString("base64url");
|
|
102
|
+
}
|
|
103
|
+
function warnInvalidSibling(key) {
|
|
104
|
+
if (warnedInvalidKeys.has(key))
|
|
105
|
+
return;
|
|
106
|
+
warnedInvalidKeys.add(key);
|
|
107
|
+
try {
|
|
108
|
+
console.warn(`[provider-sdk] Dropped invalid telemetry sibling "${key}"; contributors must return serializable log objects and bounded gateway-safe header values.`);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// A host-provided console must not let telemetry fail the request path.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function isProxySink(value) {
|
|
115
|
+
if (value === null || typeof value !== "object")
|
|
116
|
+
return false;
|
|
117
|
+
try {
|
|
118
|
+
return typeof Reflect.get(value, "recordProxyResolution") === "function";
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** One finalisation owner for every request-scoped telemetry contributor. */
|
|
125
|
+
export class RequestTelemetry {
|
|
126
|
+
trace;
|
|
127
|
+
contributors;
|
|
128
|
+
#registered = {};
|
|
129
|
+
#exposed = {};
|
|
130
|
+
#proxy;
|
|
131
|
+
constructor(trace) {
|
|
132
|
+
this.trace = trace;
|
|
133
|
+
this.contributors = this.#exposed;
|
|
134
|
+
}
|
|
135
|
+
register(contributor) {
|
|
136
|
+
if (this.#registered[contributor.key]) {
|
|
137
|
+
throw new TypeError(`Telemetry contributor "${contributor.key}" is already registered.`);
|
|
138
|
+
}
|
|
139
|
+
const registered = {
|
|
140
|
+
key: contributor.key,
|
|
141
|
+
toLogPayload: (spans) => contributor.toLogPayload(spans),
|
|
142
|
+
toHeaderPayload: (log) => contributor.toHeaderPayload(log),
|
|
143
|
+
};
|
|
144
|
+
this.#registered[contributor.key] = registered;
|
|
145
|
+
this.#exposed[contributor.key] = contributor;
|
|
146
|
+
if (contributor.key === "proxy" && isProxySink(contributor))
|
|
147
|
+
this.#proxy = contributor;
|
|
148
|
+
}
|
|
149
|
+
get proxy() {
|
|
150
|
+
return this.#proxy;
|
|
151
|
+
}
|
|
152
|
+
toLogPayload() {
|
|
153
|
+
const spans = createSpanIndex(this.trace);
|
|
154
|
+
const payload = {};
|
|
155
|
+
for (const key of Object.keys(this.#registered)) {
|
|
156
|
+
const contributor = this.#registered[key];
|
|
157
|
+
if (!contributor)
|
|
158
|
+
continue;
|
|
159
|
+
try {
|
|
160
|
+
const sibling = contributor.toLogPayload(spans);
|
|
161
|
+
if (sibling === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
JSON.stringify(sibling);
|
|
164
|
+
payload[key] = sibling;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
warnInvalidSibling(key);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
171
|
+
}
|
|
172
|
+
toHeaderValue() {
|
|
173
|
+
const spans = createSpanIndex(this.trace);
|
|
174
|
+
const siblings = {};
|
|
175
|
+
for (const key of HEADER_PRIORITY) {
|
|
176
|
+
const contributor = this.#registered[key];
|
|
177
|
+
if (!contributor)
|
|
178
|
+
continue;
|
|
179
|
+
try {
|
|
180
|
+
const log = contributor.toLogPayload(spans);
|
|
181
|
+
if (log === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
const projected = contributor.toHeaderPayload(log);
|
|
184
|
+
if (projected === undefined)
|
|
185
|
+
continue;
|
|
186
|
+
if (!isGatewayIngestible(projected)) {
|
|
187
|
+
warnInvalidSibling(key);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const serialized = JSON.stringify(projected);
|
|
191
|
+
const decoded = JSON.parse(serialized);
|
|
192
|
+
if (!isGatewayIngestible(decoded) || !isPlainRecord(decoded)) {
|
|
193
|
+
warnInvalidSibling(key);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
siblings[key] = decoded;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
warnInvalidSibling(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (Object.keys(siblings).length === 0)
|
|
203
|
+
return undefined;
|
|
204
|
+
// Gateway uses permissive json.Unmarshal (unknown keys are ignored) but
|
|
205
|
+
// requires v === 1. The observability taxonomy is additive and independent.
|
|
206
|
+
const envelope = {
|
|
207
|
+
v: 1,
|
|
208
|
+
taxonomy: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
209
|
+
...siblings,
|
|
210
|
+
};
|
|
211
|
+
let encoded = encodeEnvelope(envelope);
|
|
212
|
+
if (encoded.length <= MAX_HEADER_BYTES)
|
|
213
|
+
return encoded;
|
|
214
|
+
for (let index = HEADER_PRIORITY.length - 1; index >= 0; index -= 1) {
|
|
215
|
+
const key = HEADER_PRIORITY[index];
|
|
216
|
+
if (!key || !(key in envelope))
|
|
217
|
+
continue;
|
|
218
|
+
delete envelope[key];
|
|
219
|
+
if (!HEADER_PRIORITY.some((candidate) => candidate in envelope))
|
|
220
|
+
return undefined;
|
|
221
|
+
envelope.truncated = true;
|
|
222
|
+
encoded = encodeEnvelope(envelope);
|
|
223
|
+
if (encoded.length <= MAX_HEADER_BYTES)
|
|
224
|
+
return encoded;
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type { ProxyCacheStatus, ProxyProtocol, ProxyUserAgentSource, ProxyVendorName, SmartproxyAllocatorBodyClass, } from "../config/loader.js";
|
|
2
|
-
export type { ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "../runtime/proxy-telemetry.js";
|
|
2
|
+
export type { ProxyTelemetryHeaderPayload, ProxyHash, ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "../runtime/proxy-telemetry.js";
|
|
3
|
+
export { RequestTelemetry, closedEnum, type ClosedEnum, type GatewayIngestible, type RequestTelemetryLogPayload, type SpanIndex, type TelemetryContributor, type TelemetryKey, type TenantNeutral, } from "../runtime/request-telemetry.js";
|
|
4
|
+
export type { Span, TraceContext } from "../runtime/trace.js";
|
|
3
5
|
export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, type ErrorObservabilityDetails, type ProviderServerCloseOptions, type ProviderServerHandle, type ProviderErrorCauseFrame, type ProviderServerLogEvent, type ProviderServerLogger, type ProviderServerOperationExecutor, type ProviderServerOperationExecutorInput, type ProviderServerOptions, type ProviderServerStatefulForwardEnvelope, type ServeOptions, serve, } from "./serve.js";
|
|
4
6
|
export type { ProviderErrorObservability } from "../errors.js";
|
|
5
7
|
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestAuthFlowInvoke, type SelfTestAuthFlowRoute, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test.js";
|
package/dist/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { RequestTelemetry, closedEnum, } from "../runtime/request-telemetry.js";
|
|
1
2
|
export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, serve, } from "./serve.js";
|
|
2
3
|
export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test.js";
|
|
3
4
|
export { resolveHealthCheckInputDateTokens, } from "./self-test-input-tokens.js";
|
|
@@ -26,6 +26,7 @@ import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
|
26
26
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
27
27
|
import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
|
|
28
28
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
|
|
29
|
+
import { closedEnum, RequestTelemetry, tenantOpaqueKeys, } from "../runtime/request-telemetry.js";
|
|
29
30
|
import { createUnsupportedResolverClient, } from "../runtime/resolver-shared.js";
|
|
30
31
|
import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
|
|
31
32
|
import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
|
|
@@ -75,6 +76,16 @@ function providerErrorCode(error) {
|
|
|
75
76
|
}
|
|
76
77
|
const AUTH_FLOW_LOCALES = ["en", "ko", "ja"];
|
|
77
78
|
const retryResponseMeta = new WeakMap();
|
|
79
|
+
const RETRY_LAST_ERROR_CODES = [
|
|
80
|
+
"transport_cancelled",
|
|
81
|
+
"transport_timeout",
|
|
82
|
+
"transport_network_error",
|
|
83
|
+
"upstream_http_error",
|
|
84
|
+
"other",
|
|
85
|
+
];
|
|
86
|
+
function tenantRetryLastErrorCode(value) {
|
|
87
|
+
return RETRY_LAST_ERROR_CODES.find((candidate) => candidate === value) ?? "other";
|
|
88
|
+
}
|
|
78
89
|
const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
|
|
79
90
|
const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
|
|
80
91
|
const DEFAULT_STATEFUL_FORWARDING_MAX_SKEW_MS = 5 * 60_000;
|
|
@@ -409,7 +420,7 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
409
420
|
const proxyClientOptions = {
|
|
410
421
|
upstream: { proxy: provider.proxy },
|
|
411
422
|
affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
|
|
412
|
-
telemetry: scope.
|
|
423
|
+
telemetry: scope.telemetry.proxy,
|
|
413
424
|
engineCredentials: engineProxyCredentials,
|
|
414
425
|
};
|
|
415
426
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
|
|
@@ -417,7 +428,7 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
417
428
|
const stealthClientOptions = {
|
|
418
429
|
upstream: proxyClientOptions.upstream,
|
|
419
430
|
affinityKey: proxyClientOptions.affinityKey,
|
|
420
|
-
telemetry: scope.
|
|
431
|
+
telemetry: scope.telemetry.proxy,
|
|
421
432
|
engineCredentials: engineProxyCredentials,
|
|
422
433
|
...(signal ? { signal } : {}),
|
|
423
434
|
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
@@ -564,14 +575,14 @@ function createAuthFlowContext(provider, request, options, state, scope, signal)
|
|
|
564
575
|
const proxyClientOptions = {
|
|
565
576
|
upstream: { proxy: provider.proxy },
|
|
566
577
|
affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
|
|
567
|
-
telemetry: scope.
|
|
578
|
+
telemetry: scope.telemetry.proxy,
|
|
568
579
|
engineCredentials: engineProxyCredentials,
|
|
569
580
|
};
|
|
570
581
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
|
|
571
582
|
const stealthClientOptions = {
|
|
572
583
|
upstream: proxyClientOptions.upstream,
|
|
573
584
|
affinityKey: proxyClientOptions.affinityKey,
|
|
574
|
-
telemetry: scope.
|
|
585
|
+
telemetry: scope.telemetry.proxy,
|
|
575
586
|
engineCredentials: engineProxyCredentials,
|
|
576
587
|
...(signal ? { signal } : {}),
|
|
577
588
|
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
@@ -1014,7 +1025,7 @@ function providerErrorCauseChain(error) {
|
|
|
1014
1025
|
}
|
|
1015
1026
|
return frames.length > 0 ? frames : undefined;
|
|
1016
1027
|
}
|
|
1017
|
-
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode,
|
|
1028
|
+
function logProviderError(logger, provider, kind, route, requestId, error, status, cost, declaredErrorCode, telemetry, observabilityDetails, correlation = {}) {
|
|
1018
1029
|
const providerCode = isProviderError(error) ? providerErrorCode(error) : undefined;
|
|
1019
1030
|
const code = isProviderError(error)
|
|
1020
1031
|
? (providerCode ?? "provider_error")
|
|
@@ -1033,7 +1044,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1033
1044
|
typeof providerCode === "string" &&
|
|
1034
1045
|
!SDK_OWNED_PROVIDER_ERROR_CODES.has(providerCode) &&
|
|
1035
1046
|
declaredErrorCode === undefined;
|
|
1036
|
-
const
|
|
1047
|
+
const telemetryPayload = telemetry?.toLogPayload();
|
|
1037
1048
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1038
1049
|
// The logger is caller-supplied and may mutate the event synchronously.
|
|
1039
1050
|
// Give it an independent snapshot so those mutations cannot corrupt the
|
|
@@ -1058,7 +1069,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1058
1069
|
: {}),
|
|
1059
1070
|
status,
|
|
1060
1071
|
...cost,
|
|
1061
|
-
...(
|
|
1072
|
+
...(telemetryPayload ?? {}),
|
|
1062
1073
|
code,
|
|
1063
1074
|
errorClass,
|
|
1064
1075
|
message,
|
|
@@ -1093,8 +1104,8 @@ function logProviderCleanupError(logger, provider, kind, operationId, requestId,
|
|
|
1093
1104
|
message,
|
|
1094
1105
|
});
|
|
1095
1106
|
}
|
|
1096
|
-
function logProviderSuccess(logger, provider, kind, route, requestId, status, cost,
|
|
1097
|
-
const
|
|
1107
|
+
function logProviderSuccess(logger, provider, kind, route, requestId, status, cost, telemetry, correlation = {}) {
|
|
1108
|
+
const telemetryPayload = telemetry?.toLogPayload();
|
|
1098
1109
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
1099
1110
|
emit({
|
|
1100
1111
|
level: "info",
|
|
@@ -1113,7 +1124,7 @@ function logProviderSuccess(logger, provider, kind, route, requestId, status, co
|
|
|
1113
1124
|
: {}),
|
|
1114
1125
|
status,
|
|
1115
1126
|
...cost,
|
|
1116
|
-
...(
|
|
1127
|
+
...(telemetryPayload ?? {}),
|
|
1117
1128
|
});
|
|
1118
1129
|
}
|
|
1119
1130
|
const STREAM_CLEANUP_TIMEOUT_MS = 100;
|
|
@@ -1165,7 +1176,6 @@ function requestTraceId(headers, requestId) {
|
|
|
1165
1176
|
function createRequestScope(input) {
|
|
1166
1177
|
const requestCost = startRequestCost();
|
|
1167
1178
|
const traceConfig = resolveTraceConfigFromEnv();
|
|
1168
|
-
const proxyTelemetry = new ProxyTelemetryCollector();
|
|
1169
1179
|
const details = {
|
|
1170
1180
|
route: input.route,
|
|
1171
1181
|
requestId: input.requestId,
|
|
@@ -1188,6 +1198,9 @@ function createRequestScope(input) {
|
|
|
1188
1198
|
...(initialTraceId ? { traceId: initialTraceId } : {}),
|
|
1189
1199
|
})
|
|
1190
1200
|
: createTraceContext();
|
|
1201
|
+
const proxyCollector = new ProxyTelemetryCollector();
|
|
1202
|
+
const telemetry = new RequestTelemetry(trace);
|
|
1203
|
+
telemetry.register(proxyCollector);
|
|
1191
1204
|
let rootRunner = (fn) => fn();
|
|
1192
1205
|
let resolveRoot;
|
|
1193
1206
|
const rootTerminal = new Promise((resolve) => {
|
|
@@ -1233,7 +1246,7 @@ function createRequestScope(input) {
|
|
|
1233
1246
|
await Promise.all(streamCleanups.map(runCleanupEntry));
|
|
1234
1247
|
};
|
|
1235
1248
|
const headerSnapshot = (error) => {
|
|
1236
|
-
const providerTelemetryHeader =
|
|
1249
|
+
const providerTelemetryHeader = telemetry.toHeaderValue();
|
|
1237
1250
|
const declaredErrorCode = error === undefined ? undefined : input.declaredErrorCode?.(error);
|
|
1238
1251
|
const errorObservability = error === undefined ? undefined : errorObservabilityDetails(error, declaredErrorCode);
|
|
1239
1252
|
return {
|
|
@@ -1261,7 +1274,7 @@ function createRequestScope(input) {
|
|
|
1261
1274
|
};
|
|
1262
1275
|
const scope = {
|
|
1263
1276
|
trace,
|
|
1264
|
-
|
|
1277
|
+
telemetry,
|
|
1265
1278
|
enrich(enrichment) {
|
|
1266
1279
|
if (terminalOutcome)
|
|
1267
1280
|
return;
|
|
@@ -1334,10 +1347,10 @@ function createRequestScope(input) {
|
|
|
1334
1347
|
const cost = finishRequestCost(requestCost);
|
|
1335
1348
|
try {
|
|
1336
1349
|
if (error === undefined) {
|
|
1337
|
-
logProviderSuccess(input.logger, input.provider, input.kind, details.route, details.requestId, status, cost,
|
|
1350
|
+
logProviderSuccess(input.logger, input.provider, input.kind, details.route, details.requestId, status, cost, telemetry, details.correlation);
|
|
1338
1351
|
}
|
|
1339
1352
|
else {
|
|
1340
|
-
logProviderError(input.logger, input.provider, input.kind, details.route, details.requestId, error, status, cost, declaredErrorCode,
|
|
1353
|
+
logProviderError(input.logger, input.provider, input.kind, details.route, details.requestId, error, status, cost, declaredErrorCode, telemetry, finishedResult.errorObservability, details.correlation);
|
|
1341
1354
|
}
|
|
1342
1355
|
}
|
|
1343
1356
|
catch {
|
|
@@ -1407,18 +1420,43 @@ function toJsonSuccessResponse(result, ctx) {
|
|
|
1407
1420
|
}
|
|
1408
1421
|
const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
|
|
1409
1422
|
const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
|
|
1423
|
+
const cache = cacheMeta
|
|
1424
|
+
? {
|
|
1425
|
+
hit: cacheMeta.hit,
|
|
1426
|
+
stale: cacheMeta.stale,
|
|
1427
|
+
// TODO(owner): keep or remove implementation-shaped cache keys from tenant meta.
|
|
1428
|
+
keys: tenantOpaqueKeys(cacheMeta.keys),
|
|
1429
|
+
...(cacheMeta.source ? { source: closedEnum(cacheMeta.source) } : {}),
|
|
1430
|
+
}
|
|
1431
|
+
: undefined;
|
|
1432
|
+
const retry = retryMeta
|
|
1433
|
+
? {
|
|
1434
|
+
attempts: retryMeta.attempts,
|
|
1435
|
+
retries: retryMeta.retries,
|
|
1436
|
+
...(retryMeta.preset ? { preset: closedEnum(retryMeta.preset) } : {}),
|
|
1437
|
+
transport: closedEnum(retryMeta.transport),
|
|
1438
|
+
...(retryMeta.lastErrorCode
|
|
1439
|
+
? {
|
|
1440
|
+
lastErrorCode: closedEnum(tenantRetryLastErrorCode(retryMeta.lastErrorCode)),
|
|
1441
|
+
}
|
|
1442
|
+
: {}),
|
|
1443
|
+
...(retryMeta.lastStatus ? { lastStatus: retryMeta.lastStatus } : {}),
|
|
1444
|
+
}
|
|
1445
|
+
: undefined;
|
|
1410
1446
|
const meta = cacheMeta || retryMeta
|
|
1411
1447
|
? {
|
|
1412
|
-
...(
|
|
1448
|
+
...(cache
|
|
1413
1449
|
? {
|
|
1414
|
-
cached:
|
|
1415
|
-
stale:
|
|
1416
|
-
cache
|
|
1450
|
+
cached: cache.hit,
|
|
1451
|
+
stale: cache.stale,
|
|
1452
|
+
cache,
|
|
1417
1453
|
}
|
|
1418
1454
|
: {}),
|
|
1419
|
-
...(
|
|
1455
|
+
...(retry ? { retry } : {}),
|
|
1420
1456
|
}
|
|
1421
1457
|
: undefined;
|
|
1458
|
+
const _neutralMeta = meta;
|
|
1459
|
+
void _neutralMeta;
|
|
1422
1460
|
return {
|
|
1423
1461
|
data: result,
|
|
1424
1462
|
...(meta ? { meta } : {}),
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -181,10 +181,23 @@ export {
|
|
|
181
181
|
export type { PrevalidateResult } from "./runtime/prevalidate.js";
|
|
182
182
|
export { getProviderBaseUrl } from "./runtime/provider.js";
|
|
183
183
|
export type {
|
|
184
|
+
ProxyTelemetryHeaderPayload,
|
|
185
|
+
ProxyHash,
|
|
184
186
|
ProxyTelemetryLogPayload,
|
|
185
187
|
ProxyTelemetryResolvedPayload,
|
|
186
188
|
ProxyTelemetryUnresolvedPayload,
|
|
187
189
|
} from "./runtime/proxy-telemetry.js";
|
|
190
|
+
export {
|
|
191
|
+
RequestTelemetry,
|
|
192
|
+
closedEnum,
|
|
193
|
+
type ClosedEnum,
|
|
194
|
+
type GatewayIngestible,
|
|
195
|
+
type RequestTelemetryLogPayload,
|
|
196
|
+
type SpanIndex,
|
|
197
|
+
type TelemetryContributor,
|
|
198
|
+
type TelemetryKey,
|
|
199
|
+
type TenantNeutral,
|
|
200
|
+
} from "./runtime/request-telemetry.js";
|
|
188
201
|
export {
|
|
189
202
|
APIFUSE__CDP_POOL__URL,
|
|
190
203
|
APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
|