The withSpan helper function provides a convenient way to execute code within the context of an active span. It automatically manages span activation and restoration, ensuring that the previous active span context is properly restored after execution completes, even if an error occurs.
function withSpan<T extends ITraceHost, A extends unknown[], F extends (this: ThisParameterType<F> | ISpanScope<T>, ...args: A) => ReturnType<F>>(
traceHost: T,
span: IReadableSpan,
fn: F,
thisArg?: ThisParameterType<F>,
..._args: A
): ReturnType<F>;
The trace host instance that manages span contexts. Typically, this is your AppInsightsCore instance or the AISKU instance.
Type: ITraceHost
Example:
const core = new AppInsightsCore();
// or
const appInsights = new ApplicationInsights({ ... });
The span to set as the active span during the execution of the callback function.
Type: IReadableSpan
Example:
const tracer = otelApi.trace.getTracer('my-service');
const span = tracer.startSpan('operation-name');
The callback function to execute with the span as the active context. The function receives any additional arguments passed to withSpan.
Signature:
(this: ThisParameterType<F> | ISpanScope<T>, ...args: A) => ReturnType<F>
The this context for the callback function. If not provided, the ISpanScope instance is used as this.
Additional arguments to pass to the callback function.
Returns the result of executing the callback function. The return type matches the callback function’s return type.
For synchronous functions:
const result: number = withSpan(core, span, () => {
return 42;
});
For asynchronous functions:
const result: Promise<string> = withSpan(core, span, async () => {
return await fetchData();
});
Sets the span as active during execution and restores the previous active span afterward.
withSpan(core, span, () => {
// span is active here
console.log(core.getActiveSpan() === span); // true
});
// Previous active span is restored here
Ensures the previous active span is restored even if the callback throws an error.
const previousSpan = core.getActiveSpan();
try {
withSpan(core, span, () => {
throw new Error('Something went wrong');
});
} catch (error) {
// Previous span is still restored
console.log(core.getActiveSpan() === previousSpan); // true
}
Uses try-finally blocks internally to guarantee context restoration.
Automatically handles both synchronous and asynchronous callbacks, including Promise-based functions.
// Async callback
await withSpan(core, span, async () => {
await doAsyncWork();
// Context restored after promise settles
});
Handles complex span hierarchies automatically through proper context stacking.
withSpan(core, parentSpan, () => {
// parentSpan is active
withSpan(core, childSpan, () => {
// childSpan is active
});
// parentSpan is active again
});
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { withSpan } from '@microsoft/applicationinsights-core-js';
const appInsights = new ApplicationInsights({
config: {
connectionString: 'YOUR_CONNECTION_STRING'
}
});
appInsights.loadAppInsights();
const tracer = appInsights.trace.getTracer('my-service');
// Create a span
const span = tracer.startSpan('operation');
// Execute code with span as active context
const result = withSpan(appInsights, span, () => {
span.setAttribute('step', 'processing');
// Perform work - span is active during execution
const data = processData();
span.setAttribute('result', 'success');
return data;
});
span.end();
const span = tracer.startSpan('async-operation');
const result = await withSpan(appInsights, span, async () => {
span.setAttribute('started', Date.now());
// Async work
const response = await fetch('/api/data');
const data = await response.json();
span.setAttribute('completed', Date.now());
span.setAttribute('items', data.length);
return data;
});
span.end();
console.log('Result:', result);
const parentSpan = tracer.startSpan('parent-operation');
withSpan(core, parentSpan, () => {
parentSpan.setAttribute('level', 1);
// Create child span
const childSpan = tracer.startSpan('child-operation');
withSpan(core, childSpan, () => {
childSpan.setAttribute('level', 2);
// childSpan automatically has parentSpan as parent
console.log('Parent ID:', parentSpan.spanContext().spanId);
console.log('Child parent ID:', childSpan.spanContext().spanId);
});
childSpan.end();
});
parentSpan.end();
function processWithContext(userId: string, action: string): string {
return `User ${userId} performed ${action}`;
}
const span = tracer.startSpan('user-action');
// Pass additional arguments to callback
const result = withSpan(
core,
span,
processWithContext,
null, // thisArg
'user123', // userId
'checkout' // action
);
span.end();
console.log(result); // 'User user123 performed checkout'
class UserService {
private userId: string;
constructor(userId: string) {
this.userId = userId;
}
process(): string {
return `Processing for user ${this.userId}`;
}
}
const service = new UserService('user123');
const span = tracer.startSpan('service-operation');
// Preserve 'this' context
const result = withSpan(
core,
span,
service.process,
service // Use service as 'this'
);
span.end();
console.log(result); // 'Processing for user user123'
const span = tracer.startSpan('risky-operation');
try {
withSpan(core, span, () => {
span.setAttribute('attempting', true);
// Operation that might throw
const result = performRiskyOperation();
span.setStatus({ code: SpanStatusCode.OK });
span.setAttribute('success', true);
return result;
});
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
span.setAttribute('success', false);
} finally {
span.end(); // Always end the span
}
const operationSpan = tracer.startSpan('complete-checkout');
withSpan(core, operationSpan, async () => {
operationSpan.setAttribute('step', 'starting');
// Validate user
const validateSpan = tracer.startSpan('validate-user');
await withSpan(core, validateSpan, async () => {
validateSpan.setAttribute('user.id', '12345');
await validateUser();
});
validateSpan.end();
// Process payment
const paymentSpan = tracer.startSpan('process-payment');
await withSpan(core, paymentSpan, async () => {
paymentSpan.setAttribute('amount', 99.99);
await processPayment();
});
paymentSpan.end();
// Send confirmation
const confirmSpan = tracer.startSpan('send-confirmation');
await withSpan(core, confirmSpan, async () => {
confirmSpan.setAttribute('notification.type', 'email');
await sendConfirmation();
});
confirmSpan.end();
operationSpan.setAttribute('step', 'completed');
});
operationSpan.end();
const span = tracer.startSpan('background-task');
// Execute background work with trace context
withSpan(core, span, async () => {
span.setAttribute('task.type', 'data-sync');
span.setAttribute('started', new Date().toISOString());
// All operations here inherit span context
await syncDataToDatabase();
span.setAttribute('completed', new Date().toISOString());
});
span.end();
function executeWithTracing(operation: () => void, shouldTrace: boolean) {
if (!shouldTrace) {
// Execute without tracing
return operation();
}
// Execute with tracing
const span = tracer.startSpan('conditional-operation');
try {
return withSpan(core, span, operation);
} finally {
span.end();
}
}
// Usage
executeWithTracing(() => {
console.log('This is traced');
}, true);
executeWithTracing(() => {
console.log('This is not traced');
}, false);
// Existing function without tracing
async function fetchUserData(userId: string) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// Add tracing without modifying the function
async function fetchUserDataWithTracing(userId: string) {
const span = tracer.startSpan('fetch-user-data');
span.setAttribute('user.id', userId);
try {
const result = await withSpan(core, span, () => {
return fetchUserData(userId);
});
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
}
withSpan and useSpan are similar but differ in how they pass parameters to the callback function.
Passes additional arguments directly to the callback:
withSpan(core, span, (arg1, arg2) => {
// arg1 and arg2 are your custom arguments
console.log(arg1, arg2);
}, null, 'value1', 'value2');
Passes the ISpanScope as the first argument:
useSpan(core, span, (scope, arg1, arg2) => {
// scope is the ISpanScope
// arg1 and arg2 are your custom arguments
console.log(scope, arg1, arg2);
}, null, 'value1', 'value2');
When to use which:
withSpan: When you don’t need access to the span scope objectuseSpan: When you need to manually restore context or access scope propertiesSee the useSpan documentation for more details.
// Good - span is ended
const span = tracer.startSpan('operation');
try {
withSpan(core, span, () => {
// Work
});
} finally {
span.end();
}
// Bad - span is never ended
const span = tracer.startSpan('operation');
withSpan(core, span, () => {
// Work
}); // Forgot to end span!
// Good - use withSpan with startSpan
const span = tracer.startSpan('operation');
withSpan(core, span, () => {
// Work
});
span.end();
// Redundant - startActiveSpan already manages context
tracer.startActiveSpan('operation', (span) => {
// No need for withSpan here
});
const span = tracer.startSpan('operation');
try {
withSpan(core, span, () => {
// Work that might throw
throw new Error('Failed');
});
} catch (error) {
// Record exception
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
} finally {
span.end();
}
// Good - each span with its own withSpan
const span1 = tracer.startSpan('operation-1');
withSpan(core, span1, () => {
// Work
});
span1.end();
const span2 = tracer.startSpan('operation-2');
withSpan(core, span2, () => {
// Work
});
span2.end();
// Less ideal - unnecessary nesting
withSpan(core, span1, () => {
withSpan(core, span1, () => { // Redundant
// Work
});
});
// Good - natural async/await usage
const span = tracer.startSpan('async-op');
try {
const result = await withSpan(core, span, async () => {
return await doAsyncWork();
});
span.setStatus({ code: SpanStatusCode.OK });
} finally {
span.end();
}
function withTracing<T>(
operationName: string,
fn: () => T
): T {
const span = tracer.startSpan(operationName);
try {
return withSpan(core, span, fn);
} finally {
span.end();
}
}
// Usage
const result = withTracing('my-operation', () => {
return performWork();
});
function traced(operationName: string) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
const span = tracer.startSpan(operationName);
try {
return withSpan(core, span, () => {
return original.apply(this, args);
}, this);
} finally {
span.end();
}
};
return descriptor;
};
}
// Usage
class MyService {
@traced('process-data')
processData(data: any) {
// Automatically traced
return data.processed;
}
}
function preserveContext<T>(fn: () => T): () => T {
const span = core.getActiveSpan();
return () => {
if (span) {
return withSpan(core, span, fn);
}
return fn();
};
}
// Usage
const contextualFn = preserveContext(() => {
console.log('Maintaining trace context');
});
setTimeout(contextualFn, 1000); // Executes with preserved context
withSpan guarantees context restoration even when errors occur:
const previousSpan = core.getActiveSpan();
const span = tracer.startSpan('operation');
try {
withSpan(core, span, () => {
throw new Error('Operation failed');
});
} catch (error) {
// Context is restored to previousSpan
console.log(core.getActiveSpan() === previousSpan); // true
} finally {
span.end();
}
const span = tracer.startSpan('operation');
try {
withSpan(core, span, () => {
try {
performRiskyOperation();
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error; // Re-throw if needed
}
});
} catch (error) {
// Handle error at outer level
console.error('Operation failed:', error);
} finally {
span.end();
}
const span = tracer.startSpan('async-operation');
try {
await withSpan(core, span, async () => {
const result = await fetch('/api/data');
if (!result.ok) {
throw new Error(`HTTP ${result.status}`);
}
return result.json();
});
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
} finally {
span.end();
}
ITraceHost - Host interface for span managementIReadableSpan - Span interfaceISpanScope - Scope for restoring active span contextIOTelTracer - Tracer interface for creating spans