gin-contrib/zap: structured request logging for the Gin framework
Alternative logging through zap
At a glance
- What is it?
- This is a small Gin middleware that replaces the framework's default logger with uber-go/zap, so every request becomes a structured log line instead of plain text, with configuration for UTC timestamps, skipped paths and custom fields. It suits Go teams that already run zap elsewhere and want request logs to be queryable, and it does nothing for applications that do not use Gin.
- Who is it for?
- Adopt gin-contrib/zap when your service is built on Gin and you already emit logs through uber-go/zap, because a middleware that writes structured request lines is a few lines of wiring and saves you from parsing the default text format. Do not adopt it if your service does not use Gin, since the package is nothing but a Gin middleware, and do not adopt it expecting a full logging platform, because the middleware covers request and panic logs and nothing else.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the middleware actually replaces in Gin
The package description is one line: alternative logging through zap. The README credits it to a pull request in the gin-gonic contrib repository from a contributor named yezooz.
To understand what it does, it helps to know what Gin's own logging does. The framework ships a default logger that writes a line of plain text per request, in a human-oriented format, and a recovery middleware that catches panics. That output is fine when you are reading a terminal, and poor when you have thousands of requests a second and want to filter by status code, or join a request line with a log line from elsewhere in the same request.
This middleware replaces the access log with one that goes through uber-go/zap, the structured logging library, and it replaces the recovery handler with a variant that writes panics to the error log. The README states what the default `Ginzap` middleware does in a comment in the example: it logs all requests like a combined access and error log, it logs to stdout, and it uses RFC3339 with UTC time format.
So the value proposition is narrow and concrete. If you run zap elsewhere in your service, or you want to ship request logs to a collector that expects JSON, this middleware makes your HTTP layer speak the same language as the rest of your application. If you do not, the default logger is arguably fine and this package adds a dependency.
The licence is MIT, the primary language is Go, and the topics are `gin`, `gin-gonic` and `gin-middleware`, which is exactly the scope: a Gin middleware, nothing more.
Two entry points, and the difference between them
The package exposes two ways to install the middleware, and the choice between them is the main design decision in this library.
The first is the functional form, `Ginzap(logger, timeFormat, UTC)`, which takes the logger, the time format and a boolean for UTC. The second is the config form, `GinzapWithConfig(logger, &Config{...})`, which takes a `Config` struct. In the simplest case the two are equivalent, and the README shows the config form used where extra behaviour is needed.
The `Config` struct is where the features live. The fields documented in the examples are `TimeFormat` for the timestamp layout, `UTC` for whether timestamps are in UTC, `SkipPaths` as a list of exact paths to skip, `Skipper` as a function that receives the context and decides per request, `SkipPathRegexps` as a list of compiled regular expressions matched against the path, and `Context` as a function that returns extra `zapcore.Field` values to attach to each log line.
The two skip mechanisms are worth distinguishing. `SkipPaths` is an exact match list, so `/no_log` is skipped and `/no_log2` is not. `Skipper` is a function, which means you can express any predicate, and the README's example skips a request only when the path is `/ping` and the method is `GET`, so the `POST` variant of the same path is still logged. `SkipPathRegexps` sits between the two, matching by pattern, and the example uses `^/ping\s*` so that a family of paths can be excluded at once.
Having three mechanisms is a mild redundancy, but each covers a case the others do not, and they compose because a request is skipped if any of them says so.
Attaching request IDs, trace IDs and bodies with the Context hook
The most useful feature in the package is the `Context` hook, which is a function from `*gin.Context` to a slice of `zapcore.Field`. It runs per request and its return value is attached to that request's log line.
The README's example shows three things being added. A request ID is read from the response header `X-Request-Id` and logged as a `request_id` field if it is not empty. An OpenTelemetry trace ID and span ID are read from the request context using `trace.SpanFromContext`, checked with `SpanContext().IsValid()`, and logged as `trace_id` and `span_id`. And the request body is captured by wrapping the body reader in an `io.TeeReader` into a `bytes.Buffer`, reading it all, and then replacing `c.Request.Body` with a `io.NopCloser` over the buffer so the handler can still read the body.
That last trick is the interesting one. Reading the body in middleware would normally consume it, and the handler would then see an empty body. The tee and the restore is the standard way to make the body available twice.
It is also the part to be careful about. The example logs the entire body as a string, which for a service accepting credentials, tokens or personal data means writing secrets into your log store. The hook is arbitrary Go code, so you can drop the body line, log only its length, or restrict it to specific routes, but the shipped example does not do that for you.
The same applies to the trace fields. The example reads from the OpenTelemetry span context, which means the log line carries identifiers that only mean something if a trace is also being exported, so the tracing setup and the logging setup have to agree.
Installing it and the version floors you inherit
Installation is the standard Go module fetch:
go get github.com/gin-contrib/zapand the import in your code is the module path itself:
import "github.com/gin-contrib/zap"The version requirements are worth checking before you start. The README requires Go 1.26 or later, and the module file agrees, declaring `go 1.26.0`. The direct dependencies are `github.com/gin-gonic/gin v1.12.0` and `go.uber.org/zap v1.28.0`.
Those floors are more consequential than they look. Gin 1.12.0 is a recent release, and it brings a long tail of indirect dependencies that the module file lists in full: the bytedance sonic JSON library and its loader, the goccy JSON and YAML packages, go-playground validator, the quic-go stack, gin-contrib/sse, and the usual golang.org/x set. So pulling this middleware into a service that sits on an older Gin means upgrading Gin as well, which is a larger change than adding a logger.
The alternative to `go get` is vendoring through the module cache, and the Makefile in the repository shows the tooling the maintainers use. The `lint` and `fmt` targets both run golangci-lint through the Go tool mechanism with a separate `go.tools.mod` file, so the lint tool is pinned without becoming a dependency of the module itself. The `test` target runs `go test -v -cover -coverprofile=coverage.txt ./...`, and there is a `coverage` target that opens the profile in a browser, plus the usual `mod-download`, `mod-tidy` and `mod-verify` targets.
Panic handling, and the difference from the default recovery
The second middleware in the package is `RecoveryWithZap`, and it is used alongside the request logger in the README's example.
The README's comments describe both middlewares. The logger logs all requests, like a combined access and error log, and the recovery middleware logs all panics to the error log, with a boolean argument that means whether to output the stack info. In the example both are registered on the router:
r.Use(ginzap.Ginzap(logger, time.RFC3339, true))
r.Use(ginzap.RecoveryWithZap(logger, true))The ordering matters, and the example shows the conventional order: the logger is registered first, so the recovery middleware runs inside it and the panic is captured in the same request's log line, with the stack trace attached.
The boolean parameter deserves a decision rather than a copy-paste. A stack trace on every recovered panic is what you want in development and in a service where panics are rare and worth investigating, and it is noise in a service where a panic is a routine validation failure that your own code triggers. Passing false here keeps the structured error log while omitting the stack output, if your service treats panics as routine.
The example also wires a `/panic` route that panics deliberately, and a `/ping` route that returns a string with the current Unix time, which is a useful way to see the log line for a normal request next to the one for a panic.
Where gin-contrib/zap is the wrong tool
The obvious wrong-tool case is an application that does not use Gin. This package is a Gin middleware, so it depends on `*gin.Context` throughout, including in the `Skipper` and `Context` hooks. There is nothing to reuse in a net/http or Echo or Fiber service.
The second case is a team that wants more than request logs. This package is two middlewares. It does not do log rotation, sampling, level routing, or shipping to a collector. Zap itself is a logging library rather than a platform, so the moment you need log shipping, alerting or a query interface, you are adding another project anyway. This middleware simply makes the request log one of the structured entries in that larger setup.
The third case is a service on an older Gin. The module requires `gin v1.12.0`, and adopting the middleware means adopting that Gin version, along with the sonic, goccy, validator and quic-go versions it pulls in. For a long-lived service that has deliberately pinned its framework, adding a logger is not a small change.
The fourth is a service that does not yet use structured logging. If your application writes text logs everywhere, this middleware gives you one structured stream for HTTP and unstructured logs for everything else, and a mixed logging format is harder to work with than a consistent one.
The alternative worth naming is writing the same middleware yourself against `gin.LoggerWithConfig`, and then formatting the output yourself. The difference in approach is that a hand-written handler is about forty lines, gives you exactly the fields you want, and keeps the structured logging decision in your codebase, while this package gives you a maintained implementation of the zap integration, a config struct that has grown to cover the cases people actually hit, and the standard handling of latency, status and client IP that you would otherwise have to get right.
Maintenance, releases and the health of a small dependency
The release history shows a package that is still being worked on. Version 1.1.9 was tagged on 2026-09-21, 1.1.8 on 2026-09-05 and 1.1.7 on 2026-03-28, and the last push to the default branch was on 2026-10-03. The repository is not archived.
Two releases in September 2026, five months after the previous one, suggests a project responding to dependency bumps rather than shipping features. Given that the direct dependencies are Gin and zap, both of which release frequently, that is what you would expect: the maintenance work is keeping up with the framework and the logger, and the API surface staying small means those bumps rarely break you.
The version numbering is a simple `v1.1.x`, which under Go module rules means the import path never changes, so upgrading is a version bump in your `go.mod` and nothing else.
The licence is MIT with a `LICENSE` file at the top level, and the repository also carries a `.goreleaser.yaml` for building release artefacts, a `.golangci.yml` for lint configuration, a `zap.go` and a `zap_test.go` at the root for the implementation and its tests, and an `_example/` directory with a runnable example, which the README points to as the starting point for the full program.
The practical point for an adopter: because the module is small and the API is two functions plus a config struct, the risk of a breaking change in a patch release is low, but the risk of a breaking change in a minor release is real, since the `Config` struct has been gaining fields. Read the release notes before taking a minor bump.
Editorial conclusion
Adopt gin-contrib/zap when your service is built on Gin and you already emit logs through uber-go/zap, because a middleware that writes structured request lines is a few lines of wiring and saves you from parsing the default text format. Do not adopt it if your service does not use Gin, since the package is nothing but a Gin middleware, and do not adopt it expecting a full logging platform, because the middleware covers request and panic logs and nothing else. Before you wire it in, check that your Go toolchain is 1.26 or later, since the module requires it, and note the version floor of `gin v1.12.0` and `zap v1.28.0` in the module file, because an older Gin in your build will hold the whole dependency tree back.
Frequently asked questions
What does gin-contrib/zap do?
It is a Gin middleware that replaces the framework's default request logger with one backed by uber-go/zap, so each request becomes a structured log line. It also provides a recovery middleware that writes panics to the error log, optionally with the stack info.
How do I install gin-contrib/zap and add it to a Gin router?
Fetch it with `go get github.com/gin-contrib/zap` and import it as `github.com/gin-contrib/zap`. You then register `ginzap.Ginzap(logger, time.RFC3339, true)` for request logging and `ginzap.RecoveryWithZap(logger, true)` for panic recovery, with a logger from `zap.NewProduction()`.
How do I skip logging for specific paths in gin-contrib/zap?
There are three mechanisms in the `Config` struct: `SkipPaths` for an exact path list such as `/no_log`, `Skipper` for a function that receives the context and decides per request, and `SkipPathRegexps` for a list of compiled regular expressions matched against the request path.
Can gin-contrib/zap log request IDs and OpenTelemetry trace IDs?
Yes, through the `Context` hook in the `Config`, a function returning extra `zapcore.Field` values. The README example reads the `X-Request-Id` response header, and reads the trace and span IDs from the request context using `trace.SpanFromContext`, adding them as `request_id`, `trace_id` and `span_id` fields.
What versions of Go, Gin and zap does gin-contrib/zap need?
The README requires Go 1.26 or later, and the module file declares `go 1.26.0` with direct dependencies on `github.com/gin-gonic/gin v1.12.0` and `go.uber.org/zap v1.28.0`. Because Gin is a direct dependency with a recent floor, adopting this middleware means adopting that Gin version as well.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gin-contrib-zap)Was this useful?