@stainlessdev/xray-emitter 0.1.0-branch.pedro-unify.20f830a
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/README.md +79 -0
- package/dist/chunk-2NR6RZEP.js +1339 -0
- package/dist/chunk-2NR6RZEP.js.map +1 -0
- package/dist/chunk-3MVVO5I7.cjs +1339 -0
- package/dist/chunk-3MVVO5I7.cjs.map +1 -0
- package/dist/chunk-6UH43LVD.js +281 -0
- package/dist/chunk-6UH43LVD.js.map +1 -0
- package/dist/chunk-AHXNYJ5A.cjs +621 -0
- package/dist/chunk-AHXNYJ5A.cjs.map +1 -0
- package/dist/chunk-GNSXLLEC.cjs +281 -0
- package/dist/chunk-GNSXLLEC.cjs.map +1 -0
- package/dist/chunk-JKW6E4L3.cjs +304 -0
- package/dist/chunk-JKW6E4L3.cjs.map +1 -0
- package/dist/chunk-MPQTI5AX.js +621 -0
- package/dist/chunk-MPQTI5AX.js.map +1 -0
- package/dist/chunk-QUH3LJ5M.cjs +634 -0
- package/dist/chunk-QUH3LJ5M.cjs.map +1 -0
- package/dist/chunk-VGRLHYDA.js +634 -0
- package/dist/chunk-VGRLHYDA.js.map +1 -0
- package/dist/chunk-YVMMCTXW.js +304 -0
- package/dist/chunk-YVMMCTXW.js.map +1 -0
- package/dist/express.cjs +45 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +17 -0
- package/dist/express.d.ts +17 -0
- package/dist/express.js +45 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +64 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +21 -0
- package/dist/fastify.d.ts +21 -0
- package/dist/fastify.js +64 -0
- package/dist/fastify.js.map +1 -0
- package/dist/fetch.cjs +16 -0
- package/dist/fetch.cjs.map +1 -0
- package/dist/fetch.d.cts +19 -0
- package/dist/fetch.d.ts +19 -0
- package/dist/fetch.js +16 -0
- package/dist/fetch.js.map +1 -0
- package/dist/hono.cjs +71 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +30 -0
- package/dist/hono.d.ts +30 -0
- package/dist/hono.js +71 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.cjs +45 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +53 -0
- package/dist/internal.d.ts +53 -0
- package/dist/internal.js +45 -0
- package/dist/internal.js.map +1 -0
- package/dist/next.cjs +30 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +18 -0
- package/dist/next.d.ts +18 -0
- package/dist/next.js +30 -0
- package/dist/next.js.map +1 -0
- package/dist/node.cjs +14 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +20 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.js +14 -0
- package/dist/node.js.map +1 -0
- package/dist/remix.cjs +30 -0
- package/dist/remix.cjs.map +1 -0
- package/dist/remix.d.cts +15 -0
- package/dist/remix.d.ts +15 -0
- package/dist/remix.js +30 -0
- package/dist/remix.js.map +1 -0
- package/dist/types-Z1nirh-F.d.cts +149 -0
- package/dist/types-Z1nirh-F.d.ts +149 -0
- package/package.json +94 -0
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bindContext,
|
|
3
|
+
encodeBase64,
|
|
4
|
+
generateRequestId,
|
|
5
|
+
getContextState,
|
|
6
|
+
logWithLevel,
|
|
7
|
+
sanitizeHeaderValues,
|
|
8
|
+
sanitizeLogString
|
|
9
|
+
} from "./chunk-YVMMCTXW.js";
|
|
10
|
+
|
|
11
|
+
// src/core/route.ts
|
|
12
|
+
function normalizeRoutePattern(route) {
|
|
13
|
+
if (!route) {
|
|
14
|
+
return "/";
|
|
15
|
+
}
|
|
16
|
+
const cleaned = stripQueryAndFragment(route).trim();
|
|
17
|
+
if (!cleaned) {
|
|
18
|
+
return "/";
|
|
19
|
+
}
|
|
20
|
+
const withoutMethod = stripMethodPrefix(cleaned);
|
|
21
|
+
const leading = withoutMethod.startsWith("/") ? withoutMethod : `/${withoutMethod}`;
|
|
22
|
+
const segments = leading.split("/").filter(Boolean);
|
|
23
|
+
if (segments.length === 0) {
|
|
24
|
+
return "/";
|
|
25
|
+
}
|
|
26
|
+
const normalized = segments.map(normalizeRouteSegment).join("/");
|
|
27
|
+
return `/${normalized}`;
|
|
28
|
+
}
|
|
29
|
+
function stripMethodPrefix(value) {
|
|
30
|
+
if (!/^[A-Z]+\s+\//.test(value)) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
const spaceIndex = value.search(/\s+/);
|
|
34
|
+
if (spaceIndex < 0) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
return value.slice(spaceIndex).trim();
|
|
38
|
+
}
|
|
39
|
+
function stripQueryAndFragment(value) {
|
|
40
|
+
const hashIndex = value.indexOf("#");
|
|
41
|
+
const beforeHash = hashIndex >= 0 ? value.slice(0, hashIndex) : value;
|
|
42
|
+
const queryIndex = beforeHash.indexOf("?");
|
|
43
|
+
return queryIndex >= 0 ? beforeHash.slice(0, queryIndex) : beforeHash;
|
|
44
|
+
}
|
|
45
|
+
function normalizeRouteSegment(segment) {
|
|
46
|
+
if (segment === "*") {
|
|
47
|
+
return "{wildcard}";
|
|
48
|
+
}
|
|
49
|
+
const param = extractRouteParam(segment);
|
|
50
|
+
if (param) {
|
|
51
|
+
return `{${param}}`;
|
|
52
|
+
}
|
|
53
|
+
return segment;
|
|
54
|
+
}
|
|
55
|
+
function extractRouteParam(segment) {
|
|
56
|
+
if (!segment) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
60
|
+
return normalizeNextParam(segment);
|
|
61
|
+
}
|
|
62
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
63
|
+
const inner = segment.slice(1, -1);
|
|
64
|
+
const trimmed = stripParamDecorators(inner);
|
|
65
|
+
return extractParamName(trimmed);
|
|
66
|
+
}
|
|
67
|
+
if (segment.startsWith(":") || segment.startsWith("$")) {
|
|
68
|
+
return extractParamName(segment.slice(1));
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function normalizeNextParam(segment) {
|
|
73
|
+
let inner = segment.slice(1, -1);
|
|
74
|
+
if (inner.startsWith("[") && inner.endsWith("]")) {
|
|
75
|
+
inner = inner.slice(1, -1);
|
|
76
|
+
}
|
|
77
|
+
if (inner.startsWith("...")) {
|
|
78
|
+
inner = inner.slice(3);
|
|
79
|
+
}
|
|
80
|
+
return extractParamName(inner);
|
|
81
|
+
}
|
|
82
|
+
function stripParamDecorators(value) {
|
|
83
|
+
let trimmed = value.trim();
|
|
84
|
+
if (!trimmed) {
|
|
85
|
+
return trimmed;
|
|
86
|
+
}
|
|
87
|
+
if (trimmed.endsWith("...")) {
|
|
88
|
+
trimmed = trimmed.slice(0, -3);
|
|
89
|
+
}
|
|
90
|
+
return trimmed.replace(/[?*+]+$/, "");
|
|
91
|
+
}
|
|
92
|
+
function extractParamName(value) {
|
|
93
|
+
if (!value) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const match = value.match(/^[A-Za-z0-9_-]+/);
|
|
97
|
+
return match?.[0] ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/core/config.ts
|
|
101
|
+
var defaultCapture = {
|
|
102
|
+
requestHeaders: true,
|
|
103
|
+
responseHeaders: true,
|
|
104
|
+
requestBody: "text",
|
|
105
|
+
responseBody: "text",
|
|
106
|
+
maxBodyBytes: 65536
|
|
107
|
+
};
|
|
108
|
+
var defaultRedaction = {
|
|
109
|
+
headers: ["authorization", "cookie", "set-cookie", "x-api-key"],
|
|
110
|
+
queryParams: [],
|
|
111
|
+
bodyJsonPaths: [],
|
|
112
|
+
replacement: "[REDACTED]"
|
|
113
|
+
};
|
|
114
|
+
var defaultRequestId = {
|
|
115
|
+
header: "request-id"
|
|
116
|
+
};
|
|
117
|
+
var defaultRoute = {
|
|
118
|
+
normalize: true,
|
|
119
|
+
normalizer: normalizeRoutePattern
|
|
120
|
+
};
|
|
121
|
+
var defaultExporterBase = {
|
|
122
|
+
headers: {},
|
|
123
|
+
timeoutMs: 3e4,
|
|
124
|
+
spanProcessor: "batch"
|
|
125
|
+
};
|
|
126
|
+
var XrayConfigError = class extends Error {
|
|
127
|
+
constructor(code, message) {
|
|
128
|
+
super(message);
|
|
129
|
+
this.code = code;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
function normalizeConfig(config) {
|
|
133
|
+
if (!config || !config.serviceName || !config.serviceName.trim()) {
|
|
134
|
+
throw new XrayConfigError("INVALID_CONFIG", "serviceName is required");
|
|
135
|
+
}
|
|
136
|
+
const logger = config.logger ?? console;
|
|
137
|
+
const logLevel = config.logLevel ?? "warn";
|
|
138
|
+
const capture = normalizeCapture(config.capture);
|
|
139
|
+
const redaction = normalizeRedaction(config.redaction);
|
|
140
|
+
const requestId = normalizeRequestId(config.requestId);
|
|
141
|
+
const route = normalizeRoute(config.route);
|
|
142
|
+
const exporter = normalizeExporter(config.endpointUrl, config.exporter);
|
|
143
|
+
return {
|
|
144
|
+
serviceName: config.serviceName.trim(),
|
|
145
|
+
environment: config.environment?.trim() || void 0,
|
|
146
|
+
version: config.version?.trim() || void 0,
|
|
147
|
+
logger,
|
|
148
|
+
logLevel,
|
|
149
|
+
exporter,
|
|
150
|
+
capture,
|
|
151
|
+
redaction,
|
|
152
|
+
requestId,
|
|
153
|
+
route
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function normalizeCapture(cfg) {
|
|
157
|
+
const capture = {
|
|
158
|
+
...defaultCapture,
|
|
159
|
+
...cfg
|
|
160
|
+
};
|
|
161
|
+
if (!["none", "text", "base64"].includes(capture.requestBody)) {
|
|
162
|
+
throw new XrayConfigError(
|
|
163
|
+
"INVALID_CONFIG",
|
|
164
|
+
"capture.requestBody must be none, text, or base64"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (!["none", "text", "base64"].includes(capture.responseBody)) {
|
|
168
|
+
throw new XrayConfigError(
|
|
169
|
+
"INVALID_CONFIG",
|
|
170
|
+
"capture.responseBody must be none, text, or base64"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (!Number.isFinite(capture.maxBodyBytes) || capture.maxBodyBytes < 0) {
|
|
174
|
+
throw new XrayConfigError("INVALID_CONFIG", "capture.maxBodyBytes must be >= 0");
|
|
175
|
+
}
|
|
176
|
+
return capture;
|
|
177
|
+
}
|
|
178
|
+
function normalizeRedaction(cfg) {
|
|
179
|
+
const redaction = {
|
|
180
|
+
...defaultRedaction,
|
|
181
|
+
...cfg
|
|
182
|
+
};
|
|
183
|
+
redaction.headers = normalizeStringList(redaction.headers);
|
|
184
|
+
redaction.queryParams = normalizeStringList(redaction.queryParams);
|
|
185
|
+
redaction.bodyJsonPaths = normalizeStringList(redaction.bodyJsonPaths);
|
|
186
|
+
redaction.replacement = redaction.replacement || defaultRedaction.replacement;
|
|
187
|
+
if (!redaction.replacement) {
|
|
188
|
+
throw new XrayConfigError("INVALID_REDACTION", "redaction.replacement must be non-empty");
|
|
189
|
+
}
|
|
190
|
+
return redaction;
|
|
191
|
+
}
|
|
192
|
+
function normalizeRequestId(cfg) {
|
|
193
|
+
const requestId = {
|
|
194
|
+
...defaultRequestId,
|
|
195
|
+
...cfg
|
|
196
|
+
};
|
|
197
|
+
requestId.header = requestId.header.trim().toLowerCase();
|
|
198
|
+
if (!requestId.header) {
|
|
199
|
+
throw new XrayConfigError("INVALID_CONFIG", "requestId.header must be non-empty");
|
|
200
|
+
}
|
|
201
|
+
return requestId;
|
|
202
|
+
}
|
|
203
|
+
function normalizeRoute(cfg) {
|
|
204
|
+
const route = {
|
|
205
|
+
...defaultRoute,
|
|
206
|
+
...cfg
|
|
207
|
+
};
|
|
208
|
+
if (route.normalize && !route.normalizer) {
|
|
209
|
+
route.normalizer = normalizeRoutePattern;
|
|
210
|
+
}
|
|
211
|
+
return route;
|
|
212
|
+
}
|
|
213
|
+
function normalizeExporter(endpointUrl, cfg) {
|
|
214
|
+
const resolvedEndpoint = normalizeExporterEndpoint(cfg?.endpointUrl ?? endpointUrl);
|
|
215
|
+
const rawHeaders = cfg?.headers ?? defaultExporterBase.headers ?? {};
|
|
216
|
+
const parsed = applyEndpointAuth(resolvedEndpoint, rawHeaders);
|
|
217
|
+
const exporter = {
|
|
218
|
+
endpointUrl: parsed.endpointUrl,
|
|
219
|
+
headers: parsed.headers,
|
|
220
|
+
timeoutMs: cfg?.timeoutMs ?? defaultExporterBase.timeoutMs,
|
|
221
|
+
spanProcessor: cfg?.spanProcessor ?? defaultExporterBase.spanProcessor
|
|
222
|
+
};
|
|
223
|
+
return exporter;
|
|
224
|
+
}
|
|
225
|
+
function normalizeExporterEndpoint(endpointUrl) {
|
|
226
|
+
const envUrl = typeof process !== "undefined" ? process.env?.["STAINLESS_XRAY_ENDPOINT_URL"] : void 0;
|
|
227
|
+
const resolved = endpointUrl ?? envUrl;
|
|
228
|
+
if (!resolved || !resolved.trim()) {
|
|
229
|
+
throw new XrayConfigError(
|
|
230
|
+
"INVALID_CONFIG",
|
|
231
|
+
"endpointUrl is required (set endpointUrl or STAINLESS_XRAY_ENDPOINT_URL)"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const trimmed = resolved.trim();
|
|
235
|
+
const withoutTrailingSlash = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
236
|
+
if (withoutTrailingSlash.endsWith("/v1/traces")) {
|
|
237
|
+
return withoutTrailingSlash;
|
|
238
|
+
}
|
|
239
|
+
return `${withoutTrailingSlash}/v1/traces`;
|
|
240
|
+
}
|
|
241
|
+
function normalizeStringList(values) {
|
|
242
|
+
if (!values) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return values.map((entry) => entry.trim()).filter(Boolean);
|
|
246
|
+
}
|
|
247
|
+
var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
248
|
+
var maybeBuffer = globalThis.Buffer;
|
|
249
|
+
function applyEndpointAuth(endpointUrl, headers) {
|
|
250
|
+
const resolvedHeaders = headers ?? {};
|
|
251
|
+
let url;
|
|
252
|
+
try {
|
|
253
|
+
url = new URL(endpointUrl);
|
|
254
|
+
} catch {
|
|
255
|
+
return { endpointUrl, headers: resolvedHeaders };
|
|
256
|
+
}
|
|
257
|
+
const username = decodeUserInfo(url.username);
|
|
258
|
+
const password = decodeUserInfo(url.password);
|
|
259
|
+
if (!username && !password) {
|
|
260
|
+
return { endpointUrl, headers: resolvedHeaders };
|
|
261
|
+
}
|
|
262
|
+
url.username = "";
|
|
263
|
+
url.password = "";
|
|
264
|
+
const sanitizedUrl = url.toString();
|
|
265
|
+
if (hasAuthorizationHeader(resolvedHeaders)) {
|
|
266
|
+
return { endpointUrl: sanitizedUrl, headers: resolvedHeaders };
|
|
267
|
+
}
|
|
268
|
+
const authorization = encodeBasicAuth(username, password);
|
|
269
|
+
if (!authorization) {
|
|
270
|
+
return { endpointUrl: sanitizedUrl, headers: resolvedHeaders };
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
endpointUrl: sanitizedUrl,
|
|
274
|
+
headers: {
|
|
275
|
+
...resolvedHeaders,
|
|
276
|
+
Authorization: authorization
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function decodeUserInfo(value) {
|
|
281
|
+
if (!value) {
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
return decodeURIComponent(value);
|
|
286
|
+
} catch {
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function hasAuthorizationHeader(headers) {
|
|
291
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
|
|
292
|
+
}
|
|
293
|
+
function encodeBasicAuth(username, password) {
|
|
294
|
+
const raw = `${username}:${password}`;
|
|
295
|
+
let bytes;
|
|
296
|
+
if (textEncoder) {
|
|
297
|
+
bytes = textEncoder.encode(raw);
|
|
298
|
+
} else if (maybeBuffer) {
|
|
299
|
+
bytes = maybeBuffer.from(raw, "utf8");
|
|
300
|
+
}
|
|
301
|
+
if (!bytes) {
|
|
302
|
+
return void 0;
|
|
303
|
+
}
|
|
304
|
+
const encoded = encodeBase64(bytes);
|
|
305
|
+
if (!encoded) {
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
return `Basic ${encoded}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/core/header_redaction.ts
|
|
312
|
+
var headerNameCompactor = /[-_.]/g;
|
|
313
|
+
var defaultHeaderMatcher = newHeaderRedactionMatcher(
|
|
314
|
+
defaultSensitiveHeaderNames(),
|
|
315
|
+
defaultSensitiveKeywords()
|
|
316
|
+
);
|
|
317
|
+
function authSchemePrefix(value) {
|
|
318
|
+
if (!value) {
|
|
319
|
+
return "";
|
|
320
|
+
}
|
|
321
|
+
const lower = value.toLowerCase();
|
|
322
|
+
if (lower.startsWith("basic")) {
|
|
323
|
+
return value.slice(0, "basic".length);
|
|
324
|
+
}
|
|
325
|
+
if (lower.startsWith("bearer")) {
|
|
326
|
+
return value.slice(0, "bearer".length);
|
|
327
|
+
}
|
|
328
|
+
if (lower.startsWith("digest")) {
|
|
329
|
+
return value.slice(0, "digest".length);
|
|
330
|
+
}
|
|
331
|
+
if (lower.startsWith("negotiate")) {
|
|
332
|
+
return value.slice(0, "negotiate".length);
|
|
333
|
+
}
|
|
334
|
+
return "";
|
|
335
|
+
}
|
|
336
|
+
function redactCookieValue(value, replacement) {
|
|
337
|
+
if (!value) {
|
|
338
|
+
return replacement;
|
|
339
|
+
}
|
|
340
|
+
const parts = value.split(";");
|
|
341
|
+
const redacted = [];
|
|
342
|
+
for (const part of parts) {
|
|
343
|
+
const segment = part.trim();
|
|
344
|
+
if (!segment) {
|
|
345
|
+
redacted.push(replacement);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const idx = segment.indexOf("=");
|
|
349
|
+
if (idx <= 0) {
|
|
350
|
+
redacted.push(replacement);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const name = segment.slice(0, idx);
|
|
354
|
+
if (!name) {
|
|
355
|
+
redacted.push(replacement);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
redacted.push(`${name}=${replacement}`);
|
|
359
|
+
}
|
|
360
|
+
return redacted.join("; ");
|
|
361
|
+
}
|
|
362
|
+
function redactSetCookieValue(value, replacement) {
|
|
363
|
+
if (!value) {
|
|
364
|
+
return replacement;
|
|
365
|
+
}
|
|
366
|
+
const parts = value.split(";");
|
|
367
|
+
const first = parts.shift() ?? "";
|
|
368
|
+
const idx = first.indexOf("=");
|
|
369
|
+
if (idx <= 0) {
|
|
370
|
+
return replacement;
|
|
371
|
+
}
|
|
372
|
+
const name = first.slice(0, idx);
|
|
373
|
+
if (!name) {
|
|
374
|
+
return replacement;
|
|
375
|
+
}
|
|
376
|
+
const redacted = `${name}=${replacement}`;
|
|
377
|
+
if (parts.length === 0) {
|
|
378
|
+
return redacted;
|
|
379
|
+
}
|
|
380
|
+
return `${redacted};${parts.join(";")}`;
|
|
381
|
+
}
|
|
382
|
+
function addSensitiveHeaderNames(target, headers) {
|
|
383
|
+
for (const header of headers) {
|
|
384
|
+
const normalized = normalizeHeaderName(header);
|
|
385
|
+
if (normalized) {
|
|
386
|
+
target.add(normalized);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function buildKeywordSets(keywords) {
|
|
391
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
392
|
+
const compacted = /* @__PURE__ */ new Set();
|
|
393
|
+
for (const keyword of keywords) {
|
|
394
|
+
const normalized = normalizeHeaderName(keyword);
|
|
395
|
+
if (!normalized) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const compactedKeyword = compactNormalizedHeaderName(normalized);
|
|
399
|
+
if (compactedKeyword) {
|
|
400
|
+
compacted.add(compactedKeyword);
|
|
401
|
+
}
|
|
402
|
+
if (!normalized.includes("-") && !normalized.includes("_") && !normalized.includes(".")) {
|
|
403
|
+
tokens.add(normalized);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
compacted: Array.from(compacted).sort(),
|
|
408
|
+
tokens
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function compactNormalizedHeaderName(normalized) {
|
|
412
|
+
if (!normalized) {
|
|
413
|
+
return "";
|
|
414
|
+
}
|
|
415
|
+
return normalized.replace(headerNameCompactor, "");
|
|
416
|
+
}
|
|
417
|
+
function defaultSensitiveHeaderNames() {
|
|
418
|
+
return [
|
|
419
|
+
"authorization",
|
|
420
|
+
"cookie",
|
|
421
|
+
"proxy-authenticate",
|
|
422
|
+
"proxy-authorization",
|
|
423
|
+
"set-cookie",
|
|
424
|
+
"www-authenticate"
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
function defaultSensitiveKeywords() {
|
|
428
|
+
return [
|
|
429
|
+
"api-key",
|
|
430
|
+
"api_key",
|
|
431
|
+
"apikey",
|
|
432
|
+
"auth",
|
|
433
|
+
"authenticate",
|
|
434
|
+
"authorization",
|
|
435
|
+
"credential",
|
|
436
|
+
"password",
|
|
437
|
+
"passwd",
|
|
438
|
+
"private-key",
|
|
439
|
+
"private_key",
|
|
440
|
+
"privatekey",
|
|
441
|
+
"secret",
|
|
442
|
+
"session",
|
|
443
|
+
"sessionid",
|
|
444
|
+
"signature",
|
|
445
|
+
"token"
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
function newHeaderRedactionMatcher(names, keywords) {
|
|
449
|
+
const exactSensitive = /* @__PURE__ */ new Set();
|
|
450
|
+
addSensitiveHeaderNames(exactSensitive, names);
|
|
451
|
+
const { compacted, tokens } = buildKeywordSets(keywords);
|
|
452
|
+
return {
|
|
453
|
+
exactSensitive,
|
|
454
|
+
keywordCompacted: compacted,
|
|
455
|
+
keywordTokens: tokens
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function normalizeHeaderName(name) {
|
|
459
|
+
return name.trim().toLowerCase();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/core/redaction.ts
|
|
463
|
+
function applyRedaction(config, log) {
|
|
464
|
+
const redacted = { ...log };
|
|
465
|
+
if (redacted.requestHeaders) {
|
|
466
|
+
redacted.requestHeaders = redactHeaders(redacted.requestHeaders, config);
|
|
467
|
+
}
|
|
468
|
+
if (redacted.responseHeaders) {
|
|
469
|
+
redacted.responseHeaders = redactHeaders(redacted.responseHeaders, config);
|
|
470
|
+
}
|
|
471
|
+
redacted.url = redactUrl(redacted.url, config);
|
|
472
|
+
if (redacted.requestBody) {
|
|
473
|
+
redacted.requestBody = redactBody(redacted.requestBody, redacted.requestHeaders, config);
|
|
474
|
+
}
|
|
475
|
+
if (redacted.responseBody) {
|
|
476
|
+
redacted.responseBody = redactBody(redacted.responseBody, redacted.responseHeaders, config);
|
|
477
|
+
}
|
|
478
|
+
return redacted;
|
|
479
|
+
}
|
|
480
|
+
function redactHeaders(headers, config) {
|
|
481
|
+
const list = new Set(config.headers.map((name) => name.toLowerCase()));
|
|
482
|
+
const result = {};
|
|
483
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
484
|
+
const lower = name.toLowerCase();
|
|
485
|
+
if (!list.has(lower)) {
|
|
486
|
+
result[name] = value;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const redactValue = (entry) => redactHeaderValue(lower, entry, config.replacement);
|
|
490
|
+
if (Array.isArray(value)) {
|
|
491
|
+
result[name] = value.map(redactValue);
|
|
492
|
+
} else {
|
|
493
|
+
result[name] = redactValue(value);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
function redactHeaderValue(name, value, replacement) {
|
|
499
|
+
switch (name) {
|
|
500
|
+
case "authorization":
|
|
501
|
+
case "proxy-authorization": {
|
|
502
|
+
const scheme = authSchemePrefix(value);
|
|
503
|
+
if (!scheme) {
|
|
504
|
+
return replacement;
|
|
505
|
+
}
|
|
506
|
+
return `${scheme} ${replacement}`;
|
|
507
|
+
}
|
|
508
|
+
case "cookie":
|
|
509
|
+
return redactCookieValue(value, replacement);
|
|
510
|
+
case "set-cookie":
|
|
511
|
+
return redactSetCookieValue(value, replacement);
|
|
512
|
+
default:
|
|
513
|
+
return replacement;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function redactUrl(value, config) {
|
|
517
|
+
if (!value || config.queryParams.length === 0) {
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const parsed = new URL(value);
|
|
522
|
+
const params = parsed.searchParams;
|
|
523
|
+
if (!params || params.size === 0) {
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
const redact = new Set(config.queryParams.map((key) => key.toLowerCase()));
|
|
527
|
+
const next = new URLSearchParams();
|
|
528
|
+
params.forEach((val, key) => {
|
|
529
|
+
if (redact.has(key.toLowerCase())) {
|
|
530
|
+
next.append(key, config.replacement);
|
|
531
|
+
} else {
|
|
532
|
+
next.append(key, val);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
parsed.search = next.toString();
|
|
536
|
+
return parsed.toString();
|
|
537
|
+
} catch {
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function redactBody(body, headers, config) {
|
|
542
|
+
if (config.bodyJsonPaths.length === 0) {
|
|
543
|
+
return body;
|
|
544
|
+
}
|
|
545
|
+
if (!body.value || body.encoding !== "utf8") {
|
|
546
|
+
return body;
|
|
547
|
+
}
|
|
548
|
+
if (!isJsonContentType(headers)) {
|
|
549
|
+
return body;
|
|
550
|
+
}
|
|
551
|
+
let parsed;
|
|
552
|
+
try {
|
|
553
|
+
parsed = JSON.parse(body.value);
|
|
554
|
+
} catch {
|
|
555
|
+
return body;
|
|
556
|
+
}
|
|
557
|
+
for (const path of config.bodyJsonPaths) {
|
|
558
|
+
const segments = parseJsonPath(path);
|
|
559
|
+
if (!segments) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
redactJsonPath(parsed, segments, config.replacement);
|
|
563
|
+
}
|
|
564
|
+
const next = { ...body };
|
|
565
|
+
next.value = JSON.stringify(parsed);
|
|
566
|
+
return next;
|
|
567
|
+
}
|
|
568
|
+
function isJsonContentType(headers) {
|
|
569
|
+
if (!headers) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
const contentType = headers["content-type"] || headers["Content-Type"];
|
|
573
|
+
const value = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
574
|
+
if (!value) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
const normalized = value.split(";")[0];
|
|
578
|
+
if (!normalized) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
const trimmed = normalized.trim().toLowerCase();
|
|
582
|
+
return trimmed === "application/json" || trimmed.endsWith("+json");
|
|
583
|
+
}
|
|
584
|
+
function parseJsonPath(path) {
|
|
585
|
+
const trimmed = path.trim();
|
|
586
|
+
if (!trimmed) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
const normalized = trimmed.startsWith("$.") ? trimmed.slice(2) : trimmed;
|
|
590
|
+
if (!normalized) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
const segments = [];
|
|
594
|
+
const parts = normalized.split(".");
|
|
595
|
+
for (const part of parts) {
|
|
596
|
+
if (!part) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
let cursor = part;
|
|
600
|
+
const bracketIndex = cursor.indexOf("[");
|
|
601
|
+
if (bracketIndex === -1) {
|
|
602
|
+
segments.push(cursor);
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const name = cursor.slice(0, bracketIndex);
|
|
606
|
+
if (name) {
|
|
607
|
+
segments.push(name);
|
|
608
|
+
}
|
|
609
|
+
cursor = cursor.slice(bracketIndex);
|
|
610
|
+
const matches = cursor.match(/\[(\d+)\]/g);
|
|
611
|
+
if (!matches) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
for (const match of matches) {
|
|
615
|
+
const indexValue = match.slice(1, -1);
|
|
616
|
+
const index = Number.parseInt(indexValue, 10);
|
|
617
|
+
if (Number.isFinite(index)) {
|
|
618
|
+
segments.push(index);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return segments.length > 0 ? segments : null;
|
|
623
|
+
}
|
|
624
|
+
function redactJsonPath(value, segments, replacement) {
|
|
625
|
+
if (!value || segments.length === 0) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
let current = value;
|
|
629
|
+
for (let i = 0; i < segments.length; i += 1) {
|
|
630
|
+
const segment = segments[i];
|
|
631
|
+
if (segment === void 0) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const isLast = i === segments.length - 1;
|
|
635
|
+
if (typeof segment === "number") {
|
|
636
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (isLast) {
|
|
640
|
+
current[segment] = replacement;
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
current = current[segment];
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (!current || typeof current !== "object") {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const record = current;
|
|
650
|
+
if (!(segment in record)) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (isLast) {
|
|
654
|
+
record[segment] = replacement;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
current = record[segment];
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/core/attributes.ts
|
|
662
|
+
import {
|
|
663
|
+
ATTR_CLIENT_ADDRESS,
|
|
664
|
+
ATTR_HTTP_REQUEST_BODY_SIZE,
|
|
665
|
+
ATTR_HTTP_REQUEST_METHOD,
|
|
666
|
+
ATTR_HTTP_RESPONSE_BODY_SIZE,
|
|
667
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
668
|
+
ATTR_HTTP_ROUTE,
|
|
669
|
+
ATTR_URL_FULL,
|
|
670
|
+
ATTR_URL_PATH,
|
|
671
|
+
ATTR_USER_ID
|
|
672
|
+
} from "@opentelemetry/semantic-conventions/incubating";
|
|
673
|
+
|
|
674
|
+
// src/core/attrkey.ts
|
|
675
|
+
var AttributeKeyRequestBody = "http.request.body";
|
|
676
|
+
var AttributeKeyRequestBodyEncoding = "http.request.body.encoding";
|
|
677
|
+
var AttributeKeyRequestBodyTruncated = "http.request.body.truncated";
|
|
678
|
+
var AttributeKeyRequestID = "http.request.id";
|
|
679
|
+
var AttributeKeyResponseBody = "http.response.body";
|
|
680
|
+
var AttributeKeyResponseBodyEncoding = "http.response.body.encoding";
|
|
681
|
+
var AttributeKeyResponseBodyTruncated = "http.response.body.truncated";
|
|
682
|
+
var AttributeKeySpanDrop = "stainlessxray.internal.drop";
|
|
683
|
+
|
|
684
|
+
// src/core/attributes.ts
|
|
685
|
+
function setHeaderAttributes(span, headers, prefix) {
|
|
686
|
+
if (!headers) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const keys = Object.keys(headers).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
690
|
+
for (const key of keys) {
|
|
691
|
+
const values = headers[key];
|
|
692
|
+
if (!values || Array.isArray(values) && values.length === 0) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
span.setAttribute(prefix + key.toLowerCase(), Array.isArray(values) ? values : [values]);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function setRequestAttributes(span, request, urlFull) {
|
|
699
|
+
span.setAttribute(ATTR_HTTP_REQUEST_METHOD, request.method);
|
|
700
|
+
const effectiveUrl = urlFull ?? request.url;
|
|
701
|
+
if (effectiveUrl) {
|
|
702
|
+
span.setAttribute(ATTR_URL_FULL, effectiveUrl);
|
|
703
|
+
const path = extractPath(effectiveUrl);
|
|
704
|
+
if (path) {
|
|
705
|
+
span.setAttribute(ATTR_URL_PATH, path);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const clientAddress = clientAddressForRequest(
|
|
709
|
+
request.headers,
|
|
710
|
+
request.remoteAddress,
|
|
711
|
+
request.redactionReplacement
|
|
712
|
+
);
|
|
713
|
+
if (clientAddress) {
|
|
714
|
+
span.setAttribute(ATTR_CLIENT_ADDRESS, clientAddress);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function extractPath(url) {
|
|
718
|
+
try {
|
|
719
|
+
return new URL(url).pathname;
|
|
720
|
+
} catch {
|
|
721
|
+
const match = url.match(/^[^?#]*/);
|
|
722
|
+
return match?.[0] || void 0;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function clientAddressForRequest(headers, remoteAddress, redactionReplacement) {
|
|
726
|
+
const forwarded = forwardedClientAddress(
|
|
727
|
+
headerValues(headers, "forwarded"),
|
|
728
|
+
redactionReplacement
|
|
729
|
+
);
|
|
730
|
+
if (forwarded) {
|
|
731
|
+
return forwarded;
|
|
732
|
+
}
|
|
733
|
+
const xForwarded = xForwardedForClientAddress(
|
|
734
|
+
headerValues(headers, "x-forwarded-for"),
|
|
735
|
+
redactionReplacement
|
|
736
|
+
);
|
|
737
|
+
if (xForwarded) {
|
|
738
|
+
return xForwarded;
|
|
739
|
+
}
|
|
740
|
+
const xRealIp = xRealIpClientAddress(headerValues(headers, "x-real-ip"), redactionReplacement);
|
|
741
|
+
if (xRealIp) {
|
|
742
|
+
return xRealIp;
|
|
743
|
+
}
|
|
744
|
+
if (!remoteAddress) {
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
return remoteAddrHost(remoteAddress);
|
|
748
|
+
}
|
|
749
|
+
function forwardedClientAddress(values, redactionReplacement) {
|
|
750
|
+
for (const value of values) {
|
|
751
|
+
if (!value) {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const entries = value.split(",");
|
|
755
|
+
for (const entry of entries) {
|
|
756
|
+
const params = entry.split(";");
|
|
757
|
+
for (const param of params) {
|
|
758
|
+
const [rawKey, ...rest] = param.split("=");
|
|
759
|
+
if (!rawKey) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (rawKey.trim().toLowerCase() !== "for") {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const rawValue = rest.join("=").trim();
|
|
766
|
+
const normalized = normalizeKnownAddress(rawValue, redactionReplacement);
|
|
767
|
+
if (normalized) {
|
|
768
|
+
return normalized;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
function xForwardedForClientAddress(values, redactionReplacement) {
|
|
776
|
+
for (const value of values) {
|
|
777
|
+
if (!value) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
const entries = value.split(",");
|
|
781
|
+
for (const entry of entries) {
|
|
782
|
+
const normalized = normalizeKnownAddress(entry, redactionReplacement);
|
|
783
|
+
if (normalized) {
|
|
784
|
+
return normalized;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return void 0;
|
|
789
|
+
}
|
|
790
|
+
function xRealIpClientAddress(values, redactionReplacement) {
|
|
791
|
+
for (const value of values) {
|
|
792
|
+
if (!value) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const normalized = normalizeKnownAddress(value, redactionReplacement);
|
|
796
|
+
if (normalized) {
|
|
797
|
+
return normalized;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return void 0;
|
|
801
|
+
}
|
|
802
|
+
function normalizeKnownAddress(value, redactionReplacement) {
|
|
803
|
+
const normalized = normalizeAddress(value, redactionReplacement);
|
|
804
|
+
if (!normalized) {
|
|
805
|
+
return void 0;
|
|
806
|
+
}
|
|
807
|
+
if (normalized.toLowerCase() === "unknown") {
|
|
808
|
+
return void 0;
|
|
809
|
+
}
|
|
810
|
+
return normalized;
|
|
811
|
+
}
|
|
812
|
+
function normalizeAddress(value, redactionReplacement) {
|
|
813
|
+
if (!value) {
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
let trimmed = value.trim();
|
|
817
|
+
if (!trimmed) {
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
if (redactionReplacement && trimmed === redactionReplacement) {
|
|
821
|
+
return void 0;
|
|
822
|
+
}
|
|
823
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1) {
|
|
824
|
+
trimmed = trimmed.slice(1, -1).trim();
|
|
825
|
+
}
|
|
826
|
+
if (!trimmed) {
|
|
827
|
+
return void 0;
|
|
828
|
+
}
|
|
829
|
+
if (trimmed.startsWith("[")) {
|
|
830
|
+
const end = trimmed.indexOf("]");
|
|
831
|
+
if (end !== -1) {
|
|
832
|
+
return trimmed.slice(1, end);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const colonCount = (trimmed.match(/:/g) ?? []).length;
|
|
836
|
+
if (colonCount === 1) {
|
|
837
|
+
const host = trimmed.split(":")[0];
|
|
838
|
+
return host || void 0;
|
|
839
|
+
}
|
|
840
|
+
return trimmed;
|
|
841
|
+
}
|
|
842
|
+
function remoteAddrHost(value) {
|
|
843
|
+
return normalizeAddress(value);
|
|
844
|
+
}
|
|
845
|
+
function headerValues(headers, name) {
|
|
846
|
+
if (!headers) {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
const target = name.toLowerCase();
|
|
850
|
+
const values = [];
|
|
851
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
852
|
+
if (key.toLowerCase() !== target) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (Array.isArray(value)) {
|
|
856
|
+
values.push(...value);
|
|
857
|
+
} else {
|
|
858
|
+
values.push(value);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return values;
|
|
862
|
+
}
|
|
863
|
+
function setRequestBodyAttributes(span, body) {
|
|
864
|
+
if (!body.value) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
span.setAttribute(AttributeKeyRequestBody, body.value);
|
|
868
|
+
span.setAttribute(AttributeKeyRequestBodyEncoding, body.encoding);
|
|
869
|
+
if (body.truncated) {
|
|
870
|
+
span.setAttribute(AttributeKeyRequestBodyTruncated, true);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function setRequestBodySizeAttribute(span, size) {
|
|
874
|
+
span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, size);
|
|
875
|
+
}
|
|
876
|
+
function setResponseBodyAttributes(span, body) {
|
|
877
|
+
if (!body.value) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
span.setAttribute(AttributeKeyResponseBody, body.value);
|
|
881
|
+
span.setAttribute(AttributeKeyResponseBodyEncoding, body.encoding);
|
|
882
|
+
if (body.truncated) {
|
|
883
|
+
span.setAttribute(AttributeKeyResponseBodyTruncated, true);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function setResponseBodySizeAttribute(span, size) {
|
|
887
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_BODY_SIZE, size);
|
|
888
|
+
}
|
|
889
|
+
function setResponseStatusAttribute(span, statusCode) {
|
|
890
|
+
span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);
|
|
891
|
+
}
|
|
892
|
+
function setRouteAttribute(span, route) {
|
|
893
|
+
if (route) {
|
|
894
|
+
span.setAttribute(ATTR_HTTP_ROUTE, route);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function setUserIdAttribute(span, userId) {
|
|
898
|
+
span.setAttribute(ATTR_USER_ID, userId);
|
|
899
|
+
}
|
|
900
|
+
function setRequestIdAttribute(span, requestId) {
|
|
901
|
+
span.setAttribute(AttributeKeyRequestID, requestId);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/core/otel.ts
|
|
905
|
+
import {
|
|
906
|
+
diag,
|
|
907
|
+
ROOT_CONTEXT,
|
|
908
|
+
SpanKind,
|
|
909
|
+
SpanStatusCode
|
|
910
|
+
} from "@opentelemetry/api";
|
|
911
|
+
import {
|
|
912
|
+
AlwaysOnSampler,
|
|
913
|
+
BasicTracerProvider,
|
|
914
|
+
BatchSpanProcessor,
|
|
915
|
+
SimpleSpanProcessor
|
|
916
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
917
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
918
|
+
import {
|
|
919
|
+
ATTR_SERVICE_NAME,
|
|
920
|
+
ATTR_TELEMETRY_SDK_LANGUAGE,
|
|
921
|
+
ATTR_TELEMETRY_SDK_NAME,
|
|
922
|
+
ATTR_TELEMETRY_SDK_VERSION
|
|
923
|
+
} from "@opentelemetry/semantic-conventions";
|
|
924
|
+
var defaultAttributeCountLimit = 128;
|
|
925
|
+
function createTracerProvider(config, exporter) {
|
|
926
|
+
if (config.exporter.endpointUrl.startsWith("http://")) {
|
|
927
|
+
diag.warn("xray: OTLP endpoint uses plaintext HTTP");
|
|
928
|
+
}
|
|
929
|
+
const attributeValueLengthLimit = Math.max(1, Math.ceil(config.capture.maxBodyBytes * 4 / 3));
|
|
930
|
+
const resource = resourceFromAttributes({
|
|
931
|
+
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
932
|
+
[ATTR_TELEMETRY_SDK_LANGUAGE]: isNodeRuntime() ? "nodejs" : "webjs",
|
|
933
|
+
[ATTR_TELEMETRY_SDK_NAME]: "stainless-xray",
|
|
934
|
+
[ATTR_TELEMETRY_SDK_VERSION]: sdkVersion()
|
|
935
|
+
});
|
|
936
|
+
const spanProcessor = createSpanProcessor(config.exporter.spanProcessor, exporter);
|
|
937
|
+
const dropProcessor = new DropFilterSpanProcessor(spanProcessor);
|
|
938
|
+
const provider = new BasicTracerProvider({
|
|
939
|
+
forceFlushTimeoutMillis: 3e4,
|
|
940
|
+
generalLimits: {
|
|
941
|
+
attributeCountLimit: defaultAttributeCountLimit,
|
|
942
|
+
attributeValueLengthLimit
|
|
943
|
+
},
|
|
944
|
+
resource,
|
|
945
|
+
sampler: new AlwaysOnSampler(),
|
|
946
|
+
spanLimits: {
|
|
947
|
+
attributeCountLimit: defaultAttributeCountLimit,
|
|
948
|
+
attributePerEventCountLimit: defaultAttributeCountLimit,
|
|
949
|
+
attributePerLinkCountLimit: defaultAttributeCountLimit,
|
|
950
|
+
attributeValueLengthLimit,
|
|
951
|
+
eventCountLimit: defaultAttributeCountLimit,
|
|
952
|
+
linkCountLimit: defaultAttributeCountLimit
|
|
953
|
+
},
|
|
954
|
+
spanProcessors: [dropProcessor]
|
|
955
|
+
});
|
|
956
|
+
return provider;
|
|
957
|
+
}
|
|
958
|
+
function tracerFromProvider(provider) {
|
|
959
|
+
return provider.getTracer("stainless-xray");
|
|
960
|
+
}
|
|
961
|
+
function spanFromTracer(tracer, name) {
|
|
962
|
+
return tracer.startSpan(name, { kind: SpanKind.SERVER }, ROOT_CONTEXT);
|
|
963
|
+
}
|
|
964
|
+
function spanStatusFromError(span, err) {
|
|
965
|
+
if (err instanceof Error) {
|
|
966
|
+
span.recordException(err);
|
|
967
|
+
} else if (typeof err === "string") {
|
|
968
|
+
span.recordException(err);
|
|
969
|
+
} else {
|
|
970
|
+
span.recordException({ message: String(err) });
|
|
971
|
+
}
|
|
972
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
973
|
+
}
|
|
974
|
+
var DropFilterSpanProcessor = class {
|
|
975
|
+
constructor(next) {
|
|
976
|
+
this.next = next;
|
|
977
|
+
}
|
|
978
|
+
forceFlush() {
|
|
979
|
+
return this.next.forceFlush();
|
|
980
|
+
}
|
|
981
|
+
onEnd(span) {
|
|
982
|
+
if (span.attributes[AttributeKeySpanDrop] === true) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
this.next.onEnd(span);
|
|
986
|
+
}
|
|
987
|
+
onStart(span, parentContext) {
|
|
988
|
+
this.next.onStart(span, parentContext);
|
|
989
|
+
}
|
|
990
|
+
shutdown() {
|
|
991
|
+
return this.next.shutdown();
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
function createSpanProcessor(mode, exporter) {
|
|
995
|
+
if (mode === "simple") {
|
|
996
|
+
return new SimpleSpanProcessor(exporter);
|
|
997
|
+
}
|
|
998
|
+
return new BatchSpanProcessor(exporter, {
|
|
999
|
+
maxQueueSize: 2048,
|
|
1000
|
+
maxExportBatchSize: 512,
|
|
1001
|
+
scheduledDelayMillis: 5e3,
|
|
1002
|
+
exportTimeoutMillis: 3e4
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
function sdkVersion() {
|
|
1006
|
+
if (typeof __XRAY_VERSION__ !== "undefined") {
|
|
1007
|
+
return __XRAY_VERSION__;
|
|
1008
|
+
}
|
|
1009
|
+
return "unknown";
|
|
1010
|
+
}
|
|
1011
|
+
function isNodeRuntime() {
|
|
1012
|
+
const maybeProcess = globalThis.process;
|
|
1013
|
+
return !!maybeProcess?.versions?.node;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/core/emitter.ts
|
|
1017
|
+
function createEmitter(config, exporter) {
|
|
1018
|
+
const resolved = normalizeConfig(config);
|
|
1019
|
+
if (!exporter) {
|
|
1020
|
+
throw new XrayConfigError(
|
|
1021
|
+
"INVALID_CONFIG",
|
|
1022
|
+
"exporter is required (use @stainlessdev/xray-node or @stainlessdev/xray-fetch)"
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
logWithLevel(resolved.logger, "info", resolved.logLevel, "xray: emitter configured", {
|
|
1026
|
+
serviceName: resolved.serviceName,
|
|
1027
|
+
environment: resolved.environment,
|
|
1028
|
+
version: resolved.version,
|
|
1029
|
+
exporterEndpoint: resolved.exporter.endpointUrl,
|
|
1030
|
+
spanProcessor: resolved.exporter.spanProcessor
|
|
1031
|
+
});
|
|
1032
|
+
const tracerProvider = createTracerProvider(resolved, exporter);
|
|
1033
|
+
const tracer = tracerFromProvider(tracerProvider);
|
|
1034
|
+
return {
|
|
1035
|
+
config: resolved,
|
|
1036
|
+
startRequest: (req) => startRequest(resolved, tracer, req),
|
|
1037
|
+
endRequest: (ctx, res, err) => endRequest(resolved, ctx, res, err),
|
|
1038
|
+
flush: () => tracerProvider.forceFlush(),
|
|
1039
|
+
shutdown: () => tracerProvider.shutdown()
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function startRequest(config, tracer, req) {
|
|
1043
|
+
const startTimeMs = Number.isFinite(req.startTimeMs) ? req.startTimeMs : Date.now();
|
|
1044
|
+
req.startTimeMs = startTimeMs;
|
|
1045
|
+
const explicitRequestId = normalizeRequestIdCandidate(req.requestId);
|
|
1046
|
+
req.requestId = explicitRequestId;
|
|
1047
|
+
if (req.route && config.route.normalize) {
|
|
1048
|
+
req.route = config.route.normalizer ? config.route.normalizer(req.route) : normalizeRoutePattern(req.route);
|
|
1049
|
+
}
|
|
1050
|
+
const span = spanFromTracer(tracer, spanNameFromRequest(req));
|
|
1051
|
+
const context = {
|
|
1052
|
+
requestId: explicitRequestId ?? "",
|
|
1053
|
+
traceId: span?.spanContext().traceId,
|
|
1054
|
+
spanId: span?.spanContext().spanId,
|
|
1055
|
+
setUserId: (id) => {
|
|
1056
|
+
const state2 = getContextState(context);
|
|
1057
|
+
if (!state2) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
state2.userId = id;
|
|
1061
|
+
if (span && id) {
|
|
1062
|
+
try {
|
|
1063
|
+
setUserIdAttribute(span, id);
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
setSessionId: (id) => {
|
|
1069
|
+
const state2 = getContextState(context);
|
|
1070
|
+
if (!state2) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
state2.sessionId = id;
|
|
1074
|
+
},
|
|
1075
|
+
setAttribute: (key, value) => {
|
|
1076
|
+
const state2 = getContextState(context);
|
|
1077
|
+
if (!state2) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
state2.attributes[key] = value;
|
|
1081
|
+
if (span) {
|
|
1082
|
+
try {
|
|
1083
|
+
span.setAttribute(key, value);
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
addEvent: (name, attributes) => {
|
|
1089
|
+
const state2 = getContextState(context);
|
|
1090
|
+
if (!state2) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
state2.events.push({ name, attributes });
|
|
1094
|
+
if (span) {
|
|
1095
|
+
try {
|
|
1096
|
+
span.addEvent(name, attributes);
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
setError: (err) => {
|
|
1102
|
+
const state2 = getContextState(context);
|
|
1103
|
+
if (!state2) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
state2.error = err;
|
|
1107
|
+
if (span) {
|
|
1108
|
+
try {
|
|
1109
|
+
spanStatusFromError(span, err);
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
const state = {
|
|
1116
|
+
request: req,
|
|
1117
|
+
config,
|
|
1118
|
+
span,
|
|
1119
|
+
context,
|
|
1120
|
+
attributes: {},
|
|
1121
|
+
events: []
|
|
1122
|
+
};
|
|
1123
|
+
bindContext(context, state);
|
|
1124
|
+
return context;
|
|
1125
|
+
}
|
|
1126
|
+
function endRequest(config, ctx, res, err) {
|
|
1127
|
+
const state = getContextState(ctx);
|
|
1128
|
+
const endTimeMs = Number.isFinite(res.endTimeMs) ? res.endTimeMs : Date.now();
|
|
1129
|
+
res.endTimeMs = endTimeMs;
|
|
1130
|
+
if (!state) {
|
|
1131
|
+
const resolvedRequestId2 = resolveFinalRequestId(config, ctx.requestId, res.headers);
|
|
1132
|
+
ctx.requestId = resolvedRequestId2;
|
|
1133
|
+
const fallbackLog = {
|
|
1134
|
+
requestId: resolvedRequestId2,
|
|
1135
|
+
serviceName: config.serviceName,
|
|
1136
|
+
method: res.statusCode ? "UNKNOWN" : "UNKNOWN",
|
|
1137
|
+
url: "",
|
|
1138
|
+
durationMs: 0,
|
|
1139
|
+
statusCode: res.statusCode,
|
|
1140
|
+
timestamp: new Date(endTimeMs).toISOString()
|
|
1141
|
+
};
|
|
1142
|
+
return fallbackLog;
|
|
1143
|
+
}
|
|
1144
|
+
const request = state.request;
|
|
1145
|
+
const resolvedRequestId = resolveFinalRequestId(
|
|
1146
|
+
config,
|
|
1147
|
+
request.requestId || ctx.requestId,
|
|
1148
|
+
res.headers
|
|
1149
|
+
);
|
|
1150
|
+
request.requestId = resolvedRequestId;
|
|
1151
|
+
ctx.requestId = resolvedRequestId;
|
|
1152
|
+
const capture = resolveCapture(config.capture, state.captureOverride);
|
|
1153
|
+
const redaction = resolveRedaction(config.redaction, state.redactionOverride);
|
|
1154
|
+
const route = request.route;
|
|
1155
|
+
const url = sanitizeLogString(request.url);
|
|
1156
|
+
const log = {
|
|
1157
|
+
requestId: resolvedRequestId,
|
|
1158
|
+
traceId: state.span?.spanContext().traceId,
|
|
1159
|
+
spanId: state.span?.spanContext().spanId,
|
|
1160
|
+
serviceName: config.serviceName,
|
|
1161
|
+
method: request.method,
|
|
1162
|
+
url,
|
|
1163
|
+
route,
|
|
1164
|
+
statusCode: res.statusCode,
|
|
1165
|
+
durationMs: Math.max(0, endTimeMs - request.startTimeMs),
|
|
1166
|
+
requestHeaders: capture.requestHeaders ? sanitizeHeaderValues(request.headers) : void 0,
|
|
1167
|
+
responseHeaders: capture.responseHeaders ? sanitizeHeaderValues(res.headers) : void 0,
|
|
1168
|
+
requestBody: capture.requestBody === "none" ? void 0 : request.body,
|
|
1169
|
+
responseBody: capture.responseBody === "none" ? void 0 : res.body,
|
|
1170
|
+
userId: state.userId ?? void 0,
|
|
1171
|
+
sessionId: state.sessionId ?? void 0,
|
|
1172
|
+
error: buildError(err ?? state.error),
|
|
1173
|
+
attributes: Object.keys(state.attributes).length > 0 ? { ...state.attributes } : void 0,
|
|
1174
|
+
timestamp: new Date(endTimeMs).toISOString()
|
|
1175
|
+
};
|
|
1176
|
+
const redacted = applyRedaction(redaction, log);
|
|
1177
|
+
if (redacted.route && config.route.normalize) {
|
|
1178
|
+
const normalized = config.route.normalizer ? config.route.normalizer(redacted.route) : normalizeRoutePattern(redacted.route);
|
|
1179
|
+
redacted.route = normalized;
|
|
1180
|
+
}
|
|
1181
|
+
const span = state.span;
|
|
1182
|
+
if (span) {
|
|
1183
|
+
try {
|
|
1184
|
+
const clientAddressHeaders = redactHeaders(request.headers, redaction);
|
|
1185
|
+
setRequestAttributes(
|
|
1186
|
+
span,
|
|
1187
|
+
{
|
|
1188
|
+
...request,
|
|
1189
|
+
headers: clientAddressHeaders,
|
|
1190
|
+
redactionReplacement: redaction.replacement
|
|
1191
|
+
},
|
|
1192
|
+
redacted.url
|
|
1193
|
+
);
|
|
1194
|
+
setRequestIdAttribute(span, redacted.requestId);
|
|
1195
|
+
span.setAttribute("service.name", config.serviceName);
|
|
1196
|
+
if (redacted.statusCode != null) {
|
|
1197
|
+
setResponseStatusAttribute(span, redacted.statusCode);
|
|
1198
|
+
}
|
|
1199
|
+
if (redacted.route) {
|
|
1200
|
+
setRouteAttribute(span, redacted.route);
|
|
1201
|
+
span.updateName(`${request.method} ${redacted.route}`);
|
|
1202
|
+
} else {
|
|
1203
|
+
span.updateName(spanNameFromRequest(request));
|
|
1204
|
+
}
|
|
1205
|
+
if (redacted.requestHeaders) {
|
|
1206
|
+
setHeaderAttributes(span, redacted.requestHeaders, "http.request.header.");
|
|
1207
|
+
}
|
|
1208
|
+
if (redacted.responseHeaders) {
|
|
1209
|
+
setHeaderAttributes(span, redacted.responseHeaders, "http.response.header.");
|
|
1210
|
+
}
|
|
1211
|
+
if (redacted.requestBody) {
|
|
1212
|
+
setRequestBodyAttributes(span, redacted.requestBody);
|
|
1213
|
+
setRequestBodySizeAttribute(span, redacted.requestBody.bytes);
|
|
1214
|
+
}
|
|
1215
|
+
if (redacted.responseBody) {
|
|
1216
|
+
setResponseBodyAttributes(span, redacted.responseBody);
|
|
1217
|
+
setResponseBodySizeAttribute(span, redacted.responseBody.bytes);
|
|
1218
|
+
}
|
|
1219
|
+
if (state.userId) {
|
|
1220
|
+
setUserIdAttribute(span, state.userId);
|
|
1221
|
+
}
|
|
1222
|
+
if (err ?? state.error) {
|
|
1223
|
+
spanStatusFromError(span, err ?? state.error);
|
|
1224
|
+
}
|
|
1225
|
+
span.end();
|
|
1226
|
+
} catch (spanErr) {
|
|
1227
|
+
logWithLevel(config.logger, "warn", config.logLevel, "xray: span finalize failed", {
|
|
1228
|
+
error: spanErr instanceof Error ? spanErr.message : String(spanErr)
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return redacted;
|
|
1233
|
+
}
|
|
1234
|
+
function resolveFinalRequestId(config, explicitRequestId, responseHeaders) {
|
|
1235
|
+
const explicit = normalizeRequestIdCandidate(explicitRequestId);
|
|
1236
|
+
if (explicit) {
|
|
1237
|
+
return explicit;
|
|
1238
|
+
}
|
|
1239
|
+
const headerValue = resolveHeaderRequestId(config.requestId.header, responseHeaders);
|
|
1240
|
+
if (headerValue) {
|
|
1241
|
+
return headerValue;
|
|
1242
|
+
}
|
|
1243
|
+
return generateRequestId();
|
|
1244
|
+
}
|
|
1245
|
+
function resolveHeaderRequestId(headerName, headers) {
|
|
1246
|
+
if (!headers) {
|
|
1247
|
+
return void 0;
|
|
1248
|
+
}
|
|
1249
|
+
const target = headerName.toLowerCase();
|
|
1250
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
1251
|
+
if (name.toLowerCase() !== target) {
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
const entry = Array.isArray(value) ? value[0] : value;
|
|
1255
|
+
const normalized = normalizeRequestIdCandidate(entry);
|
|
1256
|
+
if (normalized) {
|
|
1257
|
+
return normalized;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return void 0;
|
|
1261
|
+
}
|
|
1262
|
+
function normalizeRequestIdCandidate(value) {
|
|
1263
|
+
if (!value) {
|
|
1264
|
+
return void 0;
|
|
1265
|
+
}
|
|
1266
|
+
const trimmed = value.trim();
|
|
1267
|
+
return trimmed ? trimmed : void 0;
|
|
1268
|
+
}
|
|
1269
|
+
function resolveCapture(base, override) {
|
|
1270
|
+
if (!override) {
|
|
1271
|
+
return base;
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
...base,
|
|
1275
|
+
...override
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function resolveRedaction(base, override) {
|
|
1279
|
+
const merged = {
|
|
1280
|
+
...base,
|
|
1281
|
+
...override
|
|
1282
|
+
};
|
|
1283
|
+
merged.headers = normalizeLowercaseList(merged.headers);
|
|
1284
|
+
merged.queryParams = normalizeLowercaseList(merged.queryParams);
|
|
1285
|
+
merged.bodyJsonPaths = normalizeList(merged.bodyJsonPaths);
|
|
1286
|
+
merged.replacement = merged.replacement || base.replacement;
|
|
1287
|
+
return merged;
|
|
1288
|
+
}
|
|
1289
|
+
function normalizeLowercaseList(values) {
|
|
1290
|
+
if (!values) {
|
|
1291
|
+
return [];
|
|
1292
|
+
}
|
|
1293
|
+
return values.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
1294
|
+
}
|
|
1295
|
+
function normalizeList(values) {
|
|
1296
|
+
if (!values) {
|
|
1297
|
+
return [];
|
|
1298
|
+
}
|
|
1299
|
+
return values.map((entry) => entry.trim()).filter(Boolean);
|
|
1300
|
+
}
|
|
1301
|
+
function buildError(err) {
|
|
1302
|
+
if (!err) {
|
|
1303
|
+
return void 0;
|
|
1304
|
+
}
|
|
1305
|
+
if (err instanceof Error) {
|
|
1306
|
+
return {
|
|
1307
|
+
message: err.message || "Error",
|
|
1308
|
+
type: err.name || "Error",
|
|
1309
|
+
stack: err.stack
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
message: String(err)
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function spanNameFromRequest(req) {
|
|
1317
|
+
const method = req.method || "GET";
|
|
1318
|
+
if (req.route) {
|
|
1319
|
+
return `${method} ${req.route}`;
|
|
1320
|
+
}
|
|
1321
|
+
const path = safePath(req.url);
|
|
1322
|
+
return `${method} ${path}`;
|
|
1323
|
+
}
|
|
1324
|
+
function safePath(url) {
|
|
1325
|
+
try {
|
|
1326
|
+
const parsed = new URL(url);
|
|
1327
|
+
return parsed.pathname || "/";
|
|
1328
|
+
} catch {
|
|
1329
|
+
const rawPath = url.split("?")[0] || "/";
|
|
1330
|
+
return rawPath || "/";
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
export {
|
|
1335
|
+
XrayConfigError,
|
|
1336
|
+
normalizeConfig,
|
|
1337
|
+
createEmitter
|
|
1338
|
+
};
|
|
1339
|
+
//# sourceMappingURL=chunk-2NR6RZEP.js.map
|