fields

package
v1.19.2 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2026 License: MIT Imports: 5 Imported by: 0

README

Logger Fields

Go Version License Coverage

Thread-safe, context-aware structured logging fields management system providing seamless integration with logrus and Go's standard context package, with zero external dependencies beyond standard library and internal golib packages.


Table of Contents


Overview

The fields package provides a thread-safe, context-aware wrapper for managing structured logging fields. It bridges Go's standard context.Context with logrus.Fields, enabling powerful field management capabilities while maintaining full compatibility with both ecosystems.

Design Philosophy
  1. Context Integration: Full implementation of context.Context for seamless lifecycle management
  2. Type Safety: Generic-based implementation ensuring type-safe operations with flexible value types
  3. Immutability Options: Support for both mutable operations (Add, Delete) and immutable patterns (Clone)
  4. Zero Dependencies: Only Go stdlib, logrus, and internal golib/context package
  5. Production-Ready: 95.7% test coverage, 114 specs, zero race conditions detected
Key Features
  • Context.Context Implementation: Full context lifecycle and cancellation support
  • Thread-Safe Operations: Atomic operations for read access, caller-synchronized writes
  • Logrus Integration: Bidirectional conversion with logrus.Fields
  • JSON Serialization: Built-in marshaling/unmarshaling for persistence
  • Flexible Operations: Add, Delete, Get, Merge, Walk, Map, Clone
  • Comprehensive Testing: 114 Ginkgo specs + 22 runnable examples
  • Race Detector Clean: All tests pass with -race flag (0 data races)

Architecture

Component Diagram
┌──────────────────────────────────────────────────────────┐
│                   Application Layer                      │
│         (Logging code using logrus/context)              │
└───────────────────────────┬──────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────┐
│                    Fields Interface                      │
│  ┌────────────────────────────────────────────────────┐  │
│  │  context.Context Implementation                    │  │
│  │  • Deadline() - timeout management                 │  │
│  │  • Done() - cancellation channel                   │  │
│  │  • Err() - cancellation error                      │  │
│  │  • Value() - context value retrieval               │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Field Management Operations                       │  │
│  │  • Add/Store - insert or update                    │  │
│  │  • Get/LoadOrStore - retrieve with defaults        │  │
│  │  • Delete/LoadAndDelete - remove entries           │  │
│  │  • Walk/WalkLimit - iterate over entries           │  │
│  │  • Map - transform all values                      │  │
│  │  • Merge - combine multiple Fields                 │  │
│  │  • Clone - create independent copy                 │  │
│  │  • Clean - remove all entries                      │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Integration Layer                                 │  │
│  │  • Logrus() - convert to logrus.Fields             │  │
│  │  • MarshalJSON/UnmarshalJSON - persistence         │  │
│  └────────────────────────────────────────────────────┘  │
└───────────────────────────┬──────────────────────────────┘
                            │
                            ▼
┌──────────────────────────────────────────────────────────┐
│        github.com/nabbar/golib/context.Config[string]    │
│           (Thread-safe key-value storage)                │
└──────────────────────────────────────────────────────────┘
Data Flow

Field Addition:

Application.Add("key", "value")
    → Fields.Add()
        → context.Config[string].Store()  # Thread-safe storage
        → return Fields                   # Enable method chaining

Logrus Conversion:

Application.Logrus()
    → Fields.Logrus()
        → Walk all key-value pairs
        → Build logrus.Fields map
        → return map

Context Propagation:

Application creates Fields with context
    → Fields wraps context.Config[string]
        → context.Config wraps parent context.Context
            → Cancellation propagates through chain

Key Design Points:

  • All individual operations use context.Config[string] thread-safe operations (sync.Map)
  • Read operations (Get, Logrus, Walk) are thread-safe for concurrent access
  • Single write operations (Add, Delete, LoadOrStore) are thread-safe atomic operations
  • Composite operations (Map, Merge, Clean) require external synchronization for concurrent use
  • Clone creates independent instances for parallel modification
Thread Safety Model
Operation Mechanism Guarantee
Read operations sync.Map Load() Thread-safe, concurrent reads allowed
Single writes sync.Map Store/Delete Thread-safe, atomic operations
Composite operations Walk + Store sequence Not atomic, caller must synchronize
Clone Deep copy Creates independent instance
Logrus() Walk + build map Thread-safe read, creates new map
Context methods Delegated Inherits parent context thread-safety

Memory Model:

  • Underlying sync.Map provides happens-before relationships for individual operations
  • Add, Delete, Get, LoadOrStore, LoadAndDelete are thread-safe atomic operations
  • Map, Merge, Clean are composite operations requiring external synchronization for concurrent use

Performance

Benchmarks

Results from typical usage scenarios (AMD Ryzen 9 7900X3D):

Field Operations
Operation Time/op Throughput Notes
Add field ~50 ns 20M ops/s Single field addition
Get field ~30 ns 33M ops/s Single field retrieval
Logrus conversion ~200 ns 5M ops/s For 10 fields
Clone ~500 ns 2M ops/s For 10 fields
JSON Marshal ~800 ns 1.25M ops/s For 10 fields
Memory Operations
Operation Allocations Memory Notes
New() 1 alloc ~120 bytes Initial allocation
Add() 0 allocs Stack-based After initialization
Logrus() 1 alloc Variable Creates new map
Clone() 1 alloc ~120 bytes New instance

