zax

package module
v2.3.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 25, 2025 License: GPL-3.0 Imports: 3 Imported by: 5

README ΒΆ

⚑ Zax

Context-Aware Logging for Go with Uber's Zap

Go Version Go Reference codecov Go Report Card GitHub release

CodeQL


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


🎯 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 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

πŸ“¦ Installation

go get -u github.com/yuseferi/zax/v2

Requirements: Go 1.21 or higher

πŸš€ 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
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.

traceField := zax.GetField(ctx, "trace_id")
fmt.Println(traceField.String) // "my-trace-id"
GetSugared(ctx) []interface{}

Returns fields as key-value pairs for SugaredLogger.

sugar := logger.Sugar()
sugar.With(zax.GetSugared(ctx)...).Info("sugared log")

πŸ”₯ 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:

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

🀝 Contributing

We ❀️ contributions! Here's how you can help:

  1. 🍴 Fork the repository
  2. 🌿 Create a feature branch (git checkout -b feature/amazing-feature)
  3. πŸ’» Commit your changes (git commit -m 'Add amazing feature')
  4. πŸ“€ Push to the branch (git push origin feature/amazing-feature)
  5. πŸŽ‰ 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!

Report Bug β€’ Request Feature

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Append ΒΆ added in v2.2.0

func Append(ctx context.Context, fields []zap.Field) context.Context

Append appending passed fields to the existing fields in context. it's recommended to use Append when you want to append some fields and do not lose the already added fields to context.

func Get ΒΆ

func Get(ctx context.Context) []zap.Field

Get zap stored fields from context

func GetField ΒΆ added in v2.3.0

func GetField(ctx context.Context, key string) (field zap.Field)

GetField Get a specific zap stored field from context by key

func GetSugared ΒΆ added in v2.3.4

func GetSugared(ctx context.Context) []interface{}

GetSugared converts zap.Fields stored in context to key-value pairs compatible with zap.SugaredLogger.With(...). beat in Mind in Sugar version it's limited to String, Bool, Int, Error and Interface.

func Set ΒΆ

func Set(ctx context.Context, fields []zap.Field) context.Context

Set Add passed fields in context

Types ΒΆ

type Key ΒΆ

type Key string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL