zax
Integrate Context with Zap logger, which leads to more flexibility in Go logging
About zax
โก Zax
Context-Aware Logging for Go with Uber's Zap
Zax seamlessly integrates Zap Logger with Go's context.Context, enabling you to carry structured logging fields across your entire request lifecycle without boilerplate.
๐ Table of Contents
- Why Zax?
- Features
- Installation
- Tasks
- Releases
- Quick Start
- API Reference
- Real-World Example
- Benchmarks
- Contributing
- License
๐ฏ Why Zax?
In modern Go applications, especially microservices, you often need to:
- ๐ Trace requests across multiple functions and services
- ๐ Correlate logs with trace IDs, span IDs, and user context
- ๐งน Avoid boilerplate by not passing loggers as function parameters
- โก Maintain performance without sacrificing structured logging
Zax solves these problems elegantly by storing Zap fields in context, making them available wherever you need to log.
โจ Features
| Feature | Description |
|---|---|
| ๐ Zero Dependencies | Only requires go.uber.org/zap |
| ๐ฏ Context-Native | Works seamlessly with Go's context.Context |
| โก High Performance | Minimal, predictable overhead (see Benchmarks) |
| ๐ง Simple API | Just 7 functions to learn |
| ๐ฌ SugaredLogger Support | Works with both *zap.Logger and *zap.SugaredLogger |
| ๐งช Well Tested | Comprehensive test coverage |
๐ฆ Installation
go get -u github.com/yuseferi/zax/v2
Requirements: Go 1.26.1 or higher
๐ Tasks
This project uses Task to keep local commands and CI in sync.
Install Task:
go run github.com/go-task/task/v3/cmd/task@latest --version
Common commands:
task build
task test
task test:race
task lint
task bench
task ci
task ci runs the same lint and test flow used by GitHub Actions.
๐ Releases
This project uses semantic-release to automate Git tags and GitHub releases.
Release automation is configured in .releaserc.json and runs from .github/workflows/release.yml after the Quality check workflow succeeds on master.
Use Conventional Commits so semantic-release can determine the next version:
fix:for patch releasesfeat:for minor releasesfeat!:or aBREAKING CHANGE:footer for major releases
You can preview the next release locally with:
task release:dry-run
๐ Quick Start
package main
import (
"context"
"github.com/yuseferi/zax/v2"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
ctx := context.Background()
// Add trace_id to context
ctx = zax.Set(ctx, []zap.Field{
zap.String("trace_id", "abc-123"),
zap.String("user_id", "user-456"),
})
// Log with context fields - automatically includes trace_id and user_id
logger.With(zax.Get(ctx)...).Info("request started")
// Pass context to other functions
processRequest(ctx, logger)
}
func processRequest(ctx context.Context, logger *zap.Logger) {
// All logs automatically include trace_id and user_id!
logger.With(zax.Get(ctx)...).Info("processing request")
// Append additional fields without losing existing ones
ctx = zax.Append(ctx, []zap.Field{
zap.String("step", "validation"),
})
logger.With(zax.Get(ctx)...).Info("validation complete")
}
Output:
{"level":"info","msg":"request started","trace_id":"abc-123","user_id":"user-456"}
{"level":"info","msg":"processing request","trace_id":"abc-123","user_id":"user-456"}
{"level":"info","msg":"validation complete","trace_id":"abc-123","user_id":"user-456","step":"validation"}
๐ API Reference
Core Functions
Set(ctx, fields) context.Context
Stores zap fields in context. Replaces any existing fields.
ctx = zax.Set(ctx, []zap.Field{
zap.String("trace_id", "my-trace-id"),
zap.Int("request_num", 42),
})
Append(ctx, fields) context.Context
Appends fields to existing context fields. Preserves previously set fields.
// Existing: trace_id
ctx = zax.Append(ctx, []zap.Field{
zap.String("span_id", "my-span-id"),
})
// Now has: trace_id + span_id
When the same key is added multiple times, later fields follow Zap's normal behavior and take precedence at log time.
Get(ctx) []zap.Field
Retrieves all stored fields from context.
fields := zax.Get(ctx)
logger.With(fields...).Info("message")
GetField(ctx, key) zap.Field
Retrieves a specific field by key. Returns the zero-value zap.Field when the key is not present.
traceField := zax.GetField(ctx, "trace_id")
fmt.Println(traceField.String) // "my-trace-id"
LookupField(ctx, key) (zap.Field, bool)
Like GetField, but also reports whether the key was found โ useful when a
stored field may legitimately hold a zero value.
if field, ok := zax.LookupField(ctx, "attempt"); ok {
fmt.Println(field.Integer)
}
Remove(ctx, keys...) context.Context
Returns a context with the given keys removed from the stored fields. Handy for scrubbing sensitive data (e.g. PII) before passing a context on.
ctx = zax.Remove(ctx, "user_email", "api_token")
GetSugared(ctx) []any
Returns fields as key-value pairs for SugaredLogger.
sugar := logger.Sugar()
sugar.With(zax.GetSugared(ctx)...).Info("sugared log")
GetSugared converts fields through Zap's encoder so common field types like strings, bools, numbers, errors, durations, and times are preserved.
๐ฅ Real-World Example
HTTP Middleware with Distributed Tracing
package main
import (
"context"
"net/http"
"github.com/yuseferi/zax/v2"
"go.uber.org/zap"
)
type Server struct {
logger *zap.Logger
}
// Middleware injects trace context into all requests
func (s *Server) TracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Extract or generate trace ID
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = generateTraceID()
}
// Store in context
ctx = zax.Set(ctx, []zap.Field{
zap.String("trace_id", traceID),
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
})
s.logger.With(zax.Get(ctx)...).Info("request received")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Handler automatically has access to trace context
func (s *Server) HandleUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Add handler-specific context
ctx = zax.Append(ctx, []zap.Field{
zap.String("handler", "user"),
})
user, err := s.fetchUser(ctx)
if err != nil {
s.logger.With(zax.Get(ctx)...).Error("failed to fetch user", zap.Error(err))
http.Error(w, "Internal Error", 500)
return
}
s.logger.With(zax.Get(ctx)...).Info("user fetched successfully",
zap.String("user_id", user.ID),
)
}
func (s *Server) fetchUser(ctx context.Context) (*User, error) {
// All logs here include trace_id, method, path, and handler!
s.logger.With(zax.Get(ctx)...).Debug("querying database")
// ... database logic
return &User{}, nil
}
๐ Benchmarks
Zax V2 is optimized for performance. Here's how it compares:
Run benchmarks yourself with:
task bench
# or
go test -bench . -run '^$' -benchmem ./...
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
| Pure Zap | ~43 | 128 | 1 |
| Zax V2 | ~226 | 584 | 5 |
๐ก The extra allocations come from defensively cloning fields on
Set/Append/Getand from storing them in the context, so callers can never mutate fields after they are stored. If you need the absolute minimum overhead, pass zap fields directly.
Measured on an Apple M2 Pro with Go 1.26 and zap v1.28.0:
pkg: github.com/yuseferi/zax/v2
BenchmarkLoggingWithOnlyZap-10 84292609 43.15 ns/op 128 B/op 1 allocs/op
BenchmarkLoggingWithZax-10 15688470 226.2 ns/op 584 B/op 5 allocs/op
PASS
๐ค Contributing
We โค๏ธ contributions! Here's how you can help:
- ๐ด Fork the repository
- ๐ฟ Create a feature branch (
git checkout -b feature/amazing-feature) - ๐ป Commit your changes (
git commit -m 'Add amazing feature') - ๐ค Push to the branch (
git push origin feature/amazing-feature) - ๐ Open a Pull Request
Development
# Clone the repository
git clone https://github.com/yuseferi/zax.git
cd zax
# Run tests
go test -v ./...
# Run benchmarks
go test -bench=. -benchmem
# Run linter
golangci-lint run
๐ License
This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details.
Made with โค๏ธ by Yusef Mohamadi and contributors
โญ Star this repo if you find it useful!
Frequently Asked Questions
What is zax?
zax is a Logging library for the Go programming language. Integrate Context with Zap logger, which leads to more flexibility in Go logging
How do I install zax?
Install zax with the Go module system using `go get yuseferi/zax`. Check the repository for the current installation instructions.
What category does zax belong to?
zax is listed under Logging, specifically Logging.