Zax seamlessly integrates Zap Logger with Go's context.Context, enabling you to carry structured logging fields across your entire request lifecycle without boilerplate.
Features • Installation • Quick Start • API Reference • Benchmarks • Contributing
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.
| Feature | Description |
|---|---|
| 🚀 Zero Dependencies | Only requires go.uber.org/zap |
| 🎯 Context-Native | Works seamlessly with Go's context.Context |
| ⚡ High Performance | Minimal overhead (~20ns per operation) |
| 🔧 Simple API | Just 5 functions to learn |
| 🍬 SugaredLogger Support | Works with both *zap.Logger and *zap.SugaredLogger |
| 🧪 Well Tested | Comprehensive test coverage |
go get -u github.com/yuseferi/zax/v2Requirements: Go 1.21 or higher
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"}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),
})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_idRetrieves all stored fields from context.
fields := zax.Get(ctx)
logger.With(fields...).Info("message")Retrieves a specific field by key.
traceField := zax.GetField(ctx, "trace_id")
fmt.Println(traceField.String) // "my-trace-id"Returns fields as key-value pairs for SugaredLogger.
sugar := logger.Sugar()
sugar.With(zax.GetSugared(ctx)...).Info("sugared log")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
}Zax V2 is optimized for performance. Here's how it compares:
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
| Pure Zap | ~35 | 112 | 1 |
| Zax V2 | ~57 | 72 | 2 |
| Zax V1 | ~65 | 160 | 2 |
💡 V2 uses 55% less memory than V1 by storing only fields instead of the entire logger object.
📋 Full Benchmark Results
pkg: github.com/yuseferi/zax/v2
BenchmarkLoggingWithOnlyZap-10 103801226 35.56 ns/op 112 B/op 1 allocs/op
BenchmarkLoggingWithOnlyZap-10 98576570 35.56 ns/op 112 B/op 1 allocs/op
BenchmarkLoggingWithOnlyZap-10 100000000 35.24 ns/op 112 B/op 1 allocs/op
BenchmarkLoggingWithOnlyZap-10 100000000 34.85 ns/op 112 B/op 1 allocs/op
BenchmarkLoggingWithOnlyZap-10 100000000 34.98 ns/op 112 B/op 1 allocs/op
BenchmarkLoggingWithZaxV2-10 64324434 56.02 ns/op 72 B/op 2 allocs/op
BenchmarkLoggingWithZaxV2-10 63939517 56.98 ns/op 72 B/op 2 allocs/op
BenchmarkLoggingWithZaxV2-10 63374052 57.60 ns/op 72 B/op 2 allocs/op
BenchmarkLoggingWithZaxV2-10 63417358 57.37 ns/op 72 B/op 2 allocs/op
BenchmarkLoggingWithZaxV2-10 57964246 57.97 ns/op 72 B/op 2 allocs/op
BenchmarkLoggingWithZaxV1-10 54062712 66.40 ns/op 160 B/op 2 allocs/op
BenchmarkLoggingWithZaxV1-10 53155524 65.61 ns/op 160 B/op 2 allocs/op
BenchmarkLoggingWithZaxV1-10 54428521 64.19 ns/op 160 B/op 2 allocs/op
BenchmarkLoggingWithZaxV1-10 55420744 64.28 ns/op 160 B/op 2 allocs/op
BenchmarkLoggingWithZaxV1-10 55199061 64.50 ns/op 160 B/op 2 allocs/op
PASS
ok github.com/yuseferi/zax/v2 56.919s
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
# 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 runThis 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!