Skip to content

Commit fedf46a

Browse files
logaretmclaude
andcommitted
fix(elysia): drop empty spans produced by Elysia lifecycle hooks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fd202a0 commit fedf46a

File tree

1 file changed

+40
-33
lines changed

1 file changed

+40
-33
lines changed

packages/elysia/src/withElysia.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ interface ElysiaHandlerOptions {
1717

1818
const ELYSIA_ORIGIN = 'auto.http.otel.elysia';
1919

20+
let isClientHooksSetup = false;
21+
const emptySpanIds = new Set<string>();
22+
2023
const ELYSIA_LIFECYCLE_OP_MAP: Record<string, string> = {
2124
Request: 'middleware.elysia',
2225
Parse: 'middleware.elysia',
@@ -65,42 +68,46 @@ export function withElysia<T extends Elysia>(app: T, options?: Partial<ElysiaHan
6568
// https://elysiajs.com/plugins/opentelemetry
6669
app.use(opentelemetry());
6770

68-
const client = getClient();
69-
const emptySpanIds = new Set<string>();
70-
71-
// Enrich Elysia lifecycle spans with semantic op and origin,
72-
// and mark empty spans that Elysia produces as children of lifecycle spans.
73-
client?.on('spanEnd', span => {
74-
const spanData = spanToJSON(span);
75-
76-
// Elysia produces empty spans for each function handler
77-
// users usually use arrow functions for handlers so they will show up as <unknown>
78-
// here we drop them so they don't clutter the transaction, if they get named by the user
79-
// they will still show up as the name of the function
80-
if (!spanData.description && (!spanData.data || Object.keys(spanData.data).length === 0)) {
81-
emptySpanIds.add(spanData.span_id);
82-
return;
83-
}
71+
if (!isClientHooksSetup) {
72+
const client = getClient();
73+
if (client) {
74+
isClientHooksSetup = true;
8475

85-
// Enrich Elysia lifecycle spans with semantic op and origin.
86-
// We mutate the attributes directly because the span has already ended
87-
// and `setAttribute()` is a no-op on ended OTel spans.
88-
const op = ELYSIA_LIFECYCLE_OP_MAP[spanData.description || ''];
89-
if (op && spanData.data) {
90-
const attrs = spanData.data;
91-
attrs[SEMANTIC_ATTRIBUTE_SENTRY_OP] = op;
92-
attrs[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ELYSIA_ORIGIN;
93-
}
94-
});
76+
// Enrich Elysia lifecycle spans with semantic op and origin,
77+
// and mark empty spans that Elysia produces as children of lifecycle spans.
78+
client.on('spanEnd', span => {
79+
const spanData = spanToJSON(span);
80+
81+
// Elysia produces empty spans for each function handler
82+
// users usually use arrow functions for handlers so they will show up as <unknown>
83+
// here we drop them so they don't clutter the transaction, if they get named by the user
84+
// they will still show up as the name of the function
85+
if (!spanData.description && (!spanData.data || Object.keys(spanData.data).length === 0)) {
86+
emptySpanIds.add(spanData.span_id);
87+
return;
88+
}
9589

96-
// Filter out the empty spans we marked above before sending the transaction,
97-
// then clear the set to avoid unbounded memory growth.
98-
client?.on('beforeSendEvent', event => {
99-
if (event.type === 'transaction' && event.spans) {
100-
event.spans = event.spans.filter(span => !emptySpanIds.has(span.span_id));
101-
emptySpanIds.clear();
90+
// Enrich Elysia lifecycle spans with semantic op and origin.
91+
// We mutate the attributes directly because the span has already ended
92+
// and `setAttribute()` is a no-op on ended OTel spans.
93+
const op = ELYSIA_LIFECYCLE_OP_MAP[spanData.description || ''];
94+
if (op && spanData.data) {
95+
const attrs = spanData.data;
96+
attrs[SEMANTIC_ATTRIBUTE_SENTRY_OP] = op;
97+
attrs[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ELYSIA_ORIGIN;
98+
}
99+
});
100+
101+
// Filter out the empty spans we marked above before sending the transaction,
102+
// then clear the set to avoid unbounded memory growth.
103+
client.on('beforeSendEvent', event => {
104+
if (event.type === 'transaction' && event.spans) {
105+
event.spans = event.spans.filter(span => !emptySpanIds.has(span.span_id));
106+
emptySpanIds.clear();
107+
}
108+
});
102109
}
103-
});
110+
}
104111

105112
// Set SDK processing metadata for all requests
106113
app.onRequest(context => {

0 commit comments

Comments
 (0)