Key Insights:

  • Low Overhead: Field operations take <100ns typically
  • Minimal Allocations: Most operations are stack-based after initialization
  • Scalability: Performance remains consistent with field count
  • Thread-Safe Reads: Concurrent Get operations scale linearly with cores
Memory Usage
Component Size Notes
Fields wrapper ~120 bytes Fixed size per instance
context.Config ~80 bytes Internal storage
Per field entry ~40 bytes Key + value overhead
Total (10 fields) ~600 bytes Typical usage

Memory characteristics:

  • Fixed overhead per Fields instance (~120 bytes)
  • Linear growth with number of fields
  • No memory leaks (proper cleanup on context cancellation)
  • Suitable for high-volume applications (millions of Fields instances)
Scalability

Concurrent Operations:

  • ✅ Multiple goroutines can read concurrently (Get, Logrus, Walk)
  • ✅ Multiple goroutines can write concurrently (Add, Delete, LoadOrStore, LoadAndDelete)
  • ⚠️ Composite operations (Map, Merge, Clean) require external synchronization for concurrent use
  • ✅ Clone creates independent instances safe for parallel modification
  • ✅ Tested with stress test: 10 goroutines × 50 operations each

Performance Characteristics:

  • Read operations scale linearly with CPU cores
  • Write operations (Add/Delete) scale linearly with CPU cores (sync.Map)
  • No lock contention for individual operations
  • Composite operations (Map/Merge/Clean) may require serialization
  • Recommendation: Use Clone() for concurrent composite operations

Use Cases

1. Structured Logging with Context

Problem: Maintain consistent structured logging fields across request lifecycle.

Solution: Create Fields instance at request start, propagate through context.

Advantages:

  • Automatic field inheritance through call stack
  • Context-aware logging with cancellation support
  • Thread-safe field access for concurrent operations
  • Easy field transformation and filtering

Suited for: Web applications, API servers, microservices requiring distributed tracing and structured logging.

2. Request Context Enrichment

Problem: Attach metadata to request contexts for distributed tracing.

Solution: Wrap request context with Fields, add trace/span IDs and metadata.

Advantages:

  • Full context.Context compatibility
  • Seamless integration with middleware
  • Field isolation per request
  • Easy propagation to downstream services

Suited for: HTTP servers, gRPC services, message queue consumers requiring correlation IDs and request tracking.

3. Multi-Stage Processing Pipeline

Problem: Track context and metadata through multiple processing stages.

Solution: Clone base Fields for each stage, merge results at completion.

Advantages:

  • Stage isolation with Clone()
  • Merge capabilities for aggregation
  • Context cancellation propagates through stages
  • Field transformation per stage (Map operation)

Suited for: ETL pipelines, data processing workflows, batch jobs requiring stage-level metadata.

4. Hierarchical Logging Configuration

Problem: Maintain base logging fields with request-specific overrides.

Solution: Create base Fields with service metadata, clone and enhance per request.

Advantages:

  • Base field reuse across requests
  • Request-specific field addition without affecting base
  • Memory efficient (shared base, cloned per-request)
  • Clean separation of concerns

Suited for: Multi-tenant applications, service meshes, application platforms with hierarchical configuration.

5. Audit Trail and Compliance Logging

Problem: Log structured audit data with consistent fields and serialization.

Solution: Use Fields for audit entries, JSON serialize for storage/transmission.

Advantages:

  • Built-in JSON marshaling for persistence
  • Consistent field structure enforcement
  • Easy field transformation for sanitization (Map)
  • Thread-safe for concurrent audit logging

Suited for: Financial systems, healthcare applications, compliance-heavy industries requiring detailed audit trails.


Quick Start

Installation
go get github.com/nabbar/golib/logger/fields

Requirements:

  • Go 1.24 or higher (generics support required)
  • Compatible with Linux, macOS, Windows
Basic Field Creation

Create and populate fields:

package main

import (
    "context"
    "fmt"
    
    "github.com/nabbar/golib/logger/fields"
)

func main() {
    // Create new Fields instance
    flds := fields.New(context.Background())
    
    // Add fields with method chaining
    flds.Add("service", "api").
        Add("version", "1.0.0").
        Add("env", "production")
    
    // Retrieve a field
    if val, ok := flds.Get("service"); ok {
        fmt.Printf("Service: %v\n", val)
    }
    
    // Get all fields count
    fmt.Printf("Total fields: %d\n", len(flds.Logrus()))
}
Logrus Integration

Use with logrus logger:

package main

import (
    "context"
    
    "github.com/nabbar/golib/logger/fields"
    "github.com/sirupsen/logrus"
)

func main() {
    logger := logrus.New()
    
    // Create fields
    flds := fields.New(context.Background())
    flds.Add("request_id", "req-123")
    flds.Add("user_id", 456)
    flds.Add("action", "login")
    
    // Convert to logrus.Fields and log
    logger.WithFields(flds.Logrus()).Info("User action recorded")
}
Context Propagation

Use Fields as context.Context:

package main

import (
    "context"
    "time"
    
    "github.com/nabbar/golib/logger/fields"
)

func main() {
    // Create context with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    // Create Fields with context
    flds := fields.New(ctx)
    flds.Add("trace_id", "xyz789")
    
    // Use Fields as context.Context
    select {
    case <-flds.Done():
        println("Context cancelled or timed out")
    case <-time.After(1 * time.Second):
        println("Operation completed")
    }
    
    // Check for errors
    if err := flds.Err(); err != nil {
        println("Context error:", err.Error())
    }
}
Field Transformation

Transform field values:

package main

import (
    "context"
    "fmt"
    "strings"
    
    "github.com/nabbar/golib/logger/fields"
)

func main() {
    flds := fields.New(context.Background())
    flds.Add("name", "john")
    flds.Add("city", "paris")
    flds.Add("password", "secret123")
    
    // Transform all values (e.g., sanitize)
    flds.Map(func(key string, val interface{}) interface{} {
        // Redact sensitive fields
        if key == "password" {
            return "[REDACTED]"
        }
        
        // Uppercase string values
        if str, ok := val.(string); ok {
            return strings.ToUpper(str)
        }
        
        return val
    })
    
    // Print results
    for k, v := range flds.Logrus() {
        fmt.Printf("%s: %v\n", k, v)
    }
}
Multi-Source Aggregation

Combine fields from multiple sources:

package main

import (
    "context"
    "fmt"
    
    "github.com/nabbar/golib/logger/fields"
)

func main() {
    // System-level fields
    sysFields := fields.New(context.Background())
    sysFields.Add("hostname", "server-01")
    sysFields.Add("pid", 12345)
    
    // Application-level fields
    appFields := fields.New(context.Background())
    appFields.Add("app_name", "auth-service")
    appFields.Add("app_version", "3.0.0")
    
    // Request-level fields
    reqFields := fields.New(context.Background())
    reqFields.Add("request_id", "req-xyz")
    reqFields.Add("method", "POST")
    
    // Merge all sources
    combined := sysFields.Clone()
    combined.Merge(appFields)
    combined.Merge(reqFields)
    
    fmt.Printf("Combined fields: %d\n", len(combined.Logrus()))
    
    // Log with all context
    // logger.WithFields(combined.Logrus()).Info("Request processed")
}

Best Practices

Do's ✅

Create Fields early in request lifecycle:

func handleRequest(ctx context.Context) {
    flds := fields.New(ctx)
    flds.Add("request_id", generateID())
    flds.Add("timestamp", time.Now())
    
    // Pass to downstream functions
    processRequest(flds)
}

Use Clone() for derived contexts:

baseFields := fields.New(ctx)
baseFields.Add("service", "api")

// Create independent copy for request
requestFields := baseFields.Clone()
requestFields.Add("request_id", "123")
// baseFields remains unchanged

Use method chaining for readability:

flds := fields.New(ctx).
    Add("service", "api").
    Add("version", "1.0").
    Add("env", "prod")

Sanitize sensitive data with Map:

flds.Map(func(key string, val interface{}) interface{} {
    if key == "password" || key == "token" {
        return "[REDACTED]"
    }
    return val
})
Don'ts ❌

Don't use concurrent composite operations:

// ✅ GOOD: Concurrent Add operations are safe
flds := fields.New(ctx)
go func() { flds.Add("key1", "value1") }()  // Safe
go func() { flds.Add("key2", "value2") }()  // Safe

// ❌ BAD: Concurrent Map/Merge operations need sync
go func() { flds.Map(transformFunc) }()      // Race!
go func() { flds.Merge(otherFields) }()      // Race!

// ✅ GOOD: Use Clone() for composite operations
for i := 0; i < 10; i++ {
    go func(id int) {
        localFields := baseFields.Clone()
        localFields.Add("goroutine_id", id).Map(transformFunc)
    }(i)
}

Don't mutate retrieved maps:

// ❌ BAD: Mutating returned map
logrusFields := flds.Logrus()
logrusFields["new_key"] = "value"  // Doesn't affect flds

// ✅ GOOD: Use Add() method
flds.Add("new_key", "value")

Don't forget context cancellation:

// ❌ BAD: No cancellation
flds := fields.New(context.Background())

// ✅ GOOD: Proper context lifecycle
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel()
flds := fields.New(ctx)

Don't store large objects as values:

// ❌ BAD: Large value
flds.Add("data", largeByteArray)  // Megabytes of data

// ✅ GOOD: Store reference or ID
flds.Add("data_id", dataID)
flds.Add("data_size", len(largeByteArray))
Testing

For comprehensive testing information, see TESTING.md.

Quick testing overview:

  • Framework: Ginkgo v2 + Gomega (BDD-style)
  • Coverage: 95.7% (114 specs + 22 examples)
  • Concurrency: All tests pass with -race detector (0 races)
  • Performance: Validated <100ns overhead for typical operations

Run tests:

# Basic tests
go test ./...

# With coverage
go test -cover ./...

# With race detector (requires CGO_ENABLED=1)
CGO_ENABLED=1 go test -race ./...

# Run examples
go test -v -run Example

API Reference

Interfaces
Fields

