Langfuse instrumentation methods based on OpenTelemetry
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.
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);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);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 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 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']
// }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();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();
}
}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'
}
);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');
});
});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);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 }
);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
);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
}
});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);
}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);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' }
});
}
}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'
}
});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' }
}
}
}
}
}
});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