or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

binary-resolution.mdcli-utilities.mddatabase-operations.mdengine-commands.mderror-handling.mdgenerators.mdindex.mdsyntax-highlighting.mdtracing.mdutilities.md

tracing.mddocs/

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

```