Skip to content

yuseferi/zax

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

40 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

⚑ Zax

Context-Aware Logging for Go with Uber's Zap

Go Version Go Reference codecov Go Report Card License: AGPL v3 GitHub release

CodeQL Check & Build


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

About

Golang Zap logger with context

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •  

Languages