You turned on log: ['query'] on the Prisma client, watched the console fill up with SELECTs and their parameters, and that's where the investigation stopped. The endpoint is still slow. The log tells you what SQL ran, but it doesn't tell you if that SQL used an index, if it waited on a lock, or if the problem is the query itself or the connection running it. That gap between "I can see the query" and "I understand why it's slow" is what this post is about.
My thesis: the log and the database answer two different categories of question, and the mistake isn't using one or the other — it's not knowing which question you're actually asking before you open either. Almost every "slow endpoint" investigation I've seen (my own included, more than once) starts by staring at the log as if staring harder would eventually reveal a lock or a missing index. It won't. The log was never built to show you that.
Prisma query logging PostgreSQL: what the ORM's log actually solves
The official Prisma docs on logging are clear about the scope: the logging system lets you subscribe to query, info, warn, and error events from the client, and every query-type event includes the generated SQL, the parameters, and the total duration of that call. That's the whole promise. Nothing about execution plans, nothing about locks, nothing about what happens inside PostgreSQL once it receives that query.
Setting it up looks like this:
// prisma-client.ts — basic logging with levels
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
{ level: 'warn', emit: 'stdout' },
{ level: 'error', emit: 'stdout' },
],
})
prisma.$on('query', (evento) => {
console.log('SQL:', evento.query)
console.log('Parametros:', evento.params)
console.log('Duracion (ms):', evento.duration)
})With this you already get everything the docs promise: exact SQL, parameters, total round-trip duration. That's enough to answer ORM-type questions: Is Prisma generating a JOIN I wasn't expecting? Is this findMany with a nested include firing 40 queries instead of one? Is some middleware running something twice? The log alone answers those questions, no need to touch the database.
What it does NOT answer — and to be fair to the source, the docs never claim it does — is why a specific query takes 800ms instead of 8ms. That lives on the other side.
Where people get it wrong: using the log as if it were a database profiler
The common recipe: turn on the log, notice a query taking forever, copy it, run it by hand in the SQL client, it runs "fast" there because the table's cached in the plan or because data volume at that moment is different, and close the ticket saying "it fixed itself." Three months later it happens again.
The hidden cost is that the duration number Prisma reports includes the full round trip: parameter serialization, network time to PostgreSQL, execution time in the database, and deserialization of the result back in the TypeScript client. If the connection pool is saturated or there's network latency, that number goes up without the query itself having any problem at all. The log gives you a total, not a breakdown.
Classic counterexample: a query the log shows as 300ms could be a 2ms query in PostgreSQL that spent 298ms waiting to grab a connection from the pool. If the diagnosis stops at "this query is slow, gotta optimize it," the real problem — pool configuration, number of concurrent connections, a badly set timeout — never gets touched.
flowchart LR
A[Request llega] --> B[Prisma pide conexion al pool]
B --> C{Pool disponible?}
C -->|no, espera| D[Tiempo de espera se suma al log]
C -->|si| E[PostgreSQL ejecuta la query]
E --> F[Prisma deserializa resultado]
D --> E
F --> G[Log reporta duracion total]The diagram shows why the log's number mixes together things worth separating before you touch any code.
Decision checklist: when the log is enough and when to look at PostgreSQL
This is the cutoff criteria I use before sinking time into either side:
The Prisma log is enough when:
- The suspicion is about what SQL the ORM generates (N+1 queries, includes that blow up, unnecessary selects).
- The problem shows up always, regardless of data volume, in any environment.
- You can reproduce it with an isolated call and see the exact SQL in the console.
- The question is "is this doing what I think it's doing?" and not "is this fast?"
You need to look at PostgreSQL directly when:
- A specific query takes different amounts of time depending on the time of day or data volume.
- The log shows high durations but the SQL, run by hand, "runs fine" — that's when the problem isn't the query.
- You suspect locks, contention, or concurrent queries stepping on each other.
- You need to know if an index is actually being used or not — that's what
EXPLAIN ANALYZEtells you, not the ORM's log.
PostgreSQL tools that fall into this second category: EXPLAIN (ANALYZE, BUFFERS) on the exact query you captured in the log, the pg_stat_statements extension to see real accumulated execution stats over time, and pg_stat_activity to see what's running right now and whether anything's blocked. None of these get replaced by Prisma, and that's fine — it's not Prisma's job.
What to check first, in order: Prisma's log to confirm the exact SQL → run it with EXPLAIN ANALYZE to see the actual plan → if the plan looks reasonable but the log still shows high times, suspect the connection pool before suspecting the query.
I apply a version of this same split — find which layer actually caused the problem instead of fixing the layer where it just happens to surface — in other parts of the stack too. It's the same instinct behind checking how to evaluate a library before shipping it to production before blaming a dependency for something that's actually a config issue, or behind tracing a TypeScript error back to a badly defined type further up instead of patching the symptom, like I laid out in strict null checks in production. Different tools, same discipline: don't fix where it hurts, fix where it broke.
Limits: what this evidence doesn't let you conclude
Neither the Prisma docs nor this post give a figure for how much overhead logging itself adds, nor a number like "past X queries per second it's worth instrumenting the database." Any claim like that would need a reproducible experiment with controlled load, and I don't have one — I'd rather not make it up.
You also can't conclude, just from Prisma's log, whether an index is missing, whether a table needs partitioning, or whether the problem is schema design. Those conclusions require looking at PostgreSQL's real execution plan, not the client's report.
And an honest note about the source: Prisma's docs don't compare their logging against database observability tools, nor do they suggest it's a substitute. The limit I'm laying out in this post is mine, not something the docs contradict or explicitly back up — it's a reading of what the log promises versus what it doesn't.
My take and the next step
I use Prisma's log for everything that's about SQL shape: confirming the ORM generated what I expected, catching N+1s before they hit an environment with real data, checking that an include isn't pulling in extra relations. For that it's fast and needs nothing else.
The moment the question shifts from "what SQL is this?" to "why is it slow?", I close the log console and open EXPLAIN ANALYZE. Mixing both questions into the same tool is exactly what keeps people staring at logs without moving forward.
The uncomfortable part, if I'm honest: most teams don't skip EXPLAIN ANALYZE because it's hard, they skip it because the Prisma log already feels like "doing observability," and it's not — it's doing a third of it. If you're bringing more serious observability into your stack — structured logs, pool metrics, tracing — ask first what layer each tool actually covers before adding it, the same question I asked when I brought an external model into the code pipeline in DeepSeek API in TypeScript. Adding a tool because it feels thorough is how you end up with five dashboards and still no answer for why the endpoint is slow.
FAQ
Does Prisma's log show the query's execution plan?
No. It shows the generated SQL, the parameters, and the total round-trip duration. The execution plan you have to request separately with EXPLAIN ANALYZE directly in PostgreSQL.
Does turning on log: ['query'] in production have a cost?
It adds serialization and logging overhead per query, especially if the emitted level is stdout instead of event with a lightweight handler. The official docs don't give an exact figure for that cost, so it's worth measuring it in your own environment before assuming it's negligible.
Can I use Prisma's log to detect N+1 queries?
Yes, it's one of the most direct uses: if a findMany with relations fires dozens of individual queries in the log, that's your N+1 right there. It's exactly the kind of question the log answers well because it's about SQL shape, not database performance.
Does pg_stat_statements replace Prisma's logging?
It doesn't replace it, it complements it. pg_stat_statements accumulates real execution stats inside PostgreSQL over time; Prisma's log gives you the point-in-time view of each call from the client. They answer different questions.
Why does a query the log flags as slow run fast when I execute it by hand? Because the log's number includes network time and connection pool wait time, not just execution in the database. If you run the query in isolation in a SQL client, you skip that wait and see only PostgreSQL's real time.
Is Prisma's logging useful for diagnosing locks or contention?
Not directly. For that you need to look at pg_stat_activity in PostgreSQL, which shows which sessions are running and whether any of them are blocked waiting on another. Prisma's log has no visibility into that.
Original source: Prisma logging docs
Related Articles
npm Dependencies: How to Evaluate a Library Before It Hits Production
A clean package.json says nothing about who's going to fix that library when it breaks. Practical criteria for evaluating maintenance, surface area, types and transitive deps before adding a dependency to a TypeScript stack.
Jul 28 2026 · 8′ · Tutorials · TypeScript · pnpm
Strict Null Checks in TypeScript: What the Compiler Won't Tell You and Where It Actually Hurts in Production
Compiler says OK. Runtime explodes anyway. Here are the 4 patterns where strict null checks isn't enough: assertion functions, untyped libraries, Prisma ORM, and JSON.parse — with real code from a Next.js/Prisma stack.
Jul 23 2026 · 8′ · Tutorials · Next.js · TypeScript
DeepSeek API in TypeScript: secure integration and honest model evaluation for code
DeepSeek's API is compatible with the OpenAI SDK — that makes the integration almost trivial. The real problem isn't the plumbing. It's deciding whether the model is actually worth it for your use case, without buying the hype or dismissing it out of fashion. Here's the framework.
Jul 22 2026 · 8′ · Tutorials · TypeScript · nextjs
Comments (0)
What do you think of this?
Drop your comment in 10 seconds.
We only use your login to show your name and avatar. No spam.
No comments yet. Be the first — your take matters most when we're few.