logging

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

README

Logging Package

This package provides a comprehensive, thread-safe logging system for the MariaDB Backup S3 application. It supports multiple output formats, structured logging with context, and configurable log levels.

Features

  • Thread-safe operations with mutex protection
  • Multiple formatters: Human-readable console output with colors, JSON for machine processing
  • Structured logging with fields, component names, and operation IDs
  • Context management for tracking operations across function calls
  • Configurable log levels: DEBUG, INFO, WARN, ERROR, FATAL
  • Multiple output destinations: stdout, stderr, or files
  • Go standard library only (no external logging dependencies)

Quick Start

import "github.com/capcom6/mariadb-backup-s3/internal/logging"

// Create a default logger
logger := logging.NewDefault()

// Create a context with the logger and component
ctx := logging.WithLogger(context.Background(), logger)
ctx = logging.WithComponent(ctx, "my-component")

// Log messages
logger.Info(ctx, "Application started")
logger.Error(ctx, "Something went wrong", err, logging.Fields{
    "user_id": 123,
    "action": "login",
})

Configuration

The logging system can be configured using the Config struct:

config := logging.Config{
    Level:        logging.LogLevelInfo,
    Format:       "human",        // "human" or "json"
    Output:       os.Stdout,      // Use os.Stdout, os.Stderr, or any io.Writer
    EnableColors: true,
    TimeFormat:   "2006-01-02 15:04:05.000",
}

logger, err := logging.New(config)
if err != nil {
    // handle error
}
ctx := logging.WithLogger(context.Background(), logger)

Log Levels

  • LogLevelDebug: Detailed debugging information
  • LogLevelInfo: General information messages
  • LogLevelWarn: Warning messages
  • LogLevelError: Error messages
  • LogLevelFatal: Fatal errors that terminate the application

Formatters

Human Formatter

Console-friendly output with colors and structured fields:

2025-01-23 10:30:45.123 INFO  [backup] op=backup-123456789 Starting backup duration=2.5s
2025-01-23 10:30:47.456 ERROR [upload] Upload failed error=connection timeout filename=backup.tar.gz
JSON Formatter

Machine-readable JSON output:

{
  "timestamp": "2025-01-23T10:30:45.123Z",
  "level": "INFO",
  "message": "Starting backup",
  "component": "backup",
  "operation_id": "backup-123456789",
  "fields": {
    "duration": "2.5s"
  }
}

Context Management

The logging system provides utilities for managing context across operations:

// Add component name
ctx = logging.WithComponent(ctx, "database")

// Add operation ID
ctx = logging.WithOperationID(ctx, "backup-123")

// Add custom fields
ctx = logging.WithFields(ctx, logging.Fields{
    "table": "users",
    "rows": 1000,
})

// Generate operation ID
operationID := logging.GenerateOperationID("backup")

Contextual Logging

To retrieve the logger from a context:

logger := logging.GetLogger(ctx)
if logger == nil {
    // Handle missing logger (e.g., use a default)
    logger = logging.NewDefault()
    ctx = logging.WithLogger(ctx, logger)
}

You can add component and operation ID to the context:

ctx = logging.WithComponent(ctx, "my-component")
ctx = logging.WithOperationID(ctx, "op-12345")
// or generate a new operation ID
opID := logging.GenerateOperationID("my-component")
ctx = logging.WithOperationID(ctx, opID)

Then log as usual:

logger.Info(ctx, "Message with component and operation ID")

Integration Examples

In Main Application
func main() {
    // Initialize logging
    logger := logging.NewDefault()
    ctx := logging.WithLogger(context.Background(), logger)
    ctx = logging.WithComponent(ctx, "main")

    logger.Info(ctx, "Application starting")

    // Your application logic here...

    logger.Info(ctx, "Application completed")
}
In Backup Operations
func Execute(ctx context.Context, cfg Config) error {
    logger := logging.GetLogger(ctx)
    operationID := logging.GenerateOperationID("backup")

    ctx = logging.WithOperationID(ctx, operationID)
    ctx = logging.WithComponent(ctx, "backup")

    logger.Info(ctx, "Starting backup process")

    // Backup stages with individual components
    if err := backupStage(ctx, cfg); err != nil {
        logger.Error(ctx, "Backup stage failed", err)
        return err
    }

    logger.Info(ctx, "Backup completed successfully")
    return nil
}

