/

Fluent Syntax

ByteHide Logger's fluent syntax allows you to chain multiple configuration methods together for expressive, readable logging. This Go-idiomatic approach provides a natural way to build complex log entries.

Fluent API Overview

The fluent API uses method chaining, where each method returns a logger builder that can accept further method calls:

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

// Chain methods for expressive logging
logs.WithContext("userId", "user_123").
    WithContext("action", "login").
    WithTags("auth", "success").
    Info("User authenticated")

// Multiple tags
logs.WithTags("payment", "processing", "stripe").
    WithContext("amount", 99.99).
    WithContext("currency", "USD").
    Info("Processing payment")

// Exception handling
err := someOperation()
if err != nil {
    logs.WithException(err).
        WithTags("error", "critical").
        WithContext("operation", "dataSync").
        Error("Operation failed", err)
}

Fluent Methods

WithContext()

Add key-value pairs to the log:

Go
logs.WithContext("key1", "value1").
    WithContext("key2", "value2").
    WithContext("key3", 123).
    Info("Log message")

WithTags()

Add categorization tags (multiple tags supported):

Go
logs.WithTags("tag1").
    WithTags("tag2", "tag3").
    Info("Log message")

// Or pass multiple tags at once
logs.WithTags("payment", "processing", "critical").
    Info("Log message")

WithCorrelationID()

Set request tracking ID:

Go
logs.WithCorrelationID("req_abc123").
    Info("Request processing started")

WithException()

Add error/exception context:

Go
logs.WithException(err).
    Error("Operation failed", err)

WithUser()

Set user context:

Go
user := &logs.AuthUser{
    ID:    "user_123",
    Email: "john@example.com",
}

logs.WithUser(user).
    Info("User action")

Method Chaining Patterns

Complete Request Logging

Go
func (h *Handler) ProcessRequest(w http.ResponseWriter, r *http.Request) {
    correlationId := extractOrGenerateCorrelationID(r)
    
    logs.WithCorrelationID(correlationId).
        WithContext("method", r.Method).
        WithContext("path", r.URL.Path).
        WithTags("api", "incoming").
        Debug("Request received")
    
    // Parse request
    var req OrderRequest
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("path", r.URL.Path).
            WithTags("api", "parsing").
            WithException(err).
            Error("Failed to parse request", err)
        return
    }
    
    // Process request
    order, err := h.service.CreateOrder(&req)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("customerId", req.CustomerID).
            WithContext("itemCount", len(req.Items)).
            WithTags("order", "creation", "error").
            WithException(err).
            Error("Order creation failed", err)
        return
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", order.ID).
        WithContext("customerId", order.CustomerID).
        WithContext("total", order.Total).
        WithTags("order", "creation", "success").
        Info("Order created successfully")
}

Service Layer Pattern

Go
type PaymentService struct {
    gateway PaymentGateway
}

func (s *PaymentService) ProcessPayment(ctx context.Context, request *PaymentRequest) (string, error) {
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("amount", request.Amount).
        WithContext("currency", request.Currency).
        WithTags("payment", "processing").
        Info("Starting payment processing")
    
    // Validate
    if request.Amount <= 0 {
        logs.WithCorrelationID(correlationId).
            WithContext("amount", request.Amount).
            WithTags("payment", "validation").
            Warn("Invalid payment amount")
        return "", fmt.Errorf("invalid amount")
    }
    
    // Process
    start := time.Now()
    transactionId, err := s.gateway.Charge(ctx, request)
    duration := time.Since(start)
    
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("amount", request.Amount).
            WithContext("duration_ms", duration.Milliseconds()).
            WithTags("payment", "gateway", "error").
            WithException(err).
            Error("Payment gateway request failed", err)
        return "", err
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("transactionId", transactionId).
        WithContext("amount", request.Amount).
        WithContext("duration_ms", duration.Milliseconds()).
        WithTags("payment", "gateway", "success").
        Info("Payment processed successfully")
    
    return transactionId, nil
}

Database Operations Pattern

Go
type OrderRepository struct {
    db *sql.DB
}

func (r *OrderRepository) CreateOrder(ctx context.Context, order *Order) error {
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", order.ID).
        WithContext("customerId", order.CustomerID).
        WithTags("database", "insert").
        Trace("Inserting order into database")
    
    query := `INSERT INTO orders (id, customer_id, total, created_at) 
              VALUES ($1, $2, $3, $4)`
    
    _, err := r.db.ExecContext(ctx, query, 
        order.ID, 
        order.CustomerID, 
        order.Total, 
        order.CreatedAt)
    
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("orderId", order.ID).
            WithContext("customerId", order.CustomerID).
            WithTags("database", "insert", "error").
            WithException(err).
            Error("Failed to insert order", err)
        return err
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", order.ID).
        WithContext("customerId", order.CustomerID).
        WithTags("database", "insert", "success").
        Info("Order inserted successfully")
    
    return nil
}

