Skip to content

[duplicate-code] Duplicate Code Pattern: Logger Initialization Boilerplate #798

@github-actions

Description

@github-actions

🔍 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.go with initLogger() 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.go and global_helpers.go for 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-finished to 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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions