Skip to content

[duplicate-code] Duplicate Code Pattern: Logger Setup Boilerplate #4541

@github-actions

Description

@github-actions

Part of duplicate code analysis: #4540

Summary

Each of the four concrete logger types in internal/logger/ (FileLogger, MarkdownLogger, JSONLLogger, ToolsLogger) follows an identical structural pattern with a setup*Logger function and a handle*LoggerError function, plus a global variable + mutex pair. This results in 8 near-identical function pairs across 4 files.

Duplication Details

Pattern: Logger type setup/error-handler boilerplate

  • Severity: Medium

  • Occurrences: 4 logger types × 2 functions = 8 functions with the same signature and responsibility

  • Locations:

    • internal/logger/file_logger.go (lines 27–55): setupFileLogger / handleFileLoggerError
    • internal/logger/tools_logger.go (lines 44–80): setupToolsLogger / handleToolsLoggerError
    • internal/logger/markdown_logger.go (lines 28–51): setupMarkdownLogger / handleMarkdownLoggerError
    • internal/logger/jsonl_logger.go (lines 41–65): setupJSONLLogger / handleJSONLLoggerError
  • Code Sample (setup functions follow the same shape):

    // file_logger.go
    func setupFileLogger(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 handleFileLoggerError(err error, logDir, fileName string) (*FileLogger, error) {
        log.Printf("WARNING: Failed to initialize log file: %v", err)
        fl := &FileLogger{logDir: logDir, fileName: fileName, useFallback: true, logger: log.New(os.Stdout, "", 0)}
        return fl, nil
    }
    
    // tools_logger.go (same signature, similar body)
    func setupToolsLogger(file *os.File, logDir, fileName string) (*ToolsLogger, error) { ... }
    func handleToolsLoggerError(err error, logDir, fileName string) (*ToolsLogger, error) { ... }
    
    // markdown_logger.go, jsonl_logger.go — same pattern

Each Init*Logger also repeats the same two-liner:

func InitXxxLogger(logDir, fileName string) error {
    logger, err := initLogger(logDir, fileName, os.O_APPEND, setupXxxLogger, handleXxxLoggerError)
    initGlobalLogger(&globalXxxMu, &globalXxxLogger, logger)
    return err
}

Impact Analysis

  • Maintainability: Adding a new logger type requires creating 3 new boilerplate items (setup function, error handler, Init function) following an implicit convention — easy to get wrong.
  • Bug Risk: A divergence in one logger's fallback behavior may not be caught if the pattern isn't checked across all types. For example, JSONLLogger intentionally has no fallback but this is only visible by reading each file.
  • Code Bloat: ~50 lines of near-identical structural code spread across 4 files.

Refactoring Recommendations

  1. Document the pattern explicitly (low-effort quick win)

    • Add a comment in global_helpers.go or a new LOGGER_PATTERN.md explaining the setup/error-handler convention so future authors know what to implement.
  2. Introduce a loggerFactory[T] interface or type alias (medium effort)

    • Define a type for the setup and error-handler function pair and centralize their invocation in initLogger, so each concrete logger only needs to supply struct initialization logic.
    • Estimated effort: 2–3 hours
    • Benefits: New logger types are just struct definitions + field mappings; no boilerplate functions needed.

Implementation Checklist

  • Review all four logger setup patterns
  • Decide on approach (documentation vs. abstraction)
  • Implement chosen approach
  • Verify useFallback behavior is consistent across logger types
  • Run make test to ensure no regressions

Parent Issue

See parent analysis report: #4540
Related to #4540

Generated by Duplicate Code Detector · ● 1.9M ·

  • expires on May 2, 2026, 6:04 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions