0
# Attribute Creation
1
2
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.
3
4
## Core Functions
5
6
### createTraceAttributes
7
8
Converts Langfuse trace attributes into OpenTelemetry attributes.
9
10
```typescript { .api }
11
/**
12
* Creates OpenTelemetry attributes from Langfuse trace attributes.
13
*
14
* Converts user-friendly trace attributes into the internal OpenTelemetry
15
* attribute format required by the span processor.
16
*
17
* @param attributes - Langfuse trace attributes to convert
18
* @returns OpenTelemetry attributes object with non-null values
19
*/
20
function createTraceAttributes(
21
attributes?: LangfuseTraceAttributes
22
): Attributes;
23
24
interface LangfuseTraceAttributes {
25
/** Human-readable name for the trace */
26
name?: string;
27
/** Identifier for the user associated with this trace */
28
userId?: string;
29
/** Session identifier for grouping related traces */
30
sessionId?: string;
31
/** Version identifier for the code/application */
32
version?: string;
33
/** Release identifier for deployment tracking */
34
release?: string;
35
/** Input data that initiated the trace */
36
input?: unknown;
37
/** Final output data from the trace */
38
output?: unknown;
39
/** Additional metadata for the trace */
40
metadata?: unknown;
41
/** Tags for categorizing and filtering traces */
42
tags?: string[];
43
/** Whether this trace should be publicly visible */
44
public?: boolean;
45
/** Environment where the trace was captured */
46
environment?: string;
47
}
48
49
// OpenTelemetry Attributes type
50
type Attributes = Record<string, string | number | boolean | Array<string | number | boolean>>;
51
```
52
53
**Usage:**
54
55
```typescript
56
import { createTraceAttributes } from '@langfuse/tracing';
57
58
const otelAttributes = createTraceAttributes({
59
name: 'user-checkout-flow',
60
userId: 'user-123',
61
sessionId: 'session-456',
62
tags: ['checkout', 'payment'],
63
metadata: { version: '2.1.0', cartValue: 99.99 },
64
input: { items: [{ id: '1', name: 'Product A' }] },
65
output: { orderId: 'ord-789', success: true },
66
environment: 'production',
67
public: false
68
});
69
70
// Apply to span
71
span.setAttributes(otelAttributes);
72
```
73
74
### createObservationAttributes
75
76
Converts Langfuse observation attributes into OpenTelemetry attributes based on observation type.
77
78
```typescript { .api }
79
/**
80
* Creates OpenTelemetry attributes from Langfuse observation attributes.
81
*
82
* @param type - The observation type (span, generation, event, etc.)
83
* @param attributes - Langfuse observation attributes to convert
84
* @returns OpenTelemetry attributes object with non-null values
85
*/
86
function createObservationAttributes(
87
type: LangfuseObservationType,
88
attributes: LangfuseObservationAttributes
89
): Attributes;
90
91
type LangfuseObservationType =
92
| "span"
93
| "generation"
94
| "event"
95
| "embedding"
96
| "agent"
97
| "tool"
98
| "chain"
99
| "retriever"
100
| "evaluator"
101
| "guardrail";
102
103
interface LangfuseObservationAttributes {
104
/** Input data for the operation */
105
input?: unknown;
106
/** Output data from the operation */
107
output?: unknown;
108
/** Additional metadata as key-value pairs */
109
metadata?: Record<string, unknown>;
110
/** Severity level */
111
level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR";
112
/** Human-readable status message */
113
statusMessage?: string;
114
/** Version identifier */
115
version?: string;
116
/** Environment identifier */
117
environment?: string;
118
119
// Generation-specific attributes
120
/** Timestamp when model started generating completion */
121
completionStartTime?: Date;
122
/** Name of the language model used */
123
model?: string;
124
/** Parameters passed to the model */
125
modelParameters?: { [key: string]: string | number };
126
/** Token usage and other metrics */
127
usageDetails?: { [key: string]: number };
128
/** Cost breakdown */
129
costDetails?: { [key: string]: number };
130
/** Prompt information */
131
prompt?: { name: string; version: number; isFallback: boolean };
132
}
133
```
134
135
**Usage:**
136
137
```typescript
138
import { createObservationAttributes } from '@langfuse/tracing';
139
140
// Span attributes
141
const spanAttrs = createObservationAttributes('span', {
142
input: { userId: '123', action: 'checkout' },
143
output: { success: true, orderId: 'ord-456' },
144
metadata: { duration: 1500 },
145
level: 'DEFAULT'
146
});
147
148
// Generation attributes
149
const genAttrs = createObservationAttributes('generation', {
150
input: [{ role: 'user', content: 'Hello' }],
151
output: { role: 'assistant', content: 'Hi there!' },
152
model: 'gpt-4',
153
modelParameters: { temperature: 0.7, maxTokens: 500 },
154
usageDetails: {
155
promptTokens: 10,
156
completionTokens: 15,
157
totalTokens: 25
158
},
159
costDetails: { totalCost: 0.001 }
160
});
161
162
span.setAttributes(spanAttrs);
163
```
164
165
## Attribute Processing
166
167
### Serialization
168
169
Complex objects are automatically serialized to JSON strings.
170
171
```typescript
172
const attributes = createTraceAttributes({
173
input: {
174
userId: '123',
175
items: [
176
{ id: 'item-1', quantity: 2 },
177
{ id: 'item-2', quantity: 1 }
178
]
179
},
180
metadata: {
181
timestamp: new Date(),
182
config: { retries: 3, timeout: 5000 }
183
}
184
});
185
186
// Result (simplified):
187
// {
188
// 'langfuse.trace.input': '{"userId":"123","items":[{"id":"item-1","quantity":2},{"id":"item-2","quantity":1}]}',
189
// 'langfuse.metadata.timestamp': '"2024-01-01T00:00:00.000Z"',
190
// 'langfuse.metadata.config': '{"retries":3,"timeout":5000}'
191
// }
192
```
193
194
### Metadata Flattening
195
196
Metadata objects are flattened into dot-notation keys.
197
198
```typescript
199
const attributes = createTraceAttributes({
200
metadata: {
201
database: {
202
host: 'localhost',
203
port: 5432,
204
name: 'mydb'
205
},
206
cache: {
207
enabled: true,
208
ttl: 3600
209
}
210
}
211
});
212
213
// Result (simplified):
214
// {
215
// 'langfuse.metadata.database.host': 'localhost',
216
// 'langfuse.metadata.database.port': '5432',
217
// 'langfuse.metadata.database.name': 'mydb',
218
// 'langfuse.metadata.cache.enabled': 'true',
219
// 'langfuse.metadata.cache.ttl': '3600'
220
// }
221
```
222
223
### Null Value Filtering
224
225
Null and undefined values are automatically filtered out.
226
227
```typescript
228
const attributes = createTraceAttributes({
229
name: 'my-trace',
230
userId: 'user-123',
231
sessionId: undefined, // Excluded
232
version: null, // Excluded
233
tags: ['tag1', 'tag2']
234
});
235
236
// Result only includes non-null values:
237
// {
238
// 'langfuse.trace.name': 'my-trace',
239
// 'langfuse.trace.userId': 'user-123',
240
// 'langfuse.trace.tags': ['tag1', 'tag2']
241
// }
242
```
243
244
## Use Cases
245
246
### Direct Span Updates
247
248
Apply attributes directly to OpenTelemetry spans.
249
250
```typescript
251
import { trace } from '@opentelemetry/api';
252
import { createTraceAttributes, createObservationAttributes } from '@langfuse/tracing';
253
254
const tracer = trace.getTracer('my-tracer');
255
const span = tracer.startSpan('my-operation');
256
257
// Set trace attributes
258
span.setAttributes(createTraceAttributes({
259
name: 'user-workflow',
260
userId: 'user-123',
261
tags: ['production']
262
}));
263
264
// Set observation attributes
265
span.setAttributes(createObservationAttributes('span', {
266
input: { action: 'process' },
267
output: { success: true }
268
}));
269
270
span.end();
271
```
272
273
### Custom Span Processors
274
275
Use in custom span processors for attribute transformation.
276
277
```typescript
278
import { SpanProcessor, Span, ReadableSpan } from '@opentelemetry/sdk-trace-base';
279
import { createObservationAttributes } from '@langfuse/tracing';
280
281
class CustomSpanProcessor implements SpanProcessor {
282
onStart(span: Span): void {
283
// Add default attributes to all spans
284
const defaultAttrs = createObservationAttributes('span', {
285
metadata: {
286
processor: 'custom',
287
version: '1.0.0'
288
}
289
});
290
291
span.setAttributes(defaultAttrs);
292
}
293
294
onEnd(span: ReadableSpan): void {
295
// Process completed span
296
}
297
298
shutdown(): Promise<void> {
299
return Promise.resolve();
300
}
301
302
forceFlush(): Promise<void> {
303
return Promise.resolve();
304
}
305
}
306
```
307
308
### Attribute Transformation
309
310
Transform attributes before applying to spans.
311
312
```typescript
313
import { createObservationAttributes } from '@langfuse/tracing';
314
315
function enrichObservationAttributes(
316
type: LangfuseObservationType,
317
baseAttributes: LangfuseObservationAttributes,
318
enrichments: Record<string, unknown>
319
): Attributes {
320
// Merge base attributes with enrichments
321
const enrichedAttributes = {
322
...baseAttributes,
323
metadata: {
324
...baseAttributes.metadata,
325
...enrichments
326
}
327
};
328
329
return createObservationAttributes(type, enrichedAttributes);
330
}
331
332
const attributes = enrichObservationAttributes(
333
'generation',
334
{
335
model: 'gpt-4',
336
input: 'Hello'
337
},
338
{
339
requestId: 'req-123',
340
timestamp: Date.now(),
341
region: 'us-east-1'
342
}
343
);
344
```
345
346
### Testing and Validation
347
348
Validate attribute structure in tests.
349
350
```typescript
351
import { createTraceAttributes } from '@langfuse/tracing';
352
353
describe('Trace attributes', () => {
354
it('should create valid OpenTelemetry attributes', () => {
355
const attributes = createTraceAttributes({
356
name: 'test-trace',
357
userId: 'user-123',
358
tags: ['test', 'validation']
359
});
360
361
// Validate structure
362
expect(attributes).toHaveProperty('langfuse.trace.name', 'test-trace');
363
expect(attributes).toHaveProperty('langfuse.trace.userId', 'user-123');
364
expect(attributes).toHaveProperty('langfuse.trace.tags', ['test', 'validation']);
365
});
366
367
it('should filter out null values', () => {
368
const attributes = createTraceAttributes({
369
name: 'test-trace',
370
userId: undefined,
371
sessionId: null
372
});
373
374
// Should not include null/undefined
375
expect(attributes).not.toHaveProperty('langfuse.trace.userId');
376
expect(attributes).not.toHaveProperty('langfuse.trace.sessionId');
377
});
378
});
379
```
380
381
### Attribute Inspection
382
383
Inspect generated attributes for debugging.
384
385
```typescript
386
import { createTraceAttributes, createObservationAttributes } from '@langfuse/tracing';
387
388
function logAttributes(
389
name: string,
390
attributes: Attributes
391
): void {
392
console.log(`\n=== ${name} ===`);
393
for (const [key, value] of Object.entries(attributes)) {
394
console.log(`${key}: ${JSON.stringify(value)}`);
395
}
396
}
397
398
const traceAttrs = createTraceAttributes({
399
name: 'debug-trace',
400
userId: 'user-123',
401
metadata: { debug: true }
402
});
403
404
const obsAttrs = createObservationAttributes('span', {
405
input: { test: true },
406
output: { result: 'success' }
407
});
408
409
logAttributes('Trace Attributes', traceAttrs);
410
logAttributes('Observation Attributes', obsAttrs);
411
```
412
413
## Advanced Patterns
414
415
### Conditional Attribute Creation
416
417
Create attributes conditionally based on environment or configuration.
418
419
```typescript
420
import { createTraceAttributes } from '@langfuse/tracing';
421
422
function createConditionalTraceAttributes(
423
baseAttributes: LangfuseTraceAttributes,
424
options: { includeInput?: boolean; includeOutput?: boolean } = {}
425
): Attributes {
426
const attributes: LangfuseTraceAttributes = {
427
name: baseAttributes.name,
428
userId: baseAttributes.userId,
429
sessionId: baseAttributes.sessionId
430
};
431
432
// Conditionally include input/output
433
if (options.includeInput && baseAttributes.input) {
434
attributes.input = baseAttributes.input;
435
}
436
437
if (options.includeOutput && baseAttributes.output) {
438
attributes.output = baseAttributes.output;
439
}
440
441
return createTraceAttributes(attributes);
442
}
443
444
// Production: exclude sensitive data
445
const prodAttrs = createConditionalTraceAttributes(
446
{ name: 'trace', input: { sensitive: 'data' } },
447
{ includeInput: false }
448
);
449
450
// Development: include all data
451
const devAttrs = createConditionalTraceAttributes(
452
{ name: 'trace', input: { debug: 'info' } },
453
{ includeInput: true }
454
);
455
```
456
457
### Attribute Merging
458
459
Merge multiple attribute sets.
460
461
```typescript
462
import { createObservationAttributes } from '@langfuse/tracing';
463
464
function mergeObservationAttributes(
465
type: LangfuseObservationType,
466
...attributeSets: LangfuseObservationAttributes[]
467
): Attributes {
468
// Merge all attribute sets
469
const merged: LangfuseObservationAttributes = {};
470
471
for (const attrs of attributeSets) {
472
Object.assign(merged, attrs);
473
474
// Merge metadata deeply
475
if (attrs.metadata) {
476
merged.metadata = {
477
...merged.metadata,
478
...attrs.metadata
479
};
480
}
481
}
482
483
return createObservationAttributes(type, merged);
484
}
485
486
const baseAttrs = { level: 'DEFAULT' as const };
487
const inputAttrs = { input: { data: 'value' } };
488
const outputAttrs = { output: { result: 'success' } };
489
const metadataAttrs = { metadata: { timestamp: Date.now() } };
490
491
const combined = mergeObservationAttributes(
492
'span',
493
baseAttrs,
494
inputAttrs,
495
outputAttrs,
496
metadataAttrs
497
);
498
```
499
500
### Attribute Sanitization
501
502
Sanitize sensitive data before creating attributes.
503
504
```typescript
505
import { createTraceAttributes } from '@langfuse/tracing';
506
507
function sanitizeTraceAttributes(
508
attributes: LangfuseTraceAttributes
509
): Attributes {
510
const sanitized: LangfuseTraceAttributes = { ...attributes };
511
512
// Remove sensitive fields from input
513
if (sanitized.input && typeof sanitized.input === 'object') {
514
const input = { ...sanitized.input as any };
515
delete input.password;
516
delete input.apiKey;
517
delete input.token;
518
sanitized.input = input;
519
}
520
521
// Remove sensitive fields from output
522
if (sanitized.output && typeof sanitized.output === 'object') {
523
const output = { ...sanitized.output as any };
524
delete output.creditCard;
525
delete output.ssn;
526
sanitized.output = output;
527
}
528
529
return createTraceAttributes(sanitized);
530
}
531
532
const attributes = sanitizeTraceAttributes({
533
name: 'secure-trace',
534
input: {
535
username: 'user123',
536
password: 'secret123' // Will be removed
537
},
538
output: {
539
userId: '123',
540
token: 'jwt-token' // Will be removed
541
}
542
});
543
```
544
545
### Attribute Validation
546
547
Validate attributes before creation.
548
549
```typescript
550
import { createObservationAttributes } from '@langfuse/tracing';
551
552
function validateAndCreateAttributes(
553
type: LangfuseObservationType,
554
attributes: LangfuseObservationAttributes
555
): Attributes | null {
556
// Validate required fields
557
if (type === 'generation') {
558
if (!attributes.model) {
559
console.error('Generation requires model attribute');
560
return null;
561
}
562
}
563
564
// Validate level
565
if (attributes.level) {
566
const validLevels = ['DEBUG', 'DEFAULT', 'WARNING', 'ERROR'];
567
if (!validLevels.includes(attributes.level)) {
568
console.error(`Invalid level: ${attributes.level}`);
569
return null;
570
}
571
}
572
573
// Validate metadata
574
if (attributes.metadata) {
575
try {
576
JSON.stringify(attributes.metadata);
577
} catch (error) {
578
console.error('Metadata must be JSON serializable');
579
return null;
580
}
581
}
582
583
return createObservationAttributes(type, attributes);
584
}
585
```
586
587
## Best Practices
588
589
### Use Appropriate Functions
590
591
Use the correct function for trace vs observation attributes.
592
593
```typescript
594
// Good: Use createTraceAttributes for trace-level data
595
const traceAttrs = createTraceAttributes({
596
name: 'my-trace',
597
userId: 'user-123'
598
});
599
600
// Good: Use createObservationAttributes for observation-level data
601
const obsAttrs = createObservationAttributes('span', {
602
input: { data: 'value' },
603
output: { result: 'success' }
604
});
605
606
// Avoid: Using wrong function
607
const wrongAttrs = createObservationAttributes('span', {
608
name: 'trace-name' // This is a trace attribute
609
} as any);
610
```
611
612
### Handle Serialization Failures
613
614
Account for objects that may fail to serialize.
615
616
```typescript
617
import { createObservationAttributes } from '@langfuse/tracing';
618
619
function safeCreateAttributes(
620
type: LangfuseObservationType,
621
attributes: LangfuseObservationAttributes
622
): Attributes {
623
try {
624
return createObservationAttributes(type, attributes);
625
} catch (error) {
626
console.error('Failed to create attributes:', error);
627
628
// Return minimal valid attributes
629
return createObservationAttributes(type, {
630
metadata: { error: 'Failed to serialize attributes' }
631
});
632
}
633
}
634
```
635
636
### Keep Metadata Structured
637
638
Maintain consistent metadata structure for better querying.
639
640
```typescript
641
// Good: Structured metadata
642
const attributes = createTraceAttributes({
643
metadata: {
644
performance: {
645
duration: 1500,
646
memoryUsed: 128
647
},
648
context: {
649
region: 'us-east-1',
650
environment: 'production'
651
}
652
}
653
});
654
655
// Avoid: Flat, inconsistent structure
656
const attributes = createTraceAttributes({
657
metadata: {
658
durationMs: 1500,
659
memory: 128,
660
region: 'us-east-1',
661
env: 'prod'
662
}
663
});
664
```
665
666
### Avoid Excessive Nesting
667
668
Keep metadata nesting reasonable for readability.
669
670
```typescript
671
// Good: Reasonable depth
672
const attributes = createTraceAttributes({
673
metadata: {
674
database: { host: 'localhost', port: 5432 }
675
}
676
});
677
678
// Avoid: Excessive nesting
679
const attributes = createTraceAttributes({
680
metadata: {
681
system: {
682
subsystem: {
683
component: {
684
subcomponent: {
685
detail: { value: 'deeply-nested' }
686
}
687
}
688
}
689
}
690
}
691
});
692
```
693
694
### Use TypeScript Types
695
696
Leverage TypeScript for type safety.
697
698
```typescript
699
import {
700
createTraceAttributes,
701
createObservationAttributes,
702
LangfuseTraceAttributes,
703
LangfuseObservationAttributes,
704
LangfuseObservationType
705
} from '@langfuse/tracing';
706
707
function createTypedAttributes(
708
traceAttrs: LangfuseTraceAttributes,
709
obsType: LangfuseObservationType,
710
obsAttrs: LangfuseObservationAttributes
711
) {
712
const trace = createTraceAttributes(traceAttrs);
713
const observation = createObservationAttributes(obsType, obsAttrs);
714
715
return { trace, observation };
716
}
717
718
// TypeScript ensures correct structure
719
const attrs = createTypedAttributes(
720
{ name: 'trace', userId: 'user-123' },
721
'generation',
722
{ model: 'gpt-4', input: 'Hello' }
723
);
724
```
725