@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/README.md +27 -2
- package/bun.lock +98 -246
- package/dist/index.d.ts +72 -56
- package/dist/index.js +511 -0
- package/dist/index.mjs +491 -595
- package/package.json +10 -13
- package/dist/cjs/index.d.ts +0 -70
- package/dist/cjs/index.js +0 -632
package/dist/index.mjs
CHANGED
|
@@ -1,606 +1,502 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
1
|
import { Elysia, StatusMap } from "elysia";
|
|
3
|
-
import {
|
|
4
|
-
trace,
|
|
5
|
-
context as otelContext,
|
|
6
|
-
propagation,
|
|
7
|
-
SpanStatusCode,
|
|
8
|
-
SpanKind,
|
|
9
|
-
ProxyTracerProvider
|
|
10
|
-
} from "@opentelemetry/api";
|
|
2
|
+
import { ProxyTracerProvider, SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
11
3
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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"
|
|
26
|
+
]);
|
|
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;
|
|
27
40
|
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
message: err?.message
|
|
56
|
-
});
|
|
57
|
-
span.recordException(err);
|
|
58
|
-
span.end();
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
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
|
+
}
|
|
61
68
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const createContext = (parent) => ({
|
|
70
|
+
getValue() {
|
|
71
|
+
return parent;
|
|
72
|
+
},
|
|
73
|
+
setValue() {
|
|
74
|
+
return context.active();
|
|
75
|
+
},
|
|
76
|
+
deleteValue() {
|
|
77
|
+
return context.active();
|
|
78
|
+
}
|
|
72
79
|
});
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
};
|
|
75
103
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
case 4:
|
|
98
|
-
return tracer.startActiveSpan(
|
|
99
|
-
args[0],
|
|
100
|
-
args[1],
|
|
101
|
-
args[2],
|
|
102
|
-
createActiveSpanHandler(args[3])
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
};
|
|
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;
|
|
107
125
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return tracer.startSpan(name, options, context);
|
|
126
|
+
const shouldStartNodeSDK = (provider) => {
|
|
127
|
+
return provider instanceof ProxyTracerProvider && provider.getDelegateTracer("check") === void 0;
|
|
111
128
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
args[0],
|
|
129
|
-
args[1],
|
|
130
|
-
args[2],
|
|
131
|
-
createActiveSpanHandler(args[3])
|
|
132
|
-
);
|
|
133
|
-
}
|
|
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
|
+
};
|
|
134
145
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
var setAttributes = (attributes) => !!getCurrentSpan()?.setAttributes(attributes);
|
|
138
|
-
var opentelemetry = ({
|
|
139
|
-
serviceName = "Elysia",
|
|
140
|
-
instrumentations,
|
|
141
|
-
contextManager,
|
|
142
|
-
checkIfShouldTrace,
|
|
143
|
-
...options
|
|
144
|
-
} = {}) => {
|
|
145
|
-
let tracer = trace.getTracer(serviceName);
|
|
146
|
-
if (shouldStartNodeSDK(trace.getTracerProvider())) {
|
|
147
|
-
const sdk = new NodeSDK({
|
|
148
|
-
...options,
|
|
149
|
-
serviceName,
|
|
150
|
-
instrumentations
|
|
151
|
-
});
|
|
152
|
-
sdk.start();
|
|
153
|
-
tracer = trace.getTracer(serviceName);
|
|
154
|
-
} else {
|
|
155
|
-
}
|
|
156
|
-
if (!otelContext._getContextManager?.() && contextManager)
|
|
157
|
-
try {
|
|
158
|
-
contextManager.enable();
|
|
159
|
-
otelContext.setGlobalContextManager(contextManager);
|
|
160
|
-
} catch {
|
|
161
|
-
}
|
|
162
|
-
return new Elysia({
|
|
163
|
-
name: "@elysia/opentelemetry"
|
|
164
|
-
}).wrap((fn, request) => {
|
|
165
|
-
const shouldTrace = checkIfShouldTrace ? checkIfShouldTrace(request) : true;
|
|
166
|
-
if (!shouldTrace) return fn;
|
|
167
|
-
const headers = headerHasToJSON ? (
|
|
168
|
-
// @ts-ignore bun only
|
|
169
|
-
request.headers.toJSON()
|
|
170
|
-
) : Object.fromEntries(request.headers.entries());
|
|
171
|
-
const ctx = propagation.extract(otelContext.active(), headers);
|
|
172
|
-
return tracer.startActiveSpan(
|
|
173
|
-
"Root",
|
|
174
|
-
{ kind: SpanKind.SERVER },
|
|
175
|
-
ctx,
|
|
176
|
-
(rootSpan) => {
|
|
177
|
-
const spanContext = trace.setSpan(ctx, rootSpan);
|
|
178
|
-
return (...args) => {
|
|
179
|
-
return otelContext.with(spanContext, () => fn(...args));
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
);
|
|
183
|
-
}).trace(
|
|
184
|
-
{ as: "global" },
|
|
185
|
-
({
|
|
186
|
-
id,
|
|
187
|
-
onRequest,
|
|
188
|
-
onParse,
|
|
189
|
-
onTransform,
|
|
190
|
-
onBeforeHandle,
|
|
191
|
-
onHandle,
|
|
192
|
-
onAfterHandle,
|
|
193
|
-
onError,
|
|
194
|
-
onAfterResponse,
|
|
195
|
-
onMapResponse,
|
|
196
|
-
context,
|
|
197
|
-
context: {
|
|
198
|
-
path,
|
|
199
|
-
request: { method }
|
|
200
|
-
}
|
|
201
|
-
}) => {
|
|
202
|
-
const rootSpan = trace.getActiveSpan();
|
|
203
|
-
if (!rootSpan) return;
|
|
204
|
-
function setParent(span) {
|
|
205
|
-
if (span.ended) return;
|
|
206
|
-
if (rootSpan.ended) return void span.end();
|
|
207
|
-
const newContext = trace.setSpan(otelContext.active(), span);
|
|
208
|
-
const currentContext = (
|
|
209
|
-
// @ts-expect-error private property
|
|
210
|
-
otelContext.active()._currentContext
|
|
211
|
-
);
|
|
212
|
-
currentContext?.set(
|
|
213
|
-
contextKeySpan,
|
|
214
|
-
newContext.getValue(contextKeySpan)
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
function inspect(name) {
|
|
218
|
-
return function inspect2({
|
|
219
|
-
onEvent,
|
|
220
|
-
total,
|
|
221
|
-
onStop
|
|
222
|
-
}) {
|
|
223
|
-
if (total === 0 || // @ts-ignore
|
|
224
|
-
rootSpan.ended)
|
|
225
|
-
return;
|
|
226
|
-
tracer.startActiveSpan(
|
|
227
|
-
name,
|
|
228
|
-
{},
|
|
229
|
-
createContext(rootSpan),
|
|
230
|
-
(event) => {
|
|
231
|
-
if (
|
|
232
|
-
// @ts-ignore
|
|
233
|
-
rootSpan.ended
|
|
234
|
-
)
|
|
235
|
-
return;
|
|
236
|
-
onEvent(({ name: name2, onStop: onStop2 }) => {
|
|
237
|
-
const useChildSpan = total > 1;
|
|
238
|
-
let span;
|
|
239
|
-
if (useChildSpan) {
|
|
240
|
-
span = tracer.startSpan(
|
|
241
|
-
name2,
|
|
242
|
-
{},
|
|
243
|
-
createContext(event)
|
|
244
|
-
);
|
|
245
|
-
setParent(span);
|
|
246
|
-
} else {
|
|
247
|
-
setParent(event);
|
|
248
|
-
span = event;
|
|
249
|
-
}
|
|
250
|
-
onStop2(({ error }) => {
|
|
251
|
-
setParent(rootSpan);
|
|
252
|
-
if (span.ended || rootSpan.ended) return;
|
|
253
|
-
if (error) {
|
|
254
|
-
rootSpan.setStatus({
|
|
255
|
-
code: SpanStatusCode.ERROR,
|
|
256
|
-
message: error.message
|
|
257
|
-
});
|
|
258
|
-
span.setAttributes({
|
|
259
|
-
"error.type": error.constructor?.name ?? error.name,
|
|
260
|
-
"error.stack": error.stack
|
|
261
|
-
});
|
|
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
|
-
}
|
|
274
|
-
if (useChildSpan) span.end();
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
onStop(() => {
|
|
278
|
-
setParent(rootSpan);
|
|
279
|
-
if (event.ended) return;
|
|
280
|
-
event.end();
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
);
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const url = context.url;
|
|
287
|
-
const attributes = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
288
|
-
// ? Elysia Custom attribute
|
|
289
|
-
"http.request.id": id,
|
|
290
|
-
"http.request.method": method,
|
|
291
|
-
"url.path": path,
|
|
292
|
-
"url.full": url
|
|
293
|
-
});
|
|
294
|
-
if (context.qi && context.qi !== -1)
|
|
295
|
-
attributes["url.query"] = url.slice(
|
|
296
|
-
// @ts-ignore private property
|
|
297
|
-
context.qi + 1
|
|
298
|
-
);
|
|
299
|
-
const protocolSeparator = url.indexOf("://");
|
|
300
|
-
if (protocolSeparator > 0)
|
|
301
|
-
attributes["url.scheme"] = url.slice(0, protocolSeparator);
|
|
302
|
-
onRequest(inspect("Request"));
|
|
303
|
-
onParse(inspect("Parse"));
|
|
304
|
-
onTransform(inspect("Transform"));
|
|
305
|
-
onBeforeHandle(inspect("BeforeHandle"));
|
|
306
|
-
onHandle(({ onStop }) => {
|
|
307
|
-
const span = tracer.startSpan(
|
|
308
|
-
"Handle",
|
|
309
|
-
{},
|
|
310
|
-
createContext(rootSpan)
|
|
311
|
-
);
|
|
312
|
-
setParent(span);
|
|
313
|
-
onStop(({ error }) => {
|
|
314
|
-
setParent(rootSpan);
|
|
315
|
-
if (span.ended || rootSpan.ended) return;
|
|
316
|
-
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
|
-
span.recordException(error);
|
|
326
|
-
rootSpan.recordException(error);
|
|
327
|
-
} else {
|
|
328
|
-
rootSpan.setStatus({
|
|
329
|
-
code: SpanStatusCode.OK
|
|
330
|
-
});
|
|
331
|
-
span.setStatus({
|
|
332
|
-
code: SpanStatusCode.OK
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
span.end();
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
onAfterHandle(inspect("AfterHandle"));
|
|
339
|
-
onError((event) => {
|
|
340
|
-
inspect("Error")(event);
|
|
341
|
-
event.onStop(({ error }) => {
|
|
342
|
-
setParent(rootSpan);
|
|
343
|
-
if (rootSpan.ended) return;
|
|
344
|
-
{
|
|
345
|
-
let status = context.set.status;
|
|
346
|
-
if (typeof status === "string") {
|
|
347
|
-
status = StatusMap[status];
|
|
348
|
-
} else if (typeof status !== "number" && // @ts-ignore
|
|
349
|
-
typeof error?.status === "number")
|
|
350
|
-
status = error.status;
|
|
351
|
-
if (typeof status === "number") {
|
|
352
|
-
attributes["http.response.status_code"] = status;
|
|
353
|
-
if (status >= 500)
|
|
354
|
-
rootSpan.setStatus({
|
|
355
|
-
code: SpanStatusCode.ERROR
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
rootSpan.setAttributes(attributes);
|
|
359
|
-
}
|
|
360
|
-
if (
|
|
361
|
-
// @ts-ignore
|
|
362
|
-
!rootSpan.ended
|
|
363
|
-
)
|
|
364
|
-
rootSpan.end();
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
onMapResponse(inspect("MapResponse"));
|
|
368
|
-
onTransform(() => {
|
|
369
|
-
const { cookie, request, route, path: path2 } = context;
|
|
370
|
-
if (route)
|
|
371
|
-
rootSpan.updateName(
|
|
372
|
-
// @ts-ignore private property
|
|
373
|
-
`${method} ${route || path2}`
|
|
374
|
-
);
|
|
375
|
-
if (context.route) attributes["http.route"] = context.route;
|
|
376
|
-
{
|
|
377
|
-
let contentLength = request.headers.get("content-length");
|
|
378
|
-
if (contentLength) {
|
|
379
|
-
const number = parseNumericString(contentLength);
|
|
380
|
-
if (number)
|
|
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;
|
|
388
|
-
}
|
|
389
|
-
const server = context.server;
|
|
390
|
-
if (server) {
|
|
391
|
-
attributes["server.port"] = server.port ?? 80;
|
|
392
|
-
attributes["server.address"] = server.url.hostname;
|
|
393
|
-
attributes["server.address"] = server.url.hostname;
|
|
394
|
-
}
|
|
395
|
-
let headers;
|
|
396
|
-
{
|
|
397
|
-
let hasHeaders;
|
|
398
|
-
let _headers;
|
|
399
|
-
if (context.headers) {
|
|
400
|
-
hasHeaders = true;
|
|
401
|
-
headers = context.headers;
|
|
402
|
-
_headers = Object.entries(context.headers);
|
|
403
|
-
} else if (hasHeaders = headerHasToJSON) {
|
|
404
|
-
headers = request.headers.toJSON();
|
|
405
|
-
_headers = Object.entries(headers);
|
|
406
|
-
} else {
|
|
407
|
-
headers = {};
|
|
408
|
-
_headers = request.headers.entries();
|
|
409
|
-
}
|
|
410
|
-
for (let [key, value] of _headers) {
|
|
411
|
-
key = key.toLowerCase();
|
|
412
|
-
if (hasHeaders) {
|
|
413
|
-
if (key === "user-agent") continue;
|
|
414
|
-
if (typeof value === "object")
|
|
415
|
-
attributes[`http.request.header.${key}`] = JSON.stringify(value);
|
|
416
|
-
else if (value !== void 0)
|
|
417
|
-
attributes[`http.request.header.${key}`] = value;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
if (typeof value === "object")
|
|
421
|
-
headers[key] = attributes[`http.request.header.${key}`] = JSON.stringify(value);
|
|
422
|
-
else if (value !== void 0) {
|
|
423
|
-
if (key === "user-agent") {
|
|
424
|
-
headers[key] = value;
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
headers[key] = attributes[`http.request.header.${key}`] = value;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
{
|
|
432
|
-
let headers2;
|
|
433
|
-
if (context.set.headers instanceof Headers) {
|
|
434
|
-
if (headerHasToJSON)
|
|
435
|
-
headers2 = Object.entries(
|
|
436
|
-
// @ts-ignore bun only
|
|
437
|
-
context.set.headers.toJSON()
|
|
438
|
-
);
|
|
439
|
-
else headers2 = context.set.headers.entries();
|
|
440
|
-
} else headers2 = Object.entries(context.set.headers);
|
|
441
|
-
for (let [key, value] of headers2) {
|
|
442
|
-
key = key.toLowerCase();
|
|
443
|
-
if (typeof value === "object")
|
|
444
|
-
attributes[`http.response.header.${key}`] = JSON.stringify(value);
|
|
445
|
-
else
|
|
446
|
-
attributes[`http.response.header.${key}`] = value;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (context.ip)
|
|
450
|
-
attributes["client.address"] = context.ip;
|
|
451
|
-
else {
|
|
452
|
-
const ip = headers["true-client-ip"] ?? headers["cf-connection-ip"] ?? headers["x-forwarded-for"] ?? headers["x-real-ip"] ?? server?.requestIP(request);
|
|
453
|
-
if (ip)
|
|
454
|
-
attributes["client.address"] = typeof ip === "string" ? ip : ip.address ?? ip.toString();
|
|
455
|
-
}
|
|
456
|
-
if (cookie) {
|
|
457
|
-
const _cookie = {};
|
|
458
|
-
for (const [key, { value }] of Object.entries(cookie))
|
|
459
|
-
_cookie[key] = JSON.stringify(value);
|
|
460
|
-
attributes["http.request.cookie"] = JSON.stringify(_cookie);
|
|
461
|
-
}
|
|
462
|
-
rootSpan.setAttributes(attributes);
|
|
463
|
-
});
|
|
464
|
-
onParse(() => {
|
|
465
|
-
const body = context.body;
|
|
466
|
-
if (body !== void 0 && body !== null) {
|
|
467
|
-
const value = typeof body === "object" ? JSON.stringify(body) : body.toString();
|
|
468
|
-
attributes["http.request.body"] = value;
|
|
469
|
-
if (typeof body === "object") {
|
|
470
|
-
if (body instanceof Uint8Array)
|
|
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
|
-
}
|
|
481
|
-
});
|
|
482
|
-
onMapResponse(() => {
|
|
483
|
-
const body = context.body;
|
|
484
|
-
if (body !== void 0 && body !== null) {
|
|
485
|
-
const value = typeof body === "object" ? JSON.stringify(body) : body.toString();
|
|
486
|
-
attributes["http.request.body"] = value;
|
|
487
|
-
if (typeof body === "object") {
|
|
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
|
-
}
|
|
498
|
-
}
|
|
499
|
-
{
|
|
500
|
-
let status = context.set.status ?? 200;
|
|
501
|
-
if (typeof status === "string")
|
|
502
|
-
status = StatusMap[status] ?? 200;
|
|
503
|
-
attributes["http.response.status_code"] = status;
|
|
504
|
-
}
|
|
505
|
-
const response = context.responseValue;
|
|
506
|
-
if (response !== void 0)
|
|
507
|
-
switch (typeof response) {
|
|
508
|
-
case "object":
|
|
509
|
-
if (response instanceof Response) {
|
|
510
|
-
} else if (response instanceof Uint8Array)
|
|
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
|
-
}
|
|
531
|
-
if (!rootSpan.ended) {
|
|
532
|
-
const statusCode = attributes["http.response.status_code"];
|
|
533
|
-
if (typeof statusCode === "number" && statusCode >= 500) {
|
|
534
|
-
rootSpan.setStatus({
|
|
535
|
-
code: SpanStatusCode.ERROR
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
rootSpan.setAttributes(attributes);
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
onAfterResponse((event) => {
|
|
542
|
-
inspect("AfterResponse")(event);
|
|
543
|
-
{
|
|
544
|
-
let status = context.set.status ?? 200;
|
|
545
|
-
if (typeof status === "string")
|
|
546
|
-
status = StatusMap[status] ?? 200;
|
|
547
|
-
attributes["http.response.status_code"] = status;
|
|
548
|
-
}
|
|
549
|
-
const body = context.body;
|
|
550
|
-
if (body !== void 0 && body !== null) {
|
|
551
|
-
const value = typeof body === "object" ? JSON.stringify(body) : body.toString();
|
|
552
|
-
attributes["http.request.body"] = value;
|
|
553
|
-
if (typeof body === "object") {
|
|
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
|
-
}
|
|
564
|
-
}
|
|
565
|
-
if (!rootSpan.ended) {
|
|
566
|
-
const statusCode = attributes["http.response.status_code"];
|
|
567
|
-
if (typeof statusCode === "number" && statusCode >= 500)
|
|
568
|
-
rootSpan.setStatus({
|
|
569
|
-
code: SpanStatusCode.ERROR
|
|
570
|
-
});
|
|
571
|
-
rootSpan.setAttributes(attributes);
|
|
572
|
-
}
|
|
573
|
-
event.onStop(() => {
|
|
574
|
-
setParent(rootSpan);
|
|
575
|
-
if (rootSpan.ended) return;
|
|
576
|
-
if (
|
|
577
|
-
// @ts-ignore
|
|
578
|
-
!rootSpan.ended
|
|
579
|
-
)
|
|
580
|
-
rootSpan.end();
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
context.request.signal.addEventListener("abort", () => {
|
|
584
|
-
const active = trace.getActiveSpan();
|
|
585
|
-
if (active && !active.ended) active.end();
|
|
586
|
-
if (rootSpan.ended) return;
|
|
587
|
-
rootSpan.setStatus({
|
|
588
|
-
code: SpanStatusCode.ERROR,
|
|
589
|
-
message: "Request aborted"
|
|
590
|
-
});
|
|
591
|
-
rootSpan.end();
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
);
|
|
146
|
+
const startSpan = (name, options, context) => {
|
|
147
|
+
return getTracer().startSpan(name, options, context);
|
|
595
148
|
};
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
shouldStartNodeSDK,
|
|
604
|
-
startActiveSpan,
|
|
605
|
-
startSpan
|
|
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
|
+
}
|
|
606
156
|
};
|
|
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
|
+
});
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
//#endregion
|
|
502
|
+
export { contextKeySpan, getCurrentSpan, getTracer, opentelemetry, record, setAttributes, shouldStartNodeSDK, startActiveSpan, startSpan };
|