@elysia/opentelemetry 1.4.12 → 2.0.0-exp.0

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/dist/index.mjs CHANGED
@@ -1,676 +1,502 @@
1
- // src/index.ts
2
1
  import { Elysia, StatusMap } from "elysia";
3
- import {
4
- trace,
5
- metrics,
6
- context as otelContext,
7
- propagation,
8
- SpanStatusCode,
9
- SpanKind,
10
- ProxyTracerProvider
11
- } from "@opentelemetry/api";
2
+ import { ProxyTracerProvider, SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
12
3
  import { NodeSDK } from "@opentelemetry/sdk-node";
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"
4
+
5
+ //#region src/index.ts
6
+ const headerHasToJSON = typeof new Headers().toJSON === "function";
7
+ const toHeaderNameSet = (names) => new Set((names ?? []).map((name) => name.toLowerCase()));
8
+ const SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
9
+ "token",
10
+ "access_token",
11
+ "refresh_token",
12
+ "id_token",
13
+ "password",
14
+ "passwd",
15
+ "pwd",
16
+ "secret",
17
+ "client_secret",
18
+ "api_key",
19
+ "apikey",
20
+ "api-key",
21
+ "authorization",
22
+ "credential",
23
+ "credentials",
24
+ "code",
25
+ "nonce"
33
26
  ]);
