types

package
v1.19.3 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 3 Imported by: 0

README

Logger Types

Go Version License Coverage

Core types, interfaces, and constants for structured logging with standardized field names and extensible hook mechanisms.


Table of Contents


Overview

The types package provides the foundational types for the logger subsystem in github.com/nabbar/golib. It defines standardized field names for structured logging and an extensible Hook interface for advanced log processing, filtering, and multi-destination output.

Design Philosophy
  1. Standardization: Consistent field names across all logger implementations
  2. Extensibility: Hook interface for custom log processors without core modifications
  3. Type Safety: Strong typing prevents typos and ensures field name consistency
  4. Minimal Dependencies: Only standard library and logrus required
  5. Logrus Integration: Full compatibility with logrus.Hook and io.WriteCloser interfaces
Key Features
  • 9 Standard Field Constants: Type-safe field names for structured logging
  • Extended Hook Interface: Adds lifecycle management to logrus.Hook
  • Background Processing: Run() method for async log processing
  • Context Integration: Context-based cancellation and lifecycle control
  • Thread-Safe: Constants are immutable, Hook implementations guide concurrency
  • Zero Runtime Overhead: Constants inlined at compile time
  • io.WriteCloser Support: Direct write capabilities for hooks
  • Multiple Loggers: Single hook can register with multiple logger instances

Architecture

Component Diagram
┌─────────────────────────────────────────────────────────┐
│                    logger/types                         │
├──────────────────────┬──────────────────────────────────┤
│                      │                                  │
│   Field Constants    │         Hook Interface           │
│   (fields.go)        │         (hook.go)                │
│                      │                                  │
│  - FieldTime         │  Extends:                        │
│  - FieldLevel        │    • logrus.Hook                 │
│  - FieldStack        │    • io.WriteCloser              │
│  - FieldCaller       │                                  │
│  - FieldFile         │  Methods:                        │
│  - FieldLine         │    • RegisterHook(log)           │
│  - FieldMessage      │    • Run(ctx)                    │
│  - FieldError        │    • IsRunning()                 │
│  - FieldData         │    • Fire(entry)                 │
│                      │    • Levels()                    │
│                      │    • Write(p)                    │
│                      │    • Close()                     │
└──────────────────────┴──────────────────────────────────┘
Field Constants

The package provides 9 standard field name constants organized into three categories:

Metadata fields: Information about the log entry

  • FieldTime - Timestamp (RFC3339 format)
  • FieldLevel - Severity level (debug, info, warn, error, fatal)

Trace fields: Execution context and debugging information

  • FieldStack - Full stack trace (multi-line)
  • FieldCaller - Function/method identifier (package.function)
  • FieldFile - Source code file name
  • FieldLine - Line number in source file

Content fields: Log message and associated data

  • FieldMessage - Primary log message text
  • FieldError - Error description or message
  • FieldData - Additional structured data (maps, objects)

Example JSON log output:

{
  "time": "2025-01-01T12:00:00Z",
  "level": "error",
  "message": "operation failed",
  "error": "connection timeout",
  "file": "main.go",
  "line": 42,
  "caller": "main.processRequest",
  "data": {"user_id": 123}
}
Hook Interface

The Hook interface extends logrus.Hook with lifecycle management:

Interface composition:

  • logrus.Hook - Fire(entry) and Levels() methods
  • io.WriteCloser - Write(p) and Close() methods

Additional methods:

  • RegisterHook(log) - Self-registration with logger
  • Run(ctx) - Background processing with context cancellation
  • IsRunning() - State checking for monitoring

Key characteristics:

  • Fire() called synchronously for each log entry
  • Run() executes in background goroutine
  • Context-based graceful shutdown
  • Thread-safe when properly implemented

Performance

Field Constants
  • Zero runtime overhead: Constants inlined at compile time
  • No allocations: String constants don't allocate memory
  • Optimized comparisons: Compiler optimizes constant string comparisons
  • Thread-safe: Constants are immutable by definition
Hook Implementation

Fast patterns:

// Fire() offloads work to background goroutine
func (h *Hook) Fire(entry *logrus.Entry) error {
    select {
    case h.queue <- entry:
        return nil
    default:
        return errors.New("queue full")
    }
}

Slow patterns to avoid:

// DON'T: Synchronous I/O in Fire() blocks all logging
func (h *Hook) Fire(entry *logrus.Entry) error {
    return h.sendToRemoteAPI(entry) // BLOCKS LOGGING!
}

Performance guidelines:

  • Keep Fire() < 1ms execution time
  • Use buffered channels between Fire() and Run()
  • Perform heavy operations (I/O, formatting) in Run()
  • Use atomic operations for state management

Use Cases

1. Consistent Structured Logging

Problem: Inconsistent field names across application components.

Solution: Use standard field constants.

import "github.com/nabbar/golib/logger/types"

log.WithFields(logrus.Fields{
    types.FieldFile:  "handler.go",
    types.FieldLine:  123,
    types.FieldError: err.Error(),
}).Error("request failed")
2. Multi-Destination Logging

Problem: Log to multiple destinations (file, syslog, metrics) simultaneously.

Solution: Implement Hook interface for each destination.

fileHook := &FileHook{path: "/var/log/app.log"}
syslogHook := &SyslogHook{facility: "daemon"}
metricsHook := &MetricsHook{registry: prometheus.DefaultRegisterer}

fileHook.RegisterHook(logger)
syslogHook.RegisterHook(logger)
metricsHook.RegisterHook(logger)
3. Log Filtering and Transformation

Problem: Filter sensitive data or transform log format before output.

Solution: Hook Fire() method processes entries.

type SensitiveDataFilter struct{}

func (f *SensitiveDataFilter) Fire(entry *logrus.Entry) error {
    if pwd, ok := entry.Data["password"]; ok {
        entry.Data["password"] = "***REDACTED***"
    }
    return nil
}
4. Async Log Aggregation

Problem: Aggregate logs from high-frequency sources without blocking.