Core interface providing field management and context integration:

type Fields interface {
    context.Context        // Full context.Context implementation
    json.Marshaler        // JSON serialization support
    json.Unmarshaler      // JSON deserialization support
    
    // Field Management
    Add(key string, val interface{}) Fields
    Store(key string, val interface{})
    Delete(key string) Fields
    Get(key string) (val interface{}, ok bool)
    Clean()
    
    // Advanced Operations
    Clone() Fields
    Merge(f Fields) Fields
    Map(fct func(key string, val interface{}) interface{}) Fields
    
    // Iteration
    Walk(fct libctx.FuncWalk[string]) Fields
    WalkLimit(fct libctx.FuncWalk[string], validKeys ...string) Fields
    
    // Atomic Operations
    LoadOrStore(key string, cfg interface{}) (val interface{}, loaded bool)
    LoadAndDelete(key string) (val interface{}, loaded bool)
    
    // Integration
    Logrus() logrus.Fields
}
Constructors

New(ctx context.Context) Fields

  • Creates a new Fields instance with the given context
  • Returns Fields interface wrapping context.Config[string]
  • Nil context is handled gracefully (creates background context)
  • Memory: ~120 bytes per instance
  • Thread-safe for concurrent reads after creation
Operations

Field Management:

  • Add(key, val) - Insert or update field, returns self for chaining
  • Store(key, val) - Insert or update field without return (direct storage)
  • Delete(key) - Remove field, returns self for chaining
  • Get(key) - Retrieve field value with existence check
  • Clean() - Remove all fields, preserves context

Advanced Operations:

  • Clone() - Create independent deep copy
  • Merge(fields) - Combine fields from another instance
  • Map(func) - Transform all values using callback
  • Walk(func) - Iterate all fields with callback
  • WalkLimit(func, keys...) - Iterate specific fields only

Atomic Operations:

  • LoadOrStore(key, val) - Get existing or store new atomically
  • LoadAndDelete(key) - Get and remove atomically

Integration:

  • Logrus() - Convert to logrus.Fields (creates new map)
  • MarshalJSON() - Serialize to JSON
  • UnmarshalJSON(data) - Deserialize from JSON (merges with existing)

Context Methods:

  • Deadline() - Get context deadline
  • Done() - Get cancellation channel
  • Err() - Get cancellation error
  • Value(key) - Get context value
Error Handling

The package uses transparent error propagation:

  • No package-specific errors
  • All errors from underlying context or JSON marshaling
  • Nil Fields instances handled gracefully (return nil or empty)
  • Type assertions on Get() values are caller's responsibility
  • JSON errors propagated unchanged

Contributing

Contributions are welcome! Please follow these guidelines:

Code Quality
  • Follow Go best practices and idioms
  • Maintain or improve code coverage (target: >80%)
  • Pass all tests including race detector
  • Use gofmt and golint
AI Usage Policy
  • AI must NEVER be used to generate package code or core functionality
  • AI assistance is limited to:
    • Testing (writing and improving tests)
    • Debugging (troubleshooting and bug resolution)
    • Documentation (comments, README, TESTING.md)
  • All AI-assisted work must be reviewed and validated by humans
Testing Requirements
  • Add tests for new features
  • Use Ginkgo v2 / Gomega for test framework
  • Ensure zero race conditions with -race flag
  • Update examples for new functionality
Documentation Requirements
  • Update GoDoc comments for public APIs
  • Add runnable examples for new features
  • Update README.md and TESTING.md if needed
  • Include usage examples in doc.go
Pull Request Process
  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Write clear commit messages
  4. Ensure all tests pass (go test -race ./...)
  5. Update documentation
  6. Submit PR with description of changes

Improvements & Security

Current Status

The package is production-ready with no urgent improvements or security vulnerabilities identified.

Code Quality Metrics
  • 95.7% test coverage (target: >80%)
  • Zero race conditions detected with -race flag
  • Thread-safe for concurrent reads
  • Memory-safe with proper nil handling
  • Context-aware with proper lifecycle management
Security Considerations

No Security Vulnerabilities Identified:

  • No external dependencies (only Go stdlib + golib internals + logrus)
  • No network operations or file system access
  • No cryptographic operations
  • Thread-safe design prevents race conditions

Best Practices Applied:

  • Defensive nil checks in all methods
  • Proper context cancellation handling
  • No panic propagation (all panics from underlying implementations)
  • Type-safe operations with generics
Future Enhancements (Non-urgent)

The following enhancements could be considered for future versions:

  1. Callback Notifications: Optional callbacks on field modifications for observability
  2. Field Validation: Built-in validation rules for field values
  3. Metrics Integration: Optional Prometheus/OpenTelemetry integration
  4. Field Immutability: Explicit immutable Fields variant

These are optional improvements and not required for production use. The current implementation is stable and performant.


Resources

