CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-langfuse--tracing

Langfuse instrumentation methods based on OpenTelemetry

Overview
Eval results
Files

attribute-creation.mddocs/

Attribute Creation

Attribute creation functions convert user-friendly Langfuse attributes into OpenTelemetry attribute format for span processors. These functions handle serialization, flattening, and formatting of trace and observation attributes.

Core Functions

createTraceAttributes

Converts Langfuse trace attributes into OpenTelemetry attributes.

/**
 * Creates OpenTelemetry attributes from Langfuse trace attributes.
 *
 * Converts user-friendly trace attributes into the internal OpenTelemetry
 * attribute format required by the span processor.
 *
 * @param attributes - Langfuse trace attributes to convert
 * @returns OpenTelemetry attributes object with non-null values
 */
function createTraceAttributes(
  attributes?: LangfuseTraceAttributes
): Attributes;

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

// OpenTelemetry Attributes type
type Attributes = Record<string, string | number | boolean | Array<string | number | boolean>>;

Usage:

import { createTraceAttributes } from '@langfuse/tracing';

const otelAttributes = createTraceAttributes({
  name: 'user-checkout-flow',
  userId: 'user-123',
  sessionId: 'session-456',
  tags: ['checkout', 'payment'],
  metadata: { version: '2.1.0', cartValue: 99.99 },
  input: { items: [{ id: '1', name: 'Product A' }] },
  output: { orderId: 'ord-789', success: true },
  environment: 'production',
  public: false
});

// Apply to span
span.setAttributes(otelAttributes);

createObservationAttributes

Converts Langfuse observation attributes into OpenTelemetry attributes based on observation type.

/**
 * Creates OpenTelemetry attributes from Langfuse observation attributes.
 *
 * @param type - The observation type (span, generation, event, etc.)
 * @param attributes - Langfuse observation attributes to convert
 * @returns OpenTelemetry attributes object with non-null values
 */
function createObservationAttributes(
  type: LangfuseObservationType,
  attributes: LangfuseObservationAttributes
): Attributes;

type LangfuseObservationType =
  | "span"
  | "generation"
  | "event"
  | "embedding"
  | "agent"
  | "tool"
  | "chain"
  | "retriever"
  | "evaluator"
  | "guardrail";

interface LangfuseObservationAttributes {
  /** Input data for the operation */
  input?: unknown;
  /** Output data from the operation */
  output?: unknown;
  /** Additional metadata as key-value pairs */
  metadata?: Record<string, unknown>;
  /** Severity level */
  level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
  /** Human-readable status message */
  statusMessage?: string;
  /** Version identifier */
  version?: string;
  /** Environment identifier */
  environment?: string;

  // Generation-specific attributes
  /** Timestamp when model started generating completion */
  completionStartTime?: Date;
  /** Name of the language model used */
  model?: string;
  /** Parameters passed to the model */
  modelParameters?: { [key: string]: string | number };
  /** Token usage and other metrics */
  usageDetails?: { [key: string]: number };
  /** Cost breakdown */
  costDetails?: { [key: string]: number };
  /** Prompt information */
  prompt?: { name: string; version: number; isFallback: boolean };
}

Usage:

import { createObservationAttributes } from '@langfuse/tracing';

// Span attributes
const spanAttrs = createObservationAttributes('span', {
  input: { userId: '123', action: 'checkout' },
  output: { success: true, orderId: 'ord-456' },
  metadata: { duration: 1500 },
  level: 'DEFAULT'
});

// Generation attributes
const genAttrs = createObservationAttributes('generation', {
  input: [{ role: 'user', content: 'Hello' }],
  output: { role: 'assistant', content: 'Hi there!' },
  model: 'gpt-4',
  modelParameters: { temperature: 0.7, maxTokens: 500 },
  usageDetails: {
    promptTokens: 10,
    completionTokens: 15,
    totalTokens: 25
  },
  costDetails: { totalCost: 0.001 }
});

span.setAttributes(spanAttrs);

Attribute Processing

Serialization

Complex objects are automatically serialized to JSON strings.

