Skip to main content
โšก Calmops

Logging in Go

Logging in Go

Logging is crucial for understanding application behavior, debugging issues, and monitoring production systems. Go provides a built-in log package and excellent third-party logging libraries. This guide covers logging fundamentals and best practices.

Built-in log Package

Basic Logging

package main

import "log"

func main() {
    // Print logs to stdout
    log.Print("This is a log message")
    log.Println("This is another log message")
    log.Printf("Formatted message: %d\n", 42)
    
    // Fatal logs and exits
    // log.Fatal("Fatal error")
    
    // Panic logs and panics
    // log.Panic("Panic error")
}

Log Output

package main

import (
    "log"
    "os"
)

func main() {
    // Log to stdout (default)
    log.Println("Log to stdout")
    
    // Log to stderr
    log.SetOutput(os.Stderr)
    log.Println("Log to stderr")
    
    // Log to file
    f, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        panic(err)
    }
    defer f.Close()
    
    log.SetOutput(f)
    log.Println("Log to file")
}

Log Flags

package main

import (
    "log"
)

func main() {
    // Default flags
    log.Println("Default flags")
    
    // Set custom flags
    log.SetFlags(log.LstdFlags | log.Lshortfile)
    log.Println("With file and line number")
    
    // Available flags
    // log.Ldate       - Date in local time zone
    // log.Ltime       - Time in local time zone
    // log.Lmicroseconds - Microseconds
    // log.Llongfile   - Full file path and line number
    // log.Lshortfile  - Short file path and line number
    // log.LUTC        - UTC instead of local time
    // log.Lmsgprefix  - Move prefix to end
}

Custom Logger

package main

import (
    "log"
    "os"
)

func main() {
    // Create custom logger
    logger := log.New(os.Stdout, "INFO: ", log.LstdFlags)
    logger.Println("Custom logger message")
    
    // Create error logger
    errorLogger := log.New(os.Stderr, "ERROR: ", log.LstdFlags|log.Lshortfile)
    errorLogger.Println("Error message")
}

Structured Logging

JSON Logging

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type LogEntry struct {
    Timestamp string                 `json:"timestamp"`
    Level     string                 `json:"level"`
    Message   string                 `json:"message"`
    Data      map[string]interface{} `json:"data,omitempty"`
}

func logJSON(level, message string, data map[string]interface{}) {
    entry := LogEntry{
        Timestamp: time.Now().Format(time.RFC3339),
        Level:     level,
        Message:   message,
        Data:      data,
    }
    
    b, _ := json.Marshal(entry)
    fmt.Println(string(b))
}

func main() {
    logJSON("INFO", "User created", map[string]interface{}{
        "user_id": 123,
        "email":   "[email protected]",
    })
    
    logJSON("ERROR", "Database error", map[string]interface{}{
        "operation": "INSERT",
        "table":     "users",
        "error":     "connection refused",
    })
}

Structured Logger Implementation

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Logger struct {
    level string
}

type LogEntry struct {
    Timestamp string                 `json:"timestamp"`
    Level     string                 `json:"level"`
    Message   string                 `json:"message"`
    Data      map[string]interface{} `json:"data,omitempty"`
}

func (l *Logger) log(level, message string, data map[string]interface{}) {
    entry := LogEntry{
        Timestamp: time.Now().Format(time.RFC3339),
        Level:     level,
        Message:   message,
        Data:      data,
    }
    
    b, _ := json.Marshal(entry)
    fmt.Println(string(b))
}

func (l *Logger) Info(message string, data map[string]interface{}) {
    l.log("INFO", message, data)
}

func (l *Logger) Error(message string, data map[string]interface{}) {
    l.log("ERROR", message, data)
}

func (l *Logger) Debug(message string, data map[string]interface{}) {
    l.log("DEBUG", message, data)
}

func main() {
    logger := &Logger{}
    
    logger.Info("User created", map[string]interface{}{
        "user_id": 123,
        "email":   "[email protected]",
    })
    
    logger.Error("Database error", map[string]interface{}{
        "operation": "INSERT",
        "error":     "connection refused",
    })
}

Log Levels

Implementing Log Levels

package main

import (
    "fmt"
    "log"
    "os"
)

type LogLevel int

const (
    DEBUG LogLevel = iota
    INFO
    WARNING
    ERROR
    FATAL
)

var logLevelNames = map[LogLevel]string{
    DEBUG:   "DEBUG",
    INFO:    "INFO",
    WARNING: "WARNING",
    ERROR:   "ERROR",
    FATAL:   "FATAL",
}

type Logger struct {
    level  LogLevel
    logger *log.Logger
}

func NewLogger(level LogLevel) *Logger {
    return &Logger{
        level:  level,
        logger: log.New(os.Stdout, "", log.LstdFlags),
    }
}

func (l *Logger) log(level LogLevel, message string) {
    if level >= l.level {
        l.logger.Printf("[%s] %s\n", logLevelNames[level], message)
    }
}