Package Documentation
  • GoDoc - Complete API reference with function signatures, method descriptions, and runnable examples. Essential for understanding the public interface and usage patterns.

  • doc.go - In-depth package documentation including design philosophy, architecture diagrams, advantages and limitations, typical use cases, and comprehensive usage examples. Provides detailed explanations of thread-safety mechanisms and performance characteristics.

  • TESTING.md - Comprehensive test suite documentation covering test architecture, BDD methodology with Ginkgo v2, coverage analysis (95.7%), concurrency testing, and guidelines for writing new tests. Includes troubleshooting and bug reporting templates.

  • github.com/nabbar/golib/context - Thread-safe context storage implementation used internally. Provides generic-based key-value storage with context integration. The fields package uses context.Config[string] for internal storage.

  • github.com/sirupsen/logrus - Structured logger for Go. The fields package provides seamless integration through logrus.Fields conversion, enabling structured logging with field inheritance.

External References
  • context Package - Standard library context documentation. The fields package fully implements context.Context for lifecycle management and cancellation propagation.

  • encoding/json Package - Standard library JSON encoding. The fields package implements json.Marshaler and json.Unmarshaler for persistence and network transmission.

  • Go Concurrency Patterns - Official Go blog article on concurrency patterns. Relevant for understanding safe concurrent usage of Fields instances.

  • Effective Go - Official Go programming guide covering best practices for interfaces, error handling, and concurrency. The fields package follows these conventions for idiomatic Go code.

  • Go Memory Model - Official specification of Go's memory consistency guarantees. Critical for understanding the thread-safety guarantees provided by the fields package.


AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2025 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/logger/fields
Version: See releases for versioning

Documentation

Overview

Package fields provides a thread-safe, context-aware structured logging fields management system that seamlessly integrates with logrus and Go's standard context package.

Design Philosophy

The fields package is designed around three core principles:

1. Context Integration: Full implementation of context.Context interface for seamless integration with Go's cancellation and deadline propagation mechanisms.

2. Type Safety: Generic-based implementation ensuring type-safe operations while maintaining flexibility for any value type through interface{}.

3. Immutability Options: Support for both mutable operations (Add, Delete, Map) and immutable patterns (Clone) to suit different use cases.

Architecture

The package consists of three main components:

1. Fields Interface: Public API providing logging field operations, context methods, and JSON serialization capabilities.

2. fldModel Implementation: Internal structure wrapping github.com/nabbar/golib/context.Config[string] for thread-safe storage and retrieval of key-value pairs.

3. Integration Layer: Bidirectional conversion between Fields and logrus.Fields for compatibility with existing logging infrastructure.

Key Features

Thread Safety: Read operations (Get, Logrus, Walk) are thread-safe for concurrent access. Single write operations (Add, Store, Delete, LoadOrStore, LoadAndDelete) are also thread-safe thanks to the underlying sync.Map implementation. Composite operations (Map, Merge, Clean) require external synchronization if used concurrently from multiple goroutines.

Context Propagation: Full context.Context implementation allows Fields to participate in Go's cancellation and deadline mechanisms, enabling proper cleanup in long-running operations.

JSON Serialization: Built-in JSON marshaling and unmarshaling support for persistence, network transmission, or configuration storage.

Flexible Operations:

  • Add/Store: Insert or update key-value pairs
  • Get/LoadOrStore: Retrieve values with optional defaults
  • Delete/LoadAndDelete: Remove entries with optional retrieval
  • Walk/WalkLimit: Iterate over entries with filtering support
  • Map: Transform all values using a custom function
  • Merge: Combine multiple Fields instances

Logrus Integration: Direct conversion to/from logrus.Fields for zero-friction integration with existing logrus-based logging systems.

Performance Characteristics

Memory Usage: O(n) where n is the number of fields stored. Each field requires storage for both key (string) and value (interface{}). The underlying sync.Map implementation adds minimal overhead.

Time Complexity:

  • Add, Get, Delete: O(1) average case (map operations)
  • Walk, WalkLimit: O(n) where n is number of fields
  • Map: O(n) with additional transformation cost
  • Clone: O(n) deep copy operation
  • Logrus: O(n) conversion to logrus.Fields
  • Merge: O(m) where m is number of fields in source

Thread Safety: Lock-free reads with atomic operations. Writes use appropriate synchronization mechanisms provided by the underlying context.Config implementation.

Use Cases

Structured Logging: Create field sets for structured log entries with consistent formatting and easy transformation.

flds := fields.New(ctx)
flds.Add("request_id", "abc123")
flds.Add("user_id", 42)
flds.Add("action", "login")
logger.WithFields(flds.Logrus()).Info("User action")

Request Context Enrichment: Attach metadata to request contexts for distributed tracing and debugging.

flds := fields.New(r.Context())
flds.Add("trace_id", traceID)
flds.Add("span_id", spanID)
newCtx := context.WithValue(r.Context(), "fields", flds)

Field Transformation: Apply transformations to all field values for sanitization, formatting, or encoding.

flds.Map(func(key string, val interface{}) interface{} {
	if key == "password" {
		return "[REDACTED]"
	}
	return val
})

Multi-Source Aggregation: Combine fields from multiple sources while maintaining immutability of originals.

base := fields.New(ctx).Add("service", "api")
request := base.Clone().Add("method", "POST")
response := request.Clone().Add("status", 200)

Configuration Serialization: Store and retrieve field configurations using JSON.

data, _ := json.Marshal(flds)
restored := fields.New(ctx)
json.Unmarshal(data, restored)

Limitations and Considerations

