@elysia/opentelemetry 1.4.11 → 1.4.12
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 +27 -2
- package/bunfig.toml +2 -0
- package/dist/cjs/index.d.ts +29 -1
- package/dist/cjs/index.js +202 -133
- package/dist/index.d.ts +29 -1
- package/dist/index.mjs +203 -133
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @elysia/opentelemetry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Installation
|
|
4
|
+
```bash
|
|
5
|
+
bun install @elysia/opentelemetry
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## Example
|
|
9
|
+
```typescript twoslash
|
|
10
|
+
import { Elysia } from 'elysia'
|
|
11
|
+
import { opentelemetry } from '@elysia/opentelemetry'
|
|
12
|
+
|
|
13
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'
|
|
14
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
|
|
15
|
+
|
|
16
|
+
new Elysia()
|
|
17
|
+
.use(
|
|
18
|
+
opentelemetry({
|
|
19
|
+
spanProcessors: [
|
|
20
|
+
new BatchSpanProcessor(
|
|
21
|
+
new OTLPTraceExporter()
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
See [documentation](https://elysiajs.com/plugins/opentelemetry.html) for more details.
|
package/bunfig.toml
ADDED
package/dist/cjs/index.d.ts
CHANGED
|
@@ -17,6 +17,34 @@ export interface ElysiaOpenTelemetryOptions extends OpenTeleMetryOptions {
|
|
|
17
17
|
* @returns A boolean indicating whether tracing should be enabled for this request.
|
|
18
18
|
*/
|
|
19
19
|
checkIfShouldTrace?: (req: Request) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Redact `userinfo` and sensitive query values in `url.full` / `url.query`.
|
|
22
|
+
* Omitted: default redaction. `false`: record raw URLs (may leak secrets in query or credentials).
|
|
23
|
+
*/
|
|
24
|
+
spanUrlRedaction?: false | {
|
|
25
|
+
stripCredentials?: boolean;
|
|
26
|
+
sensitiveQueryParams?: string[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Record full request/response body content on spans.
|
|
30
|
+
* `true`: record both request and response bodies.
|
|
31
|
+
* `{ request: true }` or `{ response: true }`: record only one side.
|
|
32
|
+
* Default: `false` (no body content recorded).
|
|
33
|
+
*/
|
|
34
|
+
recordBody?: boolean | {
|
|
35
|
+
request?: boolean;
|
|
36
|
+
response?: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* HTTP header names (case-insensitive) to capture as span attributes.
|
|
40
|
+
* Use `"*"` in either list to capture all headers (useful for dev/debugging; may include sensitive values).
|
|
41
|
+
* Including `"cookie"` in `requestHeaders` also emits `http.request.cookie` when `context.cookie` exists.
|
|
42
|
+
* Default: none (no headers recorded).
|
|
43
|
+
*/
|
|
44
|
+
headersToSpanAttributes?: {
|
|
45
|
+
request?: string[];
|
|
46
|
+
response?: string[];
|
|
47
|
+
};
|
|
20
48
|
}
|
|
21
49
|
export type ActiveSpanArgs<F extends (span: Span) => unknown = (span: Span) => unknown> = [name: string, fn: F] | [name: string, options: SpanOptions, fn: F] | [name: string, options: SpanOptions, context: Context, fn: F];
|
|
22
50
|
export declare const shouldStartNodeSDK: (provider: TracerProvider) => boolean;
|
|
@@ -39,7 +67,7 @@ export declare const getCurrentSpan: () => Span | undefined;
|
|
|
39
67
|
* @returns boolean - whether the attributes are set or not
|
|
40
68
|
*/
|
|
41
69
|
export declare const setAttributes: (attributes: Attributes) => boolean;
|
|
42
|
-
export declare const opentelemetry: ({ serviceName, instrumentations, contextManager, checkIfShouldTrace, ...options }?: ElysiaOpenTelemetryOptions) => Elysia<"", {
|
|
70
|
+
export declare const opentelemetry: ({ serviceName, instrumentations, contextManager, checkIfShouldTrace, spanUrlRedaction, recordBody, headersToSpanAttributes, ...options }?: ElysiaOpenTelemetryOptions) => Elysia<"", {
|
|
43
71
|
decorator: {};
|
|
44
72
|
store: {};
|
|
45
73
|
derive: {};
|
package/dist/cjs/index.js
CHANGED
|
@@ -35,6 +35,26 @@ var import_elysia = require("elysia");
|
|
|
35
35
|
var import_api = require("@opentelemetry/api");
|
|
36
36
|
var import_sdk_node = require("@opentelemetry/sdk-node");
|
|
37
37
|
var headerHasToJSON = typeof new Headers().toJSON === "function";
|
|
38
|
+
var toHeaderNameSet = (names) => new Set((names ?? []).map((name) => name.toLowerCase()));
|
|
39
|
+
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
40
|
+
"token",
|
|
41
|
+
"access_token",
|
|
42
|
+
"refresh_token",
|
|
43
|
+
"id_token",
|
|
44
|
+
"password",
|
|
45
|
+
"passwd",
|
|
46
|
+
"pwd",
|
|
47
|
+
"secret",
|
|
48
|
+
"client_secret",
|
|
49
|
+
"api_key",
|
|
50
|
+
"apikey",
|
|
51
|
+
"api-key",
|
|
52
|
+
"authorization",
|
|
53
|
+
"credential",
|
|
54
|
+
"credentials",
|
|
55
|
+
"code",
|
|
56
|
+
"nonce"
|
|
57
|
+
]);
|
|
38
58
|
var parseNumericString = (message) => {
|
|
39
59
|
if (message.length < 16) {
|
|
40
60
|
if (message.length === 0) return null;
|
|
@@ -95,6 +115,40 @@ var createContext = (parent) => ({
|
|
|
95
115
|
return import_api.context.active();
|
|
96
116
|
}
|
|
97
117
|
});
|
|
118
|
+
var serializeBody = (body) => {
|
|
119
|
+
if (body instanceof Uint8Array) return { text: "", size: body.length };
|
|
120
|
+
if (body instanceof ArrayBuffer) return { text: "", size: body.byteLength };
|
|
121
|
+
if (body instanceof Blob) return { text: "", size: body.size };
|
|
122
|
+
let text;
|
|
123
|
+
try {
|
|
124
|
+
text = typeof body === "object" ? JSON.stringify(body) : String(body);
|
|
125
|
+
} catch {
|
|
126
|
+
text = "[Unserializable]";
|
|
127
|
+
}
|
|
128
|
+
return { text, size: text.length };
|
|
129
|
+
};
|
|
130
|
+
var redactQueryString = (query, keys) => {
|
|
131
|
+
if (query === "" || keys.size === 0) return query;
|
|
132
|
+
let out = "";
|
|
133
|
+
let partStart = 0;
|
|
134
|
+
let keyEnd = -1;
|
|
135
|
+
for (let i = 0; i <= query.length; i++) {
|
|
136
|
+
const ch = i === query.length ? 38 : query.charCodeAt(i);
|
|
137
|
+
if (ch === 61 && keyEnd === -1) {
|
|
138
|
+
keyEnd = i;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (ch !== 38) continue;
|
|
142
|
+
const partEnd = i;
|
|
143
|
+
const rawKeyEnd = keyEnd === -1 ? partEnd : keyEnd;
|
|
144
|
+
const rawKey = query.slice(partStart, rawKeyEnd);
|
|
145
|
+
if (out) out += "&";
|
|
146
|
+
out += keys.has(rawKey.toLowerCase()) ? rawKey + "=[REDACTED]" : query.slice(partStart, partEnd);
|
|
147
|
+
partStart = i + 1;
|
|
148
|
+
keyEnd = -1;
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
};
|
|
98
152
|
var shouldStartNodeSDK = (provider) => {
|
|
99
153
|
return provider instanceof import_api.ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
|
|
100
154
|
};
|
|
@@ -165,8 +219,29 @@ var opentelemetry = ({
|
|
|
165
219
|
instrumentations,
|
|
166
220
|
contextManager,
|
|
167
221
|
checkIfShouldTrace,
|
|
222
|
+
spanUrlRedaction,
|
|
223
|
+
recordBody,
|
|
224
|
+
headersToSpanAttributes,
|
|
168
225
|
...options
|
|
169
226
|
} = {}) => {
|
|
227
|
+
const spanRequestHeaderSet = toHeaderNameSet(
|
|
228
|
+
headersToSpanAttributes?.request
|
|
229
|
+
);
|
|
230
|
+
const spanResponseHeaderSet = toHeaderNameSet(
|
|
231
|
+
headersToSpanAttributes?.response
|
|
232
|
+
);
|
|
233
|
+
const requestHeaderWildcard = spanRequestHeaderSet.has("*");
|
|
234
|
+
const responseHeaderWildcard = spanResponseHeaderSet.has("*");
|
|
235
|
+
const recordRequestBody = recordBody === true || recordBody && recordBody.request || false;
|
|
236
|
+
const recordResponseBody = recordBody === true || recordBody && recordBody.response || false;
|
|
237
|
+
const urlRedactOpts = spanUrlRedaction === false ? null : spanUrlRedaction ?? {};
|
|
238
|
+
const sensitiveKeys = urlRedactOpts ? /* @__PURE__ */ new Set([
|
|
239
|
+
...SENSITIVE_QUERY_KEYS,
|
|
240
|
+
...(urlRedactOpts.sensitiveQueryParams ?? []).map(
|
|
241
|
+
(k) => k.toLowerCase()
|
|
242
|
+
)
|
|
243
|
+
]) : void 0;
|
|
244
|
+
const stripCreds = urlRedactOpts?.stripCredentials !== false;
|
|
170
245
|
let tracer = import_api.trace.getTracer(serviceName);
|
|
171
246
|
if (shouldStartNodeSDK(import_api.trace.getTracerProvider())) {
|
|
172
247
|
const sdk = new import_sdk_node.NodeSDK({
|
|
@@ -184,6 +259,39 @@ var opentelemetry = ({
|
|
|
184
259
|
import_api.context.setGlobalContextManager(contextManager);
|
|
185
260
|
} catch {
|
|
186
261
|
}
|
|
262
|
+
const meter = import_api.metrics.getMeter(serviceName);
|
|
263
|
+
const httpServerDuration = meter.createHistogram(
|
|
264
|
+
"http.server.request.duration",
|
|
265
|
+
{
|
|
266
|
+
description: "Duration of HTTP server requests.",
|
|
267
|
+
unit: "s",
|
|
268
|
+
advice: {
|
|
269
|
+
explicitBucketBoundaries: [
|
|
270
|
+
5e-3,
|
|
271
|
+
0.01,
|
|
272
|
+
0.025,
|
|
273
|
+
0.05,
|
|
274
|
+
0.075,
|
|
275
|
+
0.1,
|
|
276
|
+
0.25,
|
|
277
|
+
0.5,
|
|
278
|
+
0.75,
|
|
279
|
+
1,
|
|
280
|
+
2.5,
|
|
281
|
+
5,
|
|
282
|
+
7.5,
|
|
283
|
+
10,
|
|
284
|
+
30,
|
|
285
|
+
60,
|
|
286
|
+
120,
|
|
287
|
+
300,
|
|
288
|
+
600,
|
|
289
|
+
900,
|
|
290
|
+
1800
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
);
|
|
187
295
|
return new import_elysia.Elysia({
|
|
188
296
|
name: "@elysia/opentelemetry"
|
|
189
297
|
}).wrap((fn, request) => {
|
|
@@ -274,27 +382,13 @@ var opentelemetry = ({
|
|
|
274
382
|
}
|
|
275
383
|
onStop2(({ error }) => {
|
|
276
384
|
setParent(rootSpan);
|
|
277
|
-
if (span.ended || rootSpan.ended)
|
|
385
|
+
if (span.ended || rootSpan.ended)
|
|
386
|
+
return;
|
|
278
387
|
if (error) {
|
|
279
|
-
rootSpan.setStatus({
|
|
280
|
-
code: import_api.SpanStatusCode.ERROR,
|
|
281
|
-
message: error.message
|
|
282
|
-
});
|
|
283
388
|
span.setAttributes({
|
|
284
389
|
"error.type": error.constructor?.name ?? error.name,
|
|
285
390
|
"error.stack": error.stack
|
|
286
391
|
});
|
|
287
|
-
span.setStatus({
|
|
288
|
-
code: import_api.SpanStatusCode.ERROR,
|
|
289
|
-
message: error.message
|
|
290
|
-
});
|
|
291
|
-
} else {
|
|
292
|
-
rootSpan.setStatus({
|
|
293
|
-
code: import_api.SpanStatusCode.OK
|
|
294
|
-
});
|
|
295
|
-
span.setStatus({
|
|
296
|
-
code: import_api.SpanStatusCode.OK
|
|
297
|
-
});
|
|
298
392
|
}
|
|
299
393
|
if (useChildSpan) span.end();
|
|
300
394
|
});
|
|
@@ -308,22 +402,59 @@ var opentelemetry = ({
|
|
|
308
402
|
);
|
|
309
403
|
};
|
|
310
404
|
}
|
|
311
|
-
const
|
|
405
|
+
const rawUrl = context.url;
|
|
406
|
+
const qi = context.qi;
|
|
407
|
+
const hasQuery = qi !== void 0 && qi !== -1;
|
|
408
|
+
let urlQuery = hasQuery ? rawUrl.slice(qi + 1) : void 0;
|
|
409
|
+
let urlFull = rawUrl;
|
|
410
|
+
if (urlRedactOpts) {
|
|
411
|
+
if (urlQuery !== void 0) {
|
|
412
|
+
urlQuery = redactQueryString(urlQuery, sensitiveKeys);
|
|
413
|
+
urlFull = `${rawUrl.slice(0, qi)}?${urlQuery}`;
|
|
414
|
+
}
|
|
415
|
+
if (stripCreds && urlFull.indexOf("@") > 0) {
|
|
416
|
+
try {
|
|
417
|
+
const u = new URL(urlFull);
|
|
418
|
+
if (u.username || u.password) {
|
|
419
|
+
u.username = "";
|
|
420
|
+
u.password = "";
|
|
421
|
+
urlFull = u.href;
|
|
422
|
+
}
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
312
427
|
const attributes = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
313
428
|
// ? Elysia Custom attribute
|
|
314
429
|
"http.request.id": id,
|
|
315
430
|
"http.request.method": method,
|
|
316
431
|
"url.path": path,
|
|
317
|
-
"url.full":
|
|
432
|
+
"url.full": urlFull
|
|
318
433
|
});
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
// @ts-ignore private property
|
|
322
|
-
context.qi + 1
|
|
323
|
-
);
|
|
324
|
-
const protocolSeparator = url.indexOf("://");
|
|
434
|
+
if (urlQuery !== void 0) attributes["url.query"] = urlQuery;
|
|
435
|
+
const protocolSeparator = urlFull.indexOf("://");
|
|
325
436
|
if (protocolSeparator > 0)
|
|
326
|
-
attributes["url.scheme"] =
|
|
437
|
+
attributes["url.scheme"] = urlFull.slice(
|
|
438
|
+
0,
|
|
439
|
+
protocolSeparator
|
|
440
|
+
);
|
|
441
|
+
const requestStartTime = performance.now();
|
|
442
|
+
let durationRecorded = false;
|
|
443
|
+
const recordDuration = () => {
|
|
444
|
+
if (durationRecorded) return;
|
|
445
|
+
durationRecorded = true;
|
|
446
|
+
const durationS = (performance.now() - requestStartTime) / 1e3;
|
|
447
|
+
const statusCode = attributes["http.response.status_code"];
|
|
448
|
+
const metricAttributes = {
|
|
449
|
+
"http.request.method": attributes["http.request.method"] ?? method,
|
|
450
|
+
"url.scheme": attributes["url.scheme"],
|
|
451
|
+
"http.response.status_code": statusCode,
|
|
452
|
+
"http.route": attributes["http.route"]
|
|
453
|
+
};
|
|
454
|
+
if (typeof statusCode === "number" && statusCode >= 500)
|
|
455
|
+
metricAttributes["error.type"] = String(statusCode);
|
|
456
|
+
httpServerDuration.record(durationS, metricAttributes);
|
|
457
|
+
};
|
|
327
458
|
onRequest(inspect("Request"));
|
|
328
459
|
onParse(inspect("Parse"));
|
|
329
460
|
onTransform(inspect("Transform"));
|
|
@@ -339,23 +470,8 @@ var opentelemetry = ({
|
|
|
339
470
|
setParent(rootSpan);
|
|
340
471
|
if (span.ended || rootSpan.ended) return;
|
|
341
472
|
if (error) {
|
|
342
|
-
rootSpan.setStatus({
|
|
343
|
-
code: import_api.SpanStatusCode.ERROR,
|
|
344
|
-
message: error.message
|
|
345
|
-
});
|
|
346
|
-
span.setStatus({
|
|
347
|
-
code: import_api.SpanStatusCode.ERROR,
|
|
348
|
-
message: error.message
|
|
349
|
-
});
|
|
350
473
|
span.recordException(error);
|
|
351
474
|
rootSpan.recordException(error);
|
|
352
|
-
} else {
|
|
353
|
-
rootSpan.setStatus({
|
|
354
|
-
code: import_api.SpanStatusCode.OK
|
|
355
|
-
});
|
|
356
|
-
span.setStatus({
|
|
357
|
-
code: import_api.SpanStatusCode.OK
|
|
358
|
-
});
|
|
359
475
|
}
|
|
360
476
|
span.end();
|
|
361
477
|
});
|
|
@@ -385,8 +501,10 @@ var opentelemetry = ({
|
|
|
385
501
|
if (
|
|
386
502
|
// @ts-ignore
|
|
387
503
|
!rootSpan.ended
|
|
388
|
-
)
|
|
504
|
+
) {
|
|
505
|
+
recordDuration();
|
|
389
506
|
rootSpan.end();
|
|
507
|
+
}
|
|
390
508
|
});
|
|
391
509
|
});
|
|
392
510
|
onMapResponse(inspect("MapResponse"));
|
|
@@ -398,24 +516,18 @@ var opentelemetry = ({
|
|
|
398
516
|
`${method} ${route || path2}`
|
|
399
517
|
);
|
|
400
518
|
if (context.route) attributes["http.route"] = context.route;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
attributes["http.request_content_length"] = number;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
{
|
|
410
|
-
const userAgent = request.headers.get("User-Agent");
|
|
411
|
-
if (userAgent)
|
|
412
|
-
attributes["user_agent.original"] = userAgent;
|
|
519
|
+
const contentLength = request.headers.get("content-length");
|
|
520
|
+
if (contentLength) {
|
|
521
|
+
const number = parseNumericString(contentLength);
|
|
522
|
+
if (number !== null)
|
|
523
|
+
attributes["http.request_content_length"] = number;
|
|
413
524
|
}
|
|
525
|
+
const userAgent = request.headers.get("User-Agent");
|
|
526
|
+
if (userAgent) attributes["user_agent.original"] = userAgent;
|
|
414
527
|
const server = context.server;
|
|
415
528
|
if (server) {
|
|
416
529
|
attributes["server.port"] = server.port ?? 80;
|
|
417
530
|
attributes["server.address"] = server.url.hostname;
|
|
418
|
-
attributes["server.address"] = server.url.hostname;
|
|
419
531
|
}
|
|
420
532
|
let headers;
|
|
421
533
|
{
|
|
@@ -435,21 +547,23 @@ var opentelemetry = ({
|
|
|
435
547
|
for (let [key, value] of _headers) {
|
|
436
548
|
key = key.toLowerCase();
|
|
437
549
|
if (hasHeaders) {
|
|
438
|
-
if (
|
|
550
|
+
if (!requestHeaderWildcard && !spanRequestHeaderSet.has(key))
|
|
551
|
+
continue;
|
|
439
552
|
if (typeof value === "object")
|
|
440
553
|
attributes[`http.request.header.${key}`] = JSON.stringify(value);
|
|
441
554
|
else if (value !== void 0)
|
|
442
555
|
attributes[`http.request.header.${key}`] = value;
|
|
443
556
|
continue;
|
|
444
557
|
}
|
|
445
|
-
if (typeof value === "object")
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
558
|
+
if (typeof value === "object") {
|
|
559
|
+
const serialized = JSON.stringify(value);
|
|
560
|
+
headers[key] = serialized;
|
|
561
|
+
if (requestHeaderWildcard || spanRequestHeaderSet.has(key))
|
|
562
|
+
attributes[`http.request.header.${key}`] = serialized;
|
|
563
|
+
} else if (value !== void 0) {
|
|
564
|
+
headers[key] = value;
|
|
565
|
+
if (requestHeaderWildcard || spanRequestHeaderSet.has(key))
|
|
566
|
+
attributes[`http.request.header.${key}`] = value;
|
|
453
567
|
}
|
|
454
568
|
}
|
|
455
569
|
}
|
|
@@ -465,6 +579,8 @@ var opentelemetry = ({
|
|
|
465
579
|
} else headers2 = Object.entries(context.set.headers);
|
|
466
580
|
for (let [key, value] of headers2) {
|
|
467
581
|
key = key.toLowerCase();
|
|
582
|
+
if (!responseHeaderWildcard && !spanResponseHeaderSet.has(key))
|
|
583
|
+
continue;
|
|
468
584
|
if (typeof value === "object")
|
|
469
585
|
attributes[`http.response.header.${key}`] = JSON.stringify(value);
|
|
470
586
|
else
|
|
@@ -478,7 +594,7 @@ var opentelemetry = ({
|
|
|
478
594
|
if (ip)
|
|
479
595
|
attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
|
|
480
596
|
}
|
|
481
|
-
if (cookie) {
|
|
597
|
+
if ((requestHeaderWildcard || spanRequestHeaderSet.has("cookie")) && cookie) {
|
|
482
598
|
const _cookie = {};
|
|
483
599
|
for (const [key, { value }] of Object.entries(cookie))
|
|
484
600
|
_cookie[key] = JSON.stringify(value);
|
|
@@ -488,38 +604,18 @@ var opentelemetry = ({
|
|
|
488
604
|
});
|
|
489
605
|
onParse(() => {
|
|
490
606
|
const body = context.body;
|
|
491
|
-
if (body
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
attributes["http.request.body.size"] = body.length;
|
|
497
|
-
else if (body instanceof ArrayBuffer)
|
|
498
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
499
|
-
else if (body instanceof Blob)
|
|
500
|
-
attributes["http.request.body.size"] = body.size;
|
|
501
|
-
attributes["http.request.body.size"] = value.length;
|
|
502
|
-
} else {
|
|
503
|
-
attributes["http.request.body.size"] = value.length;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
607
|
+
if (body === void 0 || body === null || !recordRequestBody)
|
|
608
|
+
return;
|
|
609
|
+
const { text, size } = serializeBody(body);
|
|
610
|
+
if (text) attributes["http.request.body"] = text;
|
|
611
|
+
attributes["http.request.body.size"] = size;
|
|
506
612
|
});
|
|
507
613
|
onMapResponse(() => {
|
|
508
614
|
const body = context.body;
|
|
509
|
-
if (body !== void 0 && body !== null) {
|
|
510
|
-
const
|
|
511
|
-
attributes["http.request.body"] =
|
|
512
|
-
|
|
513
|
-
if (body instanceof Uint8Array)
|
|
514
|
-
attributes["http.request.body.size"] = body.length;
|
|
515
|
-
else if (body instanceof ArrayBuffer)
|
|
516
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
517
|
-
else if (body instanceof Blob)
|
|
518
|
-
attributes["http.request.body.size"] = body.size;
|
|
519
|
-
attributes["http.request.body.size"] = value.length;
|
|
520
|
-
} else {
|
|
521
|
-
attributes["http.request.body.size"] = value.length;
|
|
522
|
-
}
|
|
615
|
+
if (body !== void 0 && body !== null && recordRequestBody) {
|
|
616
|
+
const { text, size } = serializeBody(body);
|
|
617
|
+
if (text) attributes["http.request.body"] = text;
|
|
618
|
+
attributes["http.request.body.size"] = size;
|
|
523
619
|
}
|
|
524
620
|
{
|
|
525
621
|
let status = context.set.status ?? 200;
|
|
@@ -528,31 +624,11 @@ var opentelemetry = ({
|
|
|
528
624
|
attributes["http.response.status_code"] = status;
|
|
529
625
|
}
|
|
530
626
|
const response = context.responseValue;
|
|
531
|
-
if (response !== void 0)
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
attributes["http.response.body.size"] = response.length;
|
|
537
|
-
else if (response instanceof ArrayBuffer)
|
|
538
|
-
attributes["http.response.body.size"] = response.byteLength;
|
|
539
|
-
else if (response instanceof Blob)
|
|
540
|
-
attributes["http.response.body.size"] = response.size;
|
|
541
|
-
else {
|
|
542
|
-
const value = JSON.stringify(response);
|
|
543
|
-
attributes["http.response.body"] = value;
|
|
544
|
-
attributes["http.response.body.size"] = value.length;
|
|
545
|
-
}
|
|
546
|
-
break;
|
|
547
|
-
default:
|
|
548
|
-
if (response === void 0 || response === null)
|
|
549
|
-
attributes["http.response.body.size"] = 0;
|
|
550
|
-
else {
|
|
551
|
-
const value = response.toString();
|
|
552
|
-
attributes["http.response.body"] = value;
|
|
553
|
-
attributes["http.response.body.size"] = value.length;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
627
|
+
if (response !== void 0 && recordResponseBody) {
|
|
628
|
+
const { text, size } = serializeBody(response);
|
|
629
|
+
if (text) attributes["http.response.body"] = text;
|
|
630
|
+
attributes["http.response.body.size"] = size;
|
|
631
|
+
}
|
|
556
632
|
if (!rootSpan.ended) {
|
|
557
633
|
const statusCode = attributes["http.response.status_code"];
|
|
558
634
|
if (typeof statusCode === "number" && statusCode >= 500) {
|
|
@@ -572,20 +648,10 @@ var opentelemetry = ({
|
|
|
572
648
|
attributes["http.response.status_code"] = status;
|
|
573
649
|
}
|
|
574
650
|
const body = context.body;
|
|
575
|
-
if (body !== void 0 && body !== null) {
|
|
576
|
-
const
|
|
577
|
-
attributes["http.request.body"] =
|
|
578
|
-
|
|
579
|
-
if (body instanceof Uint8Array)
|
|
580
|
-
attributes["http.request.body.size"] = body.length;
|
|
581
|
-
else if (body instanceof ArrayBuffer)
|
|
582
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
583
|
-
else if (body instanceof Blob)
|
|
584
|
-
attributes["http.request.body.size"] = body.size;
|
|
585
|
-
attributes["http.request.body.size"] = value.length;
|
|
586
|
-
} else {
|
|
587
|
-
attributes["http.request.body.size"] = value.length;
|
|
588
|
-
}
|
|
651
|
+
if (body !== void 0 && body !== null && recordRequestBody) {
|
|
652
|
+
const { text, size } = serializeBody(body);
|
|
653
|
+
if (text) attributes["http.request.body"] = text;
|
|
654
|
+
attributes["http.request.body.size"] = size;
|
|
589
655
|
}
|
|
590
656
|
if (!rootSpan.ended) {
|
|
591
657
|
const statusCode = attributes["http.response.status_code"];
|
|
@@ -601,8 +667,10 @@ var opentelemetry = ({
|
|
|
601
667
|
if (
|
|
602
668
|
// @ts-ignore
|
|
603
669
|
!rootSpan.ended
|
|
604
|
-
)
|
|
670
|
+
) {
|
|
671
|
+
recordDuration();
|
|
605
672
|
rootSpan.end();
|
|
673
|
+
}
|
|
606
674
|
});
|
|
607
675
|
});
|
|
608
676
|
context.request.signal.addEventListener("abort", () => {
|
|
@@ -613,6 +681,7 @@ var opentelemetry = ({
|
|
|
613
681
|
code: import_api.SpanStatusCode.ERROR,
|
|
614
682
|
message: "Request aborted"
|
|
615
683
|
});
|
|
684
|
+
recordDuration();
|
|
616
685
|
rootSpan.end();
|
|
617
686
|
});
|
|
618
687
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,34 @@ export interface ElysiaOpenTelemetryOptions extends OpenTeleMetryOptions {
|
|
|
17
17
|
* @returns A boolean indicating whether tracing should be enabled for this request.
|
|
18
18
|
*/
|
|
19
19
|
checkIfShouldTrace?: (req: Request) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Redact `userinfo` and sensitive query values in `url.full` / `url.query`.
|
|
22
|
+
* Omitted: default redaction. `false`: record raw URLs (may leak secrets in query or credentials).
|
|
23
|
+
*/
|
|
24
|
+
spanUrlRedaction?: false | {
|
|
25
|
+
stripCredentials?: boolean;
|
|
26
|
+
sensitiveQueryParams?: string[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Record full request/response body content on spans.
|
|
30
|
+
* `true`: record both request and response bodies.
|
|
31
|
+
* `{ request: true }` or `{ response: true }`: record only one side.
|
|
32
|
+
* Default: `false` (no body content recorded).
|
|
33
|
+
*/
|
|
34
|
+
recordBody?: boolean | {
|
|
35
|
+
request?: boolean;
|
|
36
|
+
response?: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* HTTP header names (case-insensitive) to capture as span attributes.
|
|
40
|
+
* Use `"*"` in either list to capture all headers (useful for dev/debugging; may include sensitive values).
|
|
41
|
+
* Including `"cookie"` in `requestHeaders` also emits `http.request.cookie` when `context.cookie` exists.
|
|
42
|
+
* Default: none (no headers recorded).
|
|
43
|
+
*/
|
|
44
|
+
headersToSpanAttributes?: {
|
|
45
|
+
request?: string[];
|
|
46
|
+
response?: string[];
|
|
47
|
+
};
|
|
20
48
|
}
|
|
21
49
|
export type ActiveSpanArgs<F extends (span: Span) => unknown = (span: Span) => unknown> = [name: string, fn: F] | [name: string, options: SpanOptions, fn: F] | [name: string, options: SpanOptions, context: Context, fn: F];
|
|
22
50
|
export declare const shouldStartNodeSDK: (provider: TracerProvider) => boolean;
|
|
@@ -39,7 +67,7 @@ export declare const getCurrentSpan: () => Span | undefined;
|
|
|
39
67
|
* @returns boolean - whether the attributes are set or not
|
|
40
68
|
*/
|
|
41
69
|
export declare const setAttributes: (attributes: Attributes) => boolean;
|
|
42
|
-
export declare const opentelemetry: ({ serviceName, instrumentations, contextManager, checkIfShouldTrace, ...options }?: ElysiaOpenTelemetryOptions) => Elysia<"", {
|
|
70
|
+
export declare const opentelemetry: ({ serviceName, instrumentations, contextManager, checkIfShouldTrace, spanUrlRedaction, recordBody, headersToSpanAttributes, ...options }?: ElysiaOpenTelemetryOptions) => Elysia<"", {
|
|
43
71
|
decorator: {};
|
|
44
72
|
store: {};
|
|
45
73
|
derive: {};
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Elysia, StatusMap } from "elysia";
|
|
3
3
|
import {
|
|
4
4
|
trace,
|
|
5
|
+
metrics,
|
|
5
6
|
context as otelContext,
|
|
6
7
|
propagation,
|
|
7
8
|
SpanStatusCode,
|
|
@@ -10,6 +11,26 @@ import {
|
|
|
10
11
|
} from "@opentelemetry/api";
|
|
11
12
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
12
13
|
var headerHasToJSON = typeof new Headers().toJSON === "function";
|
|
14
|
+
var toHeaderNameSet = (names) => new Set((names ?? []).map((name) => name.toLowerCase()));
|
|
15
|
+
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
16
|
+
"token",
|
|
17
|
+
"access_token",
|
|
18
|
+
"refresh_token",
|
|
19
|
+
"id_token",
|
|
20
|
+
"password",
|
|
21
|
+
"passwd",
|
|
22
|
+
"pwd",
|
|
23
|
+
"secret",
|
|
24
|
+
"client_secret",
|
|
25
|
+
"api_key",
|
|
26
|
+
"apikey",
|
|
27
|
+
"api-key",
|
|
28
|
+
"authorization",
|
|
29
|
+
"credential",
|
|
30
|
+
"credentials",
|
|
31
|
+
"code",
|
|
32
|
+
"nonce"
|
|
33
|
+
]);
|
|
13
34
|
var parseNumericString = (message) => {
|
|
14
35
|
if (message.length < 16) {
|
|
15
36
|
if (message.length === 0) return null;
|
|
@@ -70,6 +91,40 @@ var createContext = (parent) => ({
|
|
|
70
91
|
return otelContext.active();
|
|
71
92
|
}
|
|
72
93
|
});
|
|
94
|
+
var serializeBody = (body) => {
|
|
95
|
+
if (body instanceof Uint8Array) return { text: "", size: body.length };
|
|
96
|
+
if (body instanceof ArrayBuffer) return { text: "", size: body.byteLength };
|
|
97
|
+
if (body instanceof Blob) return { text: "", size: body.size };
|
|
98
|
+
let text;
|
|
99
|
+
try {
|
|
100
|
+
text = typeof body === "object" ? JSON.stringify(body) : String(body);
|
|
101
|
+
} catch {
|
|
102
|
+
text = "[Unserializable]";
|
|
103
|
+
}
|
|
104
|
+
return { text, size: text.length };
|
|
105
|
+
};
|
|
106
|
+
var redactQueryString = (query, keys) => {
|
|
107
|
+
if (query === "" || keys.size === 0) return query;
|
|
108
|
+
let out = "";
|
|
109
|
+
let partStart = 0;
|
|
110
|
+
let keyEnd = -1;
|
|
111
|
+
for (let i = 0; i <= query.length; i++) {
|
|
112
|
+
const ch = i === query.length ? 38 : query.charCodeAt(i);
|
|
113
|
+
if (ch === 61 && keyEnd === -1) {
|
|
114
|
+
keyEnd = i;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (ch !== 38) continue;
|
|
118
|
+
const partEnd = i;
|
|
119
|
+
const rawKeyEnd = keyEnd === -1 ? partEnd : keyEnd;
|
|
120
|
+
const rawKey = query.slice(partStart, rawKeyEnd);
|
|
121
|
+
if (out) out += "&";
|
|
122
|
+
out += keys.has(rawKey.toLowerCase()) ? rawKey + "=[REDACTED]" : query.slice(partStart, partEnd);
|
|
123
|
+
partStart = i + 1;
|
|
124
|
+
keyEnd = -1;
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
};
|
|
73
128
|
var shouldStartNodeSDK = (provider) => {
|
|
74
129
|
return provider instanceof ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
|
|
75
130
|
};
|
|
@@ -140,8 +195,29 @@ var opentelemetry = ({
|
|
|
140
195
|
instrumentations,
|
|
141
196
|
contextManager,
|
|
142
197
|
checkIfShouldTrace,
|
|
198
|
+
spanUrlRedaction,
|
|
199
|
+
recordBody,
|
|
200
|
+
headersToSpanAttributes,
|
|
143
201
|
...options
|
|
144
202
|
} = {}) => {
|
|
203
|
+
const spanRequestHeaderSet = toHeaderNameSet(
|
|
204
|
+
headersToSpanAttributes?.request
|
|
205
|
+
);
|
|
206
|
+
const spanResponseHeaderSet = toHeaderNameSet(
|
|
207
|
+
headersToSpanAttributes?.response
|
|
208
|
+
);
|
|
209
|
+
const requestHeaderWildcard = spanRequestHeaderSet.has("*");
|
|
210
|
+
const responseHeaderWildcard = spanResponseHeaderSet.has("*");
|
|
211
|
+
const recordRequestBody = recordBody === true || recordBody && recordBody.request || false;
|
|
212
|
+
const recordResponseBody = recordBody === true || recordBody && recordBody.response || false;
|
|
213
|
+
const urlRedactOpts = spanUrlRedaction === false ? null : spanUrlRedaction ?? {};
|
|
214
|
+
const sensitiveKeys = urlRedactOpts ? /* @__PURE__ */ new Set([
|
|
215
|
+
...SENSITIVE_QUERY_KEYS,
|
|
216
|
+
...(urlRedactOpts.sensitiveQueryParams ?? []).map(
|
|
217
|
+
(k) => k.toLowerCase()
|
|
218
|
+
)
|
|
219
|
+
]) : void 0;
|
|
220
|
+
const stripCreds = urlRedactOpts?.stripCredentials !== false;
|
|
145
221
|
let tracer = trace.getTracer(serviceName);
|
|
146
222
|
if (shouldStartNodeSDK(trace.getTracerProvider())) {
|
|
147
223
|
const sdk = new NodeSDK({
|
|
@@ -159,6 +235,39 @@ var opentelemetry = ({
|
|
|
159
235
|
otelContext.setGlobalContextManager(contextManager);
|
|
160
236
|
} catch {
|
|
161
237
|
}
|
|
238
|
+
const meter = metrics.getMeter(serviceName);
|
|
239
|
+
const httpServerDuration = meter.createHistogram(
|
|
240
|
+
"http.server.request.duration",
|
|
241
|
+
{
|
|
242
|
+
description: "Duration of HTTP server requests.",
|
|
243
|
+
unit: "s",
|
|
244
|
+
advice: {
|
|
245
|
+
explicitBucketBoundaries: [
|
|
246
|
+
5e-3,
|
|
247
|
+
0.01,
|
|
248
|
+
0.025,
|
|
249
|
+
0.05,
|
|
250
|
+
0.075,
|
|
251
|
+
0.1,
|
|
252
|
+
0.25,
|
|
253
|
+
0.5,
|
|
254
|
+
0.75,
|
|
255
|
+
1,
|
|
256
|
+
2.5,
|
|
257
|
+
5,
|
|
258
|
+
7.5,
|
|
259
|
+
10,
|
|
260
|
+
30,
|
|
261
|
+
60,
|
|
262
|
+
120,
|
|
263
|
+
300,
|
|
264
|
+
600,
|
|
265
|
+
900,
|
|
266
|
+
1800
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
);
|
|
162
271
|
return new Elysia({
|
|
163
272
|
name: "@elysia/opentelemetry"
|
|
164
273
|
}).wrap((fn, request) => {
|
|
@@ -249,27 +358,13 @@ var opentelemetry = ({
|
|
|
249
358
|
}
|
|
250
359
|
onStop2(({ error }) => {
|
|
251
360
|
setParent(rootSpan);
|
|
252
|
-
if (span.ended || rootSpan.ended)
|
|
361
|
+
if (span.ended || rootSpan.ended)
|
|
362
|
+
return;
|
|
253
363
|
if (error) {
|
|
254
|
-
rootSpan.setStatus({
|
|
255
|
-
code: SpanStatusCode.ERROR,
|
|
256
|
-
message: error.message
|
|
257
|
-
});
|
|
258
364
|
span.setAttributes({
|
|
259
365
|
"error.type": error.constructor?.name ?? error.name,
|
|
260
366
|
"error.stack": error.stack
|
|
261
367
|
});
|
|
262
|
-
span.setStatus({
|
|
263
|
-
code: SpanStatusCode.ERROR,
|
|
264
|
-
message: error.message
|
|
265
|
-
});
|
|
266
|
-
} else {
|
|
267
|
-
rootSpan.setStatus({
|
|
268
|
-
code: SpanStatusCode.OK
|
|
269
|
-
});
|
|
270
|
-
span.setStatus({
|
|
271
|
-
code: SpanStatusCode.OK
|
|
272
|
-
});
|
|
273
368
|
}
|
|
274
369
|
if (useChildSpan) span.end();
|
|
275
370
|
});
|
|
@@ -283,22 +378,59 @@ var opentelemetry = ({
|
|
|
283
378
|
);
|
|
284
379
|
};
|
|
285
380
|
}
|
|
286
|
-
const
|
|
381
|
+
const rawUrl = context.url;
|
|
382
|
+
const qi = context.qi;
|
|
383
|
+
const hasQuery = qi !== void 0 && qi !== -1;
|
|
384
|
+
let urlQuery = hasQuery ? rawUrl.slice(qi + 1) : void 0;
|
|
385
|
+
let urlFull = rawUrl;
|
|
386
|
+
if (urlRedactOpts) {
|
|
387
|
+
if (urlQuery !== void 0) {
|
|
388
|
+
urlQuery = redactQueryString(urlQuery, sensitiveKeys);
|
|
389
|
+
urlFull = `${rawUrl.slice(0, qi)}?${urlQuery}`;
|
|
390
|
+
}
|
|
391
|
+
if (stripCreds && urlFull.indexOf("@") > 0) {
|
|
392
|
+
try {
|
|
393
|
+
const u = new URL(urlFull);
|
|
394
|
+
if (u.username || u.password) {
|
|
395
|
+
u.username = "";
|
|
396
|
+
u.password = "";
|
|
397
|
+
urlFull = u.href;
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
287
403
|
const attributes = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
288
404
|
// ? Elysia Custom attribute
|
|
289
405
|
"http.request.id": id,
|
|
290
406
|
"http.request.method": method,
|
|
291
407
|
"url.path": path,
|
|
292
|
-
"url.full":
|
|
408
|
+
"url.full": urlFull
|
|
293
409
|
});
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
// @ts-ignore private property
|
|
297
|
-
context.qi + 1
|
|
298
|
-
);
|
|
299
|
-
const protocolSeparator = url.indexOf("://");
|
|
410
|
+
if (urlQuery !== void 0) attributes["url.query"] = urlQuery;
|
|
411
|
+
const protocolSeparator = urlFull.indexOf("://");
|
|
300
412
|
if (protocolSeparator > 0)
|
|
301
|
-
attributes["url.scheme"] =
|
|
413
|
+
attributes["url.scheme"] = urlFull.slice(
|
|
414
|
+
0,
|
|
415
|
+
protocolSeparator
|
|
416
|
+
);
|
|
417
|
+
const requestStartTime = performance.now();
|
|
418
|
+
let durationRecorded = false;
|
|
419
|
+
const recordDuration = () => {
|
|
420
|
+
if (durationRecorded) return;
|
|
421
|
+
durationRecorded = true;
|
|
422
|
+
const durationS = (performance.now() - requestStartTime) / 1e3;
|
|
423
|
+
const statusCode = attributes["http.response.status_code"];
|
|
424
|
+
const metricAttributes = {
|
|
425
|
+
"http.request.method": attributes["http.request.method"] ?? method,
|
|
426
|
+
"url.scheme": attributes["url.scheme"],
|
|
427
|
+
"http.response.status_code": statusCode,
|
|
428
|
+
"http.route": attributes["http.route"]
|
|
429
|
+
};
|
|
430
|
+
if (typeof statusCode === "number" && statusCode >= 500)
|
|
431
|
+
metricAttributes["error.type"] = String(statusCode);
|
|
432
|
+
httpServerDuration.record(durationS, metricAttributes);
|
|
433
|
+
};
|
|
302
434
|
onRequest(inspect("Request"));
|
|
303
435
|
onParse(inspect("Parse"));
|
|
304
436
|
onTransform(inspect("Transform"));
|
|
@@ -314,23 +446,8 @@ var opentelemetry = ({
|
|
|
314
446
|
setParent(rootSpan);
|
|
315
447
|
if (span.ended || rootSpan.ended) return;
|
|
316
448
|
if (error) {
|
|
317
|
-
rootSpan.setStatus({
|
|
318
|
-
code: SpanStatusCode.ERROR,
|
|
319
|
-
message: error.message
|
|
320
|
-
});
|
|
321
|
-
span.setStatus({
|
|
322
|
-
code: SpanStatusCode.ERROR,
|
|
323
|
-
message: error.message
|
|
324
|
-
});
|
|
325
449
|
span.recordException(error);
|
|
326
450
|
rootSpan.recordException(error);
|
|
327
|
-
} else {
|
|
328
|
-
rootSpan.setStatus({
|
|
329
|
-
code: SpanStatusCode.OK
|
|
330
|
-
});
|
|
331
|
-
span.setStatus({
|
|
332
|
-
code: SpanStatusCode.OK
|
|
333
|
-
});
|
|
334
451
|
}
|
|
335
452
|
span.end();
|
|
336
453
|
});
|
|
@@ -360,8 +477,10 @@ var opentelemetry = ({
|
|
|
360
477
|
if (
|
|
361
478
|
// @ts-ignore
|
|
362
479
|
!rootSpan.ended
|
|
363
|
-
)
|
|
480
|
+
) {
|
|
481
|
+
recordDuration();
|
|
364
482
|
rootSpan.end();
|
|
483
|
+
}
|
|
365
484
|
});
|
|
366
485
|
});
|
|
367
486
|
onMapResponse(inspect("MapResponse"));
|
|
@@ -373,24 +492,18 @@ var opentelemetry = ({
|
|
|
373
492
|
`${method} ${route || path2}`
|
|
374
493
|
);
|
|
375
494
|
if (context.route) attributes["http.route"] = context.route;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
attributes["http.request_content_length"] = number;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
{
|
|
385
|
-
const userAgent = request.headers.get("User-Agent");
|
|
386
|
-
if (userAgent)
|
|
387
|
-
attributes["user_agent.original"] = userAgent;
|
|
495
|
+
const contentLength = request.headers.get("content-length");
|
|
496
|
+
if (contentLength) {
|
|
497
|
+
const number = parseNumericString(contentLength);
|
|
498
|
+
if (number !== null)
|
|
499
|
+
attributes["http.request_content_length"] = number;
|
|
388
500
|
}
|
|
501
|
+
const userAgent = request.headers.get("User-Agent");
|
|
502
|
+
if (userAgent) attributes["user_agent.original"] = userAgent;
|
|
389
503
|
const server = context.server;
|
|
390
504
|
if (server) {
|
|
391
505
|
attributes["server.port"] = server.port ?? 80;
|
|
392
506
|
attributes["server.address"] = server.url.hostname;
|
|
393
|
-
attributes["server.address"] = server.url.hostname;
|
|
394
507
|
}
|
|
395
508
|
let headers;
|
|
396
509
|
{
|
|
@@ -410,21 +523,23 @@ var opentelemetry = ({
|
|
|
410
523
|
for (let [key, value] of _headers) {
|
|
411
524
|
key = key.toLowerCase();
|
|
412
525
|
if (hasHeaders) {
|
|
413
|
-
if (
|
|
526
|
+
if (!requestHeaderWildcard && !spanRequestHeaderSet.has(key))
|
|
527
|
+
continue;
|
|
414
528
|
if (typeof value === "object")
|
|
415
529
|
attributes[`http.request.header.${key}`] = JSON.stringify(value);
|
|
416
530
|
else if (value !== void 0)
|
|
417
531
|
attributes[`http.request.header.${key}`] = value;
|
|
418
532
|
continue;
|
|
419
533
|
}
|
|
420
|
-
if (typeof value === "object")
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
534
|
+
if (typeof value === "object") {
|
|
535
|
+
const serialized = JSON.stringify(value);
|
|
536
|
+
headers[key] = serialized;
|
|
537
|
+
if (requestHeaderWildcard || spanRequestHeaderSet.has(key))
|
|
538
|
+
attributes[`http.request.header.${key}`] = serialized;
|
|
539
|
+
} else if (value !== void 0) {
|
|
540
|
+
headers[key] = value;
|
|
541
|
+
if (requestHeaderWildcard || spanRequestHeaderSet.has(key))
|
|
542
|
+
attributes[`http.request.header.${key}`] = value;
|
|
428
543
|
}
|
|
429
544
|
}
|
|
430
545
|
}
|
|
@@ -440,6 +555,8 @@ var opentelemetry = ({
|
|
|
440
555
|
} else headers2 = Object.entries(context.set.headers);
|
|
441
556
|
for (let [key, value] of headers2) {
|
|
442
557
|
key = key.toLowerCase();
|
|
558
|
+
if (!responseHeaderWildcard && !spanResponseHeaderSet.has(key))
|
|
559
|
+
continue;
|
|
443
560
|
if (typeof value === "object")
|
|
444
561
|
attributes[`http.response.header.${key}`] = JSON.stringify(value);
|
|
445
562
|
else
|
|
@@ -453,7 +570,7 @@ var opentelemetry = ({
|
|
|
453
570
|
if (ip)
|
|
454
571
|
attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
|
|
455
572
|
}
|
|
456
|
-
if (cookie) {
|
|
573
|
+
if ((requestHeaderWildcard || spanRequestHeaderSet.has("cookie")) && cookie) {
|
|
457
574
|
const _cookie = {};
|
|
458
575
|
for (const [key, { value }] of Object.entries(cookie))
|
|
459
576
|
_cookie[key] = JSON.stringify(value);
|
|
@@ -463,38 +580,18 @@ var opentelemetry = ({
|
|
|
463
580
|
});
|
|
464
581
|
onParse(() => {
|
|
465
582
|
const body = context.body;
|
|
466
|
-
if (body
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
attributes["http.request.body.size"] = body.length;
|
|
472
|
-
else if (body instanceof ArrayBuffer)
|
|
473
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
474
|
-
else if (body instanceof Blob)
|
|
475
|
-
attributes["http.request.body.size"] = body.size;
|
|
476
|
-
attributes["http.request.body.size"] = value.length;
|
|
477
|
-
} else {
|
|
478
|
-
attributes["http.request.body.size"] = value.length;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
583
|
+
if (body === void 0 || body === null || !recordRequestBody)
|
|
584
|
+
return;
|
|
585
|
+
const { text, size } = serializeBody(body);
|
|
586
|
+
if (text) attributes["http.request.body"] = text;
|
|
587
|
+
attributes["http.request.body.size"] = size;
|
|
481
588
|
});
|
|
482
589
|
onMapResponse(() => {
|
|
483
590
|
const body = context.body;
|
|
484
|
-
if (body !== void 0 && body !== null) {
|
|
485
|
-
const
|
|
486
|
-
attributes["http.request.body"] =
|
|
487
|
-
|
|
488
|
-
if (body instanceof Uint8Array)
|
|
489
|
-
attributes["http.request.body.size"] = body.length;
|
|
490
|
-
else if (body instanceof ArrayBuffer)
|
|
491
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
492
|
-
else if (body instanceof Blob)
|
|
493
|
-
attributes["http.request.body.size"] = body.size;
|
|
494
|
-
attributes["http.request.body.size"] = value.length;
|
|
495
|
-
} else {
|
|
496
|
-
attributes["http.request.body.size"] = value.length;
|
|
497
|
-
}
|
|
591
|
+
if (body !== void 0 && body !== null && recordRequestBody) {
|
|
592
|
+
const { text, size } = serializeBody(body);
|
|
593
|
+
if (text) attributes["http.request.body"] = text;
|
|
594
|
+
attributes["http.request.body.size"] = size;
|
|
498
595
|
}
|
|
499
596
|
{
|
|
500
597
|
let status = context.set.status ?? 200;
|
|
@@ -503,31 +600,11 @@ var opentelemetry = ({
|
|
|
503
600
|
attributes["http.response.status_code"] = status;
|
|
504
601
|
}
|
|
505
602
|
const response = context.responseValue;
|
|
506
|
-
if (response !== void 0)
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
attributes["http.response.body.size"] = response.length;
|
|
512
|
-
else if (response instanceof ArrayBuffer)
|
|
513
|
-
attributes["http.response.body.size"] = response.byteLength;
|
|
514
|
-
else if (response instanceof Blob)
|
|
515
|
-
attributes["http.response.body.size"] = response.size;
|
|
516
|
-
else {
|
|
517
|
-
const value = JSON.stringify(response);
|
|
518
|
-
attributes["http.response.body"] = value;
|
|
519
|
-
attributes["http.response.body.size"] = value.length;
|
|
520
|
-
}
|
|
521
|
-
break;
|
|
522
|
-
default:
|
|
523
|
-
if (response === void 0 || response === null)
|
|
524
|
-
attributes["http.response.body.size"] = 0;
|
|
525
|
-
else {
|
|
526
|
-
const value = response.toString();
|
|
527
|
-
attributes["http.response.body"] = value;
|
|
528
|
-
attributes["http.response.body.size"] = value.length;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
603
|
+
if (response !== void 0 && recordResponseBody) {
|
|
604
|
+
const { text, size } = serializeBody(response);
|
|
605
|
+
if (text) attributes["http.response.body"] = text;
|
|
606
|
+
attributes["http.response.body.size"] = size;
|
|
607
|
+
}
|
|
531
608
|
if (!rootSpan.ended) {
|
|
532
609
|
const statusCode = attributes["http.response.status_code"];
|
|
533
610
|
if (typeof statusCode === "number" && statusCode >= 500) {
|
|
@@ -547,20 +624,10 @@ var opentelemetry = ({
|
|
|
547
624
|
attributes["http.response.status_code"] = status;
|
|
548
625
|
}
|
|
549
626
|
const body = context.body;
|
|
550
|
-
if (body !== void 0 && body !== null) {
|
|
551
|
-
const
|
|
552
|
-
attributes["http.request.body"] =
|
|
553
|
-
|
|
554
|
-
if (body instanceof Uint8Array)
|
|
555
|
-
attributes["http.request.body.size"] = body.length;
|
|
556
|
-
else if (body instanceof ArrayBuffer)
|
|
557
|
-
attributes["http.request.body.size"] = body.byteLength;
|
|
558
|
-
else if (body instanceof Blob)
|
|
559
|
-
attributes["http.request.body.size"] = body.size;
|
|
560
|
-
attributes["http.request.body.size"] = value.length;
|
|
561
|
-
} else {
|
|
562
|
-
attributes["http.request.body.size"] = value.length;
|
|
563
|
-
}
|
|
627
|
+
if (body !== void 0 && body !== null && recordRequestBody) {
|
|
628
|
+
const { text, size } = serializeBody(body);
|
|
629
|
+
if (text) attributes["http.request.body"] = text;
|
|
630
|
+
attributes["http.request.body.size"] = size;
|
|
564
631
|
}
|
|
565
632
|
if (!rootSpan.ended) {
|
|
566
633
|
const statusCode = attributes["http.response.status_code"];
|
|
@@ -576,8 +643,10 @@ var opentelemetry = ({
|
|
|
576
643
|
if (
|
|
577
644
|
// @ts-ignore
|
|
578
645
|
!rootSpan.ended
|
|
579
|
-
)
|
|
646
|
+
) {
|
|
647
|
+
recordDuration();
|
|
580
648
|
rootSpan.end();
|
|
649
|
+
}
|
|
581
650
|
});
|
|
582
651
|
});
|
|
583
652
|
context.request.signal.addEventListener("abort", () => {
|
|
@@ -588,6 +657,7 @@ var opentelemetry = ({
|
|
|
588
657
|
code: SpanStatusCode.ERROR,
|
|
589
658
|
message: "Request aborted"
|
|
590
659
|
});
|
|
660
|
+
recordDuration();
|
|
591
661
|
rootSpan.end();
|
|
592
662
|
});
|
|
593
663
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elysia/opentelemetry",
|
|
3
|
-
"
|
|
3
|
+
"description": "Elysia plugin to integrate OpenTelemetry",
|
|
4
|
+
"version": "1.4.12",
|
|
4
5
|
"license": "MIT",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"dev": "bun run --watch example/index.ts",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"keywords": [
|
|
37
38
|
"elysia",
|
|
38
39
|
"opentelemetry",
|
|
39
|
-
"
|
|
40
|
+
"otel"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@opentelemetry/api": "^1.9.0",
|