Production-grade Prometheus exporter for MongoDB, written in Go.
60+ metrics · 10 MB distroless image · TLS · Replica-set aware · Pre-built Grafana dashboard
Most existing exporters are either unmaintained, bloated, or missing the metrics that matter for production. This one is:
- Accurate — metrics sourced directly from
serverStatus,dbStats,replSetGetStatus, andsystem.profile; no scraping shell commands - Lightweight — single static binary, ~10 MB distroless Docker image, no shell, no package manager
- Production-ready — TLS, graceful shutdown, per-scrape timeout, panic recovery per collector, configurable cardinality controls
- Observable out of the box — ships a 45-panel Grafana dashboard covering operations, latency, connections, WiredTiger internals, replication, database statistics, and slow-query profiler
- Quick Start
- Docker Compose — Full Dev Stack
- Docker Compose — Exporter Only
- Binary / Bare Metal
- Configuration
- Enabling the Query Profiler
- Metrics Reference
- Grafana Dashboard
- Prometheus Scrape Config
- MongoDB User (Minimal Privileges)
- TLS
- Kubernetes & Helm
- Development
- Security
- Contributing
- License
git clone https://github.com/gurjarchetan/mongodb-exporter
cd mongodb-exporter
docker compose up -dOr pull the pre-built image directly:
# GitHub Container Registry (primary)
docker pull ghcr.io/gurjarchetan/mongodb-exporter:v1.0.0
# Docker Hub (mirror)
docker pull chetangurjar/mongodb-exporter:v1.0.0GHCR: https://github.com/gurjarchetan/mongodb-exporter/pkgs/container/mongodb-exporter Docker Hub: https://hub.docker.com/r/chetangurjar/mongodb-exporter
Open Grafana at http://localhost:3000 (admin / admin) — the MongoDB — Production Overview dashboard appears within ~30 seconds.
| Service | URL | Credentials |
|---|---|---|
| Grafana | http://localhost:3000 | admin / admin |
| Prometheus | http://localhost:9090 | — |
| Exporter | http://localhost:9216/metrics | — |
| MongoDB | mongodb://localhost:27017 | no auth (dev) |
The full dev stack spins up MongoDB 8.0 + Exporter + Prometheus + Grafana in a single command, pre-seeded with demo data and profiling enabled.
# Option A — root docker-compose.yml (shorthand)
docker compose up -d
# Option B — explicit path
docker compose -f deploy/docker-compose/dev-stack.yml up -dBoth compose files are equivalent. The deploy/docker-compose/dev-stack.yml version is intended for CI and cross-project usage; it references all assets via relative paths from the repository root.
| Container | Description |
|---|---|
mongodb |
MongoDB 8.0, single-node replica set rs0, profiling enabled |
mongo-init |
One-shot: initialises replica set, seeds demo databases |
mongodb-exporter |
This project — built from source via Dockerfile |
prometheus |
Scrapes exporter every 15 s, 30-day retention |
grafana |
Auto-provisioned datasource + 45-panel dashboard |
ecommerce— products, orders, usersanalytics— event streamiot_sensors— time-series sensor readings
docker compose down # stop, keep volumes
docker compose down -v # stop + delete all dataUse this when you already have MongoDB (and Prometheus/Grafana) running.
# Set your MongoDB URI
export MONGODB_URI="mongodb://exporter:secret@your-mongo:27017/admin?authSource=admin"
docker compose -f deploy/docker-compose/exporter-only.yml up -dOr create a .env file next to the compose file:
MONGODB_URI=mongodb://exporter:secret@your-mongo:27017/admin?authSource=adminThe exporter is then available at http://<host>:9216/metrics.
Image — by default
exporter-only.ymlpullschetangurjar/mongodb-exporter:latestfrom Docker Hub. To build from source, uncomment thebuild:block inside the file.
# Build
make build
# Run
./bin/mongodb-exporter \
--mongodb.uri="mongodb://exporter:secret@localhost:27017/admin?authSource=admin" \
--collector.dbstats=true \
--collector.replication=true \
--collector.profile=true \
--web.listen-address=:9216Pre-built binaries for Linux/macOS/Windows (amd64 + arm64) are available on the Releases page.
All flags can also be set via environment variables. CLI flag --foo.bar maps to env var FOO_BAR (upper-case, dots → underscores). The most common env var is MONGODB_URI.
| Flag | Default | Description |
|---|---|---|
--mongodb.uri |
mongodb://localhost:27017 |
MongoDB connection URI |
--web.listen-address |
:9216 |
HTTP listen address |
--web.telemetry-path |
/metrics |
Metrics endpoint path |
--scrape.timeout |
10s |
Per-scrape MongoDB timeout |
--collector.dbstats |
true |
Per-database storage statistics |
--collector.replication |
true |
Replica-set health, lag, oplog metrics |
--collector.currentop |
false |
Currently-running operations (may be expensive on busy servers) |
--collector.profile |
false |
Slow queries from system.profile — see Enabling the Query Profiler |
--collector.collstats |
false |
Per-collection statistics (high cardinality — use with care) |
--mongodb.tls |
false |
Enable TLS for MongoDB connection |
--mongodb.tls-ca |
"" |
Path to CA certificate file |
--log.level |
info |
Log level: debug | info | warn | error |
--version |
— | Print version information and exit |
The profiler collector (--collector.profile=true) reads system.profile on every application database and exposes three metrics:
| Metric | Description |
|---|---|
mongodb_profiling_level{database} |
Current profiler level: 0 = off, 1 = slow ops only, 2 = all ops |
mongodb_slow_queries_total{database,collection,op} |
Query count per DB/collection/op type since last scrape |
mongodb_slow_query_millis_total{database,collection,op} |
Total milliseconds spent, per DB/collection/op |
// Level 1: log ops slower than 100 ms (recommended for production)
db.getSiblingDB("mydb").setProfilingLevel(1, { slowms: 100 })
// Level 2: log all ops (development / debugging only)
db.getSiblingDB("mydb").setProfilingLevel(2)
// Check current level
db.getSiblingDB("mydb").getProfilingStatus()
// → { was: 1, slowms: 100, ... }Pass --profile and --slowms to mongod so the setting survives pod/container restarts:
# docker-compose.yml / Kubernetes pod spec
command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--profile", "1", "--slowms", "100"]./bin/mongodb-exporter --collector.profile=true ...The Slow Queries (Profiler) row in the dashboard shows a colour-coded status bar per database:
| Colour | Value | Meaning |
|---|---|---|
| 🔴 Red | 0 |
Profiling is OFF — no slow-query data will be collected |
| 🟡 Yellow | 1 |
Slow ops — only queries exceeding slowms are logged |
| 🟢 Green | 2 |
All ops — every query is logged (use for debugging only) |
| Metric | Type | Labels | Description |
|---|---|---|---|
mongodb_up |
Gauge | — | 1 if reachable, 0 if not |
mongodb_scrape_duration_seconds |
Gauge | — | Wall-clock scrape time |
mongodb_uptime_seconds |
Gauge | — | MongoDB process uptime |
mongodb_connections_current |
Gauge | — | Open connections |
mongodb_connections_available |
Gauge | — | Available connection slots |
mongodb_connections_active |
Gauge | — | Active (in-use) connections |
mongodb_connections_created_total |
Counter | — | Total connections ever made |
mongodb_opcounters_total |
Counter | type |
Op counts (insert/query/update/delete/command/getmore) |
mongodb_opcounters_repl_total |
Counter | type |
Replicated op counts |
mongodb_op_latency_micros_total |
Counter | type |
Cumulative latency µs (read/write/command) |
mongodb_op_latency_ops_total |
Counter | type |
Op count for latency histogram |
mongodb_mem_resident_bytes |
Gauge | — | Resident (RSS) memory |
mongodb_mem_virtual_bytes |
Gauge | — | Virtual memory |
mongodb_network_bytes_in_total |
Counter | — | Network bytes received |
mongodb_network_bytes_out_total |
Counter | — | Network bytes sent |
mongodb_network_requests_total |
Counter | — | Total network requests |
mongodb_asserts_total |
Counter | type |
Assertion counts by type |
mongodb_wiredtiger_cache_bytes |
Gauge | type |
WiredTiger cache (current/dirty) |
mongodb_wiredtiger_cache_max_bytes |
Gauge | — | WiredTiger cache configured maximum |
mongodb_wiredtiger_cache_io_bytes_total |
Counter | direction |
Cache I/O bytes (read/written) |
mongodb_wiredtiger_cache_evicted_pages_total |
Counter | type |
Cache page evictions |
mongodb_wiredtiger_concurrent_transactions_out |
Gauge | — | Tickets in use |
mongodb_wiredtiger_concurrent_transactions_available |
Gauge | — | Tickets available |
mongodb_wiredtiger_transactions_committed_total |
Counter | — | WT committed transactions |
mongodb_wiredtiger_transactions_rolled_back_total |
Counter | — | WT rolled-back transactions |
mongodb_transactions_current |
Gauge | state |
Multi-doc transactions by state |
mongodb_transactions_total |
Counter | state |
Multi-doc transaction outcomes |
mongodb_metrics_document_total |
Counter | state |
Document ops (inserted/returned/updated/deleted) |
mongodb_metrics_cursor_open |
Gauge | state |
Open cursors |
mongodb_metrics_cursor_timed_out_total |
Counter | — | Timed-out cursors |
mongodb_metrics_query_executor_scanned_total |
Counter | — | Index entries scanned |
mongodb_metrics_query_executor_scanned_objects_total |
Counter | — | Documents scanned |
mongodb_metrics_collection_scans_total |
Counter | type |
Collection scans (full) |
mongodb_global_lock_current_queue |
Gauge | type |
Lock queue depth |
mongodb_flow_control_is_lagged |
Gauge | — | 1 if flow-controlled/throttled |
| Metric | Labels | Description |
|---|---|---|
mongodb_db_data_size_bytes |
database |
Uncompressed data size |
mongodb_db_storage_size_bytes |
database |
On-disk storage size |
mongodb_db_index_size_bytes |
database |
Total index size |
mongodb_db_total_size_bytes |
database |
Data + index total |
mongodb_db_objects_total |
database |
Document count |
mongodb_db_collections_total |
database |
Collection count |
mongodb_db_indexes_total |
database |
Index count |
mongodb_db_avg_obj_size_bytes |
database |
Average document size |
| Metric | Labels | Description |
|---|---|---|
mongodb_repl_member_health |
member |
1 if member is up |
mongodb_repl_member_state |
member, state |
1 for current state |
mongodb_repl_lag_seconds |
member |
Replication lag vs primary |
mongodb_repl_member_uptime_seconds |
member |
Member uptime |
mongodb_oplog_max_size_bytes |
— | Configured oplog size |
mongodb_oplog_used_bytes |
— | Current oplog usage |
mongodb_oplog_time_diff_seconds |
— | Oplog window (oldest → newest entry) |
| Metric | Labels | Description |
|---|---|---|
mongodb_profiling_level |
database |
Current profiler level (0 / 1 / 2) |
mongodb_slow_queries_total |
database, collection, op |
Queries logged since last scrape |
mongodb_slow_query_millis_total |
database, collection, op |
Milliseconds spent in logged queries |
The included dashboard (monitoring/grafana/dashboards/mongodb.json) has 53 panels organised into collapsible rows:
| Row | Panels | Key metrics |
|---|---|---|
| Status | 8 stat panels | UP/DOWN · Uptime · Ops/s · Open/Active Connections · Resident memory · WiredTiger cache % · Replication lag |
| Operations & Documents | 2 timeseries | Opcounters rate · Document ops rate |
| Latency | 3 timeseries | Read / Write / Command avg latency (µs) |
| Connections & Global Lock | 3 timeseries | Connection pool · Lock queue · Open cursors |
| Memory & WiredTiger Cache | 4 panels | Resident/virtual memory · Cache utilisation gauge · Cache bytes breakdown |
| WiredTiger Internals | 3 timeseries | Concurrent ticket usage · Cache evictions & I/O · Transactions & checkpoints |
| Network | 2 timeseries | Throughput in/out · Requests/s |
| Replication | 6 panels | Oplog window stat · Oplog used % gauge · Oplog bytes · Apply ops/s · RS members table · Apply buffer |
| Database Statistics | 3 panels | Storage size bar gauge · Documents bar gauge · Detail table with colour-coded Total Size |
| Query Efficiency | 2 timeseries | Scanned vs returned ratio · Collection scans/sorts |
| Slow Query Profiler | 5 panels | Profiler level per database (colour-coded OFF/SLOW OPS/ALL OPS) · State-timeline history · Slow query rate · Avg duration |
| Asserts & Transactions | 2 timeseries | Assert rate by type · Multi-doc transaction states |
Connections · Memory · WiredTiger Cache

