Skip to content

Latest commit

 

History

History
599 lines (450 loc) · 14.1 KB

File metadata and controls

599 lines (450 loc) · 14.1 KB

Logging Package Examples

This directory contains examples demonstrating how to use the logging package for structured logging in Go applications.

📍 Example Code Location

Legacy examples:

File output examples:

🚀 Quick Reference for LLMs/Coding Agents

Basic Console Logging

import "github.com/jasoet/pkg/v2/logging"

// Initialize logging (MUST be called first, only once)
logging.Initialize("service-name", true) // true for debug mode

// Get context logger for components
logger := logging.ContextLogger(ctx, "component-name")

// Log with structured fields
logger.Info().
    Str("user_id", "123").
    Int("status", 200).
    Dur("duration", 45*time.Millisecond).
    Msg("Operation completed")

// Log errors with context
logger.Error().Err(err).Str("operation", "db_query").Msg("Failed")

File Output Logging

// File only
logging.InitializeWithFile("my-service", false,
    logging.OutputFile,
    &logging.FileConfig{Path: "/var/log/app.log"})

// Both console and file
logging.InitializeWithFile("my-service", true,
    logging.OutputConsole | logging.OutputFile,
    &logging.FileConfig{Path: "/var/log/app.log"})

Critical notes:

  • Always call Initialize() or InitializeWithFile() once at application startup
  • Use ContextLogger() for component-specific logging
  • Global log is available after initialization
  • File output uses JSON format, console uses human-readable format

Overview

The logging package provides utilities for:

  • Centralized logging setup with zerolog
  • Flexible output destinations (console, file, or both)
  • Context-aware logging with component identification
  • Structured logging with consistent fields
  • Debug and production logging configurations
  • Integration with other packages in the library

Running the Examples

Legacy Examples

To run the legacy examples:

go run -tags=example example.go
go run -tags=example otel_example.go

File Output Examples

To run the new file output examples:

# Console only output
go run -tags=example ./console

# File only output
go run -tags=example ./file

# Both console and file output
go run -tags=example ./both

# Environment-based configuration
go run -tags=example ./environment
ENV=staging go run -tags=example ./environment
ENV=production go run -tags=example ./environment

Learning Path

  1. Start with legacy example.go - Understand basic console logging
  2. Try console/ - See new API for console output
  3. Explore file/ - Learn JSON file output
  4. Study both/ - Dual output and component loggers
  5. Apply environment/ - Environment-based patterns

For detailed documentation, see logging/README.md.

Example Descriptions

The example.go file demonstrates several use cases:

1. Basic Logging Setup

Initialize the global logger for your application:

// Initialize logging with service name and debug mode
logging.Initialize("my-service", true) // debug mode enabled

// Use the global logger directly
log.Info().Msg("Application started")
log.Debug().Str("version", "1.0.0").Msg("Debug information")

2. Context-Aware Logging

Create component-specific loggers with context:

ctx := context.Background()
logger := logging.ContextLogger(ctx, "user-service")

logger.Info().Msg("User service started")
logger.Debug().Int("user_id", 123).Msg("Processing user")

3. Structured Logging

Log structured data with various field types:

logger.Info().
    Str("method", "POST").
    Str("path", "/api/users").
    Int("status", 201).
    Dur("duration", 45*time.Millisecond).
    Msg("Request completed")

4. Different Log Levels

Use appropriate log levels for different scenarios:

logger.Debug().Msg("Detailed debugging information")
logger.Info().Msg("General information")
logger.Warn().Msg("Warning: something might be wrong")
logger.Error().Err(err).Msg("An error occurred")
logger.Fatal().Msg("Fatal error - application will exit")

5. Error Logging

Properly log errors with context:

if err := someOperation(); err != nil {
    logger.Error().
        Err(err).
        Str("operation", "database_query").
        Int("retry_count", 3).
        Msg("Operation failed after retries")
}

6. Performance Monitoring

Log performance metrics and timing:

start := time.Now()
result, err := performOperation()
duration := time.Since(start)

logger.Info().
    Dur("duration", duration).
    Bool("success", err == nil).
    Int("result_count", len(result)).
    Msg("Operation completed")

7. HTTP Request Logging

Log HTTP requests with relevant details:

