Skip to content

Eliminate three duplicate code patterns in logger and config packages#1120

Merged
lpcox merged 3 commits intomainfrom
copilot/remove-duplicate-code-patterns
Feb 19, 2026
Merged

Eliminate three duplicate code patterns in logger and config packages#1120
lpcox merged 3 commits intomainfrom
copilot/remove-duplicate-code-patterns

Conversation

Copy link
Contributor

Copilot AI commented Feb 19, 2026

Static analysis identified ~60–70 duplicated lines across internal/logger and internal/config: two identical switch level dispatch blocks, duplicated gateway validation logic split across two files, and a structural quad-function pattern repeated three times with no single update point.

Changes

logFuncs map replaces switch level blocks

Added a logFuncs map in file_logger.go as the single LogLevel → log function dispatch table. Both logWithMarkdownLevel and logWithLevelAndServer now look up from it instead of maintaining independent switch blocks — adding a new log level now requires one edit.

// file_logger.go
var logFuncs = map[LogLevel]func(string, string, ...interface{}){
    LogLevelInfo:  LogInfo,
    LogLevelWarn:  LogWarn,
    LogLevelError: LogError,
    LogLevelDebug: LogDebug,
}

// markdown_logger.go — was 13-line switch, now:
func logWithMarkdownLevel(level LogLevel, category, format string, args ...interface{}) {
    logWithMarkdown(level, logFuncs[level], category, format, args...)
}

// server_file_logger.go — was 9-line switch, now:
if logFunc := logFuncs[level]; logFunc != nil {
    logFunc(category, "[%s] %s", serverID, formattedMessage)
}

validateStringPatterns delegates to validateGatewayConfig

Port-range and timeout checks were duplicated between validateGatewayConfig (validation.go) and validateStringPatterns (validation_schema.go). validateStringPatterns now calls validateGatewayConfig for those fields, retaining only the domain check (which is unique to it). Removes ~20 duplicated lines and makes validateGatewayConfig the single source of truth for gateway field validation.

Quad-function pattern documented in common.go

Added a documentation section (matching the style of the existing "Close Pattern" and "Initialization Pattern" docs) explaining the intentional three-set quad-function pattern and instructing contributors to update logFuncs plus all three wrapper files when adding a new log level.

Original prompt

This section details on the original issue you should resolve

<issue_title>[duplicate-code] Duplicate Code Analysis Report</issue_title>
<issue_description>Analysis of commit c6471a4

Summary

Static analysis of the most recent commit found 3 significant duplication patterns across the internal/logger and internal/config packages. All patterns exceed the reporting threshold (>10 lines or 3+ similar occurrences).

Detected Patterns

  1. switch level Dispatch Blocks – Severity: Medium – See sub-issue [duplicate-code] Duplicate Code Pattern: switch level Dispatch Blocks in Logger #1106
  2. Gateway Config Validation Logic – Severity: Medium – See sub-issue [duplicate-code] Duplicate Code Pattern: Gateway Config Validation in validation.go and validation_schema.go #1107
  3. Log-Level Quad-Function Pattern – Severity: Low – See sub-issue [duplicate-code] Duplicate Code Pattern: Log-Level Quad-Function Pattern Repeated Across 3 Logger Files #1108

Overall Impact

  • Total Duplicated Lines: ~60–70 lines across the three patterns
  • Affected Files: 5 files (internal/logger/markdown_logger.go, internal/logger/server_file_logger.go, internal/config/validation.go, internal/config/validation_schema.go, plus 2 additional logger files)
  • Maintainability Risk: Medium – changes to log-level dispatch or gateway validation rules must be applied in multiple places, increasing the risk of divergence
  • Refactoring Priority: Low-to-medium; the codebase already has good shared helpers (e.g., logWithLevel, initLogger, closeLogFile); these patterns are the remaining gaps

Next Steps

  1. Review individual pattern sub-issues for detailed analysis
  2. Prioritize gateway validation duplication first (highest divergence risk)
  3. Consider whether a levelToFunc map helper could replace switch level blocks