const attributes = createTraceAttributes({
  input: {
    userId: '123',
    items: [
      { id: 'item-1', quantity: 2 },
      { id: 'item-2', quantity: 1 }
    ]
  },
  metadata: {
    timestamp: new Date(),
    config: { retries: 3, timeout: 5000 }
  }
});

// Result (simplified):
// {
//   'langfuse.trace.input': '{"userId":"123","items":[{"id":"item-1","quantity":2},{"id":"item-2","quantity":1}]}',
//   'langfuse.metadata.timestamp': '"2024-01-01T00:00:00.000Z"',
//   'langfuse.metadata.config': '{"retries":3,"timeout":5000}'
// }

Metadata Flattening

Metadata objects are flattened into dot-notation keys.

const attributes = createTraceAttributes({
  metadata: {
    database: {
      host: 'localhost',
      port: 5432,
      name: 'mydb'
    },
    cache: {
      enabled: true,
      ttl: 3600
    }
  }
});

// Result (simplified):
// {
//   'langfuse.metadata.database.host': 'localhost',
//   'langfuse.metadata.database.port': '5432',
//   'langfuse.metadata.database.name': 'mydb',
//   'langfuse.metadata.cache.enabled': 'true',
//   'langfuse.metadata.cache.ttl': '3600'
// }

Null Value Filtering

Null and undefined values are automatically filtered out.

const attributes = createTraceAttributes({
  name: 'my-trace',
  userId: 'user-123',
  sessionId: undefined, // Excluded
  version: null,        // Excluded
  tags: ['tag1', 'tag2']
});

// Result only includes non-null values:
// {
//   'langfuse.trace.name': 'my-trace',
//   'langfuse.trace.userId': 'user-123',
//   'langfuse.trace.tags': ['tag1', 'tag2']
// }

Use Cases

Direct Span Updates

Apply attributes directly to OpenTelemetry spans.

import { trace } from '@opentelemetry/api';
import { createTraceAttributes, createObservationAttributes } from '@langfuse/tracing';

const tracer = trace.getTracer('my-tracer');
const span = tracer.startSpan('my-operation');

// Set trace attributes
span.setAttributes(createTraceAttributes({
  name: 'user-workflow',
  userId: 'user-123',
  tags: ['production']
}));

// Set observation attributes
span.setAttributes(createObservationAttributes('span', {
  input: { action: 'process' },
  output: { success: true }
}));

span.end();

Custom Span Processors

Use in custom span processors for attribute transformation.

import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-base';
import { createObservationAttributes } from '@langfuse/tracing';

class CustomSpanProcessor implements SpanProcessor {
  onStart(span: Span): void {
    // Add default attributes to all spans
    const defaultAttrs = createObservationAttributes('span', {
      metadata: {
        processor: 'custom',
        version: '1.0.0'
      }
    });

    span.setAttributes(defaultAttrs);
  }

  onEnd(span: ReadableSpan): void {
    // Process completed span
  }

  shutdown(): Promise<void> {
    return Promise.resolve();
  }

  forceFlush(): Promise<void> {
    return Promise.resolve();
  }
}

Attribute Transformation

Transform attributes before applying to spans.

import { createObservationAttributes } from '@langfuse/tracing';

function enrichObservationAttributes(
  type: LangfuseObservationType,
  baseAttributes: LangfuseObservationAttributes,
  enrichments: Record<string, unknown>
): Attributes {
  // Merge base attributes with enrichments
  const enrichedAttributes = {
    ...baseAttributes,
    metadata: {
      ...baseAttributes.metadata,
      ...enrichments
    }
  };

  return createObservationAttributes(type, enrichedAttributes);
}

const attributes = enrichObservationAttributes(
  'generation',
  {
    model: 'gpt-4',
    input: 'Hello'
  },
  {
    requestId: 'req-123',
    timestamp: Date.now(),
    region: 'us-east-1'
  }
);

Testing and Validation

Validate attribute structure in tests.

import { createTraceAttributes } from '@langfuse/tracing';

