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:
- Standardization: Provides consistent field names across all logger implementations
- Extensibility: Hook interface allows seamless integration of custom log processors
- Minimal Dependencies: Only depends on standard library and logrus
- Type Safety: Strong typing for field constants prevents typos and ensures consistency
- 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 ¶
- Package (AllFieldConstants)
- Package (BasicFieldUsage)
- Package (BasicHook)
- Package (CustomFieldsWithStandard)
- Package (ErrorFieldsUsage)
- Package (FieldCategories)
- Package (FieldConstantsInMapKeys)
- Package (FieldValidation)
- Package (HookContextCancellation)
- Package (HookLevelsMethod)
- Package (HookLifecycle)
- Package (HookWithFiltering)
- Package (HookWriteMethod)
- Package (InterfaceImplementation)
- Package (MultipleHooks)
- Package (ThreadSafeFieldUsage)
Constants ¶
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
}