Solution: Buffer entries in Fire(), batch process in Run().

type BatchHook struct {
    queue chan *logrus.Entry
}

func (h *BatchHook) Fire(entry *logrus.Entry) error {
    h.queue <- entry
    return nil
}

func (h *BatchHook) Run(ctx context.Context) {
    ticker := time.NewTicker(5 * time.Second)
    batch := make([]*logrus.Entry, 0, 100)
    
    for {
        select {
        case entry := <-h.queue:
            batch = append(batch, entry)
            if len(batch) >= 100 {
                h.writeBatch(batch)
                batch = batch[:0]
            }
        case <-ticker.C:
            if len(batch) > 0 {
                h.writeBatch(batch)
                batch = batch[:0]
            }
        case <-ctx.Done():
            h.writeBatch(batch)
            return
        }
    }
}
5. Log Metrics Collection

Problem: Track log volume and error rates.

Solution: Hook increments metrics in Fire().

type MetricsHook struct {
    totalLogs    atomic.Int64
    errorLogs    atomic.Int64
}

func (h *MetricsHook) Fire(entry *logrus.Entry) error {
    h.totalLogs.Add(1)
    if entry.Level <= logrus.ErrorLevel {
        h.errorLogs.Add(1)
    }
    return nil
}

Quick Start

Installation
go get github.com/nabbar/golib/logger/types
Basic Field Usage
package main

import (
    "github.com/nabbar/golib/logger/types"
    "github.com/sirupsen/logrus"
)

func main() {
    log := logrus.New()
    
    log.WithFields(logrus.Fields{
        types.FieldFile: "main.go",
        types.FieldLine: 42,
    }).Info("application started")
}
Error Logging
func processRequest(req *Request) error {
    if err := validate(req); err != nil {
        log.WithFields(logrus.Fields{
            types.FieldError:  err.Error(),
            types.FieldFile:   "handler.go",
            types.FieldLine:   123,
            types.FieldCaller: "processRequest",
        }).Error("validation failed")
        return err
    }
    return nil
}
Basic Hook Implementation
package main

import (
    "context"
    "io"
    "sync/atomic"
    
    "github.com/nabbar/golib/logger/types"
    "github.com/sirupsen/logrus"
)

type SimpleHook struct {
    running atomic.Bool
    output  io.Writer
}

func (h *SimpleHook) Fire(entry *logrus.Entry) error {
    line, _ := entry.String()
    _, err := h.output.Write([]byte(line))
    return err
}

