Files
websocket-qris/pkg/logger/README.md
2025-09-24 18:42:16 +07:00

8.3 KiB

Structured Logger Package

A comprehensive structured logging package for Go applications with support for different log levels, service-specific logging, request context, and JSON output formatting.

Features

  • Structured Logging: JSON and text format output with rich metadata
  • Multiple Log Levels: DEBUG, INFO, WARN, ERROR, FATAL
  • Service-Specific Logging: Dedicated loggers for different services
  • Request Context: Request ID and correlation ID tracking
  • Performance Timing: Built-in duration logging for operations
  • Gin Middleware: Request logging middleware for HTTP requests
  • Environment Configuration: Configurable via environment variables

Installation

The logger is already integrated into the project. Import it using:

import "api-service/pkg/logger"

Quick Start

Basic Usage

// Global functions (use default logger)
logger.Info("Application starting")
logger.Error("Something went wrong", map[string]interface{}{
    "error": err.Error(),
    "code":  "DB_CONNECTION_FAILED",
})

// Create a service-specific logger
authLogger := logger.ServiceLogger("auth-service")
authLogger.Info("User authenticated", map[string]interface{}{
    "user_id": "123",
    "method":  "oauth2",
})

Service-Specific Loggers

// Pre-defined service loggers
authLogger := logger.AuthServiceLogger()
bpjsLogger := logger.BPJSServiceLogger() 
retribusiLogger := logger.RetribusiServiceLogger()
databaseLogger := logger.DatabaseServiceLogger()

authLogger.Info("Authentication successful")
databaseLogger.Debug("Query executed", map[string]interface{}{
    "query": "SELECT * FROM users",
    "time":  "150ms",
})

Request Context Logging

// Add request context to logs
requestLogger := logger.Default().
    WithRequestID("req-123456").
    WithCorrelationID("corr-789012").
    WithField("user_id", "user-123")

requestLogger.Info("Request processing started", map[string]interface{}{
    "endpoint": "/api/v1/data",
    "method":   "POST",
})

Performance Timing

// Time operations and log duration
start := time.Now()
// ... perform operation ...
logger.LogDuration(start, "Database query completed", map[string]interface{}{
    "query":    "SELECT * FROM large_table",
    "rows":     1000,
    "database": "postgres",
})

Gin Middleware Integration

Add Request Logger Middleware

In your routes setup:

import "api-service/pkg/logger"

func RegisterRoutes(cfg *config.Config) *gin.Engine {
    router := gin.New()
    
    // Add request logging middleware
    router.Use(logger.RequestLoggerMiddleware(logger.Default()))
    
    // ... other middleware and routes
    return router
}

Access Logger in Handlers

func (h *MyHandler) MyEndpoint(c *gin.Context) {
    // Get logger from context
    logger := logger.GetLoggerFromContext(c)
    
    logger.Info("Endpoint called", map[string]interface{}{
        "user_agent": c.Request.UserAgent(),
        "client_ip":  c.ClientIP(),
    })
    
    // Get request IDs
    requestID := logger.GetRequestIDFromContext(c)
    correlationID := logger.GetCorrelationIDFromContext(c)
}

Configuration

Environment Variables

Set these environment variables to configure the logger:

# Log level (DEBUG, INFO, WARN, ERROR, FATAL)
LOG_LEVEL=INFO

# Output format (text or json)
LOG_FORMAT=text

# Service name for logs
LOG_SERVICE=api-service

# Enable JSON format
LOG_JSON=false

Programmatic Configuration

// Create custom logger with specific configuration
cfg := logger.Config{
    Level:      "DEBUG",
    JSONFormat: true,
    Service:    "my-custom-service",
}

customLogger := logger.NewFromConfig(cfg)

// Or create manually
logger := logger.New("service-name", logger.DEBUG, true)

Log Levels

Level Description Usage
DEBUG Detailed debug information Development and troubleshooting
INFO General operational messages Normal application behavior
WARN Warning conditions Something unexpected but not an error
ERROR Error conditions Operation failed but application continues
FATAL Critical conditions Application cannot continue

Output Formats

Text Format (Default)

2025-08-22T04:33:12+07:00 [INFO] auth-service: User authentication successful (handler/auth.go:45) [user_id=12345 method=oauth2]

JSON Format

{
  "timestamp": "2025-08-22T04:33:12+07:00",
  "level": "INFO",
  "service": "auth-service",
  "message": "User authentication successful",
  "file": "handler/auth.go",
  "line": 45,
  "request_id": "req-123456",
  "correlation_id": "corr-789012",
  "fields": {
    "user_id": "12345",
    "method": "oauth2"
  }
}

Best Practices

1. Use Appropriate Log Levels

// Good
logger.Debug("Detailed debug info")
logger.Info("User action completed")
logger.Warn("Rate limit approaching")
logger.Error("Database connection failed")

// Avoid
logger.Info("Error connecting to database") // Use ERROR instead

2. Add Context to Logs

// Instead of this:
logger.Error("Login failed")

// Do this:
logger.Error("Login failed", map[string]interface{}{
    "username": username,
    "reason":   "invalid_credentials",
    "attempts": loginAttempts,
    "client_ip": clientIP,
})

3. Use Service-Specific Loggers

// Create once per service
var authLogger = logger.AuthServiceLogger()

func LoginHandler(c *gin.Context) {
    authLogger.Info("Login attempt", map[string]interface{}{
        "username": c.PostForm("username"),
    })
}

4. Measure Performance

func ProcessData(data []byte) error {
    start := time.Now()
    defer func() {
        logger.LogDuration(start, "Data processing completed", map[string]interface{}{
            "data_size": len(data),
            "items":     countItems(data),
        })
    }()
    
    // ... processing logic ...
}

Migration from Standard Log Package

Before (standard log)

import "log"

log.Printf("Error: %v", err)
log.Printf("User %s logged in", username)

After (structured logger)

import "api-service/pkg/logger"

logger.Error("Operation failed", map[string]interface{}{
    "error": err.Error(),
    "context": "user_login",
})

logger.Info("User logged in", map[string]interface{}{
    "username": username,
    "method":   "password",
})

Examples

Database Operations

func (h *UserHandler) GetUser(c *gin.Context) {
    logger := logger.GetLoggerFromContext(c)
    start := time.Now()
    
    user, err := h.db.GetUser(c.Param("id"))
    if err != nil {
        logger.Error("Failed to get user", map[string]interface{}{
            "user_id": c.Param("id"),
            "error":   err.Error(),
        })
        c.JSON(500, gin.H{"error": "Internal server error"})
        return
    }
    
    logger.LogDuration(start, "User retrieved successfully", map[string]interface{}{
        "user_id": user.ID,
        "query_time": time.Since(start).String(),
    })
    
    c.JSON(200, user)
}

Authentication Service

var authLogger = logger.AuthServiceLogger()

func Authenticate(username, password string) (bool, error) {
    authLogger.Debug("Authentication attempt", map[string]interface{}{
        "username": username,
    })
    
    // Authentication logic...
    
    if authenticated {
        authLogger.Info("Authentication successful", map[string]interface{}{
            "username": username,
            "method":   "password",
        })
        return true, nil
    }
    
    authLogger.Warn("Authentication failed", map[string]interface{}{
        "username": username,
        "reason":   "invalid_credentials",
    })
    return false, nil
}

Troubleshooting

Common Issues

  1. No logs appearing: Check that log level is not set too high (e.g., ERROR when logging INFO)
  2. JSON format not working: Ensure LOG_JSON=true or logger is created with jsonFormat: true
  3. Missing context: Use WithRequestID() and WithCorrelationID() for request context

Debug Mode

Enable debug logging for development:

export LOG_LEVEL=DEBUG
export LOG_FORMAT=text

Performance Considerations

  • Logger is designed to be lightweight and fast
  • Context fields are only evaluated when the log level is enabled
  • JSON marshaling only occurs when JSON format is enabled
  • Consider log volume in production environments

License

This logger package is part of the API Service project.