The useSpan helper function is similar to withSpan but provides the ISpanScope object as the first parameter to your callback function. This allows you to manually manage span context restoration or access scope properties when needed. It automatically manages span activation and ensures proper context restoration after execution completes.
function useSpan<T extends ITraceHost, A extends unknown[], F extends (this: ThisParameterType<F> | ISpanScope<T>, scope: 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 the ISpanScope as its first parameter, followed by any additional arguments.
Signature:
(this: ThisParameterType<F> | ISpanScope<T>, scope: 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 (after the scope parameter).
Returns the result of executing the callback function. The return type matches the callback function’s return type.
For synchronous functions:
const result: number = useSpan(core, span, (scope) => {
return 42;
});
For asynchronous functions:
const result: Promise<string> = useSpan(core, span, async (scope) => {
return await fetchData();
});
The callback receives an ISpanScope object as its first parameter, providing access to:
restore() - Method to manually restore the previous active spanuseSpan(core, span, (scope) => {
// Access the scope
console.log('Scope available:', scope);
// Can manually restore if needed
// scope.restore();
});
Sets the span as active during execution and restores the previous active span afterward (unless manually restored).
useSpan(core, span, (scope) => {
// span is active here
console.log(core.getActiveSpan() === span); // true
});
// Previous active span is restored here
Unlike withSpan, you can manually restore the context within your callback if needed.
useSpan(core, span, (scope) => {
// Do some work with span active
doWork();
// Manually restore early if needed
scope.restore();
// Continue with previous context
doMoreWork();
});
Ensures the previous active span is restored even if the callback throws an error.
Automatically handles both synchronous and asynchronous callbacks, including Promise-based functions.
The main difference between useSpan and withSpan is how they pass parameters to the callback function.
Does NOT pass the scope to the callback:
withSpan(core, span, (arg1, arg2) => {
// arg1 and arg2 are your custom arguments
// No access to scope
console.log(arg1, arg2);
}, null, 'value1', 'value2');
Passes the scope as the FIRST parameter:
useSpan(core, span, (scope, arg1, arg2) => {
// scope is the ISpanScope object
// arg1 and arg2 are your custom arguments
console.log(scope, arg1, arg2);
// Can manually restore
scope.restore();
}, null, 'value1', 'value2');
When to use which:
Use withSpan |
Use useSpan |
|---|---|
| Don’t need scope access | Need to manually restore context |
| Simple callback execution | Need scope for advanced scenarios |
| Most common scenarios | Complex context management |
| Cleaner callback signature | Need explicit control |
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { useSpan } 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 = useSpan(appInsights, span, (scope) => {
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('complex-operation');
useSpan(appInsights, span, (scope) => {
span.setAttribute('phase', 'with-context');
// Do work with span active
performWorkWithContext();
// Manually restore context early
scope.restore();
span.setAttribute('phase', 'without-context');
// Continue work with previous context
performWorkWithoutContext();
});
span.end();
const span = tracer.startSpan('conditional-context');
useSpan(core, span, (scope, shouldRestoreEarly) => {
span.setAttribute('processing', true);
doInitialWork();
if (shouldRestoreEarly) {
console.log('Restoring context early');
scope.restore();
}
// If restored early, runs with previous context
// Otherwise, runs with span context
doAdditionalWork();
}, null, true); // Pass shouldRestoreEarly = true
span.end();
const span = tracer.startSpan('async-operation');
const result = await useSpan(core, span, async (scope) => {
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 span = tracer.startSpan('scope-access');
useSpan(core, span, (scope) => {
// Access scope properties
console.log('Scope type:', typeof scope);
console.log('Has restore method:', typeof scope.restore === 'function');
// Perform work
span.setAttribute('scope.available', true);
// Demonstrate manual restore
const shouldRestoreNow = checkCondition();
if (shouldRestoreNow) {
scope.restore();
}
});
span.end();
function processWithContext(
scope: ISpanScope,
userId: string,
action: string
): string {
console.log('Scope:', scope);
return `User ${userId} performed ${action}`;
}
const span = tracer.startSpan('user-action');
// Pass additional arguments to callback
const result = useSpan(
core,
span,
processWithContext,
null, // thisArg
'user123', // userId
'checkout' // action
);
span.end();
console.log(result); // 'User user123 performed checkout'
const span = tracer.startSpan('error-handling');
try {
useSpan(core, span, (scope) => {
span.setAttribute('attempting', true);
try {
performRiskyOperation();
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
// Record error and restore context before handling
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
// Restore context before error propagates
scope.restore();
throw error;
}
});
} catch (error) {
// Context already restored
console.error('Operation failed:', error);
} finally {
span.end();
}
const parentSpan = tracer.startSpan('parent-operation');
useSpan(core, parentSpan, (parentScope) => {
parentSpan.setAttribute('level', 1);
// Create child span
const childSpan = tracer.startSpan('child-operation');
useSpan(core, childSpan, (childScope) => {
childSpan.setAttribute('level', 2);
// Access both scopes
console.log('Parent scope:', parentScope);
console.log('Child scope:', childScope);
// Perform work
doNestedWork();
// Could manually restore child scope if needed
// childScope.restore();
});
childSpan.end();
// Parent scope is still active here
console.log('Back to parent context');
});
parentSpan.end();
const span = tracer.startSpan('complex-async');
await useSpan(core, span, async (scope) => {
span.setAttribute('stage', 'initial');
// Phase 1: With span context
await performPhase1();
// Conditionally restore based on async result
const shouldContinueWithContext = await checkContinuation();
if (!shouldContinueWithContext) {
scope.restore();
span.setAttribute('context.restored.early', true);
}
// Phase 2: Might be with or without context
await performPhase2();
span.setAttribute('stage', 'completed');
});
span.end();
const span = tracer.startSpan('scope-inspection');
useSpan(core, span, (scope) => {
// Inspect scope
console.log('Scope available:', !!scope);
console.log('Scope has restore:', 'restore' in scope);
// Log current state
const currentSpan = core.getActiveSpan();
console.log('Current span matches:', currentSpan === span);
// Perform work
doWork();
// Demonstrate restoration
console.log('Before restore - active span:', core.getActiveSpan()?.name);
scope.restore();
console.log('After restore - active span:', core.getActiveSpan()?.name);
});
span.end();
// Good - need scope for manual restore
useSpan(core, span, (scope) => {
doWork();
if (needsEarlyRestore()) {
scope.restore();
}
doMoreWork();
});
// Better - use withSpan if scope not needed
withSpan(core, span, () => {
doWork();
doMoreWork();
});
// Good - restore at the right time
useSpan(core, span, (scope) => {
doWorkNeedingContext();
scope.restore();
doWorkNotNeedingContext();
});
// Bad - restoring too early
useSpan(core, span, (scope) => {
scope.restore(); // Too early!
doWorkNeedingContext(); // Context already gone
});
// Good - restore once
useSpan(core, span, (scope) => {
doWork();
scope.restore();
});
// Bad - multiple restores
useSpan(core, span, (scope) => {
scope.restore();
// ... more code ...
scope.restore(); // Second restore - unexpected behavior!
});
// Good - span is ended
const span = tracer.startSpan('operation');
try {
useSpan(core, span, (scope) => {
// Work
});
} finally {
span.end();
}
// Bad - span is never ended
const span = tracer.startSpan('operation');
useSpan(core, span, (scope) => {
// Work
}); // Forgot to end span!
// Good - use useSpan with startSpan
const span = tracer.startSpan('operation');
useSpan(core, span, (scope) => {
// Work
});
span.end();
// Redundant - startActiveSpan already manages context
tracer.startActiveSpan('operation', (span) => {
// No need for useSpan here
});
// Good - proper async handling
const span = tracer.startSpan('async-op');
try {
await useSpan(core, span, async (scope) => {
await doAsyncWork();
});
} finally {
span.end();
}
// Bad - not awaiting
const span = tracer.startSpan('async-op');
useSpan(core, span, async (scope) => {
await doAsyncWork();
}); // Not awaited!
span.end(); // Ends before async work completes
function executeWithOptionalContext<T>(
span: IReadableSpan,
fn: () => T,
keepContextActive: boolean
): T {
return useSpan(core, span, (scope) => {
const result = fn();
if (!keepContextActive) {
scope.restore();
}
return result;
});
}
// Usage
executeWithOptionalContext(span, doWork, false); // Restores context
executeWithOptionalContext(span, doWork, true); // Keeps context
function withManagedScope<T>(
operationName: string,
fn: (scope: ISpanScope) => T
): T {
const span = tracer.startSpan(operationName);
try {
return useSpan(core, span, fn);
} finally {
span.end();
}
}
// Usage
withManagedScope('my-operation', (scope) => {
console.log('Have scope:', scope);
// Can manually restore if needed
return performWork();
});
function processWithEarlyExit(data: any): any {
const span = tracer.startSpan('process');
return useSpan(core, span, (scope) => {
if (!validateData(data)) {
scope.restore();
span.end();
return null; // Early exit
}
const result = processData(data);
span.end();
return result;
});
}
function delegateWithScope<T>(
span: IReadableSpan,
operation: (restore: () => void) => T
): T {
return useSpan(core, span, (scope) => {
// Delegate restore capability to the operation
return operation(() => scope.restore());
});
}
// Usage
delegateWithScope(span, (restore) => {
doWork();
if (shouldStop()) {
restore(); // Operation controls restoration
return null;
}
return continueWork();
});
useSpan guarantees context restoration even when errors occur:
const previousSpan = core.getActiveSpan();
const span = tracer.startSpan('operation');
try {
useSpan(core, span, (scope) => {
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 {
useSpan(core, span, (scope) => {
try {
performRiskyOperation();
} catch (error) {
// Restore context before handling error
scope.restore();
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
}
});
} catch (error) {
console.error('Handled:', error);
} finally {
span.end();
}
const span = tracer.startSpan('async-operation');
try {
await useSpan(core, span, async (scope) => {
try {
const result = await fetch('/api/data');
if (!result.ok) {
throw new Error(`HTTP ${result.status}`);
}
return result.json();
} catch (error) {
// Can restore before re-throwing if needed
scope.restore();
span.recordException(error);
throw error;
}
});
} catch (error) {
console.error('Request failed:', error);
} finally {
span.end();
}
const span = tracer.startSpan('state-check');
useSpan(core, span, (scope) => {
console.log('Before restore:', core.getActiveSpan() === span); // true
scope.restore();
console.log('After restore:', core.getActiveSpan() === span); // false
// Calling restore again has no effect (already restored)
scope.restore();
});
span.end();
The ISpanScope interface provides methods and properties for managing span context:
interface ISpanScope<T extends ITraceHost = ITraceHost> {
/**
* Restore the previous active span context
*/
restore(): void;
// Additional properties may be available depending on implementation
}
useSpan(core, span, (scope) => {
// Check if scope has restore method
if (typeof scope.restore === 'function') {
// Perform work
doWork();
// Restore when needed
scope.restore();
}
});
ITraceHost - Host interface for span managementIReadableSpan - Span interfaceISpanScope - Scope for restoring active span contextIOTelTracer - Tracer interface for creating spans