func (h *SimpleHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func (h *SimpleHook) RegisterHook(log *logrus.Logger) {
    log.AddHook(h)
}

func (h *SimpleHook) Run(ctx context.Context) {
    h.running.Store(true)
    defer h.running.Store(false)
    <-ctx.Done()
}

func (h *SimpleHook) IsRunning() bool {
    return h.running.Load()
}

func (h *SimpleHook) Write(p []byte) (n int, err error) {
    return h.output.Write(p)
}

func (h *SimpleHook) Close() error {
    return nil
}
Hook Lifecycle
func main() {
    log := logrus.New()
    hook := &SimpleHook{output: os.Stdout}
    
    // Register hook
    hook.RegisterHook(log)
    
    // Start background processing
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go hook.Run(ctx)
    
    // Use logger
    log.Info("processing started")
    
    // Cleanup
    cancel()
    hook.Close()
}
Multiple Hooks
func main() {
    log := logrus.New()
    
    // Multiple hooks for different purposes
    fileHook := &FileHook{path: "app.log"}
    consoleHook := &ConsoleHook{colored: true}
    metricsHook := &MetricsHook{}
    
    fileHook.RegisterHook(log)
    consoleHook.RegisterHook(log)
    metricsHook.RegisterHook(log)
    
    // All hooks receive log entries
    log.Info("this goes to file, console, and metrics")
}

Best Practices

Testing

The package includes comprehensive tests with 100% code coverage for constant definitions and interface compliance. Hook implementations are tested via mock implementations in 32 test specifications using BDD methodology (Ginkgo v2 + Gomega).

Quick test commands:

go test ./...                          # Run all tests
go test -cover ./...                   # With coverage
CGO_ENABLED=1 go test -race ./...      # With race detection

See TESTING.md for comprehensive testing documentation.

✅ DO

Use Field Constants:

// ✅ GOOD: Type-safe field names
log.WithField(types.FieldError, err.Error())

Fast Fire() Implementation:

// ✅ GOOD: Quick return, offload to Run()
func (h *Hook) Fire(entry *logrus.Entry) error {
    select {
    case h.queue <- entry:
        return nil
    default:
        return errors.New("queue full")
    }
}

Context Management:

// ✅ GOOD: Proper lifecycle
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go hook.Run(ctx)

Thread Safety:

// ✅ GOOD: Atomic state
type Hook struct {
    running atomic.Bool
    mu      sync.Mutex
    data    map[string]interface{}
}
❌ DON'T

Hardcode Field Names:

// ❌ BAD: Prone to typos
log.WithField("eror", err.Error()) // Typo!

Slow Fire() Method:

// ❌ BAD: Blocks all logging
func (h *Hook) Fire(entry *logrus.Entry) error {
    time.Sleep(100 * time.Millisecond)
    return h.sendToAPI(entry)
}

Forget Context Cancellation:

// ❌ BAD: Goroutine leak
go hook.Run(context.Background())

Race Conditions:

// ❌ BAD: Unprotected shared state
type Hook struct {
    count int // Race condition!
}

func (h *Hook) Fire(entry *logrus.Entry) error {
    h.count++ // UNSAFE!
    return nil
}

API Reference

Field Constants
const (
    FieldTime    = "time"     // Timestamp (RFC3339)
    FieldLevel   = "level"    // Severity level
    FieldStack   = "stack"    // Stack trace
    FieldCaller  = "caller"   // Function identifier
    FieldFile    = "file"     // Source file name
    FieldLine    = "line"     // Line number
    FieldMessage = "message"  // Log message
    FieldError   = "error"    // Error description
    FieldData    = "data"     // Structured data
)

Usage: Keys in logrus.Fields maps for structured logging.

Thread safety: Immutable constants, safe for concurrent use.

Hook Interface
type Hook interface {
    logrus.Hook        // Fire(entry), Levels()
    io.WriteCloser     // Write(p), Close()
    
    RegisterHook(log *logrus.Logger)
    Run(ctx context.Context)
    IsRunning() bool
}

Methods:

  • Fire(entry *logrus.Entry) error - Process log entry (from logrus.Hook)

    • Called synchronously for every matching log entry
    • MUST return quickly (< 1ms recommended)
    • Offload heavy work to Run() via channels
  • Levels() []logrus.Level - Return handled log levels (from logrus.Hook)

    • Return logrus.AllLevels to process all levels
    • Filter to reduce Fire() call frequency
  • Write(p []byte) (n int, err error) - Direct write (from io.Writer)

    • Bypass logrus for external log sources
    • Must handle concurrent calls safely
  • Close() error - Cleanup resources (from io.Closer)

    • Idempotent (safe to call multiple times)
    • Wait for in-flight operations
  • RegisterHook(log *logrus.Logger) - Register with logger

    • Call log.AddHook(h)
    • Perform initialization
    • Can register with multiple loggers
  • Run(ctx context.Context) - Background processing

    • Execute in goroutine: go hook.Run(ctx)
    • Perform heavy operations (I/O, formatting)
    • Respect ctx.Done() for graceful shutdown
  • IsRunning() bool - Check operational state

    • Return true while Run() executes
    • Use atomic.Bool for thread safety
Interface Composition

logrus.Hook (embedded):

  • Standard logrus hook integration
  • All logrus features available

io.WriteCloser (embedded):

  • Direct I/O capabilities
  • Standard cleanup interface

Additional Methods:

  • Lifecycle management (RegisterHook, Run, IsRunning)
  • Context-based control
  • Monitoring support

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Code Quality

    • Follow Go best practices and idioms
    • Maintain 100% code coverage for constant definitions
    • Pass all tests including race detector
    • Use gofmt and golint
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality
    • AI assistance is limited to:
      • Testing (writing and improving tests)
      • Debugging (troubleshooting and bug resolution)
      • Documentation (comments, README, TESTING.md)
    • All AI-assisted work must be reviewed and validated by humans
  3. Testing

    • Add tests for new features
    • Use Ginkgo v2 / Gomega for test framework
    • Ensure zero race conditions with go test -race
    • Test Hook implementations with mocks
  4. Documentation

    • Update GoDoc comments for public APIs
    • Add examples for new features
    • Update README.md and TESTING.md if needed
  5. Pull Request Process

    • Fork the repository
    • Create a feature branch
    • Write clear commit messages
    • Ensure all tests pass
    • Update documentation
    • Submit PR with description of changes

See CONTRIBUTING.md for detailed guidelines.


Improvements & Security

Current Status

The package is production-ready with no urgent improvements or security vulnerabilities identified.

Code Quality Metrics
  • 100% coverage for constant definitions (target: >80%)
  • Zero race conditions detected with -race flag
  • Thread-safe constants and interface guidance
  • Type-safe field names prevent typos
  • Standard interfaces for maximum compatibility
Future Enhancements (Non-urgent)

The following enhancements could be considered for future versions:

Field Constants:

  1. Field namespacing mechanism for custom extensions
  2. Field validation helpers
  3. Field grouping utilities
  4. Standard field sets for common scenarios

Hook Interface:

  1. Priority system for hook execution order
  2. Hook chaining helpers
  3. Built-in buffering/batching utilities
  4. Standard hook implementations (file, syslog, etc.)

Quality of Life:

  1. Field builder pattern for complex log entries
  2. Hook factory functions
  3. Testing utilities for Hook implementations
  4. Performance profiling helpers

These are optional improvements and not required for production use. The current implementation is stable and feature-complete for its intended use cases.

Suggestions and contributions are welcome via GitHub issues.


Resources

Package Documentation
  • GoDoc - Complete API reference with function signatures, method descriptions, and runnable examples. Essential for understanding the public interface and usage patterns.

  • doc.go - In-depth package documentation including design philosophy, architecture diagrams, field constant categories, Hook interface details, performance considerations, and best practices for production use.

  • TESTING.md - Comprehensive test suite documentation covering test architecture, BDD methodology with Ginkgo v2, 100% coverage analysis, example tests, and guidelines for writing new tests.

External References
  • Logrus Documentation - The structured logging library that this package extends. Understanding logrus.Hook interface is essential for implementing custom hooks.

  • Effective Go - Official Go programming guide covering best practices for interfaces, constants, and concurrent programming. This package follows these conventions.

  • Context Package - Standard library documentation for context.Context. The Hook interface uses context for lifecycle management and graceful shutdown.


AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2025 Nicolas JUHEL


Maintained by: Nicolas JUHEL
Package: github.com/nabbar/golib/logger/types
Version: See releases for versioning

Documentation

Overview

Package types provides core types, interfaces, and constants for the logger subsystem.

Overview

This package defines the foundational types used across the github.com/nabbar/golib/logger ecosystem. It establishes standardized field names for structured logging and defines the Hook interface for extending logger functionality with custom output destinations and processors.

Design Philosophy

The package follows these key principles:

  1. Standardization: Provides consistent field names across all logger implementations
  2. Extensibility: Hook interface allows seamless integration of custom log processors
  3. Minimal Dependencies: Only depends on standard library and logrus
  4. Type Safety: Strong typing for field constants prevents typos and ensures consistency
  5. Compatibility: Full integration with logrus.Hook and io.WriteCloser interfaces

Package Architecture

The package is organized into three main components:

┌─────────────────────────────────────────────────────────┐
│                    logger/types                         │
├──────────────────────┬──────────────────────────────────┤
│                      │                                  │
│   Field Constants    │         Hook Interface           │
│   (fields.go)        │         (hook.go)                │
│                      │                                  │
│  - FieldTime         │  Extends:                        │
│  - FieldLevel        │    • logrus.Hook                 │
│  - FieldStack        │    • io.WriteCloser              │
│  - FieldCaller       │                                  │
│  - FieldFile         │  Methods:                        │
│  - FieldLine         │    • RegisterHook(log)           │
│  - FieldMessage      │    • Run(ctx)                    │
│  - FieldError        │    • IsRunning()                 │
│  - FieldData         │    • Fire(entry)                 │
│                      │    • Levels()                    │
│                      │    • Write(p)                    │
│                      │    • Close()                     │
└──────────────────────┴──────────────────────────────────┘

Field Constants

The package defines standard field names for structured logging to ensure consistency across different logger implementations and output formats (JSON, text, etc.).

Field categories:

  • Metadata fields: FieldTime, FieldLevel
  • Trace fields: FieldStack, FieldCaller, FieldFile, FieldLine
  • Content fields: FieldMessage, FieldError, FieldData

Example usage in log entry:

{
  "time": "2025-01-01T12:00:00Z",      // FieldTime
  "level": "error",                     // FieldLevel
  "message": "operation failed",        // FieldMessage
  "error": "connection timeout",        // FieldError
  "file": "main.go",                    // FieldFile
  "line": 42,                           // FieldLine
  "caller": "main.processRequest",      // FieldCaller
  "stack": "goroutine 1 [running]...",  // FieldStack
  "data": {...}                         // FieldData
}

Hook Interface

The Hook interface extends logrus.Hook with additional lifecycle management and I/O capabilities. This allows for sophisticated log processors that can:

  • Intercept and process log entries before output
  • Write to multiple destinations simultaneously
  • Run in background goroutines with context-based cancellation
  • Implement complex filtering and transformation logic
  • Support graceful shutdown and resource cleanup

Interface composition:

Hook embeds:
  • logrus.Hook      - Standard logrus hook integration
  • io.WriteCloser   - Direct write and close capabilities

Additional methods:
  • RegisterHook     - Self-registration with logger instance
  • Run             - Background execution with context control
  • IsRunning       - State checking for lifecycle management

Key Features

Standardized Fields:

  • Type-safe constants prevent typos in field names
  • Consistent naming across all logger implementations
  • Easy integration with structured logging frameworks
  • Facilitates log parsing and analysis tools

Hook Extensibility:

  • Implement custom log processors without modifying core logger
  • Support for multiple concurrent hooks
  • Background processing with goroutines
  • Context-based lifecycle management
  • Clean shutdown via io.Closer interface

Logrus Integration:

  • Full compatibility with logrus.Hook interface
  • Seamless integration into existing logrus-based applications
  • Access to all logrus features (levels, formatting, fields)

Use Cases

Structured Logging:

Use field constants to ensure consistent field names across your application:

import "github.com/nabbar/golib/logger/types"

log.WithFields(logrus.Fields{
    types.FieldFile:  "handler.go",
    types.FieldLine:  123,
    types.FieldError: err.Error(),
}).Error("request processing failed")

Custom Log Processors:

Implement the Hook interface to create custom log handlers:

type EmailHook struct {
    smtpServer string
    running    atomic.Bool
}

func (h *EmailHook) Fire(entry *logrus.Entry) error {
    if entry.Level <= logrus.ErrorLevel {
        return h.sendEmail(entry)
    }
    return nil
}

func (h *EmailHook) Levels() []logrus.Level {
    return []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel}
}

