Langfuse instrumentation methods based on OpenTelemetry
Context management functions provide utilities for updating and accessing the currently active trace and observation within the OpenTelemetry context. These functions enable you to modify trace attributes from anywhere within an active observation context.
Updates the currently active trace with new attributes from within any observation context.
/**
* Updates the currently active trace with new attributes.
*
* This function finds the currently active OpenTelemetry span and updates
* it with trace-level attributes. If no active span is found, a warning is logged.
*
* @param attributes - Trace attributes to set
*/
function updateActiveTrace(attributes: LangfuseTraceAttributes): void;
interface LangfuseTraceAttributes {
/** Human-readable name for the trace */
name?: string;
/** Identifier for the user associated with this trace */
userId?: string;
/** Session identifier for grouping related traces */
sessionId?: string;
/** Version identifier for the code/application */
version?: string;
/** Release identifier for deployment tracking */
release?: string;
/** Input data that initiated the trace */
input?: unknown;
/** Final output data from the trace */
output?: unknown;
/** Additional metadata for the trace */
metadata?: unknown;
/** Tags for categorizing and filtering traces */
tags?: string[];
/** Whether this trace should be publicly visible */
public?: boolean;
/** Environment where the trace was captured */
environment?: string;
}Usage:
import { startActiveObservation, updateActiveTrace } from '@langfuse/tracing';
await startActiveObservation('user-workflow', async (observation) => {
// Update trace with user context
updateActiveTrace({
name: 'checkout-flow',
userId: 'user-123',
sessionId: 'session-456',
tags: ['checkout', 'payment'],
metadata: { cartValue: 99.99 }
});
// Perform operations...
const result = await processCheckout();
// Update trace with final output
updateActiveTrace({
output: {
orderId: result.orderId,
success: true
}
});
});Updates the currently active observation with new attributes from within the observation context.
/**
* Updates the currently active observation with new attributes.
*
* This function finds the currently active OpenTelemetry span in the execution context
* and updates it with Langfuse-specific attributes. It supports all observation types
* through TypeScript overloads for type safety.
*
* **Important**: When `asType` is omitted or undefined, the observation type attribute
* is NOT updated, preserving the existing observation type. This prevents inadvertently
* overwriting the type to "span". Only specify `asType` when you explicitly want to
* change the observation type (which is rarely needed).
*
* @param attributes - Observation-specific attributes to update
* @param options - Configuration specifying observation type (defaults to 'span')
*/
function updateActiveObservation(
attributes: LangfuseObservationAttributes,
options?: { asType?: LangfuseObservationType }
): void;
// Type-specific overloads for type safety
function updateActiveObservation(
attributes: LangfuseSpanAttributes,
options?: { asType: "span" }
): void;
function updateActiveObservation(
attributes: LangfuseGenerationAttributes,
options: { asType: "generation" }
): void;
function updateActiveObservation(
attributes: LangfuseAgentAttributes,
options: { asType: "agent" }
): void;
// ... additional overloads for tool, chain, retriever, evaluator, guardrail, embeddingUsage:
import { startActiveObservation, updateActiveObservation } from '@langfuse/tracing';
// Update active span (default)
await startActiveObservation('data-processing', async () => {
const result = await processData(inputData);
updateActiveObservation({
output: { processedRecords: result.count },
metadata: { processingTime: result.duration }
});
});
// Update active generation with LLM-specific attributes
await startActiveObservation('llm-call', async () => {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello' }]
});
updateActiveObservation({
output: response.choices[0].message,
usageDetails: {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens
},
costDetails: { totalCost: 0.025, currency: 'USD' }
}, { asType: 'generation' });
}, { asType: 'generation' });Gets the trace ID of the currently active span.
/**
* Gets the current active trace ID.
*
* If there is no span in the current context, returns undefined.
*
* @returns The trace ID of the currently active span, or undefined if no span is active
*/
function getActiveTraceId(): string | undefined;Usage:
import { startActiveObservation, getActiveTraceId } from '@langfuse/tracing';
await startActiveObservation('operation', async () => {
const traceId = getActiveTraceId();
if (traceId) {
console.log('Current trace ID:', traceId);
// Use trace ID for correlation with external systems
await logToExternalSystem({ traceId, event: 'operation-started' });
}
});Gets the observation ID (span ID) of the currently active span.
/**
* Gets the current active observation ID.
*
* If there is no OTEL span in the current context, returns undefined.
*
* @returns The ID of the currently active OTEL span, or undefined if no OTEL span is active
*/
function getActiveSpanId(): string | undefined;Usage:
import { startActiveObservation, getActiveSpanId } from '@langfuse/tracing';
await startActiveObservation('parent-operation', async () => {
const parentSpanId = getActiveSpanId();
await startActiveObservation('child-operation', async () => {
const childSpanId = getActiveSpanId();
console.log('Parent span ID:', parentSpanId);
console.log('Child span ID:', childSpanId);
});
});Update trace with user information discovered during execution.
import { startActiveObservation, updateActiveTrace } from '@langfuse/tracing';
async function handleRequest(request: Request) {
return await startActiveObservation('handle-request', async () => {
// Initially, we don't know the user
const authToken = request.headers.get('Authorization');
const user = await authenticateUser(authToken);
// Update trace with authenticated user
updateActiveTrace({
userId: user.id,
sessionId: user.sessionId,
metadata: {
userRole: user.role,
accountType: user.accountType
}
});
// Continue processing with user context
return await processRequest(request, user);
});
}Update observations as more information becomes available.
await startActiveObservation('multi-step-process', async () => {
// Step 1: Initialize
updateActiveObservation({
input: { phase: 'initialization' },
metadata: { startTime: Date.now() }
});
const config = await loadConfiguration();
// Step 2: Processing
updateActiveObservation({
metadata: {
configLoaded: true,
configVersion: config.version
}
});
const result = await processWithConfig(config);
// Step 3: Completion
updateActiveObservation({
output: result,
metadata: {
completionTime: Date.now(),
recordsProcessed: result.count
}
});
});Add detailed error context to active observations.
await startActiveObservation('risky-operation', async () => {
try {
const result = await performRiskyOperation();
updateActiveObservation({
output: result,
level: 'DEFAULT'
});
return result;
} catch (error) {
// Enhance error with context
updateActiveObservation({
level: 'ERROR',
statusMessage: error.message,
output: {
error: error.message,
errorCode: error.code,
stack: error.stack
},
metadata: {
recoveryAttempted: false,
criticalFailure: true
}
});
throw error;
}
});Add tracing information from middleware or decorators.
// Middleware example
async function timingMiddleware(
handler: () => Promise<any>
) {
const startTime = Date.now();
try {
const result = await handler();
const duration = Date.now() - startTime;
updateActiveObservation({
metadata: {
duration,
performance: duration < 1000 ? 'fast' : 'slow'
}
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
updateActiveObservation({
level: 'ERROR',
metadata: {
duration,
failedAfter: duration
}
});
throw error;
}
}
// Usage in traced operation
await startActiveObservation('operation', async () => {
return await timingMiddleware(async () => {
return await doWork();
});
});Use trace and span IDs to correlate with external logging systems.
import { startActiveObservation, getActiveTraceId, getActiveSpanId } from '@langfuse/tracing';
async function callExternalAPI(endpoint: string, data: any) {
return await startActiveObservation('external-api-call', async () => {
const traceId = getActiveTraceId();
const spanId = getActiveSpanId();
// Include trace context in external API call
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Trace-Id': traceId || 'unknown',
'X-Span-Id': spanId || 'unknown'
},
body: JSON.stringify(data)
});
// Log correlation info
console.log(`API call to ${endpoint}`, {
traceId,
spanId,
statusCode: response.status
});
updateActiveObservation({
output: {
statusCode: response.status,
correlationId: response.headers.get('X-Correlation-Id')
},
metadata: {
endpoint,
traceId,
spanId
}
});
return response.json();
});
}Update trace and observation from deeply nested function calls.
async function deeplyNestedOperation() {
// Can update trace/observation from anywhere in the call stack
updateActiveTrace({
tags: ['nested-operation'],
metadata: { depth: 'level-3' }
});
updateActiveObservation({
metadata: { nestedCallCompleted: true }
});
}
async function middleOperation() {
await deeplyNestedOperation();
updateActiveObservation({
metadata: { middleCompleted: true }
});
}
await startActiveObservation('top-level', async () => {
await middleOperation();
updateActiveObservation({
output: { allLevelsCompleted: true }
});
});Update trace attributes based on runtime conditions.
await startActiveObservation('conditional-workflow', async (obs) => {
const user = await getCurrentUser();
// Update trace based on user type
if (user.role === 'admin') {
updateActiveTrace({
tags: ['admin-operation'],
metadata: { elevated: true }
});
} else if (user.role === 'premium') {
updateActiveTrace({
tags: ['premium-operation'],
metadata: { accountTier: 'premium' }
});
}
// Perform operation
const result = await processForUser(user);
// Update based on result
if (result.requiresReview) {
updateActiveTrace({
tags: ['requires-review'],
public: false // Keep sensitive operations private
});
}
});The updateActiveObservation function provides type-specific overloads for each observation type.
// Span attributes (default)
updateActiveObservation({
input: { data: 'value' },
output: { result: 'success' },
metadata: { custom: 'info' }
});
// Generation attributes
updateActiveObservation({
model: 'gpt-4',
modelParameters: { temperature: 0.7 },
usageDetails: { totalTokens: 500 },
costDetails: { totalCost: 0.025 }
}, { asType: 'generation' });
// Agent attributes
updateActiveObservation({
output: {
toolsUsed: ['search', 'calculator'],
iterationsRequired: 3
},
metadata: { efficiency: 0.85 }
}, { asType: 'agent' });
// Tool attributes
updateActiveObservation({
output: {
result: searchResults,
latency: 1200
},
metadata: { cacheHit: false }
}, { asType: 'tool' });All context management functions require an active OpenTelemetry span context:
// ✓ Valid: Inside startActiveObservation
await startActiveObservation('operation', async () => {
updateActiveTrace({ userId: 'user-123' }); // Works
updateActiveObservation({ output: { result: 'success' } }); // Works
const traceId = getActiveTraceId(); // Returns trace ID
});
// ✓ Valid: Inside observe
const tracedFn = observe(async () => {
updateActiveTrace({ tags: ['processed'] }); // Works
updateActiveObservation({ level: 'DEFAULT' }); // Works
});
// ✓ Valid: Inside startObservation with manual context
const span = startObservation('operation');
context.with(
trace.setSpan(context.active(), span.otelSpan),
() => {
updateActiveObservation({ output: { done: true } }); // Works
}
);
span.end();// ✗ Invalid: Outside any observation context
updateActiveTrace({ userId: 'user-123' }); // Warning logged, no effect
updateActiveObservation({ output: { result: 'success' } }); // Warning logged, no effect
const traceId = getActiveTraceId(); // Returns undefinedUpdate traces when context is discovered during execution, not known upfront.
// Good: Update when information becomes available
await startActiveObservation('api-request', async () => {
const token = extractToken(request);
const user = await validateToken(token);
updateActiveTrace({
userId: user.id,
sessionId: user.sessionId
});
return await processRequest(user);
});
// Less ideal: If you know the context upfront, pass it to the observation
await startActiveObservation('api-request', async (obs) => {
obs.updateTrace({
userId: knownUser.id,
sessionId: knownUser.sessionId
});
return await processRequest(knownUser);
});When you have a reference to the observation, use its update() method directly.
// Preferred: Direct update when you have the reference
await startActiveObservation('operation', async (observation) => {
observation.update({ output: { result: 'success' } });
observation.updateTrace({ tags: ['completed'] });
});
// Use updateActiveObservation when you don't have the reference
async function helperFunction() {
// Called from within an observation but doesn't have the reference
updateActiveObservation({ metadata: { helperCalled: true } });
}
await startActiveObservation('main-operation', async () => {
await helperFunction();
});Check for active context before using IDs.
function logOperationContext() {
const traceId = getActiveTraceId();
const spanId = getActiveSpanId();
if (traceId && spanId) {
console.log('Operation context:', { traceId, spanId });
} else {
console.log('No active tracing context');
}
}
// Works in both traced and untraced contexts
await startActiveObservation('operation', async () => {
logOperationContext(); // Logs trace and span IDs
});
logOperationContext(); // Logs "No active tracing context"Group related attribute updates together for clarity.
// Good: Batch related updates
await startActiveObservation('operation', async () => {
const result = await complexOperation();
updateActiveObservation({
output: result,
level: 'DEFAULT',
metadata: {
duration: result.duration,
itemsProcessed: result.count,
efficiency: result.efficiency
}
});
});
// Avoid: Multiple separate updates
await startActiveObservation('operation', async () => {
const result = await complexOperation();
updateActiveObservation({ output: result });
updateActiveObservation({ level: 'DEFAULT' });
updateActiveObservation({ metadata: { duration: result.duration } });
updateActiveObservation({ metadata: { itemsProcessed: result.count } });
});Maintain consistent attribute types across updates.
// Good: Consistent types
updateActiveObservation({
metadata: {
count: 10, // number
status: 'success', // string
enabled: true // boolean
}
});
// Later update maintains types
updateActiveObservation({
metadata: {
count: 20, // still number
status: 'complete', // still string
enabled: false // still boolean
}
});Install with Tessl CLI
npx tessl i tessl/npm-langfuse--tracing