1- import { diag , DiagConsoleLogger , DiagLogLevel } from '@opentelemetry/api'
1+ import { Context , diag , DiagConsoleLogger , DiagLogLevel } from '@opentelemetry/api'
22import { ZoneContextManager } from '@opentelemetry/context-zone'
33import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
44import { Instrumentation , registerInstrumentations } from '@opentelemetry/instrumentation'
55import { resourceFromAttributes } from '@opentelemetry/resources'
6- import { BatchSpanProcessor , BufferConfig , WebTracerProvider } from '@opentelemetry/sdk-trace-web'
6+ import { BatchSpanProcessor , BufferConfig , ReadableSpan , Span , SpanProcessor , WebTracerProvider } from '@opentelemetry/sdk-trace-web'
77import {
88 ATTR_SERVICE_NAME ,
99 ATTR_SERVICE_VERSION ,
@@ -18,6 +18,7 @@ import {
1818 ATTR_BROWSER_LANGUAGE ,
1919 ATTR_BROWSER_MOBILE ,
2020 ATTR_BROWSER_PLATFORM ,
21+ ATTR_HTTP_URL ,
2122 ATTR_DEPLOYMENT_ENVIRONMENT_NAME ,
2223} from '@opentelemetry/semantic-conventions/incubating'
2324import { ULIDGenerator } from '@pydantic/logfire-api'
@@ -27,6 +28,10 @@ export * from '@pydantic/logfire-api'
2728
2829type TraceExporterConfig = NonNullable < typeof OTLPTraceExporter extends new ( config : infer T ) => unknown ? T : never >
2930
31+ // not present in the semantic conventions
32+ const ATTR_TARGET_XPATH = 'target_xpath'
33+ const ATTR_EVENT_TYPE = 'event_type'
34+
3035export interface LogfireConfigOptions {
3136 /**
3237 * The configuration of the batch span processor.
@@ -111,9 +116,11 @@ export function configure(options: LogfireConfigOptions) {
111116 idGenerator : new ULIDGenerator ( ) ,
112117 resource,
113118 spanProcessors : [
114- new BatchSpanProcessor (
115- new OTLPTraceExporter ( { ...options . traceExporterConfig , url : options . traceUrl } ) ,
116- options . batchSpanProcessorConfig
119+ new LogfireSpanProcessor (
120+ new BatchSpanProcessor (
121+ new OTLPTraceExporter ( { ...options . traceExporterConfig , url : options . traceUrl } ) ,
122+ options . batchSpanProcessorConfig
123+ )
117124 ) ,
118125 ] ,
119126 } )
@@ -138,3 +145,41 @@ export function configure(options: LogfireConfigOptions) {
138145 diag . info ( 'logfire-browser: shut down complete' )
139146 }
140147}
148+
149+ class LogfireSpanProcessor implements SpanProcessor {
150+ private wrapped : SpanProcessor
151+
152+ constructor ( wrapped : SpanProcessor ) {
153+ this . wrapped = wrapped
154+ }
155+
156+ async forceFlush ( ) : Promise < void > {
157+ return this . wrapped . forceFlush ( )
158+ }
159+
160+ onEnd ( span : ReadableSpan ) : void {
161+ console . log ( 'on end' , span )
162+ // Note: this is too late for the regular node instrumentation. The opentelemetry API rejects the non-primitive attribute values.
163+ // Instead, the serialization happens at the `logfire.span, logfire.startSpan`, etc.
164+ // Object.assign(span.attributes, serializeAttributes(span.attributes))
165+ this . wrapped . onEnd ( span )
166+ }
167+
168+ onStart ( span : Span , parentContext : Context ) : void {
169+ // make the fetch spans more descriptive
170+ if ( ATTR_HTTP_URL in span . attributes ) {
171+ const url = new URL ( span . attributes [ ATTR_HTTP_URL ] as string )
172+ Reflect . set ( span , 'name' , `${ span . name } ${ url . pathname } ` )
173+ }
174+
175+ // same for the interaction spans
176+ if ( ATTR_TARGET_XPATH in span . attributes ) {
177+ Reflect . set ( span , 'name' , `${ span . attributes [ ATTR_EVENT_TYPE ] } ${ span . attributes [ ATTR_TARGET_XPATH ] } ` )
178+ }
179+ this . wrapped . onStart ( span , parentContext )
180+ }
181+
182+ async shutdown ( ) : Promise < void > {
183+ return this . wrapped . shutdown ( )
184+ }
185+ }
0 commit comments