Analysis Metadata

  • Analyzed Files: 65 Go files (excluding *_test.go and test/ directory)
  • Detection Method: Manual semantic code analysis
  • Commit: c6471a4
  • Analysis Date: 2026-02-19</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Pattern 1 (#1106): Replace switch-level dispatch blocks with levelFuncs map
- Add levelFuncs map in file_logger.go mapping LogLevel to global log function
- Replace 13-line switch in logWithMarkdownLevel (markdown_logger.go)
- Replace 9-line switch in logWithLevelAndServer (server_file_logger.go)

Pattern 2 (#1107): Eliminate duplicated gateway validation in validateStringPatterns
- validateStringPatterns now delegates to validateGatewayConfig for port/timeout/payloadDir
- Only domain-specific check remains in validateStringPatterns (~20 lines removed)

Pattern 3 (#1108): Document the Log-Level Quad-Function Pattern in common.go
- New section explains the intentional pattern and instructs contributors on adding new levels

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplicate code in logger and config packages Eliminate three duplicate code patterns in logger and config packages Feb 19, 2026
@lpcox
Copy link
Collaborator

lpcox commented Feb 19, 2026

@copilot instead of naming the map levelFuncs name it logFuncs

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 19, 2026

@copilot instead of naming the map levelFuncs name it logFuncs

Done — renamed to logFuncs in cc0ccc0 (updated the declaration in file_logger.go and all references in markdown_logger.go, server_file_logger.go, and common.go).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build2791372182/b275/launcher.test /tmp/go-build2791372182/b275/launcher.test -test.testlogfile=/tmp/go-build2791372182/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2791372182/b260/config.test /tmp/go-build2791372182/b260/config.test -test.testlogfile=/tmp/go-build2791372182/b260/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go s.go cal/bin/git (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build2791372182/b275/launcher.test /tmp/go-build2791372182/b275/launcher.test -test.testlogfile=/tmp/go-build2791372182/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build2791372182/b275/launcher.test /tmp/go-build2791372182/b275/launcher.test -test.testlogfile=/tmp/go-build2791372182/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2791372182/b284/mcp.test /tmp/go-build2791372182/b284/mcp.test -test.testlogfile=/tmp/go-build2791372182/b284/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@lpcox lpcox marked this pull request as ready for review February 19, 2026 16:20
Copilot AI review requested due to automatic review settings February 19, 2026 16:20
@lpcox lpcox merged commit 7248619 into main Feb 19, 2026
14 checks passed
@lpcox lpcox deleted the copilot/remove-duplicate-code-patterns branch February 19, 2026 16:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors duplicated logic in the internal/logger and internal/config packages by centralizing log-level dispatch and reusing gateway validation logic, with added documentation describing an intentional wrapper-function pattern.

Changes:

  • Introduce a logFuncs map as the single LogLevel -> logging function dispatch table and use it from markdown and per-server logging helpers.
  • Deduplicate gateway port/timeout validation by delegating from validateStringPatterns to validateGatewayConfig.
  • Add documentation describing the intentional “quad-function” wrapper pattern and how to extend it for new log levels.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/logger/file_logger.go Adds logFuncs dispatch map for log-level to function mapping.
internal/logger/markdown_logger.go Replaces per-level switch dispatch with logFuncs[level].
internal/logger/server_file_logger.go Replaces per-level switch dispatch with logFuncs[level] (with nil guard).
internal/logger/common.go Adds documentation for the log-level wrapper pattern (but currently interrupts closeLogFile docs).
internal/config/validation_schema.go Delegates gateway port/timeout/payloadDir validation to validateGatewayConfig.
Comments suppressed due to low confidence (1)

internal/logger/common.go:229

  • The doc comment immediately preceding closeLogFile no longer starts with the function name (it now starts with “It syncs…”), because the new “Log-Level Quad-Function Pattern” section was inserted between the header line and the rest of the closeLogFile description. This makes the closeLogFile documentation harder to read and can trip comment-format linters. Consider restoring a single-line closeLogFile ... doc comment immediately above the function, and moving the quad-function documentation to a separate block so it doesn’t interrupt the function doc.

// It syncs buffered data before closing and handles errors appropriately.
// The mutex should already be held by the caller.
//
// Error handling strategy:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 434 to 439
if stdinCfg.Gateway != nil {
// Validate port: must be integer 1-65535 or variable expression
if stdinCfg.Gateway.Port != nil {
if err := rules.PortRange(*stdinCfg.Gateway.Port, "gateway.port"); err != nil {
return err
}
// Delegate port, timeout, and payloadDir validation to validateGatewayConfig
// to avoid duplicating those checks here.
if err := validateGatewayConfig(stdinCfg.Gateway); err != nil {
return err
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateStringPatterns now calls validateGatewayConfig. LoadFromStdin already calls validateGatewayConfig later in the parsing pipeline, so this change causes gateway validation (and associated logging) to run twice for stdin configs. Consider keeping validateStringPatterns focused on regex/pattern checks (e.g., the domain rules) and letting the single validateGatewayConfig call remain in the main validation flow, or otherwise ensuring validateGatewayConfig is only invoked once per load.

See below for a potential fix:

	// Validate gateway configuration patterns (domain rules only; structural checks are handled elsewhere)
	if stdinCfg.Gateway != nil {

Copilot uses AI. Check for mistakes.
Comment on lines 189 to 191
func logWithMarkdownLevel(level LogLevel, category, format string, args ...interface{}) {
var regularLogFunc func(string, string, ...interface{})

switch level {
case LogLevelInfo:
regularLogFunc = LogInfo
case LogLevelWarn:
regularLogFunc = LogWarn
case LogLevelError:
regularLogFunc = LogError
case LogLevelDebug:
regularLogFunc = LogDebug
}

logWithMarkdown(level, regularLogFunc, category, format, args...)
logWithMarkdown(level, logFuncs[level], category, format, args...)
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logWithMarkdownLevel passes logFuncs[level] directly into logWithMarkdown, and logWithMarkdown unconditionally calls the provided regularLogFunc. If a new LogLevel is added (or an unexpected value is passed) without a corresponding logFuncs entry, this will panic at runtime. Consider guarding the lookup (ok/nil check) and either defaulting to a safe logger (e.g., LogInfo/LogDebug) or skipping the regular logger call while still logging to the markdown logger.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[duplicate-code] Duplicate Code Analysis Report

3 participants