-
Notifications
You must be signed in to change notification settings - Fork 7
Description
🔍 Duplicate Code Pattern: Logger Initialization Boilerplate
Part of duplicate code analysis: #797
Summary
Four logger files (file_logger.go, jsonl_logger.go, markdown_logger.go, server_file_logger.go) share nearly identical initialization patterns for file handling, error management, and global state setup. This creates significant maintenance overhead and increases the risk of inconsistent behavior across loggers.
Duplication Details
Pattern: Logger Initialization Structure
- Severity: High
- Occurrences: 4 instances (FileLogger, JSONLLogger, MarkdownLogger, ServerFileLogger)
- Locations:
internal/logger/file_logger.go(lines 28-60, 63-68)internal/logger/jsonl_logger.go(lines 38-66, 69-74)internal/logger/markdown_logger.go(lines 28-54, 72-89)internal/logger/server_file_logger.go(lines 70-114)
Duplicated Components
1. File Initialization Pattern (~40 lines per logger)
Each logger implements nearly identical logic:
func Init*Logger(logDir, fileName string) error {
logger, err := initLogger(
logDir, fileName, os.O_APPEND, // or os.O_TRUNC
// Setup function
func(file *os.File, logDir, fileName string) (*Logger, error) {
// Create logger struct
// Initialize fields
// Return logger
},
// Error handler
func(err error, logDir, fileName string) (*Logger, error) {
// Fallback logic or error return
},
)
// Initialize global logger
return err
}2. Close Method Pattern (~8 lines per logger)
func (l *Logger) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
return closeLogFile(l.logFile, &l.mu, "loggerType")
}3. Global Logger State Pattern (~12 lines per logger)
var (
global*Logger **Logger
global*Mu sync.RWMutex
)
func initGlobal*Logger(logger **Logger) {
initGlobalLogger(&global*Mu, &global*Logger, logger)
}
func closeGlobal*Logger() error {
return closeGlobalLogger(&global*Mu, &global*Logger)
}Code Sample: File Logger
// InitFileLogger initializes the global file logger
func InitFileLogger(logDir, fileName string) error {
logger, err := initLogger(
logDir, fileName, os.O_APPEND,
func(file *os.File, logDir, fileName string) (*FileLogger, error) {
fl := &FileLogger{
logDir: logDir,
fileName: fileName,
logFile: file,
logger: log.New(file, "", 0),
}
log.Printf("Logging to file: %s", filepath.Join(logDir, fileName))
return fl, nil
},
func(err error, logDir, fileName string) (*FileLogger, error) {
// Fallback to stdout...
return fl, nil
},
)
initGlobalFileLogger(logger)
return err
}Similar structures exist in all 4 logger files with only minor variations.
Impact Analysis
Maintainability
- High Risk: Changes to initialization logic must be replicated across 4 files
- Inconsistency: Different error handling strategies across loggers (some fallback to stdout, others return errors)
- Code Bloat: ~120+ lines of similar initialization code
Bug Risk
- Medium Risk: Bug fixes must be applied to all 4 loggers independently
- Historical Issues: The codebase already recognized this pattern and created
common.gowithinitLogger()helper, but only partial consolidation occurred - Fallback Inconsistency: FileLogger/MarkdownLogger fallback to stdout, JSONLLogger doesn't - easy to miss
Code Bloat
- Total Lines: ~120+ lines of duplicated initialization patterns
- Percentage: ~16% of logger package code (727 total lines across 4 files)
Refactoring Recommendations
1. Extract Logger Factory Pattern
Effort: Medium (4-6 hours)
Create a generic logger factory that handles common initialization:
// LoggerConfig defines common logger configuration
type LoggerConfig struct {
LogDir string
FileName string
FileFlags int // os.O_APPEND, os.O_TRUNC, etc.
AllowFallback bool // Whether to fallback to stdout on error
}
// LoggerFactory creates and initializes loggers with common patterns
type LoggerFactory[T closableLogger] struct {
config LoggerConfig
}
func (f *LoggerFactory[T]) Create(
setupFunc func(*os.File, string, string) (T, error),
fallbackFunc func(error, string, string) (T, error),
) (T, error) {
// Common initialization logic
// Replaces duplicate initLogger calls
}Benefits:
- Centralized file handling and error management
- Consistent fallback behavior
- Reduces initialization code by ~80 lines
- Single source of truth for common patterns
2. Standardize Error Handling
Effort: Low (2-3 hours)
Document and enforce consistent error handling strategy:
- Define when fallbacks are appropriate (FileLogger, MarkdownLogger)
- Define when errors should propagate (JSONLLogger, ServerFileLogger)
- Add configuration flag to control fallback behavior
Benefits:
- Predictable behavior across all loggers
- Easier debugging and testing
- Clear documentation of expected behavior
3. Consolidate Close Methods
Effort: Low (1-2 hours)
Move Close() logic to a shared interface or base struct:
// BaseLogger provides common logger functionality
type BaseLogger struct {
logFile *os.File
mu sync.Mutex
logDir string
fileName string
}
func (b *BaseLogger) Close() error {
b.mu.Lock()
defer b.mu.Unlock()
return closeLogFile(b.logFile, &b.mu, b.fileName)
}Benefits:
- Eliminates 4 duplicate Close() implementations
- Ensures consistent cleanup behavior
- Reduces code by ~32 lines
Implementation Checklist
- Review existing
common.goandglobal_helpers.gofor reusable patterns - Design LoggerFactory or enhanced initLogger abstraction
- Refactor FileLogger to use new pattern (proof of concept)
- Migrate JSONLLogger, MarkdownLogger, ServerFileLogger
- Update tests to verify consistent behavior
- Document fallback strategy and error handling expectations
- Run
make agent-finishedto verify all checks pass
Parent Issue
See parent analysis report: #797
Related to #797
AI generated by Duplicate Code Detector
- expires on Feb 14, 2026, 10:13 AM UTC