CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langfuse--tracing

Langfuse instrumentation methods based on OpenTelemetry

Overview
Eval results
Files

context-management.mddocs/

Context Management

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.

Core Functions

updateActiveTrace

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
    }
  });
});

updateActiveObservation

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, embedding

Usage:

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' });

getActiveTraceId

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' });
  }
});

getActiveSpanId

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);
  });
});

Use Cases

Dynamic User Context

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);
  });
}

Progressive Attribute Updates

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
    }
  });
});

Error Context Enhancement

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;
  }
});

Cross-Cutting Concerns

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();
  });
});

External System Correlation

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();
  });
}

Nested Context Updates

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 }
  });
});

Conditional Trace Updates

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
    });
  }
});

Type Safety

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' });

Context Requirements

All context management functions require an active OpenTelemetry span context:

Valid 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 Context

// ✗ 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 undefined

Best Practices

Use for Dynamic Context

Update 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);
});

Prefer Observation.update() When Available

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();
});

Handle Missing Context Gracefully

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"

Batch Related Updates

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 } });
});

Consistent Attribute Types

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

docs

active-observations.md

attribute-creation.md

context-management.md

index.md

manual-observations.md

observation-types.md

observe-decorator.md

otel-span-attributes.md

trace-id-generation.md

tracer-provider.md

tile.json