func logRequest(logger zerolog.Logger, r *http.Request, status int, duration time.Duration) {
    logger.Info().
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Str("remote_addr", r.RemoteAddr).
        Str("user_agent", r.UserAgent()).
        Int("status", status).
        Dur("duration", duration).
        Msg("HTTP request")
}

8. Database Operation Logging

Log database operations with context:

func logDatabaseOperation(ctx context.Context, operation string, table string, duration time.Duration, err error) {
    logger := logging.ContextLogger(ctx, "database")
    
    event := logger.Info()
    if err != nil {
        event = logger.Error().Err(err)
    }
    
    event.
        Str("operation", operation).
        Str("table", table).
        Dur("duration", duration).
        Msg("Database operation")
}

Configuration Options

Log Levels

The package supports standard zerolog levels:

  • Debug: Detailed information for debugging
  • Info: General informational messages
  • Warn: Warning messages for potential issues
  • Error: Error messages for failures
  • Fatal: Fatal errors that cause application exit

Debug vs Production

// Development mode (debug enabled)
logging.Initialize("my-service", true)

// Production mode (info level and above)
logging.Initialize("my-service", false)

Logger Configuration

The global logger is configured with:

  • Console output: Human-readable format for development
  • Timestamp: RFC3339 format timestamps
  • Service name: Consistent service identification
  • Process ID: For multi-instance deployments
  • Caller information: File and line number for debugging

Field Types and Usage

String Fields

logger.Info().
    Str("user_id", "12345").
    Str("action", "login").
    Msg("User action")

Numeric Fields

logger.Info().
    Int("count", 42).
    Int64("timestamp", time.Now().Unix()).
    Float64("percentage", 95.5).
    Msg("Metrics")

Boolean Fields

logger.Info().
    Bool("success", true).
    Bool("cache_hit", false).
    Msg("Operation result")

Duration Fields

logger.Info().
    Dur("duration", 150*time.Millisecond).
    Dur("timeout", 30*time.Second).
    Msg("Timing information")

Error Fields

logger.Error().
    Err(err).
    Str("context", "user authentication").
    Msg("Authentication failed")

Time Fields

logger.Info().
    Time("started_at", startTime).
    Time("completed_at", time.Now()).
    Msg("Process timeline")

Integration with Other Packages

Database Package Integration

import (
    "github.com/jasoet/pkg/db"
    "github.com/jasoet/pkg/logging"
)

func setupDatabase(ctx context.Context) (*gorm.DB, error) {
    logger := logging.ContextLogger(ctx, "database-setup")
    
    config := &db.ConnectionConfig{
        DbType: db.Postgresql,
        Host:   "localhost",
        // ... other config
    }
    
    logger.Info().Str("db_type", string(config.DbType)).Msg("Connecting to database")
    
    database, err := config.Pool()
    if err != nil {
        logger.Error().Err(err).Msg("Database connection failed")
        return nil, err
    }
    
    logger.Info().Msg("Database connection successful")
    return database, nil
}

REST Package Integration

import (
    "github.com/jasoet/pkg/rest"
    "github.com/jasoet/pkg/logging"
)

func makeAPICall(ctx context.Context) {
    logger := logging.ContextLogger(ctx, "api-client")
    
    client, err := rest.NewClient(&rest.Config{
        BaseURL: "https://api.example.com",
    })
    
    logger.Info().Str("base_url", "https://api.example.com").Msg("Making API call")
    
    // Make request with logging
    response, err := client.Get("/users")
    if err != nil {
        logger.Error().Err(err).Msg("API call failed")
        return
    }
    
    logger.Info().Int("status", response.StatusCode).Msg("API call successful")
}

Server Package Integration

import (
    "github.com/jasoet/pkg/server"
    "github.com/jasoet/pkg/logging"
)

func startServer(ctx context.Context) {
    logger := logging.ContextLogger(ctx, "http-server")
    
    config := &server.Config{
        Port: 8080,
        // ... other config
    }
    
    logger.Info().Int("port", config.Port).Msg("Starting HTTP server")
    
    srv := server.New(config)
    // Server automatically includes logging middleware
}

Best Practices

1. Initialize Once