Error Handling with Fluent API

Go
func (s *Service) SafeExecute(ctx context.Context, operationName string, fn func() error) error {
    correlationId := ctx.Value("correlationId").(string)
    start := time.Now()
    
    logs.WithCorrelationID(correlationId).
        WithContext("operation", operationName).
        WithTags("operation", "start").
        Trace("Operation starting")
    
    defer func() {
        if r := recover(); r != nil {
            duration := time.Since(start)
            logs.WithCorrelationID(correlationId).
                WithContext("operation", operationName).
                WithContext("duration_ms", duration.Milliseconds()).
                WithContext("panic", fmt.Sprintf("%v", r)).
                WithTags("operation", "panic", "critical").
                Critical("Panic occurred during operation")
        }
    }()
    
    err := fn()
    duration := time.Since(start)
    
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("operation", operationName).
            WithContext("duration_ms", duration.Milliseconds()).
            WithTags("operation", "error").
            WithException(err).
            Error("Operation failed", err)
        return err
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("operation", operationName).
        WithContext("duration_ms", duration.Milliseconds()).
        WithTags("operation", "success").
        Info("Operation completed successfully")
    
    return nil
}

Real-World Scenario

Complete API Endpoint

Go
func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    correlationId := extractOrGenerateCorrelationID(r)
    user := extractUserFromRequest(r)
    
    // Request received
    logs.WithCorrelationID(correlationId).
        WithContext("method", r.Method).
        WithContext("path", r.URL.Path).
        WithContext("userId", user.ID).
        WithTags("api", "order", "create", "request").
        Debug("Order creation request received")
    
    // Parse request
    var req CreateOrderRequest
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("userId", user.ID).
            WithTags("api", "parsing", "error").
            WithException(err).
            Error("Failed to decode request body", err)
        http.Error(w, "Invalid request", http.StatusBadRequest)
        return
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("userId", user.ID).
        WithContext("itemCount", len(req.Items)).
        WithTags("api", "parsing", "success").
        Debug("Request decoded successfully")
    
    // Create order
    ctx := r.Context()
    ctx = context.WithValue(ctx, "correlationId", correlationId)
    
    order, err := h.service.CreateOrder(ctx, user, &req)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("userId", user.ID).
            WithContext("itemCount", len(req.Items)).
            WithTags("service", "order", "creation", "error").
            WithException(err).
            Error("Order creation failed", err)
        http.Error(w, "Internal server error", http.StatusInternalServerError)
        return
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("userId", user.ID).
        WithContext("orderId", order.ID).
        WithContext("total", order.Total).
        WithTags("api", "order", "create", "success").
        Info("Order created successfully")
    
    // Return response
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(order)
}

Go Idiomatic Patterns

Prefer Multi-Line Chains

Go
// Good: readability with line breaks
logs.WithContext("userId", userID).
    WithContext("action", "login").
    WithTags("auth", "success").
    Info("User logged in")

// Avoid: single line is hard to read
logs.WithContext("userId", userID).WithContext("action", "login").WithTags("auth", "success").Info("User logged in")

Consistent Indentation

Go
// Good: clear hierarchy
logs.WithCorrelationID(correlationId).
    WithContext("orderId", order.ID).
    WithContext("total", order.Total).
    WithTags("order", "processing").
    Info("Processing order")

// Avoid: inconsistent indentation
logs.WithCorrelationID(correlationId).
    WithContext("orderId", order.ID).
  WithContext("total", order.Total).
    WithTags("order", "processing").
    Info("Processing order")

Logical Grouping

Go
// Good: group related context
logs.WithContext("orderId", order.ID).
    WithContext("customerId", order.CustomerID).
    WithContext("total", order.Total).
    WithTags("order", "processing").
    WithTags("payment", "pending").
    Info("Order waiting for payment")

// Good: separate concerns logically
logs.WithTags("database", "query").
    WithContext("table", "orders").
    WithContext("duration_ms", 150).
    Debug("Query executed")

Best Practices

Fluent Syntax Best Practices

  • Chain methods logically - group related calls together
  • Use line breaks - each method on its own line for readability
  • Consistent formatting - maintain alignment and indentation
  • Order methods consistently - context, then tags, then exception, then log level
  • Prefer fluent API - for complex logging with multiple attributes
  • Use direct calls - for simple single-message logging
  • Document patterns - establish team conventions for fluent chains

Fluent API Ordering

Suggested order for method chaining:

  1. WithCorrelationID() - request tracking
  2. WithContext() - structured data (call multiple times)
  3. WithTags() - categorization (call multiple times)
  4. WithException() / WithUser() - special contexts
  5. Log method - Info(), Error(), etc.

Next Steps

Previous
Exception Handling