@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/bun.lock +98 -246
- package/dist/index.d.ts +72 -84
- package/dist/index.js +511 -0
- package/dist/index.mjs +489 -663
- package/package.json +8 -12
- package/bunfig.toml +0 -2
- package/dist/cjs/index.d.ts +0 -98
- package/dist/cjs/index.js +0 -701
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Elysia } from
|
|
2
|
-
import {
|
|
3
|
-
import { NodeSDK } from
|
|
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,91 +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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
};
|
|
48
50
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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>;
|
|
62
64
|
};
|
|
63
|
-
|
|
65
|
+
declare const getCurrentSpan: () => Span | undefined;
|
|
64
66
|
/**
|
|
65
67
|
* Set attributes to the current span
|
|
66
68
|
*
|
|
67
69
|
* @returns boolean - whether the attributes are set or not
|
|
68
70
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}, {}, {
|
|
86
|
-
derive: {};
|
|
87
|
-
resolve: {};
|
|
88
|
-
schema: {};
|
|
89
|
-
standaloneSchema: {};
|
|
90
|
-
response: {};
|
|
91
|
-
}, {
|
|
92
|
-
derive: {};
|
|
93
|
-
resolve: {};
|
|
94
|
-
schema: {};
|
|
95
|
-
standaloneSchema: {};
|
|
96
|
-
response: {};
|
|
97
|
-
}>;
|
|
98
|
-
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;
|