func (h *EmailHook) RegisterHook(log *logrus.Logger) {
    log.AddHook(h)
}

func (h *EmailHook) Run(ctx context.Context) {
    h.running.Store(true)
    defer h.running.Store(false)
    <-ctx.Done()
}

func (h *EmailHook) IsRunning() bool {
    return h.running.Load()
}

func (h *EmailHook) Write(p []byte) (n int, err error) {
    // Custom write logic
    return len(p), nil
}

func (h *EmailHook) Close() error {
    // Cleanup resources
    return nil
}

Multi-Destination Logging:

Use hooks to simultaneously log to multiple destinations:

logger := logrus.New()
fileHook := &FileHook{path: "/var/log/app.log"}
syslogHook := &SyslogHook{facility: "daemon"}
metricsHook := &MetricsHook{registry: prometheus.DefaultRegisterer}

fileHook.RegisterHook(logger)
syslogHook.RegisterHook(logger)
metricsHook.RegisterHook(logger)

Log Filtering and Transformation:

Implement hooks to filter or modify log entries:

type SensitiveDataFilter struct{}

func (f *SensitiveDataFilter) Fire(entry *logrus.Entry) error {
    if pwd, ok := entry.Data["password"]; ok {
        entry.Data["password"] = "***REDACTED***"
    }
    return nil
}

Performance Considerations

Field Constants:

  • Zero runtime overhead - constants are inlined at compile time
  • No memory allocation for field name strings
  • Compiler-optimized string comparisons

Hook Interface:

  • Fire() is called synchronously for every log entry
  • Keep Fire() implementations fast to avoid blocking logging
  • Use Run() goroutine for heavy processing (buffering, batching)
  • Consider using channels to offload work from Fire() to Run()

Recommended patterns:

// FAST: Simple filtering
func (h *FastHook) Fire(entry *logrus.Entry) error {
    if entry.Level > logrus.ErrorLevel {
        return nil // Skip non-error logs
    }
    h.queue <- entry // Send to background processor
    return nil
}

// SLOW: Avoid this pattern
func (h *SlowHook) Fire(entry *logrus.Entry) error {
    time.Sleep(100 * time.Millisecond) // Blocks logging!
    return h.sendToRemoteAPI(entry)    // Synchronous network call!
}

Limitations

Field Constants:

  • Field names are predefined and cannot be customized per-logger
  • Adding new standard fields requires package modification
  • No namespacing mechanism for field names
  • Workaround: Use custom fields in logrus.Fields alongside standard fields