describe('Trace attributes', () => {
  it('should create valid OpenTelemetry attributes', () => {
    const attributes = createTraceAttributes({
      name: 'test-trace',
      userId: 'user-123',
      tags: ['test', 'validation']
    });

    // Validate structure
    expect(attributes).toHaveProperty('langfuse.trace.name', 'test-trace');
    expect(attributes).toHaveProperty('langfuse.trace.userId', 'user-123');
    expect(attributes).toHaveProperty('langfuse.trace.tags', ['test', 'validation']);
  });

  it('should filter out null values', () => {
    const attributes = createTraceAttributes({
      name: 'test-trace',
      userId: undefined,
      sessionId: null
    });

    // Should not include null/undefined
    expect(attributes).not.toHaveProperty('langfuse.trace.userId');
    expect(attributes).not.toHaveProperty('langfuse.trace.sessionId');
  });
});

Attribute Inspection

Inspect generated attributes for debugging.

import { createTraceAttributes, createObservationAttributes } from '@langfuse/tracing';

function logAttributes(
  name: string,
  attributes: Attributes
): void {
  console.log(`\n=== ${name} ===`);
  for (const [key, value] of Object.entries(attributes)) {
    console.log(`${key}: ${JSON.stringify(value)}`);
  }
}

const traceAttrs = createTraceAttributes({
  name: 'debug-trace',
  userId: 'user-123',
  metadata: { debug: true }
});

const obsAttrs = createObservationAttributes('span', {
  input: { test: true },
  output: { result: 'success' }
});

logAttributes('Trace Attributes', traceAttrs);
logAttributes('Observation Attributes', obsAttrs);

Advanced Patterns

Conditional Attribute Creation

Create attributes conditionally based on environment or configuration.

import { createTraceAttributes } from '@langfuse/tracing';

function createConditionalTraceAttributes(
  baseAttributes: LangfuseTraceAttributes,
  options: { includeInput?: boolean; includeOutput?: boolean } = {}
): Attributes {
  const attributes: LangfuseTraceAttributes = {
    name: baseAttributes.name,
    userId: baseAttributes.userId,
    sessionId: baseAttributes.sessionId
  };

  // Conditionally include input/output
  if (options.includeInput && baseAttributes.input) {
    attributes.input = baseAttributes.input;
  }

  if (options.includeOutput && baseAttributes.output) {
    attributes.output = baseAttributes.output;
  }

  return createTraceAttributes(attributes);
}

// Production: exclude sensitive data
const prodAttrs = createConditionalTraceAttributes(
  { name: 'trace', input: { sensitive: 'data' } },
  { includeInput: false }
);

// Development: include all data
const devAttrs = createConditionalTraceAttributes(
  { name: 'trace', input: { debug: 'info' } },
  { includeInput: true }
);

Attribute Merging

Merge multiple attribute sets.

import { createObservationAttributes } from '@langfuse/tracing';

function mergeObservationAttributes(
  type: LangfuseObservationType,
  ...attributeSets: LangfuseObservationAttributes[]
): Attributes {
  // Merge all attribute sets
  const merged: LangfuseObservationAttributes = {};

  for (const attrs of attributeSets) {
    Object.assign(merged, attrs);

    // Merge metadata deeply
    if (attrs.metadata) {
      merged.metadata = {
        ...merged.metadata,
        ...attrs.metadata
      };
    }
  }

  return createObservationAttributes(type, merged);
}

const baseAttrs = { level: 'DEFAULT' as const };
const inputAttrs = { input: { data: 'value' } };
const outputAttrs = { output: { result: 'success' } };
const metadataAttrs = { metadata: { timestamp: Date.now() } };

const combined = mergeObservationAttributes(
  'span',
  baseAttrs,
  inputAttrs,
  outputAttrs,
  metadataAttrs
);

Attribute Sanitization

Sanitize sensitive data before creating attributes.

import { createTraceAttributes } from '@langfuse/tracing';

function sanitizeTraceAttributes(
  attributes: LangfuseTraceAttributes
): Attributes {
  const sanitized: LangfuseTraceAttributes = { ...attributes };

  // Remove sensitive fields from input
  if (sanitized.input && typeof sanitized.input === 'object') {
    const input = { ...sanitized.input as any };
    delete input.password;
    delete input.apiKey;
    delete input.token;
    sanitized.input = input;
  }

  // Remove sensitive fields from output
  if (sanitized.output && typeof sanitized.output === 'object') {
    const output = { ...sanitized.output as any };
    delete output.creditCard;
    delete output.ssn;
    sanitized.output = output;
  }

  return createTraceAttributes(sanitized);
}

