If you have operated MongoDB for any length of time, you know mtools. For years it was how we all read production logs. mloginfo, mplotqueries, and a slow query was suddenly visible.
Then MongoDB 4.4 changed the log format to structured JSON, and the log tools stopped understanding what they were reading.
That was six years ago. The support issue was opened in June 2020 and it is still open. We are on 8.0 now, and a lot of us have been getting by with grep, jq pipelines held together by hope, and a certain amount of squinting.
I got tired of it and built the replacement. It is called mdbkit, it is MIT licensed, and it is public as of this week.
Try it in sixty seconds without touching a cluster
This is the part I would want first if this were someone else's tool.
pip install --upgrade mdbkit
# macOS ships no pip: brew install pipx && pipx install mdbkit
mdbkit demo -o demo.log
mdbkit queries demo.log
mdbkit demo generates a realistic MongoDB log containing an actual incident. No cluster, no connection string, nothing of yours involved. You get to judge the tool on a problem you can inspect before you point it at anything that matters.
Here is what queries gives you on that demo log:
saqib@MacbookPro mongodb_logs % mdbkit queries demo.log
== mdbkit queries (slow query shapes) ==
lines: 1,004 parsed: 1,004 unparsed: 0 span: 2026-07-01T08:00:00+04:00 -> 2026-07-01T09:29:44.615000+04:00
namespace op count cumMs mean max docsEx scan plan shape
------------- --------- ----- ----- ----- ----- ---------- ------- -------------- ---------------------------------------------
shop.events aggregate 53 5.6m 6.3s 8.9s 47,170,000 98889:1 COLLSCAN+SORT {tenantId:eq, ts:gte} sort:{ts:-1}
shop.orders find 89 2.4m 1.6s 2.4s 11,125,000 2976:1 COLLSCAN+SORT {createdAt:gt, status:eq} sort:{createdAt:-1}
shop.sessions find 58 34.7s 597ms 888ms 2,784,000 155:1 IXSCAN{active} {active:eq, lastSeen:gt} sort:{lastSeen:-1}
shop.products update 42 25.7s 612ms 799ms 2,268,000 54000:1 COLLSCAN {sku:eq}
shop.users find 56 6.7s 119ms 139ms 56 1:1 IXSCAN{email} {email:eq}
cumMs = total wall time accumulated across ALL occurrences (not one query)
docsEx = total documents examined across all occurrences
scan = docsExamined per returned doc (high = index missing or weak)
plan = most common query plan; COLLSCAN/+SORT = index needed
empty plan (?) = plan not present in log (below slowms threshold)
next : mdbkit advise <log> [--ns <namespace>] for index candidates
That is not a screenshot I curated. demo is seeded, so those two commands produce that exact table on your machine too, down to the last digit. It behaves the same on my laptop, on yours, and on a conference projector.
The column that matters is the scan ratio. It is documents examined against documents returned, per query shape. A shape sitting at 98889:1 is reading roughly ninety nine thousand documents to hand back one. A shape at 1:1 is doing exactly the work it needs to.
That single ratio is most of slow query triage. Everything else is detail.
The workflow I actually use
Three commands, in order.
First, find the shape that is hurting.
mdbkit queries /var/log/mongodb/mongod.log
Slow operations grouped by query shape rather than listed individually. This matters more than it sounds. A log with 40,000 slow query lines is usually eight or nine distinct shapes repeating, and the one costing you the most is rarely the one that appears most often.
Then, ask what would fix it.
mdbkit advise /var/log/mongodb/mongod.log
This proposes candidate indexes based on the shapes it observed. More on how that works below, because it is the part people should be suspicious of.
And when something has already gone wrong, build the timeline.
mdbkit triage /var/log/mongodb/mongod.log
Elections, rollbacks, stalls, connection storms, in order, with timestamps. When you are twenty minutes into an incident and someone senior is asking what happened, a timeline you did not have to assemble by hand is worth a great deal.
The log cannot tell you everything
There is one failure mode a mongod log structurally cannot explain: the log simply stops, then starts again a minute later with no error in between.
The process was killed before it could write anything. That answer lives in the system log, one line up from where you were looking.
mdbkit triage mongod.log --oslog /var/log/syslog
[CRIT] Process start(s) in window: mongod startup marker at 09:14:35
[CRIT] System: oom-kill: The kernel OOM killer terminated a task.
1 occurrence (last at 09:14:03). Processes: mongod.
next: This explains an unexplained restart in the log above.
oslog reads /var/log/syslog or /var/log/messages and looks for the things that end database processes: OOM kills, file descriptor limits, segmentation faults, I/O errors, filesystems remounted read only, systemd service exits. Used with triage, the unexplained restart gets matched to the kernel line that caused it.
On journald systems there is no text log to read, and mdbkit will not run commands on your behalf, so it prints the journalctl invocation for you to capture and feed back in.
There is also ftdc, which decodes diagnostic.data offline. Every one of your nodes has been recording CPU, cache, queue depth and disk every second since the day you installed it, whether or not you run any monitoring. Plus serverstatus for making sense of a serverStatus dump, and compare for answering whether the index you added last week actually helped.
It never connects to your database
No driver. No URI. No network code anywhere in it.
mdbkit reads log files you hand it. It is strictly read only. Where an action would help, it prints the command for you to review and run yourself, rather than running anything on your behalf.
I built it this way because I would not run a stranger's tool on a production host, and I do not expect you to either. So rather than asking you to trust me, the README shows you how to verify it independently. grep the source for socket and driver imports. Run it under strace and watch it make no network calls. It takes about a minute and you should do it.
Zero runtime dependencies too, nothing in the supply chain beyond the Python standard library. That was a deliberate constraint and it cost me some convenience, but a tool that ships onto database servers should not drag a dependency tree behind it.
The index advice is rules, not AI
I gave a talk at the Dubai MongoDB User Group recently about how confidently language models describe a MongoDB that no longer exists. They will happily reference plannerVersion, a field removed in 5.0. They do not know about EXPRESS stages in 8.0. And more importantly, they cannot know your cardinality, your write mix, or what is currently sitting in your plan cache, because none of that is in your query text.
So mdbkit's index advice is deterministic. Rules over observed query shapes. The same log always produces the same recommendation.
Every suggestion comes with the evidence it reasoned from, a confidence level, and how to validate it before you act. It says candidate, never command. And it will never tell you to drop an index, because deciding an index is unused requires knowing about traffic that a log file cannot show you.
That is a deliberately narrow promise. I would rather it be right about a small thing than confident about a large one.
What would help most
Two things, and they are both easy for you and hard for me.
Real world log lines that parse incorrectly. Every MongoDB deployment logs slightly differently, and I have only seen the estates I have worked on. If mdbkit chokes on a line, that line is the most useful thing you can send me.
Index advice that is wrong or unhelpful. If it suggests something you know is a bad idea for your workload, I want to know why, because that is a rule that needs fixing.
Every release so far has come from someone doing exactly that. An FTDC decode that took twenty five minutes and pinned a CPU. A crash on a batched write. A restart with no explanation, which is why oslog exists at all.
Supports MongoDB 4.4 through 8.0. Python 3.8+.
- Source: https://github.com/saqibameen86/mdbkit
- Package: https://pypi.org/project/mdbkit
Free, MIT, and built because the gap annoyed me for long enough.
If you liked this, I also wrote about deleting 2TB from a live MongoDB cluster without anyone noticing and why I take down a healthy primary on purpose.
Top comments (0)