Hook Interface:

  • Fire() must return quickly to avoid blocking all logging
  • No built-in buffering or batching mechanisms
  • Context in Run() is not available in Fire() method
  • Multiple hooks execute in registration order (no priority system)
  • Error from Fire() is logged but doesn't stop other hooks

Logrus Dependency:

  • Tightly coupled to logrus.Logger and logrus.Hook interfaces
  • Migration to other logging frameworks requires reimplementation
  • Inherits logrus performance characteristics and limitations

Best Practices

Using Field Constants:

// DO: Use constants for standard fields
log.WithField(types.FieldError, err.Error())

// DON'T: Hardcode field names
log.WithField("error", err.Error())

Implementing Hooks:

// DO: Fast Fire() with background processing
func (h *Hook) Fire(entry *logrus.Entry) error {
    select {
    case h.queue <- entry:
        return nil
    default:
        return errors.New("queue full")
    }
}

// DON'T: Slow synchronous operations in Fire()
func (h *Hook) Fire(entry *logrus.Entry) error {
    return h.writeToDatabase(entry) // Blocks all logging!
}

Context Management:

// DO: Use context for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go hook.Run(ctx)

// DON'T: Forget to cancel context
go hook.Run(context.Background()) // Goroutine leak!

Resource Cleanup:

// DO: Always close hooks
defer hook.Close()

// DO: Check IsRunning() before operations
if hook.IsRunning() {
    hook.Write(data)
}

Thread Safety

Field Constants:

  • Thread-safe: constants are immutable
  • Safe for concurrent reads from multiple goroutines

Hook Interface:

  • Implementation-specific: hook implementations must handle their own synchronization
  • Fire() may be called concurrently from multiple goroutines
  • Run() typically executes in a single goroutine
  • Use sync.Mutex or atomic operations to protect shared state

Integration with golib

This package is used by:

  • github.com/nabbar/golib/logger/config - Logger configuration
  • github.com/nabbar/golib/logger/entry - Log entry management
  • github.com/nabbar/golib/logger/fields - Field manipulation
  • github.com/nabbar/golib/logger/gorm - GORM logger integration

External Dependencies

Required:

  • github.com/sirupsen/logrus - Structured logging library
  • Standard library (context, io)

No transitive dependencies beyond logrus.

Compatibility

Minimum Go version: 1.18

The package maintains semantic versioning and ensures backward compatibility within major versions. Field constant values are considered part of the public API and will not change within a major version.

Examples

For comprehensive examples, see the example_test.go file.

Example (AllFieldConstants)

Example_allFieldConstants demonstrates all available field constants.

This example shows the complete set of standard field names defined in the types package.

package main

import (
	"fmt"

	"github.com/nabbar/golib/logger/types"
)

func main() {
	// Display all field constants in deterministic order
	fields := []struct {
		name  string
		value string
	}{
		{"Time", types.FieldTime},
		{"Level", types.FieldLevel},
		{"Stack", types.FieldStack},
		{"Caller", types.FieldCaller},
		{"File", types.FieldFile},
		{"Line", types.FieldLine},
		{"Message", types.FieldMessage},
		{"Error", types.FieldError},
		{"Data", types.FieldData},
	}

	for _, field := range fields {
		fmt.Printf("%s: %s\n", field.name, field.value)
	}

}
Output:

Time: time
Level: level
Stack: stack
Caller: caller
File: file
Line: line
Message: message
Error: error
Data: data
Example (BasicFieldUsage)

Example_basicFieldUsage demonstrates using field constants with logrus.

This example shows the simplest way to use standardized field names in structured logging.

package main

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

func main() {
	// Create a logger
	log := logrus.New()
	log.SetOutput(io.Discard) // Discard output for example

	// Use field constants for structured logging
	log.WithFields(logrus.Fields{
		types.FieldFile:    "main.go",
		types.FieldLine:    42,
		types.FieldMessage: "operation completed",
	}).Info("example log entry")

	fmt.Println("Field constants used successfully")
}
Output:

Field constants used successfully
Example (BasicHook)

Example_basicHook demonstrates implementing a basic Hook.

This example shows the minimal implementation required to satisfy the Hook interface.

package main

import (
	"context"
	"fmt"
	"io"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Create and register hook
	hook := &simpleHook{}
	hook.RegisterHook(log)

	// Use logger - hook will intercept entries
	log.Info("test message")

	fmt.Println("Hook registered and used successfully")
}
Output:

Hook registered and used successfully
Example (CustomFieldsWithStandard)

Example_customFieldsWithStandard demonstrates mixing custom and standard fields.

This example shows how to use standard field constants alongside custom application-specific fields.

package main

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Mix standard and custom fields
	log.WithFields(logrus.Fields{
		// Standard fields
		types.FieldFile:  "api.go",
		types.FieldLine:  99,
		types.FieldError: "timeout",

		// Custom fields
		"request_id": "abc-123",
		"user_id":    456,
		"endpoint":   "/api/v1/users",
	}).Error("API request failed")

	fmt.Println("Mixed standard and custom fields")
}
Output:

Mixed standard and custom fields
Example (ErrorFieldsUsage)

Example_errorFieldsUsage demonstrates logging errors with standardized fields.

This example shows how to use field constants when logging errors with additional context information.

package main

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Simulate an error scenario
	err := fmt.Errorf("connection timeout")

	// Log error with standard fields
	log.WithFields(logrus.Fields{
		types.FieldError:  err.Error(),
		types.FieldFile:   "handler.go",
		types.FieldLine:   123,
		types.FieldCaller: "processRequest",
	}).Error("request processing failed")

	fmt.Println("Error logged with standard fields")
}
Output:

Error logged with standard fields
Example (FieldCategories)

Example_fieldCategories demonstrates grouping fields by category.

This example shows how to organize fields into logical categories (metadata, trace, content).

