ADR-008: Logging Abstraction Layer Implementation¶
Status¶
Accepted - Implemented
Context¶
The codebase previously had direct dependencies on the logrus logging framework throughout the application, making it difficult to:
- Test components that perform logging operations
- Change logging frameworks in the future
- Provide consistent logging patterns across parsers
- Inject different logging configurations for different components
This tight coupling to a specific logging framework violated dependency inversion principles and made the codebase less maintainable and testable.
Decision¶
We have implemented a comprehensive logging abstraction layer with the following components:
1. Logger Interface (internal/logging/logger.go)¶
type Logger interface {
Debug(msg string, fields ...Field)
Info(msg string, fields ...Field)
Warn(msg string, fields ...Field)
Error(msg string, fields ...Field)
WithError(err error) Logger
WithField(key string, value interface{}) Logger
WithFields(fields ...Field) Logger
Fatal(msg string, fields ...Field)
Fatalf(msg string, args ...interface{})
}
type Field struct {
Key string
Value interface{}
}
2. LogrusAdapter Implementation (internal/logging/logrus_adapter.go)¶
- Wraps logrus.Logger to implement our Logger interface
- Provides constructor functions for different configurations
- Supports both JSON and text formatting
- Includes backward compatibility methods
3. Dependency Injection Through BaseParser¶
- All parsers embed
BaseParserwhich manages logger injection - Consistent logger access through
GetLogger()method - Constructor pattern ensures proper logger initialization
4. Structured Logging Pattern¶
- Use of
Fieldstruct for key-value pairs - Consistent field naming across the application
- Context-rich log messages with relevant metadata
Consequences¶
Positive¶
- Improved Testability: Components can be tested with mock loggers
- Framework Independence: Can change logging frameworks without modifying business logic
- Consistent Patterns: All parsers use the same logging approach through BaseParser
- Structured Logging: Consistent field-based logging across the application
- Dependency Injection: Clean separation of concerns with injected dependencies
- Backward Compatibility: Existing logrus usage patterns still work during migration
Negative¶
- Additional Abstraction: Slight increase in code complexity
- Performance Overhead: Minimal overhead from interface calls
- Migration Effort: Gradual migration of existing direct logrus usage
Implementation Details¶
Parser Integration¶
All parsers now embed BaseParser and receive logger through constructor:
type MyParser struct {
parser.BaseParser
}
func NewMyParser(logger logging.Logger) *MyParser {
return &MyParser{
BaseParser: parser.NewBaseParser(logger),
}
}
func (p *MyParser) Parse(r io.Reader) ([]models.Transaction, error) {
p.GetLogger().Info("Starting parse operation",
logging.Field{Key: "parser", Value: "MyParser"})
// implementation
}
Testing Support¶
Mock logger implementation for testing:
type MockLogger struct {
Entries []LogEntry
}
func (m *MockLogger) Info(msg string, fields ...logging.Field) {
m.Entries = append(m.Entries, LogEntry{
Level: "INFO",
Message: msg,
Fields: fields,
})
}
Configuration¶
Logger creation with different configurations:
// JSON format for production
logger := logging.NewLogrusAdapter("info", "json")
// Text format for development
logger := logging.NewLogrusAdapter("debug", "text")
Alternatives Considered¶
- Direct Logrus Usage: Rejected due to tight coupling and testing difficulties
- Standard Library log: Rejected due to lack of structured logging support
- Other Logging Frameworks: Considered but logrus provides good balance of features and performance
Related ADRs¶
- ADR-001: Parser Interface Standardization
- ADR-003: Functional Programming Adoption
- ADR-004: Configuration Management Strategy
References¶
- Go logging best practices
- Dependency injection patterns in Go
- Interface segregation principle
- Clean Architecture principles