Context Lifetime: Fields instances hold a reference to their context. Ensure proper context cancellation to avoid resource leaks in long-running applications.

Value Types: While any type can be stored via interface{}, complex types (structs, pointers) should be used carefully. JSON serialization may not preserve all type information.

Nil Handling: The package handles nil Fields instances gracefully, returning nil or empty results. However, this behavior should not be relied upon in production code. Always check for nil before operations.

Deep Copy Behavior: Clone() performs a deep copy of the internal map but does not deep copy the values themselves. If values are pointers or references, modifications to the underlying data will affect all clones.

logrus.Fields Conversion: The Logrus() method creates a new map on each call. For performance-critical code, cache the result if multiple accesses are needed.

Walk Function Behavior: Walk and WalkLimit continue iteration until the callback returns false or all entries are processed. The iteration order is not guaranteed due to the underlying map implementation.

Best Practices

Prefer Clone for Immutability: When creating derived field sets, use Clone() to avoid unintended modifications to the original.

derived := original.Clone().Add("extra", "field")

Use WalkLimit for Selective Operations: When only specific fields are needed, use WalkLimit to improve performance.

flds.WalkLimit(func(key string, val interface{}) bool {
	// Process only specified keys
	return true
}, "request_id", "trace_id")

Context-Aware Cleanup: Always pass an appropriate context to New() for proper lifecycle management.

ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel()
flds := fields.New(ctx)

Avoid Storing Large Values: Fields are designed for metadata, not data storage. Keep values small and reference larger data structures by ID or key.

Type Assertions with Care: When retrieving values, always check type assertions to avoid panics.

if val, ok := flds.Get("key"); ok {
	if str, ok := val.(string); ok {
		// Use str safely
	}
}

Thread Safety Model

The Fields implementation provides strong thread-safety guarantees:

Thread-Safe Operations (no synchronization needed):

  • Read operations: Get, Logrus, Walk, WalkLimit
  • Single write operations: Add, Store, Delete, LoadOrStore, LoadAndDelete
  • Clone() creates independent instances safe for parallel modification

Composite Operations (require external synchronization if concurrent):

  • Map: iterates and modifies all values
  • Merge: iterates source and stores in receiver
  • Clean: removes all entries

Safe Concurrent Patterns:

// Multiple goroutines can safely call Add/Store/Delete
go func() { flds.Add("key1", "value1") }()
go func() { flds.Add("key2", "value2") }()

// For composite operations, use Clone per goroutine
go func() {
	local := flds.Clone()
	local.Map(transformFunc)
}()

Examples

See the example_test.go file for comprehensive examples demonstrating:

  • Basic field creation and manipulation
  • Integration with logrus logger
  • Context propagation and cancellation
  • JSON serialization and deserialization
  • Field transformation with Map
  • Merging multiple field sources
  • Walking and filtering operations
  • github.com/nabbar/golib/context: Underlying context management
  • github.com/sirupsen/logrus: Logging framework integration
  • encoding/json: Standard JSON serialization

Versioning and Compatibility

This package follows semantic versioning. The API is stable and backwards compatible within major versions. Breaking changes will only occur in major version increments.

Minimum Go version: 1.18 (requires generics support)

License

MIT License - See LICENSE file for details.

Copyright (c) 2025 Nicolas JUHEL

Example

Example demonstrates a typical usage pattern combining multiple operations.

package main

import (
	"context"
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	// Create base fields
	flds := fields.New(context.Background())

	// Add fields with chaining
	flds.Add("service", "api").
		Add("version", "1.0").
		Add("env", "prod")

	// Clone for specific request
	reqFields := flds.Clone()
	reqFields.Add("request_id", "12345")

	// Get logrus-compatible fields
	logFields := reqFields.Logrus()

	fmt.Println(len(logFields))

}
Output:

4

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Fields