package main

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Metadata fields
	metadataFields := logrus.Fields{
		types.FieldTime:  "2025-01-01T12:00:00Z",
		types.FieldLevel: "info",
	}

	// Trace fields
	traceFields := logrus.Fields{
		types.FieldFile:   "main.go",
		types.FieldLine:   42,
		types.FieldCaller: "main.run",
		types.FieldStack:  "...",
	}

	// Content fields
	contentFields := logrus.Fields{
		types.FieldMessage: "processing started",
		types.FieldData:    map[string]interface{}{"id": 123},
	}

	// Combine all fields
	allFields := make(logrus.Fields)
	for k, v := range metadataFields {
		allFields[k] = v
	}
	for k, v := range traceFields {
		allFields[k] = v
	}
	for k, v := range contentFields {
		allFields[k] = v
	}

	log.WithFields(allFields).Info("categorized fields")
	fmt.Println("Fields categorized successfully")
}
Output:

Fields categorized successfully
Example (FieldConstantsInMapKeys)

Example_fieldConstantsInMapKeys demonstrates using fields as map keys.

This example shows using field constants as map keys for building structured log data programmatically.

package main

import (
	"fmt"

	"github.com/nabbar/golib/logger/types"
)

func main() {
	// Build log data structure
	logEntry := map[string]interface{}{
		types.FieldTime:    "2025-01-01T12:00:00Z",
		types.FieldLevel:   "error",
		types.FieldMessage: "database query failed",
		types.FieldError:   "connection lost",
		types.FieldFile:    "db.go",
		types.FieldLine:    234,
	}

	// Verify structure
	if _, hasError := logEntry[types.FieldError]; hasError {
		fmt.Println("Error field present in log entry")
	}
}
Output:

Error field present in log entry
Example (FieldValidation)

Example_fieldValidation demonstrates checking field names.

This example shows how to validate that log entries contain expected standard fields.

package main

import (
	"fmt"

	"github.com/nabbar/golib/logger/types"
)

func main() {
	// Simulate log entry data
	logData := map[string]interface{}{
		types.FieldTime:    "2025-01-01T12:00:00Z",
		types.FieldLevel:   "info",
		types.FieldMessage: "test message",
	}

	// Validate required fields
	requiredFields := []string{
		types.FieldTime,
		types.FieldLevel,
		types.FieldMessage,
	}

	allPresent := true
	for _, field := range requiredFields {
		if _, exists := logData[field]; !exists {
			allPresent = false
			break
		}
	}

	if allPresent {
		fmt.Println("All required fields present")
	}
}
Output:

All required fields present
Example (HookContextCancellation)

Example_hookContextCancellation demonstrates context-based cancellation.

This example shows how the Run() method respects context cancellation for graceful shutdown.

package main

import (
	"context"
	"fmt"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	hook := &simpleHook{}

	ctx, cancel := context.WithCancel(context.Background())

	// Start hook in background
	done := make(chan bool)
	go func() {
		hook.Run(ctx)
		done <- true
	}()

	// Cancel context
	cancel()

	// Wait for hook to stop
	<-done

	fmt.Println("Hook stopped gracefully")
}
Output:

Hook stopped gracefully
Example (HookLevelsMethod)

Example_hookLevelsMethod demonstrates the Levels() method.

This example shows how the Levels() method controls which log levels are sent to the hook's Fire() method.

package main

import (
	"context"
	"fmt"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	// Create a hook that only receives error and fatal logs
	hook := &simpleHook{}

	// Override Levels method (in real code, implement in type)
	levels := hook.Levels()

	fmt.Printf("Hook receives %d log levels\n", len(levels))
}
Output:

Hook receives 7 log levels
Example (HookLifecycle)

Example_hookLifecycle demonstrates the complete Hook lifecycle.

This example shows how to create, register, run, and close a hook with proper context management.

package main

import (
	"context"
	"fmt"
	"io"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Create hook
	hook := &simpleHook{}

	// Register with logger
	hook.RegisterHook(log)

	// Start background processing
	ctx, cancel := context.WithCancel(context.Background())

	// Start hook and wait for it to be running
	done := make(chan bool)
	go func() {
		hook.Run(ctx)
		done <- true
	}()

	// Give goroutine time to start
	for !hook.IsRunning() {
		// Spin until running
	}

	// Use the logger
	log.Info("processing started")

	// Check hook status
	if hook.IsRunning() {
		fmt.Println("Hook is running")
	}

	// Cleanup
	cancel()
	<-done // Wait for Run to finish
	_ = hook.Close()

	fmt.Println("Hook lifecycle completed")
}
Output:

Hook is running
Hook lifecycle completed
Example (HookWithFiltering)

Example_hookWithFiltering demonstrates filtering log entries in a hook.

This example shows how to implement a hook that only processes certain log levels or entries matching specific criteria.

package main

import (
	"context"
	"fmt"
	"io"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// levelFilterHook filters log entries by level.
type levelFilterHook struct {
	minLevel logrus.Level
	running  atomic.Bool
}

// Fire processes entries at or above minLevel.
func (h *levelFilterHook) Fire(entry *logrus.Entry) error {
	if entry.Level <= h.minLevel {

		return nil
	}
	return nil
}

// Levels returns all levels (filtering happens in Fire).
func (h *levelFilterHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *levelFilterHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *levelFilterHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *levelFilterHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *levelFilterHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *levelFilterHook) Close() error {
	return nil
}

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)
	log.SetLevel(logrus.TraceLevel)

	// Create hook that only processes Error level and above
	hook := &levelFilterHook{minLevel: logrus.ErrorLevel}
	hook.RegisterHook(log)

	// These will be filtered out
	log.Debug("debug message")
	log.Info("info message")

	// This will be processed
	log.Error("error message")

	fmt.Println("Hook with filtering working")
}
Output:

Hook with filtering working
Example (HookWriteMethod)

Example_hookWriteMethod demonstrates direct writing to a hook.

This example shows using the io.Writer interface to write directly to a hook, bypassing the logrus.Entry mechanism.

package main