WiredTiger Internals · Network

Replication · Database Statistics

Slow Query Profiler — profiler level per database (colour-coded), state-timeline history, slow query rate & avg duration

Asserts · Multi-document Transactions

- Open Grafana → Dashboards → Import
- Upload
monitoring/grafana/dashboards/mongodb.json - Select your Prometheus datasource
- Click Import
The dashboard JSON is generated by generate_dashboard.py (no external dependencies):
python3 generate_dashboard.py
# OK: 53 panels written to monitoring/grafana/dashboards/mongodb.jsonscrape_configs:
- job_name: mongodb
scrape_interval: 15s
scrape_timeout: 10s
static_configs:
- targets:
- mongodb-exporter:9216 # adjust host/port
relabel_configs:
- source_labels: [__address__]
target_label: instanceCreate a dedicated monitoring user with the least-privilege roles:
db.getSiblingDB("admin").createUser({
user: "mongodb_exporter",
pwd: "STRONG_RANDOM_PASSWORD",
roles: [
{ role: "clusterMonitor", db: "admin" },
{ role: "read", db: "local" } // required for oplog metrics
]
})If --collector.profile=true is enabled, the user also needs read on each profiled database:
db.getSiblingDB("myapp").grantRolesToUser("mongodb_exporter", [
{ role: "read", db: "myapp" }
])Connection URI:
mongodb://mongodb_exporter:STRONG_RANDOM_PASSWORD@localhost:27017/admin?authSource=admin
./bin/mongodb-exporter \
--mongodb.uri="mongodb://exporter:pass@mongo.internal:27017/admin" \
--mongodb.tls=true \
--mongodb.tls-ca=/etc/ssl/certs/mongo-ca.crtIn Docker Compose:
mongodb-exporter:
command:
- --mongodb.tls=true
- --mongodb.tls-ca=/certs/ca.crt
volumes:
- /path/to/ca.crt:/certs/ca.crt:roTLS 1.2 is the minimum enforced version.
# Edit deploy/kubernetes/deployment.yaml to set your MONGODB_URI secret, then:
kubectl apply -f deploy/kubernetes/helm install mongodb-exporter ./deploy/helm \
--set mongodb.uri="mongodb://exporter:pass@mongo:27017/admin?authSource=admin" \
--set collector.profile=true- Go 1.21+
- Docker + Docker Compose v2
golangci-lint(optional, for linting)
make build # compile binary → bin/mongodb-exporter
make test # go test -race ./...
make lint # golangci-lint run
make docker-build # build Docker image
make up # start full observability stack
make down # stop stack (keep data)
make logs # tail all container logs
make scrape # curl http://localhost:9216/metrics- Create
collector/my_collector.goimplementingprometheus.Collector - Register it in
exporter/exporter.go(guarded by a config flag if optional) - Add a CLI flag in
main.go - Add metrics to the
Metrics Referencetable above and to the dashboard generator
.
├── collector/ # Individual metric collectors
│ ├── server_status.go # 60+ metrics from serverStatus
│ ├── db_stats.go # Per-database storage stats
│ ├── replication.go # Replica-set health, oplog
│ ├── profile.go # Slow-query profiler + profiling level
│ ├── current_op.go # Running operations
│ └── coll_stats.go # Per-collection stats
├── exporter/ # Wires collectors, owns MongoDB client
├── deploy/
│ ├── docker-compose/
│ │ ├── dev-stack.yml # Full stack (MongoDB + Exporter + Prometheus + Grafana)
│ │ └── exporter-only.yml # Exporter only (connect to existing MongoDB)
│ ├── kubernetes/ # Deployment, Service, ConfigMap manifests
│ └── helm/ # Helm chart
├── monitoring/
│ ├── prometheus.yml
│ └── grafana/
│ ├── dashboards/mongodb.json # 45-panel production dashboard
│ └── provisioning/
├── scripts/
│ ├── mongo-init.js # Replica set init, user creation
│ └── seed-data.js # Demo data (ecommerce, analytics, iot_sensors)
├── generate_dashboard.py # Dashboard JSON generator
├── Dockerfile # Multi-stage distroless build
├── docker-compose.yml # Root-level shorthand for dev stack
└── Makefile
- Least privilege — only
clusterMonitor+readonlocal; no write access required - Credentials via env — pass
MONGODB_URIas an environment variable or Kubernetes Secret; never in CLI flags visible viaps - Distroless image — no shell, no package manager, minimal attack surface; runs as
nonroot(UID 65534) with a read-only filesystem - TLS — 1.2+ enforced when
--mongodb.tlsis set; custom CA certificate supported - No cardinality explosion —
collstatscollector is off by default;profilecollector hard-caps at 10 000 entries per scrape
Contributions are welcome! Please:
- Open an issue describing the problem or feature before sending a large PR
- Follow existing code style (
gofmt,golangci-lintclean) - Add or update the relevant
Metrics Referencetable entries - Keep the dashboard generator (
generate_dashboard.py) in sync with new metrics - Ensure
go test -race ./...passes
