@@ -17,6 +17,9 @@ interface ElysiaHandlerOptions {
1717
1818const ELYSIA_ORIGIN = 'auto.http.otel.elysia' ;
1919
20+ let isClientHooksSetup = false ;
21+ const emptySpanIds = new Set < string > ( ) ;
22+
2023const 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