const attributes = sanitizeTraceAttributes({
  name: 'secure-trace',
  input: {
    username: 'user123',
    password: 'secret123' // Will be removed
  },
  output: {
    userId: '123',
    token: 'jwt-token' // Will be removed
  }
});

Attribute Validation

Validate attributes before creation.

import { createObservationAttributes } from '@langfuse/tracing';

function validateAndCreateAttributes(
  type: LangfuseObservationType,
  attributes: LangfuseObservationAttributes
): Attributes | null {
  // Validate required fields
  if (type === 'generation') {
    if (!attributes.model) {
      console.error('Generation requires model attribute');
      return null;
    }
  }

  // Validate level
  if (attributes.level) {
    const validLevels = ['DEBUG', 'DEFAULT', 'WARNING', 'ERROR'];
    if (!validLevels.includes(attributes.level)) {
      console.error(`Invalid level: ${attributes.level}`);
      return null;
    }
  }

  // Validate metadata
  if (attributes.metadata) {
    try {
      JSON.stringify(attributes.metadata);
    } catch (error) {
      console.error('Metadata must be JSON serializable');
      return null;
    }
  }

  return createObservationAttributes(type, attributes);
}

Best Practices

Use Appropriate Functions

Use the correct function for trace vs observation attributes.

// Good: Use createTraceAttributes for trace-level data
const traceAttrs = createTraceAttributes({
  name: 'my-trace',
  userId: 'user-123'
});

// Good: Use createObservationAttributes for observation-level data
const obsAttrs = createObservationAttributes('span', {
  input: { data: 'value' },
  output: { result: 'success' }
});

// Avoid: Using wrong function
const wrongAttrs = createObservationAttributes('span', {
  name: 'trace-name' // This is a trace attribute
} as any);

Handle Serialization Failures

Account for objects that may fail to serialize.

import { createObservationAttributes } from '@langfuse/tracing';

function safeCreateAttributes(
  type: LangfuseObservationType,
  attributes: LangfuseObservationAttributes
): Attributes {
  try {
    return createObservationAttributes(type, attributes);
  } catch (error) {
    console.error('Failed to create attributes:', error);

    // Return minimal valid attributes
    return createObservationAttributes(type, {
      metadata: { error: 'Failed to serialize attributes' }
    });
  }
}

Keep Metadata Structured

Maintain consistent metadata structure for better querying.

// Good: Structured metadata
const attributes = createTraceAttributes({
  metadata: {
    performance: {
      duration: 1500,
      memoryUsed: 128
    },
    context: {
      region: 'us-east-1',
      environment: 'production'
    }
  }
});

// Avoid: Flat, inconsistent structure
const attributes = createTraceAttributes({
  metadata: {
    durationMs: 1500,
    memory: 128,
    region: 'us-east-1',
    env: 'prod'
  }
});

Avoid Excessive Nesting

Keep metadata nesting reasonable for readability.

// Good: Reasonable depth
const attributes = createTraceAttributes({
  metadata: {
    database: { host: 'localhost', port: 5432 }
  }
});

// Avoid: Excessive nesting
const attributes = createTraceAttributes({
  metadata: {
    system: {
      subsystem: {
        component: {
          subcomponent: {
            detail: { value: 'deeply-nested' }
          }
        }
      }
    }
  }
});

Use TypeScript Types

Leverage TypeScript for type safety.

import {
  createTraceAttributes,
  createObservationAttributes,
  LangfuseTraceAttributes,
  LangfuseObservationAttributes,
  LangfuseObservationType
} from '@langfuse/tracing';

function createTypedAttributes(
  traceAttrs: LangfuseTraceAttributes,
  obsType: LangfuseObservationType,
  obsAttrs: LangfuseObservationAttributes
) {
  const trace = createTraceAttributes(traceAttrs);
  const observation = createObservationAttributes(obsType, obsAttrs);

  return { trace, observation };
}

// TypeScript ensures correct structure
const attrs = createTypedAttributes(
  { name: 'trace', userId: 'user-123' },
  'generation',
  { model: 'gpt-4', input: 'Hello' }
);

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