type Fields interface {
	context.Context
	json.Marshaler
	json.Unmarshaler

	// Clone creates a deep copy of the Fields instance.
	//
	// The returned Fields instance is completely independent and modifications to either
	// the original or the clone will not affect the other. This is essential for creating
	// derived field sets without side effects.
	//
	// Note: While the internal map is deep copied, the values themselves are not. If values
	// are pointers or references, modifications to the underlying data will affect all clones.
	//
	// Returns nil if the receiver is nil.
	//
	// Example:
	//   base := fields.New(ctx).Add("service", "api")
	//   request := base.Clone().Add("request_id", "123")
	//   // base still has only "service" field
	Clone() Fields

	// Clean removes all key-value pairs from the Fields instance.
	//
	// This is useful for resetting a Fields instance to empty state while preserving
	// the underlying context. After calling Clean(), the Fields instance can be reused.
	//
	// This is a composite operation that requires external synchronization if used
	// concurrently with other operations.
	Clean()

	// Add inserts or updates a key-value pair in the Fields instance.
	//
	// If the key already exists, its value is overwritten with the new value.
	// If the key does not exist, a new key-value pair is added.
	//
	// The method returns the same Fields instance to enable method chaining.
	// This operation is thread-safe and can be called concurrently from multiple goroutines.
	//
	// Any type can be stored as a value via interface{}, but consider JSON serialization
	// compatibility if persistence is needed.
	//
	// Example:
	//   flds.Add("key1", "value1").Add("key2", 42).Add("key3", true)
	Add(key string, val interface{}) Fields

	// Delete removes the key-value pair associated with the given key.
	//
	// If the key does not exist, this is a no-op (no error is returned).
	// The method returns the same Fields instance to enable method chaining.
	// This operation is thread-safe.
	//
	// Example:
	//   flds.Delete("temp_key").Delete("another_key")
	Delete(key string) Fields

	// Merge combines all key-value pairs from the source Fields into the receiver.
	//
	// For keys that exist in both Fields instances, the source value overwrites the
	// receiver's value. The source Fields instance is not modified.
	//
	// This is a composite operation that requires external synchronization if used
	// concurrently with other operations.
	//
	// Returns the receiver to enable method chaining.
	// Returns the receiver unchanged if source is nil.
	//
	// Example:
	//   base.Merge(extra)  // Adds all fields from extra to base
	Merge(f Fields) Fields

	// Walk iterates over all key-value pairs, calling the provided function for each pair.
	//
	// The iteration continues until either:
	//   - All pairs have been visited
	//   - The callback function returns false
	//
	// The iteration order is not guaranteed due to the underlying map implementation.
	//
	// Returns the receiver to enable method chaining.
	//
	// Example:
	//   flds.Walk(func(key string, val interface{}) bool {
	//       fmt.Printf("%s: %v\n", key, val)
	//       return true  // Continue iteration
	//   })
	Walk(fct libctx.FuncWalk[string]) Fields

	// WalkLimit iterates only over the specified keys, calling the provided function for each.
	//
	// Only the keys listed in validKeys will be visited. If a listed key does not exist,
	// it is silently skipped. This is more efficient than Walk when only specific fields
	// are needed.
	//
	// Returns the receiver to enable method chaining.
	//
	// Example:
	//   flds.WalkLimit(func(key string, val interface{}) bool {
	//       // Only processes "request_id" and "trace_id"
	//       return true
	//   }, "request_id", "trace_id")
	WalkLimit(fct libctx.FuncWalk[string], validKeys ...string) Fields

	// Get retrieves the value associated with the given key.
	//
	// Returns the value and true if the key exists, or nil and false if it does not.
	// This follows the standard Go map access pattern.
	//
	// Example:
	//   if val, ok := flds.Get("key"); ok {
	//       // Use val safely
	//   }
	Get(key string) (val interface{}, ok bool)

	// Store inserts or updates a key-value pair without returning the Fields instance.
	//
	// This method is similar to Add() but doesn't return the Fields instance, making it
	// suitable for use when method chaining is not needed. It's a direct storage operation.
	//
	// This operation is thread-safe and can be called concurrently from multiple goroutines
	// thanks to the underlying sync.Map implementation.
	//
	// Example:
	//   flds.Store("config_key", configValue)
	//   flds.Store("timestamp", time.Now())
	Store(key string, cfg interface{})

	// LoadOrStore atomically retrieves or stores a value for the given key.
	//
	// If the key exists, returns the existing value and true.
	// If the key does not exist, stores the provided value and returns it with false.
	//
	// This is useful for lazy initialization patterns where a default value should be
	// set only if the key doesn't already exist.
	//
	// Returns:
	//   - val: The existing value if loaded=true, or the stored value if loaded=false
	//   - loaded: true if the key existed, false if the value was stored
	//
	// Example:
	//   val, loaded := flds.LoadOrStore("counter", 0)
	//   if !loaded {
	//       // First access, counter was initialized to 0
	//   }
	LoadOrStore(key string, cfg interface{}) (val interface{}, loaded bool)

	// LoadAndDelete atomically retrieves and removes a value for the given key.
	//
	// If the key exists, returns the value and true, and the key is deleted.
	// If the key does not exist, returns nil and false.
	//
	// This is useful for one-time operations or cleanup scenarios.
	//
	// Returns:
	//   - val: The value if it existed, nil otherwise
	//   - loaded: true if the key existed and was deleted, false otherwise
	//
	// Example:
	//   if val, existed := flds.LoadAndDelete("temp"); existed {
	//       // Process val, field is now deleted
	//   }
	LoadAndDelete(key string) (val interface{}, loaded bool)

	// Logrus returns the logrus.Fields instance associated with the current Fields instance.
	//
	// This method is useful when you want to directly access the logrus.Fields instance
	// associated with the current Fields instance.
	//
	// The returned logrus.Fields instance is a reference to the same instance as the one
	// associated with the current Fields instance. Any modification to the returned logrus.Fields
	// instance will affect the Fields instance.
	//
	// The returned logrus.Fields instance is valid until the Fields instance is modified or
	// until the Fields instance is garbage collected.
	//
	// If the Fields instance is nil, this method will return nil.
	Logrus() logrus.Fields
	// Map applies a transformation function to all key-value pairs in the Fields instance.
	//
	// The transformation function is called for each key-value pair. It takes the key
	// and value as arguments, and returns the new value to store.
	//
	// This is a composite operation that requires external synchronization if used
	// concurrently with other operations. The transformation is applied in-place.
	//
	// Example:
	//   flds.Map(func(key string, val interface{}) interface{} {
	//       if key == "password" {
	//           return "[REDACTED]"
	//       }
	//       return val
	//   })
	Map(fct func(key string, val interface{}) interface{}) Fields
}

