@elysia/opentelemetry 1.4.11 → 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.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { Elysia } from 'elysia';
2
- import { type ContextManager, type Context, type SpanOptions, type Span, type Attributes, TraceAPI, TracerProvider } from '@opentelemetry/api';
3
- import { NodeSDK } from '@opentelemetry/sdk-node';
1
+ import { Elysia } from "elysia";
2
+ import { Attributes, Context, ContextManager, Span, SpanOptions, TraceAPI, TracerProvider } from "@opentelemetry/api";
3
+ import { NodeSDK } from "@opentelemetry/sdk-node";
4
+
5
+ //#region src/index.d.ts
4
6
  type OpenTeleMetryOptions = NonNullable<ConstructorParameters<typeof NodeSDK>[0]>;
5
7
  /**
6
8
  * Initialize OpenTelemetry SDK
@@ -8,63 +10,77 @@ type OpenTeleMetryOptions = NonNullable<ConstructorParameters<typeof NodeSDK>[0]
8
10
  * For best practice, you should be using preload OpenTelemetry SDK if possible
9
11
  * however, this is a simple way to initialize OpenTelemetry SDK
10
12
  */
11
- export interface ElysiaOpenTelemetryOptions extends OpenTeleMetryOptions {
12
- contextManager?: ContextManager;
13
- /**
14
- * Optional function to determine whether a given request should be traced.
15
- *
16
- * @param req - The incoming request object to evaluate.
17
- * @returns A boolean indicating whether tracing should be enabled for this request.
18
- */
19
- checkIfShouldTrace?: (req: Request) => boolean;
13
+ interface ElysiaOpenTelemetryOptions extends OpenTeleMetryOptions {
14
+ contextManager?: ContextManager;
15
+ /**
16
+ * Optional function to determine whether a given request should be traced.
17
+ *
18
+ * @param req - The incoming request object to evaluate.
19
+ * @returns A boolean indicating whether tracing should be enabled for this request.
20
+ */
21
+ checkIfShouldTrace?: (req: Request) => boolean;
22
+ /**
23
+ * Redact `userinfo` and sensitive query values in `url.full` / `url.query`.
24
+ * Omitted: default redaction. `false`: record raw URLs (may leak secrets in query or credentials).
25
+ */
26
+ spanUrlRedaction?: false | {
27
+ stripCredentials?: boolean;
28
+ sensitiveQueryParams?: string[];
29
+ };
30
+ /**
31
+ * Record full request/response body content on spans.
32
+ * `true`: record both request and response bodies.
33
+ * `{ request: true }` or `{ response: true }`: record only one side.
34
+ * Default: `false` (no body content recorded).
35
+ */
36
+ recordBody?: boolean | {
37
+ request?: boolean;
38
+ response?: boolean;
39
+ };
40
+ /**
41
+ * HTTP header names (case-insensitive) to capture as span attributes.
42
+ * Use `"*"` in either list to capture all headers (useful for dev/debugging; may include sensitive values).
43
+ * Including `"cookie"` in `requestHeaders` also emits `http.request.cookie` when `context.cookie` exists.
44
+ * Default: none (no headers recorded).
45
+ */
46
+ headersToSpanAttributes?: {
47
+ request?: string[];
48
+ response?: string[];
49
+ };
20
50
  }
21
- 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
- export declare const shouldStartNodeSDK: (provider: TracerProvider) => boolean;
23
- export type Tracer = ReturnType<TraceAPI['getTracer']>;
24
- export type StartSpan = Tracer['startSpan'];
25
- export type StartActiveSpan = Tracer['startActiveSpan'];
26
- export declare const contextKeySpan: unique symbol;
27
- export declare const getTracer: () => ReturnType<TraceAPI["getTracer"]>;
28
- export declare const startSpan: (name: string, options?: SpanOptions, context?: Context) => Span;
29
- export declare const startActiveSpan: StartActiveSpan;
30
- export declare const record: {
31
- <F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
32
- <F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
33
- <F extends (span: Span) => unknown>(name: string, options: SpanOptions, context: Context, fn: F): ReturnType<F>;
51
+ 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];
52
+ declare const shouldStartNodeSDK: (provider: TracerProvider) => boolean;
53
+ type Tracer = ReturnType<TraceAPI['getTracer']>;
54
+ type StartSpan = Tracer['startSpan'];
55
+ type StartActiveSpan = Tracer['startActiveSpan'];
56
+ declare const contextKeySpan: unique symbol;
57
+ declare const getTracer: () => ReturnType<TraceAPI["getTracer"]>;
58
+ declare const startSpan: (name: string, options?: SpanOptions, context?: Context) => Span;
59
+ declare const startActiveSpan: StartActiveSpan;
60
+ declare const record: {
61
+ <F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
62
+ <F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
63
+ <F extends (span: Span) => unknown>(name: string, options: SpanOptions, context: Context, fn: F): ReturnType<F>;
34
64
  };
35
- export declare const getCurrentSpan: () => Span | undefined;
65
+ declare const getCurrentSpan: () => Span | undefined;
36
66
  /**
37
67
  * Set attributes to the current span
38
68
  *
39
69
  * @returns boolean - whether the attributes are set or not
40
70
  */
41
- export declare const setAttributes: (attributes: Attributes) => boolean;
42
- export declare const opentelemetry: ({ serviceName, instrumentations, contextManager, checkIfShouldTrace, ...options }?: ElysiaOpenTelemetryOptions) => Elysia<"", {
43
- decorator: {};
44
- store: {};
45
- derive: {};
46
- resolve: {};
47
- }, {
48
- typebox: {};
49
- error: {};
50
- }, {
51
- schema: {};
52
- standaloneSchema: {};
53
- macro: {};
54
- macroFn: {};
55
- parser: {};
56
- response: {};
57
- }, {}, {
58
- derive: {};
59
- resolve: {};
60
- schema: {};
61
- standaloneSchema: {};
62
- response: {};
63
- }, {
64
- derive: {};
65
- resolve: {};
66
- schema: {};
67
- standaloneSchema: {};
68
- response: {};
69
- }>;
70
- export {};
71
+ declare const setAttributes: (attributes: Attributes) => boolean;
72
+ declare const opentelemetry: ({
73
+ serviceName,
74
+ instrumentations,
75
+ contextManager,
76
+ checkIfShouldTrace,
77
+ spanUrlRedaction,
78
+ recordBody,
79
+ headersToSpanAttributes,
80
+ ...options
81
+ }?: ElysiaOpenTelemetryOptions) => Elysia<"", "local", import("elysia/dist/types").DefaultSingleton, {
82
+ typebox: {};
83
+ error: [];
84
+ }, import("elysia/dist/types").DefaultMetadata, {}, import("elysia/dist/types").DefaultEphemeral, import("elysia/dist/types").DefaultEphemeral>;
85
+ //#endregion
86
+ export { ActiveSpanArgs, ElysiaOpenTelemetryOptions, StartActiveSpan, StartSpan, Tracer, contextKeySpan, getCurrentSpan, getTracer, opentelemetry, record, setAttributes, shouldStartNodeSDK, startActiveSpan, startSpan };
package/dist/index.js ADDED
@@ -0,0 +1,511 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let elysia = require("elysia");
3
+ let _opentelemetry_api = require("@opentelemetry/api");
4
+ let _opentelemetry_sdk_node = require("@opentelemetry/sdk-node");
5
+
6
+ //#region src/index.ts
7
+ const headerHasToJSON = typeof new Headers().toJSON === "function";
8
+ const toHeaderNameSet = (names) => new Set((names ?? []).map((name) => name.toLowerCase()));
9
+ const SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
10
+ "token",
11
+ "access_token",
12
+ "refresh_token",
13
+ "id_token",
14
+ "password",
15
+ "passwd",
16
+ "pwd",
17
+ "secret",
18
+ "client_secret",
19
+ "api_key",
20
+ "apikey",
21
+ "api-key",
22
+ "authorization",
23
+ "credential",
24
+ "credentials",
25
+ "code",
26
+ "nonce"
27
+ ]);
28
+ const parseNumericString = (message) => {
29
+ if (message.length < 16) {
30
+ if (message.length === 0) return null;
31
+ const length = Number(message);
32
+ if (Number.isNaN(length)) return null;
33
+ return length;
34
+ }
35
+ if (message.length === 16) {
36
+ const number = Number(message);
37
+ if (number.toString() !== message || message.trim().length === 0 || Number.isNaN(number)) return null;
38
+ return number;
39
+ }
40
+ return null;
41
+ };
42
+ const createActiveSpanHandler = (fn) => function(span) {
43
+ try {
44
+ const result = fn(span);
45
+ if (result instanceof Promise || typeof result?.then === "function") return Promise.resolve(result).then((value) => {
46
+ span.end();
47
+ return value;
48
+ }, (rejectResult) => {
49
+ span.setStatus({
50
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
51
+ message: rejectResult instanceof Error ? rejectResult.message : JSON.stringify(rejectResult ?? "Unknown error")
52
+ });
53
+ span.recordException(rejectResult);
54
+ span.end();
55
+ throw rejectResult;
56
+ });
57
+ span.end();
58
+ return result;
59
+ } catch (error) {
60
+ const err = error;
61
+ span.setStatus({
62
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
63
+ message: err?.message
64
+ });
65
+ span.recordException(err);
66
+ span.end();
67
+ throw error;
68
+ }
69
+ };
70
+ const createContext = (parent) => ({
71
+ getValue() {
72
+ return parent;
73
+ },
74
+ setValue() {
75
+ return _opentelemetry_api.context.active();
76
+ },
77
+ deleteValue() {
78
+ return _opentelemetry_api.context.active();
79
+ }
80
+ });
81
+ const serializeBody = (body) => {
82
+ if (body instanceof Uint8Array) return {
83
+ text: "",
84
+ size: body.length
85
+ };
86
+ if (body instanceof ArrayBuffer) return {
87
+ text: "",
88
+ size: body.byteLength
89
+ };
90
+ if (body instanceof Blob) return {
91
+ text: "",
92
+ size: body.size
93
+ };
94
+ let text;
95
+ try {
96
+ text = typeof body === "object" ? JSON.stringify(body) : String(body);
97
+ } catch {
98
+ text = "[Unserializable]";
99
+ }
100
+ return {
101
+ text,
102
+ size: text.length
103
+ };
104
+ };
105
+ const redactQueryString = (query, keys) => {
106
+ if (query === "" || keys.size === 0) return query;
107
+ let out = "";
108
+ let partStart = 0;
109
+ let keyEnd = -1;
110
+ for (let i = 0; i <= query.length; i++) {
111
+ const ch = i === query.length ? 38 : query.charCodeAt(i);
112
+ if (ch === 61 && keyEnd === -1) {
113
+ keyEnd = i;
114
+ continue;
115
+ }
116
+ if (ch !== 38) continue;
117
+ const partEnd = i;
118
+ const rawKeyEnd = keyEnd === -1 ? partEnd : keyEnd;
119
+ const rawKey = query.slice(partStart, rawKeyEnd);
120
+ if (out) out += "&";
121
+ out += keys.has(rawKey.toLowerCase()) ? rawKey + "=[REDACTED]" : query.slice(partStart, partEnd);
122
+ partStart = i + 1;
123
+ keyEnd = -1;
124
+ }
125
+ return out;
126
+ };
127
+ const shouldStartNodeSDK = (provider) => {
128
+ return provider instanceof _opentelemetry_api.ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
129
+ };
130
+ const contextKeySpan = Symbol.for("OpenTelemetry Context Key SPAN");
131
+ const getTracer = () => {
132
+ const tracer = _opentelemetry_api.trace.getTracer("Elysia");
133
+ return {
134
+ ...tracer,
135
+ startSpan(name, options, context) {
136
+ return tracer.startSpan(name, options, context);
137
+ },
138
+ startActiveSpan(...args) {
139
+ switch (args.length) {
140
+ case 2: return tracer.startActiveSpan(args[0], createActiveSpanHandler(args[1]));
141
+ case 3: return tracer.startActiveSpan(args[0], args[1], createActiveSpanHandler(args[2]));
142
+ case 4: return tracer.startActiveSpan(args[0], args[1], args[2], createActiveSpanHandler(args[3]));
143
+ }
144
+ }
145
+ };
146
+ };
147
+ const startSpan = (name, options, context) => {
148
+ return getTracer().startSpan(name, options, context);
149
+ };
150
+ const startActiveSpan = (...args) => {
151
+ const tracer = getTracer();
152
+ switch (args.length) {
153
+ case 2: return tracer.startActiveSpan(args[0], createActiveSpanHandler(args[1]));
154
+ case 3: return tracer.startActiveSpan(args[0], args[1], createActiveSpanHandler(args[2]));
155
+ case 4: return tracer.startActiveSpan(args[0], args[1], args[2], createActiveSpanHandler(args[3]));
156
+ }
157
+ };
158
+ const record = startActiveSpan;
159
+ const getCurrentSpan = () => _opentelemetry_api.trace.getActiveSpan();
160
+ /**
161
+ * Set attributes to the current span
162
+ *
163
+ * @returns boolean - whether the attributes are set or not
164
+ */
165
+ const setAttributes = (attributes) => !!getCurrentSpan()?.setAttributes(attributes);
166
+ const opentelemetry = ({ serviceName = "Elysia", instrumentations, contextManager, checkIfShouldTrace, spanUrlRedaction, recordBody, headersToSpanAttributes, ...options } = {}) => {
167
+ const spanRequestHeaderSet = toHeaderNameSet(headersToSpanAttributes?.request);
168
+ const spanResponseHeaderSet = toHeaderNameSet(headersToSpanAttributes?.response);
169
+ const requestHeaderWildcard = spanRequestHeaderSet.has("*");
170
+ const responseHeaderWildcard = spanResponseHeaderSet.has("*");
171
+ const recordRequestBody = recordBody === true || recordBody && recordBody.request || false;
172
+ const recordResponseBody = recordBody === true || recordBody && recordBody.response || false;
173
+ const urlRedactOpts = spanUrlRedaction === false ? null : spanUrlRedaction ?? {};
174
+ const sensitiveKeys = urlRedactOpts ? /* @__PURE__ */ new Set([...SENSITIVE_QUERY_KEYS, ...(urlRedactOpts.sensitiveQueryParams ?? []).map((k) => k.toLowerCase())]) : void 0;
175
+ const stripCreds = urlRedactOpts?.stripCredentials !== false;
176
+ let tracer = _opentelemetry_api.trace.getTracer(serviceName);
177
+ if (shouldStartNodeSDK(_opentelemetry_api.trace.getTracerProvider())) {
178
+ new _opentelemetry_sdk_node.NodeSDK({
179
+ ...options,
180
+ serviceName,
181
+ instrumentations
182
+ }).start();
183
+ tracer = _opentelemetry_api.trace.getTracer(serviceName);
184
+ }
185
+ if (!_opentelemetry_api.context._getContextManager?.() && contextManager) try {
186
+ contextManager.enable();
187
+ _opentelemetry_api.context.setGlobalContextManager(contextManager);
188
+ } catch {}
189
+ const httpServerDuration = _opentelemetry_api.metrics.getMeter(serviceName).createHistogram("http.server.request.duration", {
190
+ description: "Duration of HTTP server requests.",
191
+ unit: "s",
192
+ advice: { explicitBucketBoundaries: [
193
+ .005,
194
+ .01,
195
+ .025,
196
+ .05,
197
+ .075,
198
+ .1,
199
+ .25,
200
+ .5,
201
+ .75,
202
+ 1,
203
+ 2.5,
204
+ 5,
205
+ 7.5,
206
+ 10,
207
+ 30,
208
+ 60,
209
+ 120,
210
+ 300,
211
+ 600,
212
+ 900,
213
+ 1800
214
+ ] }
215
+ });
216
+ return new elysia.Elysia({ name: "@elysia/opentelemetry" }).wrap((fn) => {
217
+ return (request) => {
218
+ if (!(checkIfShouldTrace ? checkIfShouldTrace(request) : true)) return fn(request);
219
+ const headers = headerHasToJSON ? request.headers.toJSON() : Object.fromEntries(request.headers.entries());
220
+ const ctx = _opentelemetry_api.propagation.extract(_opentelemetry_api.context.active(), headers);
221
+ return tracer.startActiveSpan("Root", { kind: _opentelemetry_api.SpanKind.SERVER }, ctx, (rootSpan) => {
222
+ const spanContext = _opentelemetry_api.trace.setSpan(ctx, rootSpan);
223
+ return _opentelemetry_api.context.with(spanContext, () => fn(request));
224
+ });
225
+ };
226
+ }).trace("global", ({ id, onRequest, onParse, onTransform, onBeforeHandle, onHandle, onAfterHandle, onError, onAfterResponse, onMapResponse, context, context: { path, request: { method } } }) => {
227
+ const rootSpan = _opentelemetry_api.trace.getActiveSpan();
228
+ if (!rootSpan) return;
229
+ function setParent(span) {
230
+ if (span.ended) return;
231
+ if (rootSpan.ended) return void span.end();
232
+ const newContext = _opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), span);
233
+ _opentelemetry_api.context.active()._currentContext?.set(contextKeySpan, newContext.getValue(contextKeySpan));
234
+ }
235
+ function inspect(name) {
236
+ return function inspect({ onEvent, total, onStop }) {
237
+ if (total === 0 || rootSpan.ended) return;
238
+ tracer.startActiveSpan(name, {}, createContext(rootSpan), (event) => {
239
+ if (rootSpan.ended) return;
240
+ onEvent(({ name, onStop }) => {
241
+ const useChildSpan = total > 1;
242
+ let span;
243
+ if (useChildSpan) {
244
+ span = tracer.startSpan(name, {}, createContext(event));
245
+ setParent(span);
246
+ } else {
247
+ setParent(event);
248
+ span = event;
249
+ }
250
+ onStop(({ error }) => {
251
+ setParent(rootSpan);
252
+ if (span.ended || rootSpan.ended) return;
253
+ if (error) span.setAttributes({
254
+ "error.type": error.constructor?.name ?? error.name,
255
+ "error.stack": error.stack
256
+ });
257
+ if (useChildSpan) span.end();
258
+ });
259
+ });
260
+ onStop(() => {
261
+ setParent(rootSpan);
262
+ if (event.ended) return;
263
+ event.end();
264
+ });
265
+ });
266
+ };
267
+ }
268
+ const rawUrl = context.request.url;
269
+ const qi = context.qi;
270
+ let urlQuery = qi !== void 0 && qi !== -1 ? rawUrl.slice(qi + 1) : void 0;
271
+ let urlFull = rawUrl;
272
+ if (urlRedactOpts) {
273
+ if (urlQuery !== void 0) {
274
+ urlQuery = redactQueryString(urlQuery, sensitiveKeys);
275
+ urlFull = `${rawUrl.slice(0, qi)}?${urlQuery}`;
276
+ }
277
+ if (stripCreds && urlFull.indexOf("@") > 0) try {
278
+ const u = new URL(urlFull);
279
+ if (u.username || u.password) {
280
+ u.username = "";
281
+ u.password = "";
282
+ urlFull = u.href;
283
+ }
284
+ } catch {}
285
+ }
286
+ const attributes = Object.assign(Object.create(null), {
287
+ "http.request.id": id,
288
+ "http.request.method": method,
289
+ "url.path": path,
290
+ "url.full": urlFull
291
+ });
292
+ if (urlQuery !== void 0) attributes["url.query"] = urlQuery;
293
+ const protocolSeparator = urlFull.indexOf("://");
294
+ if (protocolSeparator > 0) attributes["url.scheme"] = urlFull.slice(0, protocolSeparator);
295
+ const requestStartTime = performance.now();
296
+ let durationRecorded = false;
297
+ const recordDuration = () => {
298
+ if (durationRecorded) return;
299
+ durationRecorded = true;
300
+ const durationS = (performance.now() - requestStartTime) / 1e3;
301
+ const statusCode = attributes["http.response.status_code"];
302
+ const metricAttributes = {
303
+ "http.request.method": attributes["http.request.method"] ?? method,
304
+ "url.scheme": attributes["url.scheme"],
305
+ "http.response.status_code": statusCode,
306
+ "http.route": attributes["http.route"]
307
+ };
308
+ if (typeof statusCode === "number" && statusCode >= 500) metricAttributes["error.type"] = String(statusCode);
309
+ httpServerDuration.record(durationS, metricAttributes);
310
+ };
311
+ onRequest(inspect("Request"));
312
+ onParse(inspect("Parse"));
313
+ onTransform(inspect("Transform"));
314
+ onBeforeHandle(inspect("BeforeHandle"));
315
+ onHandle(({ onStop }) => {
316
+ const span = tracer.startSpan("Handle", {}, createContext(rootSpan));
317
+ setParent(span);
318
+ onStop(({ error }) => {
319
+ setParent(rootSpan);
320
+ if (span.ended || rootSpan.ended) return;
321
+ if (error) {
322
+ span.recordException(error);
323
+ rootSpan.recordException(error);
324
+ }
325
+ span.end();
326
+ });
327
+ });
328
+ onAfterHandle(inspect("AfterHandle"));
329
+ onError((event) => {
330
+ inspect("Error")(event);
331
+ event.onStop(({ error }) => {
332
+ setParent(rootSpan);
333
+ if (rootSpan.ended) return;
334
+ {
335
+ let status = context.set.status;
336
+ if (typeof status === "string") status = elysia.StatusMap[status];
337
+ else if (typeof status !== "number" && typeof error?.status === "number") status = error.status;
338
+ if (typeof status === "number") {
339
+ attributes["http.response.status_code"] = status;
340
+ if (status >= 500) rootSpan.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
341
+ }
342
+ rootSpan.setAttributes(attributes);
343
+ }
344
+ });
345
+ });
346
+ onMapResponse(inspect("MapResponse"));
347
+ onTransform(() => {
348
+ const { cookie, request, route, path } = context;
349
+ const routeName = route ?? path;
350
+ rootSpan.updateName(`${method} ${routeName}`);
351
+ attributes["http.route"] = routeName;
352
+ /**
353
+ * ? Caution: This is not a standard way to get content-length
354
+ *
355
+ * As state in OpenTelemetry specification:
356
+ * The size of the request payload body in bytes.
357
+ * This is the number of bytes transferred excluding headers and is often,
358
+ * but not always, present as the Content-Length header.
359
+ * For requests using transport encoding, this should be the compressed size.
360
+ **/
361
+ const contentLength = request.headers.get("content-length");
362
+ if (contentLength) {
363
+ const number = parseNumericString(contentLength);
364
+ if (number !== null) attributes["http.request_content_length"] = number;
365
+ }
366
+ const userAgent = request.headers.get("User-Agent");
367
+ if (userAgent) attributes["user_agent.original"] = userAgent;
368
+ const server = context.server;
369
+ if (server) {
370
+ attributes["server.port"] = server.port ?? 80;
371
+ attributes["server.address"] = server.url.hostname;
372
+ }
373
+ let headers;
374
+ {
375
+ let hasHeaders;
376
+ let _headers;
377
+ if (context.headers) {
378
+ hasHeaders = true;
379
+ headers = context.headers;
380
+ _headers = Object.entries(context.headers);
381
+ } else if (hasHeaders = headerHasToJSON) {
382
+ headers = request.headers.toJSON();
383
+ _headers = Object.entries(headers);
384
+ } else {
385
+ headers = {};
386
+ _headers = request.headers.entries();
387
+ }
388
+ for (let [key, value] of _headers) {
389
+ key = key.toLowerCase();
390
+ if (hasHeaders) {
391
+ if (!requestHeaderWildcard && !spanRequestHeaderSet.has(key)) continue;
392
+ if (typeof value === "object") attributes[`http.request.header.${key}`] = JSON.stringify(value);
393
+ else if (value !== void 0) attributes[`http.request.header.${key}`] = value;
394
+ continue;
395
+ }
396
+ if (typeof value === "object") {
397
+ const serialized = JSON.stringify(value);
398
+ headers[key] = serialized;
399
+ if (requestHeaderWildcard || spanRequestHeaderSet.has(key)) attributes[`http.request.header.${key}`] = serialized;
400
+ } else if (value !== void 0) {
401
+ headers[key] = value;
402
+ if (requestHeaderWildcard || spanRequestHeaderSet.has(key)) attributes[`http.request.header.${key}`] = value;
403
+ }
404
+ }
405
+ }
406
+ {
407
+ let headers;
408
+ if (context.set.headers instanceof Headers) if (headerHasToJSON) headers = Object.entries(context.set.headers.toJSON());
409
+ else headers = context.set.headers.entries();
410
+ else headers = Object.entries(context.set.headers);
411
+ for (let [key, value] of headers) {
412
+ key = key.toLowerCase();
413
+ if (!responseHeaderWildcard && !spanResponseHeaderSet.has(key)) continue;
414
+ if (typeof value === "object") attributes[`http.response.header.${key}`] = JSON.stringify(value);
415
+ else attributes[`http.response.header.${key}`] = value;
416
+ }
417
+ }
418
+ if (context.ip) attributes["client.address"] = context.ip;
419
+ else {
420
+ const ip = headers["true-client-ip"] ?? headers["cf-connection-ip"] ?? headers["x-forwarded-for"] ?? headers["x-real-ip"] ?? server?.requestIP(request);
421
+ if (ip) attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
422
+ }
423
+ if ((requestHeaderWildcard || spanRequestHeaderSet.has("cookie")) && cookie) {
424
+ const _cookie = {};
425
+ for (const [key, { value }] of Object.entries(cookie)) _cookie[key] = JSON.stringify(value);
426
+ attributes["http.request.cookie"] = JSON.stringify(_cookie);
427
+ }
428
+ rootSpan.setAttributes(attributes);
429
+ });
430
+ onParse(() => {
431
+ const body = context.body;
432
+ if (body === void 0 || body === null || !recordRequestBody) return;
433
+ const { text, size } = serializeBody(body);
434
+ if (text) attributes["http.request.body"] = text;
435
+ attributes["http.request.body.size"] = size;
436
+ });
437
+ onMapResponse(() => {
438
+ const body = context.body;
439
+ if (body !== void 0 && body !== null && recordRequestBody) {
440
+ const { text, size } = serializeBody(body);
441
+ if (text) attributes["http.request.body"] = text;
442
+ attributes["http.request.body.size"] = size;
443
+ }
444
+ {
445
+ let status = context.set.status ?? 200;
446
+ if (typeof status === "string") status = elysia.StatusMap[status] ?? 200;
447
+ attributes["http.response.status_code"] = status;
448
+ }
449
+ const response = context.responseValue;
450
+ if (response !== void 0 && recordResponseBody) {
451
+ const { text, size } = serializeBody(response);
452
+ if (text) attributes["http.response.body"] = text;
453
+ attributes["http.response.body.size"] = size;
454
+ }
455
+ if (!rootSpan.ended) {
456
+ const statusCode = attributes["http.response.status_code"];
457
+ if (typeof statusCode === "number" && statusCode >= 500) rootSpan.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
458
+ rootSpan.setAttributes(attributes);
459
+ }
460
+ });
461
+ onAfterResponse((event) => {
462
+ inspect("AfterResponse")(event);
463
+ {
464
+ let status = context.set.status ?? 200;
465
+ if (typeof status === "string") status = elysia.StatusMap[status] ?? 200;
466
+ attributes["http.response.status_code"] = status;
467
+ }
468
+ const body = context.body;
469
+ if (body !== void 0 && body !== null && recordRequestBody) {
470
+ const { text, size } = serializeBody(body);
471
+ if (text) attributes["http.request.body"] = text;
472
+ attributes["http.request.body.size"] = size;
473
+ }
474
+ if (!rootSpan.ended) {
475
+ const statusCode = attributes["http.response.status_code"];
476
+ if (typeof statusCode === "number" && statusCode >= 500) rootSpan.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
477
+ rootSpan.setAttributes(attributes);
478
+ }
479
+ event.onStop(() => {
480
+ setParent(rootSpan);
481
+ if (rootSpan.ended) return;
482
+ if (!rootSpan.ended) {
483
+ recordDuration();
484
+ rootSpan.end();
485
+ }
486
+ });
487
+ });
488
+ context.request.signal.addEventListener("abort", () => {
489
+ const active = _opentelemetry_api.trace.getActiveSpan();
490
+ if (active && !active.ended) active.end();
491
+ if (rootSpan.ended) return;
492
+ rootSpan.setStatus({
493
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
494
+ message: "Request aborted"
495
+ });
496
+ recordDuration();
497
+ rootSpan.end();
498
+ });
499
+ });
500
+ };
501
+
502
+ //#endregion
503
+ exports.contextKeySpan = contextKeySpan;
504
+ exports.getCurrentSpan = getCurrentSpan;
505
+ exports.getTracer = getTracer;
506
+ exports.opentelemetry = opentelemetry;
507
+ exports.record = record;
508
+ exports.setAttributes = setAttributes;
509
+ exports.shouldStartNodeSDK = shouldStartNodeSDK;
510
+ exports.startActiveSpan = startActiveSpan;
511
+ exports.startSpan = startSpan;