0
# Tracing and Observability
1
2
The tracing domain provides comprehensive distributed tracing support with OpenTelemetry integration for monitoring, debugging, and performance analysis of Prisma operations across the entire request lifecycle.
3
4
## Types and Interfaces
5
6
### Core Tracing Types
7
8
#### `SpanCallback<R>`
9
10
Callback function signature for span execution with optional span and context parameters.
11
12
```typescript { .api }
13
type SpanCallback<R> = (span?: Span, context?: Context) => R
14
```
15
16
**Parameters:**
17
- `span?: Span` - Optional OpenTelemetry span instance
18
- `context?: Context` - Optional tracing context
19
20
**Returns:** `R` - Return value from callback execution
21
22
#### `ExtendedSpanOptions`
23
24
Extended options for creating and configuring tracing spans.
25
26
```typescript { .api }
27
interface ExtendedSpanOptions {
28
name: string // Span name/operation identifier
29
internal?: boolean // Whether span is internal to Prisma
30
active?: boolean // Whether span propagates context
31
context?: Context // Context to append span to
32
}
33
```
34
35
**Properties:**
36
- `name: string` - Descriptive name for the span operation
37
- `internal?: boolean` - Mark as internal span (for Prisma internals)
38
- `active?: boolean` - Whether span should propagate context to child operations
39
- `context?: Context` - Parent context to attach span to
40
41
### Time and Identification Types
42
43
#### `HrTime`
44
45
High-resolution time tuple for precise timing measurements.
46
47
```typescript { .api }
48
type HrTime = [number, number]
49
```
50
51
**Format:** `[seconds, nanoseconds]` - High-precision timestamp
52
53
**Example:**
54
```typescript
55
const startTime: HrTime = [1640995200, 123456789]
56
// Represents 1640995200.123456789 seconds since epoch
57
```
58
59
#### `EngineSpanId`
60
61
Unique identifier for engine-generated spans.
62
63
```typescript { .api }
64
type EngineSpanId = string
65
```
66
67
**Format:** UUID or hex-encoded unique identifier
68
69
#### `EngineSpanKind`
70
71
Categorization of engine spans by their operational context.
72
73
```typescript { .api }
74
type EngineSpanKind = 'client' | 'internal'
75
```
76
77
**Values:**
78
- `'client'` - Client-facing operations (queries, mutations)
79
- `'internal'` - Internal engine operations (connection management, parsing)
80
81
### Engine Tracing Structures
82
83
#### `EngineSpan`
84
85
Complete span information from the Prisma engine with timing and relationship data.
86
87
```typescript { .api }
88
interface EngineSpan {
89
id: EngineSpanId // Unique span identifier
90
parentId: string | null // Parent span ID (null for root spans)
91
name: string // Operation name
92
startTime: HrTime // Span start timestamp
93
endTime: HrTime // Span end timestamp
94
kind: EngineSpanKind // Span category
95
attributes?: Record<string, unknown> // Optional span attributes
96
links?: EngineSpanId[] // Optional linked span IDs
97
}
98
```
99
100
**Usage for Performance Analysis:**
101
```typescript
102
function analyzeSpanPerformance(span: EngineSpan): {
103
duration: number
104
category: string
105
} {
106
// Calculate duration from HrTime
107
const startMs = span.startTime[0] * 1000 + span.startTime[1] / 1000000
108
const endMs = span.endTime[0] * 1000 + span.endTime[1] / 1000000
109
const duration = endMs - startMs
110
111
return {
112
duration,
113
category: span.kind === 'client' ? 'Query Operation' : 'Engine Internal'
114
}
115
}
116
```
117
118
### Event and Logging Types
119
120
#### `LogLevel`
121
122
Logging severity levels for trace events and debugging.
123
124
```typescript { .api }
125
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'query'
126
```
127
128
**Levels:**
129
- `'trace'` - Finest level debugging information
130
- `'debug'` - Detailed debugging information
131
- `'info'` - General informational messages
132
- `'warn'` - Warning conditions
133
- `'error'` - Error conditions
134
- `'query'` - Database query operations
135
136
#### `EngineTraceEvent`
137
138
Trace event with timing, context, and structured attributes.
139
140
```typescript { .api }
141
interface EngineTraceEvent {
142
spanId: EngineSpanId // Associated span identifier
143
target: string // Event target/source module
144
level: LogLevel // Event severity level
145
timestamp: HrTime // Event timestamp
146
attributes: Record<string, unknown> // Event attributes
147
}
148
```
149
150
**Common Attributes:**
151
- `message?: string` - Event message
152
- `query?: string` - SQL query (for query events)
153
- `duration_ms?: number` - Operation duration
154
- `params?: unknown[]` - Query parameters
155
156
**Example:**
157
```typescript
158
const queryEvent: EngineTraceEvent = {
159
spanId: 'span-123',
160
target: 'query_engine::executor',
161
level: 'query',
162
timestamp: [1640995200, 456789123],
163
attributes: {
164
message: 'Executing database query',
165
query: 'SELECT * FROM users WHERE id = $1',
166
params: [42],
167
duration_ms: 15.2
168
}
169
}
170
```
171
172
### Complete Tracing Structure
173
174
#### `EngineTrace`
175
176
Complete tracing information containing all spans and events for an operation.
177
178
```typescript { .api }
179
interface EngineTrace {
180
spans: EngineSpan[] // All spans in the trace
181
events: EngineTraceEvent[] // All events in the trace
182
}
183
```
184
185
**Usage for Trace Analysis:**
186
```typescript
187
function analyzeTrace(trace: EngineTrace): {
188
totalDuration: number
189
spanCount: number
190
queryCount: number
191
errorCount: number
192
} {
193
const queryEvents = trace.events.filter(e => e.level === 'query')
194
const errorEvents = trace.events.filter(e => e.level === 'error')
195
196
// Find root span (no parent) to calculate total duration
197
const rootSpan = trace.spans.find(s => s.parentId === null)
198
let totalDuration = 0
199
200
if (rootSpan) {
201
const startMs = rootSpan.startTime[0] * 1000 + rootSpan.startTime[1] / 1000000
202
const endMs = rootSpan.endTime[0] * 1000 + rootSpan.endTime[1] / 1000000
203
totalDuration = endMs - startMs
204
}
205
206
return {
207
totalDuration,
208
spanCount: trace.spans.length,
209
queryCount: queryEvents.length,
210
errorCount: errorEvents.length
211
}
212
}
213
```
214
215
### Tracing Interface
216
217
#### `TracingHelper`
218
219
Core interface for tracing operations and span management.
220
221
```typescript { .api }
222
interface TracingHelper {
223
isEnabled(): boolean
224
getTraceParent(context?: Context): string
225
dispatchEngineSpans(spans: EngineSpan[]): void
226
getActiveContext(): Context | undefined
227
runInChildSpan<R>(
228
nameOrOptions: string | ExtendedSpanOptions,
229
callback: SpanCallback<R>
230
): R
231
}
232
```
233
234
**Methods:**
235
236
##### `isEnabled(): boolean`
237
Checks if tracing is currently enabled and available.
238
239
##### `getTraceParent(context?: Context): string`
240
Gets the trace parent header for distributed tracing propagation.
241
242
##### `dispatchEngineSpans(spans: EngineSpan[]): void`
243
Processes and forwards engine spans to the tracing backend.
244
245
##### `getActiveContext(): Context | undefined`
246
Retrieves the current active tracing context.
247
248
##### `runInChildSpan<R>(nameOrOptions, callback): R`
249
Executes callback within a new child span context.
250
251
**Parameters:**
252
- `nameOrOptions: string | ExtendedSpanOptions` - Span name or full options
253
- `callback: SpanCallback<R>` - Function to execute within span
254
255
### Global Tracing Configuration
256
257
#### `PrismaInstrumentationGlobalValue`
258
259
Global configuration interface for Prisma instrumentation and tracing.
260
261
```typescript { .api }
262
interface PrismaInstrumentationGlobalValue {
263
helper?: TracingHelper // Optional tracing helper instance
264
}
265
```
266
267
**Global Access:**
268
```typescript
269
declare global {
270
var __PRISMA_INSTRUMENTATION__: PrismaInstrumentationGlobalValue | undefined
271
}
272
```
273
274
## Examples
275
276
### Basic Tracing Setup and Usage
277
278
```typescript
279
import type {
280
TracingHelper,
281
ExtendedSpanOptions,
282
EngineSpan,
283
EngineTrace
284
} from '@prisma/internals'
285
286
class PrismaTracingManager {
287
private helper?: TracingHelper
288
289
constructor() {
290
// Check for global tracing helper
291
this.helper = globalThis.__PRISMA_INSTRUMENTATION__?.helper
292
}
293
294
/**
295
* Check if tracing is available and enabled
296
*/
297
isTracingEnabled(): boolean {
298
return this.helper?.isEnabled() ?? false
299
}
300
301
/**
302
* Execute operation with tracing
303
*/
304
async traceOperation<T>(
305
operationName: string,
306
operation: () => Promise<T>,
307
options?: Partial<ExtendedSpanOptions>
308
): Promise<T> {
309
if (!this.helper || !this.helper.isEnabled()) {
310
// No tracing - execute directly
311
return await operation()
312
}
313
314
const spanOptions: ExtendedSpanOptions = {
315
name: operationName,
316
internal: false,
317
active: true,
318
...options
319
}
320
321
return this.helper.runInChildSpan(spanOptions, async (span, context) => {
322
try {
323
const result = await operation()
324
325
// Add success attributes
326
if (span) {
327
span.setAttributes({
328
'operation.status': 'success',
329
'operation.name': operationName
330
})
331
}
332
333
return result
334
} catch (error) {
335
// Add error attributes
336
if (span) {
337
span.recordException(error)
338
span.setAttributes({
339
'operation.status': 'error',
340
'operation.name': operationName,
341
'error.message': error.message
342
})
343
}
344
345
throw error
346
}
347
})
348
}
349
350
/**
351
* Process engine spans for analysis
352
*/
353
processEngineSpans(spans: EngineSpan[]): void {
354
if (!this.helper) return
355
356
// Add custom processing before forwarding
357
const processedSpans = spans.map(span => ({
358
...span,
359
attributes: {
360
...span.attributes,
361
'prisma.version': '5.0.0', // Add version info
362
'processed_at': Date.now()
363
}
364
}))
365
366
this.helper.dispatchEngineSpans(processedSpans)
367
}
368
369
/**
370
* Get trace context for propagation
371
*/
372
getTraceContext(): string | undefined {
373
if (!this.helper) return undefined
374
375
const activeContext = this.helper.getActiveContext()
376
return this.helper.getTraceParent(activeContext)
377
}
378
}
379
```
380
381
### Advanced Trace Analysis
382
383
```typescript
384
import type {
385
EngineTrace,
386
EngineSpan,
387
EngineTraceEvent,
388
LogLevel
389
} from '@prisma/internals'
390
391
interface TraceMetrics {
392
totalDuration: number
393
spanCount: number
394
queryCount: number
395
errorCount: number
396
slowestQuery: {
397
query: string
398
duration: number
399
} | null
400
spanHierarchy: SpanNode[]
401
}
402
403
interface SpanNode {
404
span: EngineSpan
405
children: SpanNode[]
406
events: EngineTraceEvent[]
407
duration: number
408
}
409
410
class TraceAnalyzer {
411
412
/**
413
* Comprehensive trace analysis
414
*/
415
analyzeTrace(trace: EngineTrace): TraceMetrics {
416
const spanHierarchy = this.buildSpanHierarchy(trace.spans, trace.events)
417
const rootSpan = spanHierarchy[0] // Assuming first is root
418
419
const queryEvents = trace.events.filter(e => e.level === 'query')
420
const errorEvents = trace.events.filter(e => e.level === 'error')
421
422
// Find slowest query
423
let slowestQuery = null
424
let maxDuration = 0
425
426
for (const event of queryEvents) {
427
const duration = event.attributes.duration_ms as number || 0
428
if (duration > maxDuration) {
429
maxDuration = duration
430
slowestQuery = {
431
query: event.attributes.query as string || 'Unknown',
432
duration
433
}
434
}
435
}
436
437
return {
438
totalDuration: rootSpan ? rootSpan.duration : 0,
439
spanCount: trace.spans.length,
440
queryCount: queryEvents.length,
441
errorCount: errorEvents.length,
442
slowestQuery,
443
spanHierarchy
444
}
445
}
446
447
/**
448
* Build hierarchical span tree
449
*/
450
private buildSpanHierarchy(
451
spans: EngineSpan[],
452
events: EngineTraceEvent[]
453
): SpanNode[] {
454
const spanMap = new Map<string, SpanNode>()
455
const rootSpans: SpanNode[] = []
456
457
// Create span nodes
458
for (const span of spans) {
459
const node: SpanNode = {
460
span,
461
children: [],
462
events: events.filter(e => e.spanId === span.id),
463
duration: this.calculateSpanDuration(span)
464
}
465
466
spanMap.set(span.id, node)
467
468
if (!span.parentId) {
469
rootSpans.push(node)
470
}
471
}
472
473
// Build parent-child relationships
474
for (const span of spans) {
475
if (span.parentId) {
476
const parent = spanMap.get(span.parentId)
477
const child = spanMap.get(span.id)
478
479
if (parent && child) {
480
parent.children.push(child)
481
}
482
}
483
}
484
485
return rootSpans
486
}
487
488
/**
489
* Calculate span duration from HrTime
490
*/
491
private calculateSpanDuration(span: EngineSpan): number {
492
const startMs = span.startTime[0] * 1000 + span.startTime[1] / 1000000
493
const endMs = span.endTime[0] * 1000 + span.endTime[1] / 1000000
494
return endMs - startMs
495
}
496
497
/**
498
* Generate performance report
499
*/
500
generateReport(metrics: TraceMetrics): string {
501
let report = 'π Trace Performance Report\n'
502
report += 'β'.repeat(50) + '\n\n'
503
504
report += `π Overview:\n`
505
report += ` Total Duration: ${metrics.totalDuration.toFixed(2)}ms\n`
506
report += ` Spans: ${metrics.spanCount}\n`
507
report += ` Database Queries: ${metrics.queryCount}\n`
508
report += ` Errors: ${metrics.errorCount}\n\n`
509
510
if (metrics.slowestQuery) {
511
report += `π Slowest Query (${metrics.slowestQuery.duration.toFixed(2)}ms):\n`
512
report += ` ${metrics.slowestQuery.query}\n\n`
513
}
514
515
report += `π Span Hierarchy:\n`
516
report += this.renderSpanHierarchy(metrics.spanHierarchy, 0)
517
518
return report
519
}
520
521
/**
522
* Render span hierarchy as tree
523
*/
524
private renderSpanHierarchy(nodes: SpanNode[], depth: number): string {
525
let output = ''
526
const indent = ' '.repeat(depth)
527
528
for (const node of nodes) {
529
const icon = node.span.kind === 'client' ? 'π΅' : 'π'
530
output += `${indent}${icon} ${node.span.name} (${node.duration.toFixed(2)}ms)\n`
531
532
// Show events for this span
533
for (const event of node.events) {
534
const eventIcon = this.getEventIcon(event.level)
535
output += `${indent} ${eventIcon} ${event.attributes.message || event.target}\n`
536
}
537
538
// Recursively render children
539
if (node.children.length > 0) {
540
output += this.renderSpanHierarchy(node.children, depth + 1)
541
}
542
}
543
544
return output
545
}
546
547
private getEventIcon(level: LogLevel): string {
548
switch (level) {
549
case 'error': return 'π΄'
550
case 'warn': return 'π‘'
551
case 'query': return 'ποΈ'
552
case 'info': return 'βΉοΈ'
553
case 'debug': return 'π§'
554
case 'trace': return 'π'
555
default: return 'π'
556
}
557
}
558
}
559
```
560
561
### Custom Tracing Implementation
562
563
```typescript
564
import type {
565
TracingHelper,
566
ExtendedSpanOptions,
567
SpanCallback,
568
EngineSpan
569
} from '@prisma/internals'
570
571
interface CustomSpan {
572
id: string
573
name: string
574
startTime: number
575
endTime?: number
576
attributes: Record<string, any>
577
parent?: CustomSpan
578
}
579
580
class CustomTracingHelper implements TracingHelper {
581
private enabled = true
582
private spans = new Map<string, CustomSpan>()
583
private activeSpan?: CustomSpan
584
585
isEnabled(): boolean {
586
return this.enabled
587
}
588
589
getTraceParent(context?: any): string {
590
if (this.activeSpan) {
591
return `00-${this.generateTraceId()}-${this.activeSpan.id}-01`
592
}
593
return `00-${this.generateTraceId()}-${this.generateSpanId()}-01`
594
}
595
596
dispatchEngineSpans(spans: EngineSpan[]): void {
597
console.log(`π€ Dispatching ${spans.length} engine spans`)
598
599
for (const span of spans) {
600
console.log(` Span: ${span.name} (${span.kind})`)
601
if (span.attributes) {
602
console.log(` Attributes:`, span.attributes)
603
}
604
}
605
}
606
607
getActiveContext(): any {
608
return { activeSpan: this.activeSpan }
609
}
610
611
runInChildSpan<R>(
612
nameOrOptions: string | ExtendedSpanOptions,
613
callback: SpanCallback<R>
614
): R {
615
const options = typeof nameOrOptions === 'string'
616
? { name: nameOrOptions }
617
: nameOrOptions
618
619
const span: CustomSpan = {
620
id: this.generateSpanId(),
621
name: options.name,
622
startTime: Date.now(),
623
attributes: {},
624
parent: this.activeSpan
625
}
626
627
this.spans.set(span.id, span)
628
const previousActive = this.activeSpan
629
this.activeSpan = span
630
631
try {
632
// Create mock OpenTelemetry span-like object
633
const mockSpan = {
634
setAttributes: (attrs: Record<string, any>) => {
635
Object.assign(span.attributes, attrs)
636
},
637
recordException: (error: Error) => {
638
span.attributes.error = {
639
message: error.message,
640
stack: error.stack
641
}
642
}
643
}
644
645
const result = callback(mockSpan as any, { activeSpan: span })
646
647
span.endTime = Date.now()
648
649
if (options.internal !== true) {
650
console.log(`β Span completed: ${span.name} (${span.endTime - span.startTime}ms)`)
651
}
652
653
return result
654
655
} catch (error) {
656
span.endTime = Date.now()
657
span.attributes.error = {
658
message: error.message,
659
stack: error.stack
660
}
661
662
console.log(`β Span failed: ${span.name} (${span.endTime - span.startTime}ms)`)
663
throw error
664
665
} finally {
666
this.activeSpan = previousActive
667
}
668
}
669
670
private generateTraceId(): string {
671
return Math.random().toString(16).substring(2, 18).padStart(16, '0')
672
}
673
674
private generateSpanId(): string {
675
return Math.random().toString(16).substring(2, 10).padStart(8, '0')
676
}
677
678
/**
679
* Get all collected spans
680
*/
681
getCollectedSpans(): CustomSpan[] {
682
return Array.from(this.spans.values())
683
}
684
685
/**
686
* Clear all spans
687
*/
688
clearSpans(): void {
689
this.spans.clear()
690
}
691
}
692
693
// Usage example with Prisma operations
694
async function tracedPrismaOperations() {
695
const tracingHelper = new CustomTracingHelper()
696
697
// Install as global helper
698
globalThis.__PRISMA_INSTRUMENTATION__ = {
699
helper: tracingHelper
700
}
701
702
const manager = new PrismaTracingManager()
703
704
// Traced database operations
705
await manager.traceOperation('user-creation', async () => {
706
// Simulate user creation
707
await new Promise(resolve => setTimeout(resolve, 100))
708
709
await manager.traceOperation('email-validation', async () => {
710
// Simulate email validation
711
await new Promise(resolve => setTimeout(resolve, 50))
712
})
713
714
await manager.traceOperation('password-hashing', async () => {
715
// Simulate password hashing
716
await new Promise(resolve => setTimeout(resolve, 200))
717
})
718
})
719
720
// Generate report
721
const spans = tracingHelper.getCollectedSpans()
722
console.log('\nπ Collected Spans:')
723
724
for (const span of spans) {
725
const duration = (span.endTime || Date.now()) - span.startTime
726
console.log(` ${span.name}: ${duration}ms`)
727
728
if (Object.keys(span.attributes).length > 0) {
729
console.log(` Attributes:`, span.attributes)
730
}
731
}
732
}
733
```