Fields provides a thread-safe, context-aware structured logging fields management interface.

This interface combines three key capabilities:

  1. context.Context implementation for lifecycle management and cancellation propagation
  2. json.Marshaler/Unmarshaler for serialization and persistence
  3. Key-value storage with various access patterns

Thread Safety:

  • Read operations (Get, Logrus, Walk) are thread-safe for concurrent access
  • Single write operations (Add, Store, Delete, LoadOrStore, LoadAndDelete) are thread-safe thanks to the underlying sync.Map implementation
  • Composite operations (Map, Merge, Clean) require external synchronization if used concurrently
  • For concurrent composite operations, use Clone() to create independent instances per goroutine

Context Integration: Fields fully implements context.Context, allowing it to participate in Go's cancellation and deadline mechanisms. The context provided to New() determines the lifecycle of the Fields instance.

See example_test.go for comprehensive usage examples.

Example (ComplexTypes)

ExampleFields_complexTypes demonstrates handling complex data types.

package main

import (
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	flds := fields.New(nil)

	// Add various types
	flds.Add("string", "text")
	flds.Add("int", 42)
	flds.Add("float", 3.14)
	flds.Add("bool", true)
	flds.Add("slice", []int{1, 2, 3})
	flds.Add("map", map[string]string{"key": "value"})

	fmt.Printf("Total fields: %d\n", len(flds.Logrus()))

}
Output:

Total fields: 6
Example (Context)

ExampleFields_context demonstrates context integration. This shows how Fields implements context.Context interface.

package main

import (
	"context"
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	flds := fields.New(ctx)
	flds.Add("trace_id", "xyz789")

	// Fields implements context.Context
	select {
	case <-flds.Done():
		fmt.Println("Context cancelled")
	default:
		fmt.Println("Context active")
	}

}
Output:

Context active
Example (MultiSourceAggregation)

ExampleFields_multiSourceAggregation demonstrates combining fields from multiple sources. This shows a real-world scenario of aggregating metadata from different components.

package main

import (
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	// System-level fields
	sysFields := fields.New(nil)
	sysFields.Add("hostname", "server-01")
	sysFields.Add("pid", 12345)

	// Application-level fields
	appFields := fields.New(nil)
	appFields.Add("app_name", "auth-service")
	appFields.Add("app_version", "3.0.0")

	// Request-level fields
	reqFields := fields.New(nil)
	reqFields.Add("request_id", "req-xyz")
	reqFields.Add("user_agent", "Mozilla/5.0")

	// Merge all sources
	combined := sysFields.Clone()
	combined.Merge(appFields)
	combined.Merge(reqFields)

	fmt.Printf("Combined fields: %d\n", len(combined.Logrus()))

}
Output:

Combined fields: 6
Example (StructuredLogging)

ExampleFields_structuredLogging demonstrates a complete structured logging workflow. This is a comprehensive example combining multiple features.

package main

import (
	"context"
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	// Initialize base fields for the service
	baseFields := fields.New(context.Background())
	baseFields.Add("service", "user-api")
	baseFields.Add("version", "2.1.0")
	baseFields.Add("environment", "production")

	// Create request-specific fields by cloning
	requestFields := baseFields.Clone()
	requestFields.Add("request_id", "req-abc-123")
	requestFields.Add("method", "POST")
	requestFields.Add("path", "/api/users")

	// Transform sensitive data
	requestFields.Map(func(key string, val interface{}) interface{} {
		if key == "password" {
			return "[REDACTED]"
		}
		return val
	})

	// Convert to logrus format for logging
	logrusFields := requestFields.Logrus()

	fmt.Printf("Total fields: %d\n", len(logrusFields))
	fmt.Printf("Has service: %v\n", logrusFields["service"] != nil)
	fmt.Printf("Has request_id: %v\n", logrusFields["request_id"] != nil)

}
Output:

Total fields: 6
Has service: true
Has request_id: true

func New

func New(ctx context.Context) Fields

New creates a new Fields instance from the given context.Context.

It returns a new Fields instance which is associated with the given context.Context. The returned Fields instance can be used to add, remove, or modify key/val pairs.

If the given context.Context is nil, this method will return nil.

Example usage:

 flds := New(context.Background)
 flds.Add("key", "value")
 flds.Map(func(key string, val interface{}) interface{} {
	return fmt.Sprintf("%s-%s", key, val)
 })

The above example shows how to create a new Fields instance from a context.Context, and how to use the returned Fields instance to add, remove, or modify key/val pairs.

Example

ExampleNew demonstrates basic Fields creation. This is the simplest use case for creating a new Fields instance.

package main

import (
	"context"
	"fmt"

	"github.com/nabbar/golib/logger/fields"
)

func main() {
	// Create a new Fields instance with background context
	flds := fields.New(context.Background())

	// Add a simple field
	flds.Add("message", "hello")

	// Get logrus compatible fields
	fmt.Println(len(flds.Logrus()))

}
Output:

1

Jump to

Keyboard shortcuts

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