import (
	"context"
	"fmt"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	hook := &simpleHook{}

	// Write directly to hook
	data := []byte("direct write data\n")
	n, err := hook.Write(data)

	if err == nil && n == len(data) {
		fmt.Println("Direct write successful")
	}
}
Output:

Direct write successful
Example (InterfaceImplementation)

Example_interfaceImplementation demonstrates checking interface compliance.

This example shows how to verify that a type implements the Hook interface at compile time.

package main

import (
	"context"
	"fmt"
	"sync/atomic"

	"github.com/nabbar/golib/logger/types"
	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	// Compile-time interface check
	var _ types.Hook = (*simpleHook)(nil)

	fmt.Println("Hook interface implemented correctly")
}
Output:

Hook interface implemented correctly
Example (MultipleHooks)

Example_multipleHooks demonstrates using multiple hooks simultaneously.

This example shows how to register multiple hooks with a single logger, allowing log entries to be processed by multiple handlers.

package main

import (
	"context"
	"fmt"
	"io"
	"sync/atomic"

	"github.com/sirupsen/logrus"
)

// simpleHook is a minimal Hook implementation for examples.
type simpleHook struct {
	running atomic.Bool
	entries []string
}

// Fire processes a log entry.
func (h *simpleHook) Fire(entry *logrus.Entry) error {
	h.entries = append(h.entries, entry.Message)
	return nil
}

// Levels returns the log levels this hook processes.
func (h *simpleHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

// RegisterHook registers the hook with a logger.
func (h *simpleHook) RegisterHook(log *logrus.Logger) {
	log.AddHook(h)
}

// Run runs the hook until context is cancelled.
func (h *simpleHook) Run(ctx context.Context) {
	h.running.Store(true)
	defer h.running.Store(false)
	<-ctx.Done()
}

// IsRunning returns whether the hook is running.
func (h *simpleHook) IsRunning() bool {
	return h.running.Load()
}

// Write implements io.Writer.
func (h *simpleHook) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// Close implements io.Closer.
func (h *simpleHook) Close() error {
	return nil
}

func main() {
	log := logrus.New()
	log.SetOutput(io.Discard)

	// Create multiple hooks
	hook1 := &simpleHook{}
	hook2 := &simpleHook{}

	// Register all hooks
	hook1.RegisterHook(log)
	hook2.RegisterHook(log)

	// Log entry will be sent to both hooks
	log.Info("distributed log entry")

	fmt.Println("Multiple hooks registered successfully")
}
Output:

Multiple hooks registered successfully
Example (ThreadSafeFieldUsage)

Example_threadSafeFieldUsage demonstrates thread-safe field access.

This example shows that field constants can be safely accessed from multiple goroutines without synchronization.

package main

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

func main() {
	done := make(chan bool, 3)

	// Multiple goroutines using field constants
	for i := 0; i < 3; i++ {
		go func(id int) {
			log := logrus.New()
			log.SetOutput(io.Discard)

			log.WithFields(logrus.Fields{
				types.FieldFile: fmt.Sprintf("goroutine_%d.go", id),
				types.FieldLine: id * 100,
			}).Info("concurrent access")

			done <- true
		}(i)
	}

	// Wait for all goroutines
	for i := 0; i < 3; i++ {
		<-done
	}

	fmt.Println("Concurrent field access successful")
}
Output:

Concurrent field access successful

Index

Examples

Constants

View Source
const (
	// FieldTime is the field name for log entry timestamp.
	// Typically formatted as RFC3339: "2025-01-01T12:00:00Z"
	FieldTime = "time"

	// FieldLevel is the field name for log severity level.
	// Common values: "debug", "info", "warn", "error", "fatal", "panic"
	FieldLevel = "level"

	// FieldStack is the field name for full stack trace.
	// Contains multi-line stack trace starting from the point of logging.
	// Usually included only for error and fatal level logs.
	FieldStack = "stack"

	// FieldCaller is the field name for calling function identifier.
	// Typically formatted as "package.function" or "package.Type.method".
	// Example: "main.processRequest" or "server.Handler.ServeHTTP"
	FieldCaller = "caller"

	// FieldFile is the field name for source code file.
	// Contains the file name (not full path) where the log was generated.
	// Example: "handler.go", "server.go"
	FieldFile = "file"

	// FieldLine is the field name for source code line number.
	// Contains the line number within FieldFile where the log was generated.
	// Type: integer. Example: 42, 123
	FieldLine = "line"

	// FieldMessage is the field name for primary log message.
	// Contains the main descriptive text of the log entry.
	// This is the human-readable description of what happened.
	FieldMessage = "message"

	// FieldError is the field name for error description.
	// Contains the error message or error string when an error occurred.
	// Typically populated from err.Error() or error descriptions.
	FieldError = "error"

	// FieldData is the field name for additional structured data.
	// Contains any extra contextual information as structured data.
	// Can hold maps, slices, or any JSON-serializable data structure.
	FieldData = "data"
)

Standard field name constants for structured logging.

These constants define the canonical field names used across the logger subsystem to ensure consistency in structured log output. They are used as keys in logrus.Fields maps and appear in formatted log output (JSON, text, etc.).

The constants are organized into three logical categories:

Metadata fields: Contain information about the log entry itself

  • FieldTime: Timestamp when the log entry was created
  • FieldLevel: Severity level (debug, info, warn, error, fatal)

Trace fields: Provide execution context and debugging information

  • FieldStack: Full stack trace (usually for errors)
  • FieldCaller: Function or method name that generated the log
  • FieldFile: Source code file name
  • FieldLine: Line number in source code file

Content fields: Carry the actual log message and associated data

  • FieldMessage: Primary log message text
  • FieldError: Error message or description
  • FieldData: Additional structured data (maps, objects, etc.)

Example usage:

log.WithFields(logrus.Fields{
    types.FieldFile:  "handler.go",
    types.FieldLine:  123,
    types.FieldError: err.Error(),
}).Error("request failed")

Variables

This section is empty.

Functions

This section is empty.

Types

type Hook

type Hook interface {
	logrus.Hook
	io.WriteCloser

	// RegisterHook registers this hook with the given logger instance.
	//
	// This method should call log.AddHook(h) to integrate the hook into the
	// logger's processing pipeline. It may also perform additional initialization
	// such as creating output files, establishing network connections, or
	// allocating buffers.
	//
	// This method does not return an error as the hook is responsible for
	// handling any initialization errors internally (e.g., by logging them
	// or storing them for later retrieval).
	//
	// The method should be called once during application startup before any
	// logging occurs. Calling it multiple times with different loggers will
	// register the same hook instance with multiple loggers.
	//
	// Parameters:
	//   - log: The logrus.Logger instance to register with
	//
	// Thread safety:
	//   - Safe to call concurrently with different logger instances
	//   - Not safe to call concurrently with the same logger instance
	RegisterHook(log *logrus.Logger)

	// Run executes the hook's background processing loop until the context is cancelled.
	//
	// This method should be called in a goroutine to enable background processing
	// of log entries without blocking the main application. It typically receives
	// work from Fire() via buffered channels and performs heavy operations like:
	//   - Batching log entries before writing
	//   - Writing to slow destinations (network, disk)
	//   - Formatting or transforming log data
	//   - Aggregating metrics from log entries
	//
	// The method must respect context cancellation and return promptly when
	// ctx.Done() is signalled. This allows for graceful shutdown where the hook
	// can flush pending entries and close resources.
	//
	// Implementation pattern:
	//
	//	func (h *Hook) Run(ctx context.Context) {
	//	    for {
	//	        select {
	//	        case work := <-h.workQueue:
	//	            h.process(work)
	//	        case <-ctx.Done():
	//	            h.flush() // Process remaining work
	//	            return
	//	        }
	//	    }
	//	}
	//
	// Parameters:
	//   - ctx: Context for cancellation and deadline control
	//
	// Thread safety:
	//   - Should only be called once per hook instance
	//   - Typically called with "go hook.Run(ctx)" pattern
	Run(ctx context.Context)

	// IsRunning returns whether the hook's Run() method is currently executing.
	//
	// This method provides a way to check the operational state of the hook,
	// useful for monitoring, status reporting, and coordination with other
	// components. It should return true from when Run() starts until it returns.
	//
	// Typical implementation uses an atomic.Bool or similar mechanism:
	//
	//	func (h *Hook) Run(ctx context.Context) {
	//	    h.running.Store(true)
	//	    defer h.running.Store(false)
	//	    // ... processing loop ...
	//	}
	//
	//	func (h *Hook) IsRunning() bool {
	//	    return h.running.Load()
	//	}
	//
	// Returns:
	//   - true if Run() is currently executing
	//   - false if Run() has not been started or has already returned
	//
	// Thread safety:
	//   - Must be safe for concurrent calls from multiple goroutines
	//   - Must provide consistent state without race conditions
	IsRunning() bool
}

Hook defines an extended logger hook interface for advanced log processing.

This interface extends logrus.Hook with lifecycle management, I/O capabilities, and background processing support. It allows for sophisticated log handlers that can intercept log entries, write to multiple destinations, and run in background goroutines with graceful shutdown.

Interface composition:

  • logrus.Hook: Provides Fire(entry) and Levels() methods for log interception
  • io.WriteCloser: Provides Write(p) and Close() methods for direct I/O

Additional methods provide lifecycle management:

  • RegisterHook: Self-registration with logger instances
  • Run: Background execution with context-based cancellation
  • IsRunning: State checking for monitoring and coordination

Implementation requirements:

Fire() method (from logrus.Hook):

  • Called synchronously for every log entry matching Levels()
  • MUST return quickly to avoid blocking all logging
  • Should offload heavy processing to Run() goroutine via channels
  • Returning an error logs the error but doesn't stop other hooks

Levels() method (from logrus.Hook):

  • Returns slice of log levels this hook processes
  • Return logrus.AllLevels to process all levels
  • Filter by level to reduce Fire() call overhead

Write() method (from io.Writer):

  • Allows direct writing to the hook bypassing logrus
  • Useful for external log sources or raw data injection
  • Should handle concurrent calls safely

Close() method (from io.Closer):

  • Called during cleanup to release resources
  • Should be idempotent (safe to call multiple times)
  • Should wait for in-flight operations to complete

RegisterHook() method:

  • Called once during initialization
  • Should call log.AddHook(h) to register with logrus
  • May perform additional initialization

Run() method:

  • Runs in background goroutine until context is cancelled
  • Use for heavy processing: buffering, batching, network I/O
  • Should respect ctx.Done() for graceful shutdown
  • Typically receives work from Fire() via channels

IsRunning() method:

  • Returns true if Run() goroutine is active
  • Used for status monitoring and coordination
  • Should be safe for concurrent calls

Thread safety:

  • Fire() may be called concurrently from multiple goroutines
  • All methods should handle concurrent access safely
  • Use sync.Mutex or atomic operations for shared state

Example implementation:

type MyHook struct {
    queue   chan *logrus.Entry
    running atomic.Bool
    mu      sync.Mutex
}

func (h *MyHook) Fire(entry *logrus.Entry) error {
    select {
    case h.queue <- entry:
        return nil
    default:
        return errors.New("queue full")
    }
}

func (h *MyHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func (h *MyHook) RegisterHook(log *logrus.Logger) {
    log.AddHook(h)
}

func (h *MyHook) Run(ctx context.Context) {
    h.running.Store(true)
    defer h.running.Store(false)
    for {
        select {
        case entry := <-h.queue:
            h.processEntry(entry)
        case <-ctx.Done():
            return
        }
    }
}

func (h *MyHook) IsRunning() bool {
    return h.running.Load()
}

func (h *MyHook) Write(p []byte) (n int, err error) {
    return len(p), nil
}

func (h *MyHook) Close() error {
    return nil
}

Jump to

Keyboard shortcuts

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