webpprof is a Telescope-like request profiler and debug toolbar for Go
(Golang) web applications. It shows everything one HTTP request did — SQL
queries, cache operations, background jobs, logs, mail, outgoing HTTP calls,
middleware, and panics — in one searchable local UI.
Early-stage: webpprof is pre-v1. Public APIs and persisted capture formats may change between minor releases, so pin a version and review the changelog before upgrading.
Use it to find why an endpoint is slow, inspect the SQL it executed, follow
related operations through context.Context, and replay a captured HTTP request
as cURL. webpprof runs inside the application and needs no external collector,
Docker stack, or database.
webpprof is a development and diagnostic tool, not a long-term production APM. Captures are bounded and kept in memory by default, with optional local persistence for short investigations.
Go already has excellent runtime profiling and observability tools. webpprof adds a request-centric view for application debugging:
- Why is this HTTP endpoint slow?
- Which SQL queries, cache operations, and outgoing calls did it execute?
- Which logs, jobs, mail, and exceptions belong to it?
- What happened before a panic or failed response?
- Can an AI coding agent inspect the same captured timeline?
It complements pprof, tracing, and production APMs with a quick local Go
request profiler, SQL query profiler, debug toolbar, and observability dashboard.
- Inspect method, route, status, duration, headers, bounded bodies, raw HTTP, and ready-to-run cURL for captured requests.
- Correlate middleware, SQL, cache, jobs, logs, mail, outgoing HTTP,
exceptions, and custom events through
context.Context; inspect Schedule, Callable, and measured Task work as standalone execution roots. - Find possible N+1 queries, SQL-heavy requests, sequential HTTP calls, cache miss/query bursts, slow middleware, and direct operation failures.
- Explore a request-wide waterfall with nesting, critical path, bottleneck, and operation-time breakdown.
- Search and filter live events by entity, duration, status, time, and tags.
- Profile
net/http, Gin, Chi, Echo, Fiber, gRPC, pgx, GORM, Bun,database/sql, Redis, queues, messaging, logging, mail, and OpenTelemetry. - Let Codex, Claude, Cursor, and other MCP clients inspect the profiler through
the separate read-only
webpprof-mcpbinary.
The repository includes an end-to-end benchmark that marshals and redacts a custom event, records it into a full 10,000-entry store, and performs one FIFO eviction per operation. Results on Go 1.25.13, darwin/arm64, Apple M3 Pro:
| Configuration | Time/op | Bytes/op | Allocs/op |
|---|---|---|---|
| Event kind disabled | 52 ns | 224 B | 1 |
| In-memory, steady-state eviction | 3.89 µs | 4,162 B | 51 |
| JSONL journal, steady-state eviction | 8.82 µs | 5,204 B | 60 |
| SQLite, steady-state eviction | 73.4 µs | 5,790 B | 88 |
Run both benchmark suites on deployment-like hardware:
go test . -run '^$' -bench BenchmarkProfilerOverhead -benchmem -count=6
go test ./storage/sqlite -run '^$' -bench BenchmarkProfilerSQLiteSteadyStateEviction -benchmem -count=6The SQLite row includes synchronous eviction and write I/O. Run the relevant suite before enabling additional capture kinds in a hot path. These numbers are a reproducible reference, not a latency guarantee.
Install the core module:
go get github.com/levskiy0/webpprof@latestStart a private profiler server and wrap the application handler:
profiler, err := webpprof.Start(
"127.0.0.1:6061",
webpprof.WithToken(os.Getenv("WEBPPROF_TOKEN")),
webpprof.WithExcludedRequests("GET /health", "GET *.js", "GET *.webp"),
)
if err != nil {
return err
}
defer profiler.Shutdown(context.Background())
handler := webpprofhttp.MiddlewareWith(profiler, applicationHandler)Pass the handler's context to database, cache, logger, queue, mail, and HTTP client operations. Their profilers use that context to attach events to the request:
if err := repository.Find(r.Context(), playerID); err != nil {
return err
}
logger.InfoContext(r.Context(), "player loaded", "player_id", playerID)Open http://127.0.0.1:6061/debug/webpprof/ and enter the token. Use
webpprof.New(router, options...) instead when the application owns the HTTP
server that serves the profiler UI.
Third-party integrations are independent nested Go modules, so installing Gin, pgx, or GORM support does not add unrelated SDKs to the application's module graph:
go get github.com/levskiy0/webpprof/profiler/gin@latest
go get github.com/levskiy0/webpprof/profiler/pgx@latest
go get github.com/levskiy0/webpprof/profiler/gorm@latestSee all integrations and setup examples.
webpprof-mcp is a separate process. It reads a running profiler through its
private HTTP API and exposes bounded, read-only MCP tools over stdio:
Codex / Claude / Cursor <-- MCP over stdio --> webpprof-mcp <-- HTTP --> Go application
Installing the Go library does not install the MCP executable. Install its independent module from any directory:
go install github.com/levskiy0/webpprof/cmd/webpprof-mcp@latest
webpprof-mcp --versionFor a reproducible install, replace @latest with @v0.5.0.
The executable is written to GOBIN, or GOPATH/bin when GOBIN is unset.
The MCP command is versioned independently. Its versions are published as
cmd/webpprof-mcp/vX.Y.Z Go module tags through proxy.golang.org, without a
separate GitHub Release. Use @latest or pin an exact command version.
Register it in Codex:
codex mcp add webpprof \
--env WEBPPROF_TOKEN="$WEBPPROF_TOKEN" \
-- webpprof-mcp --url http://127.0.0.1:6061/debug/webpprof/The server provides tools to check status, list and wait for requests, inspect automatic findings, and search related events. Payloads, values, arguments, and stacks are omitted unless explicitly requested; tools never replay requests, clear events, or mutate the application.
See MCP installation, client configuration, tools, and security.
Run the bundled application from the repository root:
go run ./exampleOpen http://127.0.0.1:3030/, generate a successful,
failed, or panic request, then inspect it at
http://127.0.0.1:3030/debug/webpprof/.
The example is a real net/http application using database/sql, pure-Go
SQLite, structured log/slog, SQL EXPLAIN, and SQLite-backed profiler storage.
Its composition root shows the complete integration in one place: wrap the
HTTP handler, SQL driver, and slog handler once. Its ordinary handlers also use
the optional Measure helper to create service-level spans while SQL and logs
remain automatic. The clearly marked /api/manual/* routes contain the custom
integration and synthetic diagnostics. See example/README.md
for the annotated wiring, automatic behavior, routes, and configuration.
| Entity | Automatic profilers | Examples of recorded data |
|---|---|---|
| HTTP request | http, Gin, Chi, Echo, Fiber, gRPC |
Route, status, headers, bounded bodies, duration, error |
| Middleware | http, Gin |
Name, state, total span, measured middleware work, error |
| SQL query | Bun, GORM, pgx, database/sql, OTel |
SQL, connection, rows, duration, callsite, optional EXPLAIN |
| Cache | go-cache, go-redis | Store, operation, key, hit, TTL, duration, error |
| Job | go-queue, Asynq | Queue, state, attempts, bounded arguments, duration, error |
| Log | slog, Zap, zerolog |
Level, message, structured fields, stack |
| email, go-mail | Transport, recipients, subject, state, duration, error | |
| Outgoing call | HTTP, gRPC | Method, target, status, bounded payloads, duration, error |
| Messaging | NATS, kafka-go | Subject/topic, producer/consumer state, size, duration, error |
| Schedule | schedule | Name, planned time, state, duration, error or panic |
| Callable | callable | Custom command name, state, duration, payload/result, error or panic |
| Task | core StartTask / MeasureTask |
Application operation name, state, fields, duration, error or panic |
| Exception/event | HTTP recovery or manual API | Type, message, stack, custom fields and tags |
All entity types also have context-aware manual logging APIs. See the complete event and entity reference.
Use Task for a long-running application operation that is neither an incoming request, a scheduled callback, nor a callable command. It becomes an independent execution root; pass the callback context to dependencies so its queries, logs, cache operations, and outgoing calls appear in the same scope:
measurement := profiler.MeasureTask(ctx, webpprof.Task{
Name: "reports.players.generate",
Fields: map[string]any{"format": "pdf"},
}, func(taskCtx context.Context) error {
return reports.Generate(taskCtx)
})
return measurement.ErrUse StartTask and FinishResult when the lifecycle crosses function
boundaries or result fields are only known at completion.
For application services and unsupported dependencies, measure a block without writing stopwatch/error boilerplate:
measurement := profiler.Measure(ctx, webpprof.Event{
Kind: "service",
Name: "players.refresh",
}, func(ctx context.Context) error {
return players.Refresh(ctx) // nested profilers inherit this Event as parent
})
metrics.Record(measurement.Failed(), measurement.Duration)
return measurement.ErrMeasureValueWith covers (T, error) functions. StartEvent plus
FinishResult provides a manual lifecycle for async wrappers or result-derived
status and fields. All helpers use only the standard library, honor
WithoutRecording, preserve panics after recording them, and are documented in
writing a custom profiler.
Core and standard-library profilers ship in the root module:
| Package | Integration point |
|---|---|
profiler/http |
Incoming http.Handler, named middleware, and http.RoundTripper |
profiler/sql |
driver.Connector or driver.Driver before sql.OpenDB |
profiler/slog |
Standard slog.Handler |
profiler/email |
Dependency-neutral mail Sender |
profiler/schedule |
Scheduled func(context.Context) callbacks |
profiler/callable |
Custom func(context.Context) error commands |
Optional integrations are isolated modules:
| Area | Modules |
|---|---|
| HTTP and RPC | Gin, Chi, Echo, Fiber, gRPC |
| SQL and ORM | pgx, GORM, Bun |
| Cache | go-cache, go-redis |
| Jobs | go-queue, Asynq |
| Messaging | NATS, kafka-go |
| Logging | Zap, zerolog |
| Mail and tracing | go-mail, OpenTelemetry |
The application retains ownership of wrapped dependencies and closes them as
usual. Use one profiler per operation path: stacking Bun, GORM, pgx,
database/sql, or OTel instrumentation around the same query records
duplicates.
See installation and recipes for every profiler and SQL callsites, EXPLAIN, and replay.
The request middleware stores a capture in context.Context. Context-aware
profilers and Log*Context functions inherit the request ID, tags, and current
parent operation:
flowchart LR
A["Incoming request"] --> B["webpprof middleware"]
B --> C["context.Context"]
C --> D["SQL / cache / logs"]
C --> E["jobs / mail / HTTP calls"]
D --> F["Request timeline and findings"]
E --> F
Automatic findings currently cover repeated query fingerprints, SQL wall-clock coverage, sequential safe HTTP calls, cache miss/query bursts, slow middleware, slow operations, and failed execution roots, jobs, mail, or HTTP calls. Schedule and Callable wrappers plus the Task lifecycle create independent roots and parent their nested work. Bottleneck analysis follows those parent links and prefers a nested operation when it explains most of an inclusive wrapper span. It also requires operation-specific absolute latency thresholds, so an otherwise fast operation is not labeled merely for being the longest.
See request correlation, tags, middleware timing, and finding rules.
Captures default to at most 10,000 events or 64 MiB for 30 minutes, with a 64 KiB limit per HTTP body. Configure retention, byte limits, sampling, selective capture, redaction, disabled event kinds, and optional local storage at startup:
sqliteStorage, err := webpprofsqlite.Open(context.Background(), "./var/webpprof/events.db")
if err != nil {
return err
}
profiler := webpprof.New(
mux,
webpprof.WithToken(os.Getenv("WEBPPROF_TOKEN")),
webpprof.WithRetention(2*time.Hour),
webpprof.WithMaxEvents(25_000),
webpprof.WithMaxBytes(128<<20),
webpprof.WithRequestSampleRate(0.25),
webpprof.WithStorage(sqliteStorage),
)SQLite is an independent optional module:
go get github.com/levskiy0/webpprof/storage/sqlite@latest. Without a token,
captured data and live updates stay unavailable. Local-only unauthenticated
access requires the explicit webpprof.WithUnsafeUnauthenticatedAccess()
option. Existing SQLite users should follow the
migration example.
Keep the profiler on loopback or a private administrative network and always set a strong token outside source control. Captures can contain personal data, SQL, request bodies, mail, and stack traces even after automatic redaction.
- Capture, storage migration, server filters, and selective capture
- Custom dashboard widgets and metrics
- Deployment and data security
| Guide | Use it for |
|---|---|
| MCP server | Installing webpprof-mcp and connecting AI coding agents |
| Integrations | Framework, SQL, cache, queue, messaging, logging, and mail setup |
| Configuration | Capture limits, filters, sampling, storage, import, and export |
| Dashboard | Built-in and custom metrics, counters, and charts |
| Correlation and findings | Context propagation, tags, middleware, and automatic analysis |
| Event reference | Manual APIs, Meta, entity fields, and background work |
| SQL profiling | Callsites, source links, EXPLAIN, and Go replay |
| Custom profilers | Implementing an adapter for another dependency |
The repository uses go.work for local development; consumers do not need it.
Each optional integration and the MCP command has its own go.mod.
make checkThe check runs dependency isolation, module verification, go vet, all Go
tests, JavaScript syntax validation, and whitespace checks.
webpprof is available under the MIT License.
