Comprehensive internal utility library for Prisma's CLI operations, schema management, generator handling, and engine interactions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
SpanCallback<R>Callback function signature for span execution with optional span and context parameters.
type SpanCallback<R> = (span?: Span, context?: Context) => RParameters:
span?: Span - Optional OpenTelemetry span instancecontext?: Context - Optional tracing contextReturns: R - Return value from callback execution
ExtendedSpanOptionsExtended options for creating and configuring tracing spans.
interface ExtendedSpanOptions {
name: string // Span name/operation identifier
internal?: boolean // Whether span is internal to Prisma
active?: boolean // Whether span propagates context
context?: Context // Context to append span to
}Properties:
name: string - Descriptive name for the span operationinternal?: boolean - Mark as internal span (for Prisma internals)active?: boolean - Whether span should propagate context to child operationscontext?: Context - Parent context to attach span toHrTimeHigh-resolution time tuple for precise timing measurements.
type HrTime = [number, number]Format: [seconds, nanoseconds] - High-precision timestamp
Example:
const startTime: HrTime = [1640995200, 123456789]
// Represents 1640995200.123456789 seconds since epochEngineSpanIdUnique identifier for engine-generated spans.
type EngineSpanId = stringFormat: UUID or hex-encoded unique identifier
EngineSpanKindCategorization of engine spans by their operational context.
type EngineSpanKind = 'client' | 'internal'Values:
'client' - Client-facing operations (queries, mutations)'internal' - Internal engine operations (connection management, parsing)EngineSpanComplete span information from the Prisma engine with timing and relationship data.
interface EngineSpan {
id: EngineSpanId // Unique span identifier
parentId: string | null // Parent span ID (null for root spans)
name: string // Operation name
startTime: HrTime // Span start timestamp
endTime: HrTime // Span end timestamp
kind: EngineSpanKind // Span category
attributes?: Record<string, unknown> // Optional span attributes
links?: EngineSpanId[] // Optional linked span IDs
}Usage for Performance Analysis:
function analyzeSpanPerformance(span: EngineSpan): {
duration: number
category: string
} {
// Calculate duration from HrTime
const startMs = span.startTime[0] * 1000 + span.startTime[1] / 1000000
const endMs = span.endTime[0] * 1000 + span.endTime[1] / 1000000
const duration = endMs - startMs
return {
duration,
category: span.kind === 'client' ? 'Query Operation' : 'Engine Internal'
}
}LogLevelLogging severity levels for trace events and debugging.
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'query'Levels:
'trace' - Finest level debugging information'debug' - Detailed debugging information'info' - General informational messages'warn' - Warning conditions'error' - Error conditions'query' - Database query operationsEngineTraceEventTrace event with timing, context, and structured attributes.
interface EngineTraceEvent {
spanId: EngineSpanId // Associated span identifier
target: string // Event target/source module
level: LogLevel // Event severity level
timestamp: HrTime // Event timestamp
attributes: Record<string, unknown> // Event attributes
}Common Attributes:
message?: string - Event messagequery?: string - SQL query (for query events)duration_ms?: number - Operation durationparams?: unknown[] - Query parametersExample:
const queryEvent: EngineTraceEvent = {
spanId: 'span-123',
target: 'query_engine::executor',
level: 'query',
timestamp: [1640995200, 456789123],
attributes: {
message: 'Executing database query',
query: 'SELECT * FROM users WHERE id = $1',
params: [42],
duration_ms: 15.2
}
}EngineTraceComplete tracing information containing all spans and events for an operation.
interface EngineTrace {
spans: EngineSpan[] // All spans in the trace
events: EngineTraceEvent[] // All events in the trace
}Usage for Trace Analysis:
function analyzeTrace(trace: EngineTrace): {
totalDuration: number
spanCount: number
queryCount: number
errorCount: number
} {
const queryEvents = trace.events.filter(e => e.level === 'query')
const errorEvents = trace.events.filter(e => e.level === 'error')
// Find root span (no parent) to calculate total duration
const rootSpan = trace.spans.find(s => s.parentId === null)
let totalDuration = 0
if (rootSpan) {
const startMs = rootSpan.startTime[0] * 1000 + rootSpan.startTime[1] / 1000000
const endMs = rootSpan.endTime[0] * 1000 + rootSpan.endTime[1] / 1000000
totalDuration = endMs - startMs
}
return {
totalDuration,
spanCount: trace.spans.length,
queryCount: queryEvents.length,
errorCount: errorEvents.length
}
}TracingHelperCore interface for tracing operations and span management.
interface TracingHelper {
isEnabled(): boolean
getTraceParent(context?: Context): string
dispatchEngineSpans(spans: EngineSpan[]): void
getActiveContext(): Context | undefined
runInChildSpan<R>(
nameOrOptions: string | ExtendedSpanOptions,
callback: SpanCallback<R>
): R
}Methods:
isEnabled(): booleanChecks if tracing is currently enabled and available.
getTraceParent(context?: Context): stringGets the trace parent header for distributed tracing propagation.
dispatchEngineSpans(spans: EngineSpan[]): voidProcesses and forwards engine spans to the tracing backend.
getActiveContext(): Context | undefinedRetrieves the current active tracing context.
runInChildSpan<R>(nameOrOptions, callback): RExecutes callback within a new child span context.
Parameters:
nameOrOptions: string | ExtendedSpanOptions - Span name or full optionscallback: SpanCallback<R> - Function to execute within spanPrismaInstrumentationGlobalValueGlobal configuration interface for Prisma instrumentation and tracing.
interface PrismaInstrumentationGlobalValue {
helper?: TracingHelper // Optional tracing helper instance
}Global Access:
declare global {
var __PRISMA_INSTRUMENTATION__: PrismaInstrumentationGlobalValue | undefined
}import type {
TracingHelper,
ExtendedSpanOptions,
EngineSpan,
EngineTrace
} from '@prisma/internals'
class PrismaTracingManager {
private helper?: TracingHelper
constructor() {
// Check for global tracing helper
this.helper = globalThis.__PRISMA_INSTRUMENTATION__?.helper
}
/**
* Check if tracing is available and enabled
*/
isTracingEnabled(): boolean {
return this.helper?.isEnabled() ?? false
}
/**
* Execute operation with tracing
*/
async traceOperation<T>(
operationName: string,
operation: () => Promise<T>,
options?: Partial<ExtendedSpanOptions>
): Promise<T> {
if (!this.helper || !this.helper.isEnabled()) {
// No tracing - execute directly
return await operation()
}
const spanOptions: ExtendedSpanOptions = {
name: operationName,
internal: false,
active: true,
...options
}
return this.helper.runInChildSpan(spanOptions, async (span, context) => {
try {
const result = await operation()
// Add success attributes
if (span) {
span.setAttributes({
'operation.status': 'success',
'operation.name': operationName
})
}
return result
} catch (error) {
// Add error attributes
if (span) {
span.recordException(error)
span.setAttributes({
'operation.status': 'error',
'operation.name': operationName,
'error.message': error.message
})
}
throw error
}
})
}
/**
* Process engine spans for analysis
*/
processEngineSpans(spans: EngineSpan[]): void {
if (!this.helper) return
// Add custom processing before forwarding
const processedSpans = spans.map(span => ({
...span,
attributes: {
...span.attributes,
'prisma.version': '5.0.0', // Add version info
'processed_at': Date.now()
}
}))
this.helper.dispatchEngineSpans(processedSpans)
}
/**
* Get trace context for propagation
*/
getTraceContext(): string | undefined {
if (!this.helper) return undefined
const activeContext = this.helper.getActiveContext()
return this.helper.getTraceParent(activeContext)
}
}import type {
EngineTrace,
EngineSpan,
EngineTraceEvent,
LogLevel
} from '@prisma/internals'
interface TraceMetrics {
totalDuration: number
spanCount: number
queryCount: number
errorCount: number
slowestQuery: {
query: string
duration: number
} | null
spanHierarchy: SpanNode[]
}
interface SpanNode {
span: EngineSpan
children: SpanNode[]
events: EngineTraceEvent[]
duration: number
}
class TraceAnalyzer {
/**
* Comprehensive trace analysis
*/
analyzeTrace(trace: EngineTrace): TraceMetrics {
const spanHierarchy = this.buildSpanHierarchy(trace.spans, trace.events)
const rootSpan = spanHierarchy[0] // Assuming first is root
const queryEvents = trace.events.filter(e => e.level === 'query')
const errorEvents = trace.events.filter(e => e.level === 'error')
// Find slowest query
let slowestQuery = null
let maxDuration = 0
for (const event of queryEvents) {
const duration = event.attributes.duration_ms as number || 0
if (duration > maxDuration) {
maxDuration = duration
slowestQuery = {
query: event.attributes.query as string || 'Unknown',
duration
}
}
}
return {
totalDuration: rootSpan ? rootSpan.duration : 0,
spanCount: trace.spans.length,
queryCount: queryEvents.length,
errorCount: errorEvents.length,
slowestQuery,
spanHierarchy
}
}
/**
* Build hierarchical span tree
*/
private buildSpanHierarchy(
spans: EngineSpan[],
events: EngineTraceEvent[]
): SpanNode[] {
const spanMap = new Map<string, SpanNode>()
const rootSpans: SpanNode[] = []
// Create span nodes
for (const span of spans) {
const node: SpanNode = {
span,
children: [],
events: events.filter(e => e.spanId === span.id),
duration: this.calculateSpanDuration(span)
}
spanMap.set(span.id, node)
if (!span.parentId) {
rootSpans.push(node)
}
}
// Build parent-child relationships
for (const span of spans) {
if (span.parentId) {
const parent = spanMap.get(span.parentId)
const child = spanMap.get(span.id)
if (parent && child) {
parent.children.push(child)
}
}
}
return rootSpans
}
/**
* Calculate span duration from HrTime
*/
private calculateSpanDuration(span: EngineSpan): number {
const startMs = span.startTime[0] * 1000 + span.startTime[1] / 1000000
const endMs = span.endTime[0] * 1000 + span.endTime[1] / 1000000
return endMs - startMs
}
/**
* Generate performance report
*/
generateReport(metrics: TraceMetrics): string {
let report = '🔍 Trace Performance Report\n'
report += '═'.repeat(50) + '\n\n'
report += `📊 Overview:\n`
report += ` Total Duration: ${metrics.totalDuration.toFixed(2)}ms\n`
report += ` Spans: ${metrics.spanCount}\n`
report += ` Database Queries: ${metrics.queryCount}\n`
report += ` Errors: ${metrics.errorCount}\n\n`
if (metrics.slowestQuery) {
report += `🐌 Slowest Query (${metrics.slowestQuery.duration.toFixed(2)}ms):\n`
report += ` ${metrics.slowestQuery.query}\n\n`
}
report += `📈 Span Hierarchy:\n`
report += this.renderSpanHierarchy(metrics.spanHierarchy, 0)
return report
}
/**
* Render span hierarchy as tree
*/
private renderSpanHierarchy(nodes: SpanNode[], depth: number): string {
let output = ''
const indent = ' '.repeat(depth)
for (const node of nodes) {
const icon = node.span.kind === 'client' ? '🔵' : '🔘'
output += `${indent}${icon} ${node.span.name} (${node.duration.toFixed(2)}ms)\n`
// Show events for this span
for (const event of node.events) {
const eventIcon = this.getEventIcon(event.level)
output += `${indent} ${eventIcon} ${event.attributes.message || event.target}\n`
}
// Recursively render children
if (node.children.length > 0) {
output += this.renderSpanHierarchy(node.children, depth + 1)
}
}
return output
}
private getEventIcon(level: LogLevel): string {
switch (level) {
case 'error': return '🔴'
case 'warn': return '🟡'
case 'query': return '🗃️'
case 'info': return 'ℹ️'
case 'debug': return '🔧'
case 'trace': return '🔍'
default: return '📝'
}
}
}import type {
TracingHelper,
ExtendedSpanOptions,
SpanCallback,
EngineSpan
} from '@prisma/internals'
interface CustomSpan {
id: string
name: string
startTime: number
endTime?: number
attributes: Record<string, any>
parent?: CustomSpan
}
class CustomTracingHelper implements TracingHelper {
private enabled = true
private spans = new Map<string, CustomSpan>()
private activeSpan?: CustomSpan
isEnabled(): boolean {
return this.enabled
}
getTraceParent(context?: any): string {
if (this.activeSpan) {
return `00-${this.generateTraceId()}-${this.activeSpan.id}-01`
}
return `00-${this.generateTraceId()}-${this.generateSpanId()}-01`
}
dispatchEngineSpans(spans: EngineSpan[]): void {
console.log(`📤 Dispatching ${spans.length} engine spans`)
for (const span of spans) {
console.log(` Span: ${span.name} (${span.kind})`)
if (span.attributes) {
console.log(` Attributes:`, span.attributes)
}
}
}
getActiveContext(): any {
return { activeSpan: this.activeSpan }
}
runInChildSpan<R>(
nameOrOptions: string | ExtendedSpanOptions,
callback: SpanCallback<R>
): R {
const options = typeof nameOrOptions === 'string'
? { name: nameOrOptions }
: nameOrOptions
const span: CustomSpan = {
id: this.generateSpanId(),
name: options.name,
startTime: Date.now(),
attributes: {},
parent: this.activeSpan
}
this.spans.set(span.id, span)
const previousActive = this.activeSpan
this.activeSpan = span
try {
// Create mock OpenTelemetry span-like object
const mockSpan = {
setAttributes: (attrs: Record<string, any>) => {
Object.assign(span.attributes, attrs)
},
recordException: (error: Error) => {
span.attributes.error = {
message: error.message,
stack: error.stack
}
}
}
const result = callback(mockSpan as any, { activeSpan: span })
span.endTime = Date.now()
if (options.internal !== true) {
console.log(`✅ Span completed: ${span.name} (${span.endTime - span.startTime}ms)`)
}
return result
} catch (error) {
span.endTime = Date.now()
span.attributes.error = {
message: error.message,
stack: error.stack
}
console.log(`❌ Span failed: ${span.name} (${span.endTime - span.startTime}ms)`)
throw error
} finally {
this.activeSpan = previousActive
}
}
private generateTraceId(): string {
return Math.random().toString(16).substring(2, 18).padStart(16, '0')
}
private generateSpanId(): string {
return Math.random().toString(16).substring(2, 10).padStart(8, '0')
}
/**
* Get all collected spans
*/
getCollectedSpans(): CustomSpan[] {
return Array.from(this.spans.values())
}
/**
* Clear all spans
*/
clearSpans(): void {
this.spans.clear()
}
}
// Usage example with Prisma operations
async function tracedPrismaOperations() {
const tracingHelper = new CustomTracingHelper()
// Install as global helper
globalThis.__PRISMA_INSTRUMENTATION__ = {
helper: tracingHelper
}
const manager = new PrismaTracingManager()
// Traced database operations
await manager.traceOperation('user-creation', async () => {
// Simulate user creation
await new Promise(resolve => setTimeout(resolve, 100))
await manager.traceOperation('email-validation', async () => {
// Simulate email validation
await new Promise(resolve => setTimeout(resolve, 50))
})
await manager.traceOperation('password-hashing', async () => {
// Simulate password hashing
await new Promise(resolve => setTimeout(resolve, 200))
})
})
// Generate report
const spans = tracingHelper.getCollectedSpans()
console.log('\n📊 Collected Spans:')
for (const span of spans) {
const duration = (span.endTime || Date.now()) - span.startTime
console.log(` ${span.name}: ${duration}ms`)
if (Object.keys(span.attributes).length > 0) {
console.log(` Attributes:`, span.attributes)
}
}
}Install with Tessl CLI
npx tessl i tessl/npm-prisma--internals