34
- var parseNumericString = (message) => {
35
- if (message.length < 16) {
36
- if (message.length === 0) return null;
37
- const length = Number(message);
38
- if (Number.isNaN(length)) return null;
39
- return length;
40
- }
41
- if (message.length === 16) {
42
- const number = Number(message);
43
- if (number.toString() !== message || message.trim().length === 0 || Number.isNaN(number))
44
- return null;
45
- return number;
46
- }
47
- return null;
27
+ const parseNumericString = (message) => {
28
+ if (message.length < 16) {
29
+ if (message.length === 0) return null;
30
+ const length = Number(message);
31
+ if (Number.isNaN(length)) return null;
32
+ return length;
33
+ }
34
+ if (message.length === 16) {
35
+ const number = Number(message);
36
+ if (number.toString() !== message || message.trim().length === 0 || Number.isNaN(number)) return null;
37
+ return number;
38
+ }
39
+ return null;
48
40
  };
49
- var createActiveSpanHandler = (fn) => function(span) {
50
- try {
51
- const result = fn(span);
52
- if (result instanceof Promise || typeof result?.then === "function")
53
- return Promise.resolve(result).then(
54
- (value) => {
55
- span.end();
56
- return value;
57
- },
58
- (rejectResult) => {
59
- span.setStatus({
60
- code: SpanStatusCode.ERROR,
61
- message: rejectResult instanceof Error ? rejectResult.message : JSON.stringify(
62
- rejectResult ?? "Unknown error"
63
- )
64
- });
65
- span.recordException(rejectResult);
66
- span.end();
67
- throw rejectResult;
68
- }
69
- );
70
- span.end();
71
- return result;
72
- } catch (error) {
73
- const err = error;
74
- span.setStatus({
75
- code: SpanStatusCode.ERROR,
76
- message: err?.message
77
- });
78
- span.recordException(err);
79
- span.end();
80
- throw error;
81
- }
41
+ const createActiveSpanHandler = (fn) => function(span) {
42
+ try {
43
+ const result = fn(span);
44
+ if (result instanceof Promise || typeof result?.then === "function") return Promise.resolve(result).then((value) => {
45
+ span.end();
46
+ return value;
47
+ }, (rejectResult) => {
48
+ span.setStatus({
49
+ code: SpanStatusCode.ERROR,
50
+ message: rejectResult instanceof Error ? rejectResult.message : JSON.stringify(rejectResult ?? "Unknown error")
51
+ });
52
+ span.recordException(rejectResult);
53
+ span.end();
54
+ throw rejectResult;
55
+ });
56
+ span.end();
57
+ return result;
58
+ } catch (error) {
59
+ const err = error;
60
+ span.setStatus({
61
+ code: SpanStatusCode.ERROR,
62
+ message: err?.message
63
+ });
64
+ span.recordException(err);
65
+ span.end();
66
+ throw error;
67
+ }
82
68
  };
83
- var createContext = (parent) => ({
84
- getValue() {
85
- return parent;
86
- },
87
- setValue() {
88
- return otelContext.active();
89
- },
90
- deleteValue() {
91
- return otelContext.active();
92
- }
69
+ const createContext = (parent) => ({
70
+ getValue() {
71
+ return parent;
72
+ },
73
+ setValue() {
74
+ return context.active();
75
+ },
76
+ deleteValue() {
77
+ return context.active();
78
+ }
93
79
  });
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 };
80
+ const serializeBody = (body) => {
81
+ if (body instanceof Uint8Array) return {
82
+ text: "",
83
+ size: body.length
84
+ };
85
+ if (body instanceof ArrayBuffer) return {
86
+ text: "",
87
+ size: body.byteLength
88
+ };
89
+ if (body instanceof Blob) return {
90
+ text: "",
91
+ size: body.size
92
+ };
93
+ let text;
94
+ try {
95
+ text = typeof body === "object" ? JSON.stringify(body) : String(body);
96
+ } catch {
97
+ text = "[Unserializable]";
98
+ }
99
+ return {
100
+ text,
101
+ size: text.length
102
+ };
105
103
  };
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;
104
+ const redactQueryString = (query, keys) => {
105
+ if (query === "" || keys.size === 0) return query;
106
+ let out = "";
107
+ let partStart = 0;
108
+ let keyEnd = -1;
109
+ for (let i = 0; i <= query.length; i++) {
110
+ const ch = i === query.length ? 38 : query.charCodeAt(i);
111
+ if (ch === 61 && keyEnd === -1) {
112
+ keyEnd = i;
113
+ continue;
114
+ }
115
+ if (ch !== 38) continue;
116
+ const partEnd = i;
117
+ const rawKeyEnd = keyEnd === -1 ? partEnd : keyEnd;
118
+ const rawKey = query.slice(partStart, rawKeyEnd);
119
+ if (out) out += "&";
120
+ out += keys.has(rawKey.toLowerCase()) ? rawKey + "=[REDACTED]" : query.slice(partStart, partEnd);
121
+ partStart = i + 1;
122
+ keyEnd = -1;
123
+ }
124
+ return out;
127
125
  };
128
- var shouldStartNodeSDK = (provider) => {
129
- return provider instanceof ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
126
+ const shouldStartNodeSDK = (provider) => {
127
+ return provider instanceof ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
130
128
  };
131
- var contextKeySpan = Symbol.for("OpenTelemetry Context Key SPAN");
132
- var getTracer = () => {
133
- const tracer = trace.getTracer("Elysia");
134
- return {
135
- ...tracer,
136
- startSpan(name, options, context) {
137
- return tracer.startSpan(name, options, context);
138
- },
139
- startActiveSpan(...args) {
140
- switch (args.length) {
141
- case 2:
142
- return tracer.startActiveSpan(
143
- args[0],
144
- createActiveSpanHandler(args[1])
145
- );
146
- case 3:
147
- return tracer.startActiveSpan(
148
- args[0],
149
- args[1],
150
- createActiveSpanHandler(args[2])
151
- );
152
- case 4:
153
- return tracer.startActiveSpan(
154
- args[0],
155
- args[1],
156
- args[2],
157
- createActiveSpanHandler(args[3])
158
- );
159
- }
160
- }
161
- };
129
+ const contextKeySpan = Symbol.for("OpenTelemetry Context Key SPAN");
130
+ const getTracer = () => {
131
+ const tracer = trace.getTracer("Elysia");
132
+ return {
133
+ ...tracer,
134
+ startSpan(name, options, context) {
135
+ return tracer.startSpan(name, options, context);
136
+ },
137
+ startActiveSpan(...args) {
138
+ switch (args.length) {
139
+ case 2: return tracer.startActiveSpan(args[0], createActiveSpanHandler(args[1]));
140
+ case 3: return tracer.startActiveSpan(args[0], args[1], createActiveSpanHandler(args[2]));
141
+ case 4: return tracer.startActiveSpan(args[0], args[1], args[2], createActiveSpanHandler(args[3]));
142
+ }
143
+ }
144
+ };
162
145
  };
163
- var startSpan = (name, options, context) => {
164
- const tracer = getTracer();
165
- return tracer.startSpan(name, options, context);
146
+ const startSpan = (name, options, context) => {
147
+ return getTracer().startSpan(name, options, context);
166
148
  };
167
- var startActiveSpan = (...args) => {
168
- const tracer = getTracer();
169
- switch (args.length) {
170
- case 2:
171
- return tracer.startActiveSpan(
172
- args[0],
173
- createActiveSpanHandler(args[1])
174
- );
175
- case 3:
176
- return tracer.startActiveSpan(
177
- args[0],
178
- args[1],
179
- createActiveSpanHandler(args[2])
180
- );
181
- case 4:
182
- return tracer.startActiveSpan(
183
- args[0],
184
- args[1],
185
- args[2],
186
- createActiveSpanHandler(args[3])
187
- );
188
- }
149
+ const startActiveSpan = (...args) => {
150
+ const tracer = getTracer();
151
+ switch (args.length) {
152
+ case 2: return tracer.startActiveSpan(args[0], createActiveSpanHandler(args[1]));
153
+ case 3: return tracer.startActiveSpan(args[0], args[1], createActiveSpanHandler(args[2]));
154
+ case 4: return tracer.startActiveSpan(args[0], args[1], args[2], createActiveSpanHandler(args[3]));
155
+ }
189
156
  };
190
- var record = startActiveSpan;
191
- var getCurrentSpan = () => trace.getActiveSpan();
192
- var setAttributes = (attributes) => !!getCurrentSpan()?.setAttributes(attributes);
193
- var opentelemetry = ({
194
- serviceName = "Elysia",
195
- instrumentations,
196
- contextManager,
197
- checkIfShouldTrace,
198
- spanUrlRedaction,
199
- recordBody,
200
- headersToSpanAttributes,
201
- ...options
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;
221
- let tracer = trace.getTracer(serviceName);
222
- if (shouldStartNodeSDK(trace.getTracerProvider())) {
223
- const sdk = new NodeSDK({
224
- ...options,
225
- serviceName,
226
- instrumentations
227
- });
228
- sdk.start();
229
- tracer = trace.getTracer(serviceName);
230
- } else {
231
- }
232
- if (!otelContext._getContextManager?.() && contextManager)
233
- try {
234
- contextManager.enable();
235
- otelContext.setGlobalContextManager(contextManager);
236
- } catch {
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
- );
271
- return new Elysia({
272
- name: "@elysia/opentelemetry"
273
- }).wrap((fn, request) => {
274
- const shouldTrace = checkIfShouldTrace ? checkIfShouldTrace(request) : true;
275
- if (!shouldTrace) return fn;
276
- const headers = headerHasToJSON ? (
277
- // @ts-ignore bun only
278
- request.headers.toJSON()
279
- ) : Object.fromEntries(request.headers.entries());
280
- const ctx = propagation.extract(otelContext.active(), headers);
281
- return tracer.startActiveSpan(
282
- "Root",
283
- { kind: SpanKind.SERVER },
284
- ctx,
285
- (rootSpan) => {
286
- const spanContext = trace.setSpan(ctx, rootSpan);
287
- return (...args) => {
288
- return otelContext.with(spanContext, () => fn(...args));
289
- };
290
- }
291
- );
292
- }).trace(
293
- { as: "global" },
294
- ({
295
- id,
296
- onRequest,
297
- onParse,
298
- onTransform,
299
- onBeforeHandle,
300
- onHandle,
301
- onAfterHandle,
302
- onError,
303
- onAfterResponse,
304
- onMapResponse,
305
- context,
306
- context: {
307
- path,
308
- request: { method }
309
- }
310
- }) => {
311
- const rootSpan = trace.getActiveSpan();
312
- if (!rootSpan) return;
313
- function setParent(span) {
314
- if (span.ended) return;
315
- if (rootSpan.ended) return void span.end();
316
- const newContext = trace.setSpan(otelContext.active(), span);
317
- const currentContext = (
318
- // @ts-expect-error private property
319
- otelContext.active()._currentContext
320
- );
321
- currentContext?.set(
322
- contextKeySpan,
323
- newContext.getValue(contextKeySpan)
324
- );
325
- }
326
- function inspect(name) {
327
- return function inspect2({
328
- onEvent,
329
- total,
330
- onStop
331
- }) {
332
- if (total === 0 || // @ts-ignore
333
- rootSpan.ended)
334
- return;
335
- tracer.startActiveSpan(
336
- name,
337
- {},
338
- createContext(rootSpan),
339
- (event) => {
340
- if (
341
- // @ts-ignore
342
- rootSpan.ended
343
- )
344
- return;
345
- onEvent(({ name: name2, onStop: onStop2 }) => {
346
- const useChildSpan = total > 1;
347
- let span;
348
- if (useChildSpan) {
349
- span = tracer.startSpan(
350
- name2,
351
- {},
352
- createContext(event)
353
- );
354
- setParent(span);
355
- } else {
356
- setParent(event);
357
- span = event;
358
- }
359
- onStop2(({ error }) => {
360
- setParent(rootSpan);
361
- if (span.ended || rootSpan.ended)
362
- return;
363
- if (error) {
364
- span.setAttributes({
365
- "error.type": error.constructor?.name ?? error.name,
366
- "error.stack": error.stack
367
- });
368
- }
369
- if (useChildSpan) span.end();
370
- });
371
- });
372
- onStop(() => {
373
- setParent(rootSpan);
374
- if (event.ended) return;
375
- event.end();
376
- });
377
- }
378
- );
379
- };
380
- }
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
- }
403
- const attributes = Object.assign(/* @__PURE__ */ Object.create(null), {
404
- // ? Elysia Custom attribute
405
- "http.request.id": id,
406
- "http.request.method": method,
407
- "url.path": path,
408
- "url.full": urlFull
409
- });
410
- if (urlQuery !== void 0) attributes["url.query"] = urlQuery;
411
- const protocolSeparator = urlFull.indexOf("://");
412
- if (protocolSeparator > 0)
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
- };
434
- onRequest(inspect("Request"));
435
- onParse(inspect("Parse"));
436
- onTransform(inspect("Transform"));
437
- onBeforeHandle(inspect("BeforeHandle"));
438
- onHandle(({ onStop }) => {
439
- const span = tracer.startSpan(
440
- "Handle",
441
- {},
442
- createContext(rootSpan)
443
- );
444
- setParent(span);
445
- onStop(({ error }) => {
446
- setParent(rootSpan);
447
- if (span.ended || rootSpan.ended) return;
448
- if (error) {
449
- span.recordException(error);
450
- rootSpan.recordException(error);
451
- }
452
- span.end();
453
- });
454
- });
455
- onAfterHandle(inspect("AfterHandle"));
456
- onError((event) => {
457
- inspect("Error")(event);
458
- event.onStop(({ error }) => {
459
- setParent(rootSpan);
460
- if (rootSpan.ended) return;
461
- {
462
- let status = context.set.status;
463
- if (typeof status === "string") {
464
- status = StatusMap[status];
465
- } else if (typeof status !== "number" && // @ts-ignore
466
- typeof error?.status === "number")
467
- status = error.status;
468
- if (typeof status === "number") {
469
- attributes["http.response.status_code"] = status;
470
- if (status >= 500)
471
- rootSpan.setStatus({
472
- code: SpanStatusCode.ERROR
473
- });
474
- }
475
- rootSpan.setAttributes(attributes);
476
- }
477
- if (
478
- // @ts-ignore
479
- !rootSpan.ended
480
- ) {
481
- recordDuration();
482
- rootSpan.end();
483
- }
484
- });
485
- });
486
- onMapResponse(inspect("MapResponse"));
487
- onTransform(() => {
488
- const { cookie, request, route, path: path2 } = context;
489
- if (route)
490
- rootSpan.updateName(
491
- // @ts-ignore private property
492
- `${method} ${route || path2}`
493
- );
494
- if (context.route) attributes["http.route"] = context.route;
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;
500
- }
501
- const userAgent = request.headers.get("User-Agent");
502
- if (userAgent) attributes["user_agent.original"] = userAgent;
503
- const server = context.server;
504
- if (server) {
505
- attributes["server.port"] = server.port ?? 80;
506
- attributes["server.address"] = server.url.hostname;
507
- }
508
- let headers;
509
- {
510
- let hasHeaders;
511
- let _headers;
512
- if (context.headers) {
513
- hasHeaders = true;
514
- headers = context.headers;
515
- _headers = Object.entries(context.headers);
516
- } else if (hasHeaders = headerHasToJSON) {
517
- headers = request.headers.toJSON();
518
- _headers = Object.entries(headers);
519
- } else {
520
- headers = {};
521
- _headers = request.headers.entries();
522
- }
523
- for (let [key, value] of _headers) {
524
- key = key.toLowerCase();
525
- if (hasHeaders) {
526
- if (!requestHeaderWildcard && !spanRequestHeaderSet.has(key))
527
- continue;
528
- if (typeof value === "object")
529
- attributes[`http.request.header.${key}`] = JSON.stringify(value);
530
- else if (value !== void 0)
531
- attributes[`http.request.header.${key}`] = value;
532
- continue;
533
- }
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;
543
- }
544
- }
545
- }
546
- {
547
- let headers2;
548
- if (context.set.headers instanceof Headers) {
549
- if (headerHasToJSON)
550
- headers2 = Object.entries(
551
- // @ts-ignore bun only
552
- context.set.headers.toJSON()
553
- );
554
- else headers2 = context.set.headers.entries();
555
- } else headers2 = Object.entries(context.set.headers);
556
- for (let [key, value] of headers2) {
557
- key = key.toLowerCase();
558
- if (!responseHeaderWildcard && !spanResponseHeaderSet.has(key))
559
- continue;
560
- if (typeof value === "object")
561
- attributes[`http.response.header.${key}`] = JSON.stringify(value);
562
- else
563
- attributes[`http.response.header.${key}`] = value;
564
- }
565
- }
566
- if (context.ip)
567
- attributes["client.address"] = context.ip;
568
- else {
569
- const ip = headers["true-client-ip"] ?? headers["cf-connection-ip"] ?? headers["x-forwarded-for"] ?? headers["x-real-ip"] ?? server?.requestIP(request);
570
- if (ip)
571
- attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
572
- }
573
- if ((requestHeaderWildcard || spanRequestHeaderSet.has("cookie")) && cookie) {
574
- const _cookie = {};
575
- for (const [key, { value }] of Object.entries(cookie))
576
- _cookie[key] = JSON.stringify(value);
577
- attributes["http.request.cookie"] = JSON.stringify(_cookie);
578
- }
579
- rootSpan.setAttributes(attributes);
580
- });
581
- onParse(() => {
582
- const body = context.body;
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;
588
- });
589
- onMapResponse(() => {
590
- const body = context.body;
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;
595
- }
596
- {
597
- let status = context.set.status ?? 200;
598
- if (typeof status === "string")
599
- status = StatusMap[status] ?? 200;
600
- attributes["http.response.status_code"] = status;
601
- }
602
- const response = context.responseValue;
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
- }
608
- if (!rootSpan.ended) {
609
- const statusCode = attributes["http.response.status_code"];
610
- if (typeof statusCode === "number" && statusCode >= 500) {
611
- rootSpan.setStatus({
612
- code: SpanStatusCode.ERROR
613
- });
614
- }
615
- rootSpan.setAttributes(attributes);
616
- }
617
- });
618
- onAfterResponse((event) => {
619
- inspect("AfterResponse")(event);
620
- {
621
- let status = context.set.status ?? 200;
622
- if (typeof status === "string")
623
- status = StatusMap[status] ?? 200;
624
- attributes["http.response.status_code"] = status;
625
- }
626
- const body = context.body;
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;
631
- }
632
- if (!rootSpan.ended) {
633
- const statusCode = attributes["http.response.status_code"];
634
- if (typeof statusCode === "number" && statusCode >= 500)
635
- rootSpan.setStatus({
636
- code: SpanStatusCode.ERROR
637
- });
638
- rootSpan.setAttributes(attributes);
639
- }
640
- event.onStop(() => {
641
- setParent(rootSpan);
642
- if (rootSpan.ended) return;
643
- if (
644
- // @ts-ignore
645
- !rootSpan.ended
646
- ) {
647
- recordDuration();
648
- rootSpan.end();
649
- }
650
- });
651
- });
652
- context.request.signal.addEventListener("abort", () => {
653
- const active = trace.getActiveSpan();
654
- if (active && !active.ended) active.end();
655
- if (rootSpan.ended) return;
656
- rootSpan.setStatus({
657
- code: SpanStatusCode.ERROR,
658
- message: "Request aborted"
659
- });
660
- recordDuration();
661
- rootSpan.end();
662
- });
663
- }
664
- );
665
- };
666
- export {
667
- contextKeySpan,
668
- getCurrentSpan,
669
- getTracer,
670
- opentelemetry,
671
- record,
672
- setAttributes,
673
- shouldStartNodeSDK,
674
- startActiveSpan,
675
- startSpan
157
+ const record = startActiveSpan;
158
+ const getCurrentSpan = () => trace.getActiveSpan();
159
+ /**
160
+ * Set attributes to the current span
161
+ *
162
+ * @returns boolean - whether the attributes are set or not
163
+ */
164
+ const setAttributes = (attributes) => !!getCurrentSpan()?.setAttributes(attributes);
165
+ const opentelemetry = ({ serviceName = "Elysia", instrumentations, contextManager, checkIfShouldTrace, spanUrlRedaction, recordBody, headersToSpanAttributes, ...options } = {}) => {
166
+ const spanRequestHeaderSet = toHeaderNameSet(headersToSpanAttributes?.request);
167
+ const spanResponseHeaderSet = toHeaderNameSet(headersToSpanAttributes?.response);
168
+ const requestHeaderWildcard = spanRequestHeaderSet.has("*");
169
+ const responseHeaderWildcard = spanResponseHeaderSet.has("*");
170
+ const recordRequestBody = recordBody === true || recordBody && recordBody.request || false;
171
+ const recordResponseBody = recordBody === true || recordBody && recordBody.response || false;
172
+ const urlRedactOpts = spanUrlRedaction === false ? null : spanUrlRedaction ?? {};
173
+ const sensitiveKeys = urlRedactOpts ? /* @__PURE__ */ new Set([...SENSITIVE_QUERY_KEYS, ...(urlRedactOpts.sensitiveQueryParams ?? []).map((k) => k.toLowerCase())]) : void 0;
174
+ const stripCreds = urlRedactOpts?.stripCredentials !== false;
175
+ let tracer = trace.getTracer(serviceName);
176
+ if (shouldStartNodeSDK(trace.getTracerProvider())) {
177
+ new NodeSDK({
178
+ ...options,
179
+ serviceName,
180
+ instrumentations
181
+ }).start();
182
+ tracer = trace.getTracer(serviceName);
183
+ }
184
+ if (!context._getContextManager?.() && contextManager) try {
185
+ contextManager.enable();
186
+ context.setGlobalContextManager(contextManager);
187
+ } catch {}
188
+ const httpServerDuration = metrics.getMeter(serviceName).createHistogram("http.server.request.duration", {
189
+ description: "Duration of HTTP server requests.",
190
+ unit: "s",
191
+ advice: { explicitBucketBoundaries: [
192
+ .005,
193
+ .01,
194
+ .025,
195
+ .05,
196
+ .075,
197
+ .1,
198
+ .25,
199
+ .5,
200
+ .75,
201
+ 1,
202
+ 2.5,
203
+ 5,
204
+ 7.5,
205
+ 10,
206
+ 30,
207
+ 60,
208
+ 120,
209
+ 300,
210
+ 600,
211
+ 900,
212
+ 1800
213
+ ] }
214
+ });
215
+ return new Elysia({ name: "@elysia/opentelemetry" }).wrap((fn) => {
216
+ return (request) => {
217
+ if (!(checkIfShouldTrace ? checkIfShouldTrace(request) : true)) return fn(request);
218
+ const headers = headerHasToJSON ? request.headers.toJSON() : Object.fromEntries(request.headers.entries());
219
+ const ctx = propagation.extract(context.active(), headers);
220
+ return tracer.startActiveSpan("Root", { kind: SpanKind.SERVER }, ctx, (rootSpan) => {
221
+ const spanContext = trace.setSpan(ctx, rootSpan);
222
+ return context.with(spanContext, () => fn(request));
223
+ });
224
+ };
225
+ }).trace("global", ({ id, onRequest, onParse, onTransform, onBeforeHandle, onHandle, onAfterHandle, onError, onAfterResponse, onMapResponse, context: context$1, context: { path, request: { method } } }) => {
226
+ const rootSpan = trace.getActiveSpan();
227
+ if (!rootSpan) return;
228
+ function setParent(span) {
229
+ if (span.ended) return;
230
+ if (rootSpan.ended) return void span.end();
231
+ const newContext = trace.setSpan(context.active(), span);
232
+ context.active()._currentContext?.set(contextKeySpan, newContext.getValue(contextKeySpan));
233
+ }
234
+ function inspect(name) {
235
+ return function inspect({ onEvent, total, onStop }) {
236
+ if (total === 0 || rootSpan.ended) return;
237
+ tracer.startActiveSpan(name, {}, createContext(rootSpan), (event) => {
238
+ if (rootSpan.ended) return;
239
+ onEvent(({ name, onStop }) => {
240
+ const useChildSpan = total > 1;
241
+ let span;
242
+ if (useChildSpan) {
243
+ span = tracer.startSpan(name, {}, createContext(event));
244
+ setParent(span);
245
+ } else {
246
+ setParent(event);
247
+ span = event;
248
+ }
249
+ onStop(({ error }) => {
250
+ setParent(rootSpan);
251
+ if (span.ended || rootSpan.ended) return;
252
+ if (error) span.setAttributes({
253
+ "error.type": error.constructor?.name ?? error.name,
254
+ "error.stack": error.stack
255
+ });
256
+ if (useChildSpan) span.end();
257
+ });
258
+ });
259
+ onStop(() => {
260
+ setParent(rootSpan);
261
+ if (event.ended) return;
262
+ event.end();
263
+ });
264
+ });
265
+ };
266
+ }
267
+ const rawUrl = context$1.request.url;
268
+ const qi = context$1.qi;
269
+ let urlQuery = qi !== void 0 && qi !== -1 ? rawUrl.slice(qi + 1) : void 0;
270
+ let urlFull = rawUrl;
271
+ if (urlRedactOpts) {
272
+ if (urlQuery !== void 0) {
273
+ urlQuery = redactQueryString(urlQuery, sensitiveKeys);
274
+ urlFull = `${rawUrl.slice(0, qi)}?${urlQuery}`;
275
+ }
276
+ if (stripCreds && urlFull.indexOf("@") > 0) try {
277
+ const u = new URL(urlFull);
278
+ if (u.username || u.password) {
279
+ u.username = "";
280
+ u.password = "";
281
+ urlFull = u.href;
282
+ }
283
+ } catch {}
284
+ }
285
+ const attributes = Object.assign(Object.create(null), {
286
+ "http.request.id": id,
287
+ "http.request.method": method,
288
+ "url.path": path,
289
+ "url.full": urlFull
290
+ });
291
+ if (urlQuery !== void 0) attributes["url.query"] = urlQuery;
292
+ const protocolSeparator = urlFull.indexOf("://");
293
+ if (protocolSeparator > 0) attributes["url.scheme"] = urlFull.slice(0, protocolSeparator);
294
+ const requestStartTime = performance.now();
295
+ let durationRecorded = false;
296
+ const recordDuration = () => {
297
+ if (durationRecorded) return;
298
+ durationRecorded = true;
299
+ const durationS = (performance.now() - requestStartTime) / 1e3;
300
+ const statusCode = attributes["http.response.status_code"];
301
+ const metricAttributes = {
302
+ "http.request.method": attributes["http.request.method"] ?? method,
303
+ "url.scheme": attributes["url.scheme"],
304
+ "http.response.status_code": statusCode,
305
+ "http.route": attributes["http.route"]
306
+ };
307
+ if (typeof statusCode === "number" && statusCode >= 500) metricAttributes["error.type"] = String(statusCode);
308
+ httpServerDuration.record(durationS, metricAttributes);
309
+ };
310
+ onRequest(inspect("Request"));
311
+ onParse(inspect("Parse"));
312
+ onTransform(inspect("Transform"));
313
+ onBeforeHandle(inspect("BeforeHandle"));
314
+ onHandle(({ onStop }) => {
315
+ const span = tracer.startSpan("Handle", {}, createContext(rootSpan));
316
+ setParent(span);
317
+ onStop(({ error }) => {
318
+ setParent(rootSpan);
319
+ if (span.ended || rootSpan.ended) return;
320
+ if (error) {
321
+ span.recordException(error);
322
+ rootSpan.recordException(error);
323
+ }
324
+ span.end();
325
+ });
326
+ });
327
+ onAfterHandle(inspect("AfterHandle"));
328
+ onError((event) => {
329
+ inspect("Error")(event);
330
+ event.onStop(({ error }) => {
331
+ setParent(rootSpan);
332
+ if (rootSpan.ended) return;
333
+ {
334
+ let status = context$1.set.status;
335
+ if (typeof status === "string") status = StatusMap[status];
336
+ else if (typeof status !== "number" && typeof error?.status === "number") status = error.status;
337
+ if (typeof status === "number") {
338
+ attributes["http.response.status_code"] = status;
339
+ if (status >= 500) rootSpan.setStatus({ code: SpanStatusCode.ERROR });
340
+ }
341
+ rootSpan.setAttributes(attributes);
342
+ }
343
+ });
344
+ });
345
+ onMapResponse(inspect("MapResponse"));
346
+ onTransform(() => {
347
+ const { cookie, request, route, path } = context$1;
348
+ const routeName = route ?? path;
349
+ rootSpan.updateName(`${method} ${routeName}`);
350
+ attributes["http.route"] = routeName;
351
+ /**
352
+ * ? Caution: This is not a standard way to get content-length
353
+ *
354
+ * As state in OpenTelemetry specification:
355
+ * The size of the request payload body in bytes.
356
+ * This is the number of bytes transferred excluding headers and is often,
357
+ * but not always, present as the Content-Length header.
358
+ * For requests using transport encoding, this should be the compressed size.
359
+ **/
360
+ const contentLength = request.headers.get("content-length");
361
+ if (contentLength) {
362
+ const number = parseNumericString(contentLength);
363
+ if (number !== null) attributes["http.request_content_length"] = number;
364
+ }
365
+ const userAgent = request.headers.get("User-Agent");
366
+ if (userAgent) attributes["user_agent.original"] = userAgent;
367
+ const server = context$1.server;
368
+ if (server) {
369
+ attributes["server.port"] = server.port ?? 80;
370
+ attributes["server.address"] = server.url.hostname;
371
+ }
372
+ let headers;
373
+ {
374
+ let hasHeaders;
375
+ let _headers;
376
+ if (context$1.headers) {
377
+ hasHeaders = true;
378
+ headers = context$1.headers;
379
+ _headers = Object.entries(context$1.headers);
380
+ } else if (hasHeaders = headerHasToJSON) {
381
+ headers = request.headers.toJSON();
382
+ _headers = Object.entries(headers);
383
+ } else {
384
+ headers = {};
385
+ _headers = request.headers.entries();
386
+ }
387
+ for (let [key, value] of _headers) {
388
+ key = key.toLowerCase();
389
+ if (hasHeaders) {
390
+ if (!requestHeaderWildcard && !spanRequestHeaderSet.has(key)) continue;
391
+ if (typeof value === "object") attributes[`http.request.header.${key}`] = JSON.stringify(value);
392
+ else if (value !== void 0) attributes[`http.request.header.${key}`] = value;
393
+ continue;
394
+ }
395
+ if (typeof value === "object") {
396
+ const serialized = JSON.stringify(value);
397
+ headers[key] = serialized;
398
+ if (requestHeaderWildcard || spanRequestHeaderSet.has(key)) attributes[`http.request.header.${key}`] = serialized;
399
+ } else if (value !== void 0) {
400
+ headers[key] = value;
401
+ if (requestHeaderWildcard || spanRequestHeaderSet.has(key)) attributes[`http.request.header.${key}`] = value;
402
+ }
403
+ }
404
+ }
405
+ {
406
+ let headers;
407
+ if (context$1.set.headers instanceof Headers) if (headerHasToJSON) headers = Object.entries(context$1.set.headers.toJSON());
408
+ else headers = context$1.set.headers.entries();
409
+ else headers = Object.entries(context$1.set.headers);
410
+ for (let [key, value] of headers) {
411
+ key = key.toLowerCase();
412
+ if (!responseHeaderWildcard && !spanResponseHeaderSet.has(key)) continue;
413
+ if (typeof value === "object") attributes[`http.response.header.${key}`] = JSON.stringify(value);
414
+ else attributes[`http.response.header.${key}`] = value;
415
+ }
416
+ }
417
+ if (context$1.ip) attributes["client.address"] = context$1.ip;
418
+ else {
419
+ const ip = headers["true-client-ip"] ?? headers["cf-connection-ip"] ?? headers["x-forwarded-for"] ?? headers["x-real-ip"] ?? server?.requestIP(request);
420
+ if (ip) attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
421
+ }
422
+ if ((requestHeaderWildcard || spanRequestHeaderSet.has("cookie")) && cookie) {
423
+ const _cookie = {};
424
+ for (const [key, { value }] of Object.entries(cookie)) _cookie[key] = JSON.stringify(value);
425
+ attributes["http.request.cookie"] = JSON.stringify(_cookie);
426
+ }
427
+ rootSpan.setAttributes(attributes);
428
+ });
429
+ onParse(() => {
430
+ const body = context$1.body;
431
+ if (body === void 0 || body === null || !recordRequestBody) return;
432
+ const { text, size } = serializeBody(body);
433
+ if (text) attributes["http.request.body"] = text;
434
+ attributes["http.request.body.size"] = size;
435
+ });
436
+ onMapResponse(() => {
437
+ const body = context$1.body;
438
+ if (body !== void 0 && body !== null && recordRequestBody) {
439
+ const { text, size } = serializeBody(body);
440
+ if (text) attributes["http.request.body"] = text;
441
+ attributes["http.request.body.size"] = size;
442
+ }
443
+ {
444
+ let status = context$1.set.status ?? 200;
445
+ if (typeof status === "string") status = StatusMap[status] ?? 200;
446
+ attributes["http.response.status_code"] = status;
447
+ }
448
+ const response = context$1.responseValue;
449
+ if (response !== void 0 && recordResponseBody) {
450
+ const { text, size } = serializeBody(response);
451
+ if (text) attributes["http.response.body"] = text;
452
+ attributes["http.response.body.size"] = size;
453
+ }
454
+ if (!rootSpan.ended) {
455
+ const statusCode = attributes["http.response.status_code"];
456
+ if (typeof statusCode === "number" && statusCode >= 500) rootSpan.setStatus({ code: SpanStatusCode.ERROR });
457
+ rootSpan.setAttributes(attributes);
458
+ }
459
+ });
460
+ onAfterResponse((event) => {
461
+ inspect("AfterResponse")(event);
462
+ {
463
+ let status = context$1.set.status ?? 200;
464
+ if (typeof status === "string") status = StatusMap[status] ?? 200;
465
+ attributes["http.response.status_code"] = status;
466
+ }
467
+ const body = context$1.body;
468
+ if (body !== void 0 && body !== null && recordRequestBody) {
469
+ const { text, size } = serializeBody(body);
470
+ if (text) attributes["http.request.body"] = text;
471
+ attributes["http.request.body.size"] = size;
472
+ }
473
+ if (!rootSpan.ended) {
474
+ const statusCode = attributes["http.response.status_code"];
475
+ if (typeof statusCode === "number" && statusCode >= 500) rootSpan.setStatus({ code: SpanStatusCode.ERROR });
476
+ rootSpan.setAttributes(attributes);
477
+ }
478
+ event.onStop(() => {
479
+ setParent(rootSpan);
480
+ if (rootSpan.ended) return;
481
+ if (!rootSpan.ended) {
482
+ recordDuration();
483
+ rootSpan.end();
484
+ }
485
+ });
486
+ });
487
+ context$1.request.signal.addEventListener("abort", () => {
488
+ const active = trace.getActiveSpan();
489
+ if (active && !active.ended) active.end();
490
+ if (rootSpan.ended) return;
491
+ rootSpan.setStatus({
492
+ code: SpanStatusCode.ERROR,
493
+ message: "Request aborted"
494
+ });
495
+ recordDuration();
496
+ rootSpan.end();
497
+ });
498
+ });
676
499
  };
500
+
501
+ //#endregion
502
+ export { contextKeySpan, getCurrentSpan, getTracer, opentelemetry, record, setAttributes, shouldStartNodeSDK, startActiveSpan, startSpan };