feat(webapp): tag Prisma spans with db.datasource attribute#3422
feat(webapp): tag Prisma spans with db.datasource attribute#3422
Conversation
Wrap the writer and replica Prisma clients with a $extends middleware that sets an OTel context key around each operation, and a span processor that reads the key and stamps 'db.datasource' = 'writer' | 'replica' on every span created in that scope. Also directly tags the prisma:client:operation span via trace.getActiveSpan() since that outer span is created before the extension middleware runs and would otherwise miss the context. Motivation: writer and replica emit identical span names through the same global instrumentation, so pool-saturation monitors on prisma:engine:connection could not distinguish the two pools. With this change, monitors can filter by the new attribute. Context propagation note: PrismaPromise is lazy, so wrapping query(args) directly with context.with leaves the thenable unstarted and Prisma's .then() fires outside the scope. The inner 'async () => await query(args)' forces the .then() inside the context.with callback so engine spans see the correct active context. Not tagged: prisma:client:load_engine (one-time startup, irrelevant).
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
🧰 Additional context used📓 Path-based instructions (8)**/*.{ts,tsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
{packages/core,apps/webapp}/**/*.{ts,tsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
**/*.ts📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
Files:
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.ts{,x}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
apps/webapp/**/*.{ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Files:
apps/webapp/**/*.server.ts📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Files:
🧠 Learnings (12)📓 Common learnings📚 Learning: 2025-11-27T16:26:37.432ZApplied to files:
📚 Learning: 2026-03-02T12:43:17.177ZApplied to files:
📚 Learning: 2026-03-24T10:42:43.111ZApplied to files:
📚 Learning: 2026-04-13T21:44:00.032ZApplied to files:
📚 Learning: 2026-03-02T12:43:25.254ZApplied to files:
📚 Learning: 2026-04-15T15:39:06.868ZApplied to files:
📚 Learning: 2026-04-16T13:45:18.782ZApplied to files:
📚 Learning: 2026-03-22T13:26:12.060ZApplied to files:
📚 Learning: 2026-03-22T19:24:14.403ZApplied to files:
📚 Learning: 2026-03-02T12:43:25.254ZApplied to files:
📚 Learning: 2026-03-29T19:16:28.864ZApplied to files:
🔇 Additional comments (5)
WalkthroughThe changes implement OpenTelemetry instrumentation to automatically tag Prisma database operation spans with a Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Stamp every Prisma span with
db.datasource: "writer" | "replica"so traces can distinguish which client the query went through.Both
PrismaClientinstances share the same global@prisma/instrumentation, so their spans come out with identical names and attributes today. This makes them trivially filterable.How
Two pieces in
apps/webapp/app/:v3/tracer.server.ts— aDatasourceAttributeSpanProcessorreads an OTel context key inonStartand callsspan.setAttribute("db.datasource", value). Registered as the first span processor.db.server.ts—tagDatasource(datasource, client)wraps eachPrismaClientwith$extends({ query: { $allOperations } }). The middleware sets the context key around the query and directly tags the active span (to catchprisma:client:operation, which Prisma creates before the middleware fires).Context-propagation gotcha
PrismaPromiseis lazy —query(args)returns a thenable that only starts when someone.then()s it. The naivecontext.with(ctx, () => query(args))restores ALS synchronously, so when Prisma's internal code awaits the thenable later, the engine spans fire with the original ALS. Wrapping asasync () => await query(args)forces the.then()inside thecontext.withcallback, so ALS stays on our context for the engine spans.Coverage
prisma:engine:*(connection,db_query,serialize,query, etc.),prisma:client:operation,prisma:client:serialize,prisma:client:connectprisma:client:load_engine— one-time startup, fires before any queryConcurrent
Promise.all([writer.x, replica.y])correctly tags each pool separately (ALS isolates per-Promise chain).Performance
One
context.with(~200ns) and onesetAttributeper span (effectively free per OTel JS benchmarks) per Prisma op. Negligible against a query path measured in milliseconds.Test plan
db.datasourceappears onprisma:engine:connectionspans after the webapp is restarted