func (l *Logger) Debug(message string) {
    l.log(DEBUG, message)
}

func (l *Logger) Info(message string) {
    l.log(INFO, message)
}

func (l *Logger) Warning(message string) {
    l.log(WARNING, message)
}

func (l *Logger) Error(message string) {
    l.log(ERROR, message)
}

func (l *Logger) Fatal(message string) {
    l.log(FATAL, message)
    os.Exit(1)
}

func main() {
    logger := NewLogger(INFO)
    
    logger.Debug("Debug message")      // Not logged
    logger.Info("Info message")        // Logged
    logger.Warning("Warning message")  // Logged
    logger.Error("Error message")      // Logged
}

Third-Party Logging Libraries

Using logrus

# Install logrus
go get github.com/sirupsen/logrus
package main

import (
    "github.com/sirupsen/logrus"
)

func main() {
    log := logrus.New()
    
    // Set log level
    log.SetLevel(logrus.InfoLevel)
    
    // Log with fields
    log.WithFields(logrus.Fields{
        "user_id": 123,
        "email":   "[email protected]",
    }).Info("User created")
    
    log.WithFields(logrus.Fields{
        "operation": "INSERT",
        "error":     "connection refused",
    }).Error("Database error")
}

Using zap

# Install zap
go get go.uber.org/zap
package main

import (
    "go.uber.org/zap"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    
    logger.Info("User created",
        zap.Int("user_id", 123),
        zap.String("email", "[email protected]"),
    )
    
    logger.Error("Database error",
        zap.String("operation", "INSERT"),
        zap.String("error", "connection refused"),
    )
}

Logging Best Practices

Context-Aware Logging

package main

import (
    "context"
    "log"
)

type contextKey string

const requestIDKey contextKey = "request_id"

func logWithContext(ctx context.Context, message string) {
    requestID := ctx.Value(requestIDKey)
    log.Printf("[%v] %s\n", requestID, message)
}

func main() {
    ctx := context.WithValue(context.Background(), requestIDKey, "req-123")
    logWithContext(ctx, "Processing request")
}

Request Logging Middleware

package main

import (
    "log"
    "net/http"
    "time"
)

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        
        next.ServeHTTP(w, r)
        
        duration := time.Since(start)
        log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, duration)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Hello"))
    })
    
    http.ListenAndServe(":8080", loggingMiddleware(mux))
}

Practical Examples

Application Logger

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type AppLogger struct {
    minLevel string
}

type LogEntry struct {
    Timestamp string                 `json:"timestamp"`
    Level     string                 `json:"level"`
    Message   string                 `json:"message"`
    Data      map[string]interface{} `json:"data,omitempty"`
}

func (l *AppLogger) log(level, message string, data map[string]interface{}) {
    entry := LogEntry{
        Timestamp: time.Now().Format(time.RFC3339),
        Level:     level,
        Message:   message,
        Data:      data,
    }
    
    b, _ := json.Marshal(entry)
    fmt.Println(string(b))
}

func (l *AppLogger) Info(message string, data map[string]interface{}) {
    l.log("INFO", message, data)
}

func (l *AppLogger) Error(message string, data map[string]interface{}) {
    l.log("ERROR", message, data)
}

func (l *AppLogger) Debug(message string, data map[string]interface{}) {
    l.log("DEBUG", message, data)
}

func main() {
    logger := &AppLogger{}
    
    logger.Info("Application started", nil)
    
    logger.Info("User created", map[string]interface{}{
        "user_id": 123,
        "email":   "[email protected]",
    })
    
    logger.Error("Database error", map[string]interface{}{
        "operation": "INSERT",
        "error":     "connection refused",
    })
}

Best Practices

โœ… Good Practices

  1. Use structured logging - JSON format for parsing
  2. Include context - Request IDs, user IDs, etc.
  3. Use appropriate log levels - DEBUG, INFO, WARNING, ERROR
  4. Log important events - User actions, errors, state changes
  5. Include timestamps - For correlation and debugging
  6. Use consistent format - Across all logs
  7. Don’t log sensitive data - Passwords, tokens, PII
  8. Rotate log files - Prevent disk space issues

โŒ Anti-Patterns

// โŒ Bad: Logging sensitive data
log.Printf("User password: %s", password)

// โœ… Good: Don't log sensitive data
log.Printf("User authenticated: %s", username)

// โŒ Bad: No context
log.Println("Error occurred")

// โœ… Good: Include context
log.Printf("Error in user creation: %v", err)

// โŒ Bad: Inconsistent format
log.Println("User created")
fmt.Println("Product updated")

// โœ… Good: Consistent format
logger.Info("User created")
logger.Info("Product updated")

Summary

Effective logging is essential:

  • Use the log package for basic logging
  • Implement structured logging for production
  • Use appropriate log levels
  • Include context in logs
  • Don’t log sensitive data
  • Use consistent format
  • Rotate log files
  • Consider third-party libraries for advanced features

Master logging for better application observability.

Comments