Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 46 additions & 25 deletions src/Elastic.Documentation.Site/Assets/telemetry/tracing.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,61 @@
/**
* React utilities for OpenTelemetry tracing in components.
*/
import { trace, context, SpanStatusCode, Span } from '@opentelemetry/api'
import { trace, SpanStatusCode, Span } from '@opentelemetry/api'

/**
* Creates a span and executes a function within its context.
*
* This ensures that:
* - The span is automatically set as the active context
* - Any automatic instrumentation (e.g., fetch, XHR) creates child spans
* - Nested calls to traceSpan() properly create child spans
* - The span is properly ended even if errors occur
*
* @param spanName - Name of the span to create
* @param fn - Async function to execute within the span context
* @param attributes - Optional attributes to set on the span
* @returns Promise resolving to the function's return value
*/
export async function traceSpan<T>(
spanName: string,
fn: (span: Span) => Promise<T>,
attributes?: Record<string, string | number | boolean>
): Promise<T> {
const tracer = trace.getTracer('docs-frontend')
const span = tracer.startSpan(spanName, undefined, context.active())

if (attributes) {
span.setAttributes(attributes)
}
// startActiveSpan automatically:
// 1. Creates the span
// 2. Sets it as the active context
// 3. Executes the callback within that context
// 4. Ends the span after execution
return tracer.startActiveSpan(spanName, async (span) => {
if (attributes) {
span.setAttributes(attributes)
}

try {
const result = await fn(span)
span.setStatus({ code: SpanStatusCode.OK })
return result
} catch (error) {
// Check if this is an AbortError (user cancelled/typed more)
if (error instanceof Error && error.name === 'AbortError') {
// Cancellation is NOT an error - it's expected behavior
span.setAttribute('cancelled', true)
try {
const result = await fn(span)
span.setStatus({ code: SpanStatusCode.OK })
} else {
// Real error - mark as ERROR
span.setStatus({
code: SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : String(error),
})
span.recordException(error as Error)
return result
} catch (error) {
// Check if this is an AbortError (user cancelled/typed more)
if (error instanceof Error && error.name === 'AbortError') {
// Cancellation is NOT an error - it's expected behavior
span.setAttribute('cancelled', true)
span.setStatus({ code: SpanStatusCode.OK })
} else {
// Real error - mark as ERROR
span.setStatus({
code: SpanStatusCode.ERROR,
message:
error instanceof Error ? error.message : String(error),
})
span.recordException(error as Error)
}
throw error
} finally {
span.end()
}
throw error
} finally {
span.end()
}
})
}
Loading