/

Metadata Context

Metadata context allows you to attach key-value pairs to log entries, providing essential information for debugging and understanding log events. This is the primary mechanism for adding structured data to your logs.

Context Basics

Use WithContext() to attach metadata to individual log entries:

Go
import "github.com/bytehide/bytehide-logs-go"

// Single context value
logs.WithContext("userId", "user_123").
    Info("User logged in")

// Multiple context values
logs.WithContext("userId", "user_123").
    WithContext("email", "john@example.com").
    WithContext("ipAddress", "192.168.1.1").
    Info("User authentication successful")

// Different data types
logs.WithContext("orderId", "ORD-456").
    WithContext("amount", 99.99).
    WithContext("itemCount", 3).
    WithContext("paid", true).
    Info("Order processed")

Global Metadata Context

Use AddMetaContext() to add metadata that persists across multiple log entries:

Go
import "github.com/bytehide/bytehide-logs-go"

// Add once at request start
func init() {
    logs.AddMetaContext("appVersion", "1.0.0")
    logs.AddMetaContext("environment", "production")
    logs.AddMetaContext("region", "us-east-1")
}

// Automatically included in all logs
logs.Info("User login")
// Logged with: {appVersion: 1.0.0, environment: production, region: us-east-1, message: User login}

logs.Debug("Cache hit")
// Logged with: {appVersion: 1.0.0, environment: production, region: us-east-1, message: Cache hit}

Practical Examples

HTTP Request Handling

Go
func (h *OrderHandler) HandleCreateOrder(w http.ResponseWriter, r *http.Request) {
    // Log request details
    logs.WithContext("method", r.Method).
        WithContext("path", r.URL.Path).
        WithContext("remoteAddr", r.RemoteAddr).
        WithContext("userAgent", r.Header.Get("User-Agent")).
        Debug("Incoming request")
    
    // Parse request body
    var req CreateOrderRequest
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        logs.WithContext("path", r.URL.Path).
            WithContext("error", err.Error()).
            Error("Failed to decode request", err)
        http.Error(w, "Bad Request", http.StatusBadRequest)
        return
    }
    
    // Log parsed request context
    logs.WithContext("customerId", req.CustomerID).
        WithContext("itemCount", len(req.Items)).
        WithContext("total", req.Total).
        Debug("Request parsed successfully")
    
    // Process order
    order, err := h.service.CreateOrder(&req)
    if err != nil {
        logs.WithContext("customerId", req.CustomerID).
            WithContext("itemCount", len(req.Items)).
            Error("Order creation failed", err)
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }
    
    // Log response context
    logs.WithContext("orderId", order.ID).
        WithContext("customerId", req.CustomerID).
        WithContext("total", order.Total).
        WithContext("status", order.Status).
        Info("Order created successfully")
    
    // Return response
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(order)
}

Database Operations

Go
func (r *OrderRepository) GetOrdersByCustomer(ctx context.Context, customerId string) ([]Order, error) {
    logs.WithContext("customerId", customerId).
        Trace("Querying orders by customer")
    
    query := "SELECT id, customer_id, total, status, created_at FROM orders WHERE customer_id = $1 ORDER BY created_at DESC"
    
    rows, err := r.db.QueryContext(ctx, query, customerId)
    if err != nil {
        logs.WithContext("customerId", customerId).
            WithContext("query", "GetOrdersByCustomer").
            Error("Query execution failed", err)
        return nil, err
    }
    defer rows.Close()
    
    var orders []Order
    for rows.Next() {
        var order Order
        err := rows.Scan(&order.ID, &order.CustomerID, &order.Total, &order.Status, &order.CreatedAt)
        if err != nil {
            logs.WithContext("customerId", customerId).
                Error("Failed to scan row", err)
            return nil, err
        }
        orders = append(orders, order)
    }
    
    if err = rows.Err(); err != nil {
        logs.WithContext("customerId", customerId).
            Error("Row iteration failed", err)
        return nil, err
    }
    
    logs.WithContext("customerId", customerId).
        WithContext("orderCount", len(orders)).
        Trace("Orders retrieved successfully")
    
    return orders, nil
}

Service Layer with Context

Go
type PaymentService struct {
    gateway PaymentGateway
    repo    PaymentRepository
}

func (s *PaymentService) ProcessPayment(ctx context.Context, orderId string, amount float64) (string, error) {
    logs.WithContext("orderId", orderId).
        WithContext("amount", amount).
        Debug("Processing payment")
    
    // Validate amount
    if amount <= 0 {
        logs.WithContext("orderId", orderId).
            WithContext("amount", amount).
            Warn("Invalid payment amount")
        return "", fmt.Errorf("invalid amount: %f", amount)
    }
    
    // Create charge request
    start := time.Now()
    transactionId, err := s.gateway.Charge(ctx, amount)
    duration := time.Since(start)
    
    if err != nil {
        logs.WithContext("orderId", orderId).
            WithContext("amount", amount).
            WithContext("duration_ms", duration.Milliseconds()).
            Error("Payment gateway request failed", err)
        return "", err
    }
    
    logs.WithContext("orderId", orderId).
        WithContext("amount", amount).
        WithContext("transactionId", transactionId).
        WithContext("duration_ms", duration.Milliseconds()).
        Debug("Payment gateway charge succeeded")
    
    // Save transaction
    transaction := &Transaction{
        ID:         transactionId,
        OrderID:    orderId,
        Amount:     amount,
        Status:     "completed",
        ProcessedAt: time.Now(),
    }
    
    err = s.repo.Save(transaction)
    if err != nil {
        logs.WithContext("orderId", orderId).
            WithContext("transactionId", transactionId).
            WithContext("amount", amount).
            Error("Failed to save transaction", err)
        return "", err
    }
    
    logs.WithContext("orderId", orderId).
        WithContext("transactionId", transactionId).
        WithContext("amount", amount).
        Info("Payment processed and recorded successfully")
    
    return transactionId, nil
}

Cache Operations

Go
type CacheService struct {
    cache Cache
}

func (s *CacheService) GetOrFetch(ctx context.Context, key string, fetcher func() (interface{}, error)) (interface{}, error) {
    logs.WithContext("key", key).
        Trace("Checking cache")
    
    // Try to get from cache
    value, err := s.cache.Get(key)
    if err == nil {
        logs.WithContext("key", key).
            WithContext("source", "cache").
            Trace("Cache hit")
        return value, nil
    }
    
    logs.WithContext("key", key).
        WithContext("source", "fetch").
        Debug("Cache miss, fetching value")
    
    // Fetch fresh value
    start := time.Now()
    value, err = fetcher()
    duration := time.Since(start)
    
    if err != nil {
        logs.WithContext("key", key).
            WithContext("duration_ms", duration.Milliseconds()).
            Error("Failed to fetch value", err)
        return nil, err
    }
    
    logs.WithContext("key", key).
        WithContext("duration_ms", duration.Milliseconds()).
        Debug("Value fetched successfully")
    
    // Store in cache
    err = s.cache.Set(key, value, 24*time.Hour)
    if err != nil {
        logs.WithContext("key", key).
            Warn("Failed to cache value")
        // Don't return error - value was fetched successfully
    }
    
    logs.WithContext("key", key).
        WithContext("ttlHours", 24).
        Trace("Value cached")
    
    return value, nil
}

Performance Monitoring

Go
type PerformanceMonitor struct {
    threshold time.Duration
}

func (pm *PerformanceMonitor) MeasureOperation(name string, operation func() error) error {
    start := time.Now()
    
    logs.WithContext("operation", name).
        Trace("Operation started")
    
    err := operation()
    
    duration := time.Since(start)
    durationMs := duration.Milliseconds()
    
    if err != nil {
        logs.WithContext("operation", name).
            WithContext("duration_ms", durationMs).
            WithContext("status", "error").
            Error("Operation failed", err)
        return err
    }
    
    if duration > pm.threshold {
        logs.WithContext("operation", name).
            WithContext("duration_ms", durationMs).
            WithContext("threshold_ms", pm.threshold.Milliseconds()).
            WithContext("status", "slow").
            Warn("Operation exceeded threshold")
    } else {
        logs.WithContext("operation", name).
            WithContext("duration_ms", durationMs).
            WithContext("status", "success").
            Debug("Operation completed")
    }
    
    return nil
}

Error Context with Recovery

Go
func (h *Handler) SafeExecute(operation func() error) error {
    operationName := "unknown"
    startTime := time.Now()
    
    defer func() {
        if r := recover(); r != nil {
            duration := time.Since(startTime)
            logs.WithContext("operation", operationName).
                WithContext("duration_ms", duration.Milliseconds()).
                WithContext("panic", fmt.Sprintf("%v", r)).
                Critical("Panic occurred during operation")
        }
    }()
    
    logs.WithContext("operation", operationName).
        Trace("Executing operation")
    
    err := operation()
    if err != nil {
        duration := time.Since(startTime)
        logs.WithContext("operation", operationName).
            WithContext("duration_ms", duration.Milliseconds()).
            WithContext("error", err.Error()).
            Error("Operation failed", err)
        return err
    }
    
    duration := time.Since(startTime)
    logs.WithContext("operation", operationName).
        WithContext("duration_ms", duration.Milliseconds()).
        WithContext("status", "success").
        Debug("Operation completed successfully")
    
    return nil
}

Context Value Types

ByteHide Logger supports various data types in context:

Go
// Strings
logs.WithContext("userId", "user_123").Info("msg")

// Numbers
logs.WithContext("count", 42).Info("msg")
logs.WithContext("amount", 99.99).Info("msg")
logs.WithContext("duration_ms", 1500).Info("msg")

// Booleans
logs.WithContext("success", true).Info("msg")
logs.WithContext("cached", false).Info("msg")

// Dates/Times
logs.WithContext("timestamp", time.Now()).Info("msg")
logs.WithContext("createdAt", createdTime).Info("msg")

// Collections (as strings)
logs.WithContext("tags", "[tag1,tag2,tag3]").Info("msg")
logs.WithContext("ids", "[1,2,3,4,5]").Info("msg")

Best Practices

Context Best Practices

  • Use consistent key names - standardize across your codebase
  • Include business context - orderId, userId, transactionId
  • Include technical context - duration_ms, error, status
  • Avoid sensitive data - use data masking for passwords, tokens
  • Use meaningful values - provide context that aids debugging
  • Limit context size - don't add excessive key-value pairs
  • Use proper data types - numbers as numbers, booleans as booleans

Naming Conventions

Adopt consistent naming:

  • IDs: userId, orderId, transactionId
  • Durations: duration_ms, executionTime_ms
  • Counts: itemCount, pageSize
  • Status: status, statusCode, httpStatus
  • References: customerId, productId, accountId

Next Steps

Previous
Duplicate Suppression