Environment Variables

The logging system can be configured via environment variables:

  • LOG_LEVEL: Set log level (debug, info, warn, error, fatal). Default: info
  • LOG_FORMAT: Set format (human, json). Default: human
  • LOG_OUTPUT: Set output destination:
    • stdout (default): Standard output
    • stderr: Standard error
    • Any file path: Write logs to the specified file
  • NO_COLOR: When set (any non-empty value), disables colored output for human format

Example usage:

# Set log level to debug and output to JSON format
export LOG_LEVEL=debug
export LOG_FORMAT=json
export LOG_OUTPUT=/var/log/mariadb-backup.log

# Disable colors in human format
export NO_COLOR=1

Thread Safety

All logging operations are thread-safe and can be called concurrently from multiple goroutines. The implementation uses sync.RWMutex to protect internal state while allowing concurrent reads.

Performance

The logging system is designed for high performance:

  • Minimal allocations in hot paths
  • Efficient field merging
  • Lazy formatter initialization
  • Context-aware logging to avoid unnecessary work

Error Handling

The logging system handles errors gracefully:

  • Formatter errors are logged to stderr and don't crash the application
  • Invalid configurations return errors during initialization
  • Missing context information falls back to sensible defaults

Testing

The logging system is designed to be testable:

func TestMyFunction(t *testing.T) {
    // Create an in-memory buffer for testing
    var buf bytes.Buffer

    config := logging.Config{
        Level:  logging.LogLevelDebug,
        Format: "json",
        Output: &buf, // Use an io.Writer like &bytes.Buffer{}
    }

    logger, err := logging.New(config)
    if err != nil {
        t.Fatal(err)
    }

    // Set the logger in the context
    ctx := logging.WithLogger(context.Background(), logger)

    // Use the logger in your tests
    logger.Info(ctx, "Test message")

    // Verify log output
    logOutput := buf.String()
    if !strings.Contains(logOutput, `"message":"Test message"`) {
        t.Error("Expected log message not found")
    }
}

Documentation

Index

Constants

View Source
const (
	ContextKeyInstance contextKey = "logging_instance"
)

Variables

View Source
var (
	ErrValidationFailed = errors.New("validation failed")
)

Functions

func GenerateOperationID

func GenerateOperationID(component string) string

GenerateOperationID generates a simple operation ID based on component and current time.

func WithComponent

func WithComponent(ctx context.Context, component string) context.Context

WithComponent adds a component name to the context.

func WithFields

func WithFields(ctx context.Context, fields Fields) context.Context

WithFields merges additional fields into context.

func WithLogger

func WithLogger(ctx context.Context, logger Logger) context.Context

func WithOperationID

func WithOperationID(ctx context.Context, operationID string) context.Context

WithOperationID adds an operation ID to the context.

Types

type Config

type Config struct {
	Level        LogLevel  `json:"level"`       // Level is the minimum log level to output
	Format       Format    `json:"format"`      // Format is the output format ("human" or "json")
	Output       io.Writer `json:"-"`           // Output is the output stream (empty for os.stdout)
	EnableColors bool      `json:"colors"`      // EnableColors enables colored output for human format
	TimeFormat   string    `json:"time_format"` // TimeFormat is the format for timestamps
}

Config holds the configuration for the logging system.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default logging configuration.

func (Config) Validate

func (c Config) Validate() error

Validate validates the logging configuration.

type Fields

type Fields map[string]any

Fields is a type for log fields to avoid confusion with map parameters.

type Format

type Format string
const (
	FormatHuman Format = "human"
	FormatJSON  Format = "json"
)

type Formatter