func main() {
    // Initialize logging at application startup
    logging.Initialize("my-service", os.Getenv("DEBUG") == "true")
    
    // Rest of application...
}

2. Use Context Loggers

// Create component-specific loggers
func UserService(ctx context.Context) {
    logger := logging.ContextLogger(ctx, "user-service")
    
    // Use logger throughout the component
    logger.Info().Msg("User service operation")
}

3. Consistent Field Names

// Use consistent field names across your application
logger.Info().
    Str("user_id", userID).        // Always use "user_id"
    Str("request_id", requestID).  // Always use "request_id"
    Dur("duration", duration).     // Always use "duration"
    Msg("Operation completed")

4. Meaningful Messages

// Good: Descriptive message with context
logger.Info().
    Str("operation", "user_creation").
    Str("user_id", "12345").
    Msg("User created successfully")

// Avoid: Generic messages without context
logger.Info().Msg("Success")

5. Error Context

// Provide context with errors
logger.Error().
    Err(err).
    Str("function", "CreateUser").
    Str("input", userInput).
    Msg("Failed to create user")

6. Performance Considerations

// Use conditional logging for expensive operations
if logger.Debug().Enabled() {
    expensiveDebugData := generateDebugData()
    logger.Debug().
        Interface("debug_data", expensiveDebugData).
        Msg("Debug information")
}

Log Analysis and Monitoring

JSON Output for Production

For production environments, you might want JSON output:

// Custom logger setup for production
func setupProductionLogger(serviceName string) {
    zerolog.SetGlobalLevel(zerolog.InfoLevel)
    log.Logger = zerolog.New(os.Stdout). // JSON output to stdout
        With().
        Timestamp().
        Str("service", serviceName).
        Int("pid", os.Getpid()).
        Logger()
}

Structured Query Examples

With structured logging, you can easily query logs:

# Find all errors from a specific component
jq 'select(.level == "error" and .component == "database")' logs.json

# Find slow operations
jq 'select(.duration_ms > 1000)' logs.json

# Count requests by status code
jq -r '.status' logs.json | sort | uniq -c

Testing with Logging

Test Logger Setup

func TestWithLogging(t *testing.T) {
    // Setup test logger
    logging.Initialize("test-service", true)
    
    ctx := context.Background()
    logger := logging.ContextLogger(ctx, "test")
    
    // Your test code with logging
    logger.Info().Str("test", t.Name()).Msg("Running test")
}

Capturing Logs in Tests

func TestLogOutput(t *testing.T) {
    var buf bytes.Buffer
    
    // Create logger that writes to buffer
    testLogger := zerolog.New(&buf).With().Timestamp().Logger()
    
    testLogger.Info().Str("test", "example").Msg("Test message")
    
    // Verify log output
    output := buf.String()
    assert.Contains(t, output, "Test message")
    assert.Contains(t, output, "test")
}

Common Patterns

Request ID Tracking

func handleRequest(w http.ResponseWriter, r *http.Request) {
    requestID := generateRequestID()
    ctx := context.WithValue(r.Context(), "request_id", requestID)
    
    logger := logging.ContextLogger(ctx, "api")
    logger.Info().
        Str("request_id", requestID).
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Msg("Request started")
    
    // Handle request...
    
    logger.Info().
        Str("request_id", requestID).
        Msg("Request completed")
}

Service Boundaries

func CallExternalService(ctx context.Context, serviceURL string) error {
    logger := logging.ContextLogger(ctx, "external-service")
    
    logger.Info().
        Str("service_url", serviceURL).
        Msg("Calling external service")
    
    start := time.Now()
    
    // Make call...
    
    logger.Info().
        Str("service_url", serviceURL).
        Dur("duration", time.Since(start)).
        Msg("External service call completed")
    
    return nil
}

Troubleshooting

Common Issues

  1. No Log Output: Ensure Initialize() is called before using loggers
  2. Wrong Log Level: Check debug parameter in Initialize()
  3. Missing Context: Use ContextLogger() for component-specific logging
  4. Performance Impact: Use conditional logging for expensive debug operations

Debug Tips

  • Use debug mode during development: logging.Initialize("service", true)
  • Add request IDs for tracing requests across services
  • Include timing information for performance analysis
  • Use structured fields for easier log analysis