type Formatter interface {
	// Format formats a log entry for output
	Format(entry *LogEntry, writer io.Writer) error
	// Name returns the name of the formatter
	Name() string
}

Formatter defines the interface for formatting log entries.

type HumanFormatter

type HumanFormatter struct {
	// contains filtered or unexported fields
}

HumanFormatter formats log entries in a human-readable format with optional colors.

func NewHumanFormatter

func NewHumanFormatter(enableColors bool, timeFormat string) *HumanFormatter

NewHumanFormatter creates a new human formatter.

func (*HumanFormatter) Format

func (f *HumanFormatter) Format(entry *LogEntry, writer io.Writer) error

Format formats a log entry in human-readable format.

func (*HumanFormatter) Name

func (f *HumanFormatter) Name() string

Name returns the formatter name.

type JSONFormatter

type JSONFormatter struct {
	// contains filtered or unexported fields
}

JSONFormatter formats log entries as JSON objects.

func NewJSONFormatter

func NewJSONFormatter(timeFormat string) *JSONFormatter

NewJSONFormatter creates a new JSON formatter.

func (*JSONFormatter) Format

func (f *JSONFormatter) Format(entry *LogEntry, writer io.Writer) error

Format formats a log entry as JSON.

func (*JSONFormatter) Name

func (f *JSONFormatter) Name() string

Name returns the formatter name.

type LogContext

type LogContext struct {
	// Component is the component or module name
	Component string
	// OperationID is an identifier for the current operation
	OperationID string
	// Fields contains additional context fields
	Fields map[string]any
}

LogContext holds contextual information for logging operations.

type LogEntry

type LogEntry struct {
	Timestamp   time.Time      // Timestamp when the log entry was created
	Level       LogLevel       // Level of the log entry
	Message     string         // Message is the main log message
	Component   string         // Component is the component or module that generated the log entry
	OperationID string         // OperationID is an optional identifier for grouping related log entries
	Fields      map[string]any // Fields contains additional structured data
	Error       error          // Error contains error information if the log entry is error-related
}

LogEntry represents a structured log entry with all necessary information.

type LogLevel

type LogLevel int

LogLevel represents the severity level of log entries.

const (
	// LogLevelDebug is the lowest level, used for detailed debugging information.
	LogLevelDebug LogLevel = iota
	// LogLevelInfo is used for general information messages.
	LogLevelInfo
	// LogLevelWarn is used for warning messages.
	LogLevelWarn
	// LogLevelError is used for error messages.
	LogLevelError
	// LogLevelFatal is used for fatal error messages that terminate the application.
	LogLevelFatal
)

func (LogLevel) MarshalText

func (l LogLevel) MarshalText() ([]byte, error)

MarshalText makes LogLevel encode as its string (e.g., "INFO") in JSON and text encoders.

func (LogLevel) String

func (l LogLevel) String() string

String returns the string representation of the log level.

type Logger

type Logger interface {
	// Debug logs a message at debug level
	Debug(ctx context.Context, message string, fields ...Fields)
	// Info logs a message at info level
	Info(ctx context.Context, message string, fields ...Fields)
	// Warn logs a message at warn level
	Warn(ctx context.Context, message string, fields ...Fields)
	// Error logs a message at error level
	Error(ctx context.Context, message string, err error, fields ...Fields)
	// Fatal logs a message at fatal level and terminates the application
	Fatal(ctx context.Context, message string, err error, fields ...Fields)

	// WithContext creates a new logger with the specified context
	WithContext(component string, operationID string, fields ...Fields) Logger
	// SetLevel sets the minimum log level
	SetLevel(level LogLevel)
	// SetFormatter sets the log formatter
	SetFormatter(formatter Formatter)
	// Flush ensures all log entries are written
	Flush() error

	// Close closes the logger
	Close() error
}

Logger defines the interface for logging operations.

func GetLogger

func GetLogger(ctx context.Context) Logger

func New

func New(config Config) (Logger, error)

New creates a new logger instance.

func NewDefault

func NewDefault() Logger

NewDefault creates a new logger with default configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL