Skip to content

Give the four log-entry structs a shared LogEntry interface - #52107

Merged
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-shared-base-type-log-entry-structs
Aug 12, 2026
Merged

Give the four log-entry structs a shared LogEntry interface#52107
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-shared-base-type-log-entry-structs

Conversation

Copilot AI commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

AccessLogEntry, FirewallLogEntry, AuditLogEntry, and GatewayLogEntry each model a parsed log line but share no common shape, so any code wanting to handle "a log entry" generically must special-case all four. Their fields diverge enough (Timestamp is string in three, float64 in AuditLogEntry; only gateway has Level) that an embedded base struct would force a wire-format change, so this uses an interface with accessors instead.

pkg/cli/log_entry.go (new)

  • LogEntry interface: EntryTimestamp(), EntrySource(), EntryLevel(), EntryMessage().
  • LogEntrySource constants (access, firewall, audit, gateway) and shared LogLevelInfo/LogLevelError.
  • Value-receiver implementations for all four types, delegating severity to the existing classifiers (isAllowedSquidStatus, isRequestAllowed, isEntryAllowed) rather than reimplementing them.
  • Epoch timestamps (squid/firewall strings, audit float64) normalize to RFC3339 UTC; gateway timestamps are already RFC3339 and pass through. Unparseable values are returned unchanged.
  • Compile-time var _ LogEntry = ... assertions, plus FormatLogEntry as a first generic consumer.

No struct fields, JSON tags, or parsers were touched, so serialization and existing call sites are unaffected.

entries := []LogEntry{
    AccessLogEntry{Timestamp: "1701234567.123", Status: "TCP_MISS/200", Method: "GET", URL: "http://example.com"},
    AuditLogEntry{Timestamp: 1701234567.123, Host: "example.com:443", Method: "CONNECT", Status: 200, Decision: "TCP_TUNNEL"},
    GatewayLogEntry{Timestamp: "2024-01-12T10:00:00Z", Level: LogLevelInfo, Event: "tool_call"},
}
for _, e := range entries {
    fmt.Println(FormatLogEntry(e))
}
// 2023-11-29T05:09:27Z [access] info: GET http://example.com TCP_MISS/200
// 2023-11-29T05:09:27Z [audit] info: CONNECT example.com:443 TCP_TUNNEL
// 2024-01-12T10:00:00Z [gateway] info: tool_call

pkg/cli/log_entry_test.go (new)

Table-driven coverage of allowed/blocked cases per source, generic formatting over a mixed []LogEntry, and non-epoch timestamp passthrough.


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 15.2 AIC · ⌖ 5.24 AIC · ⊞ 8.5K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor log entry structs to share a base type Give the four log-entry structs a shared LogEntry interface Aug 11, 2026
Copilot AI requested a review from pelikhan August 11, 2026 16:22
@github-actions

Copy link
Copy Markdown
Contributor

Great work on this refactor! 🎉 This PR cleanly introduces a shared LogEntry interface for the four independent log-entry structs, solving the code duplication problem identified in #52091.

What stands out:

  • Well-designed abstraction — Using an interface instead of an embedded struct preserves backward compatibility while enabling generic log handling
  • Comprehensive tests — Table-driven tests cover happy/denied paths for all four sources, plus edge cases like non-epoch timestamp passthrough
  • Thoughtful implementation — Value-receiver methods and reuse of existing severity classifiers (isAllowedSquidStatus, isRequestAllowed, isEntryAllowed) keeps the code maintainable
  • Clear documentation — The PR body explains the design trade-offs and includes a working example

This looks ready for review once you remove draft status. Nice integration with the existing codebase! 🚀

Generated by ✅ Contribution Check · auto · 56.8 AIC · ⌖ 5.01 AIC · ⊞ 8.8K ·

@pelikhan
pelikhan marked this pull request as ready for review August 11, 2026 17:32
Copilot AI balanced review requested due to automatic review settings August 11, 2026 17:32
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #52107

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.individual.githubcopilot.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.individual.githubcopilot.com"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Introduces a common interface for generic handling of the four parsed log-entry types.

Changes:

  • Adds shared source and severity constants with accessor implementations.
  • Adds generic timestamp normalization, message generation, and formatting.
  • Adds table-driven tests across all log sources.
Show a summary per file
File Description
pkg/cli/log_entry.go Defines and implements the shared LogEntry API.
pkg/cli/log_entry_test.go Tests accessors, formatting, and timestamp passthrough.

Review details

Tip

Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread pkg/cli/log_entry.go
Comment on lines +153 to +158
func (g GatewayLogEntry) EntryLevel() string {
if g.Level != "" {
return g.Level
}
return levelFromAllowed(g.Error == "" && g.Status != LogLevelError)
}

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 27.2 AIC · ⌖ 9.12 AIC · ⊞ 5.4K

Comment thread pkg/cli/log_entry.go
}
return levelFromAllowed(g.Error == "" && g.Status != LogLevelError)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Potential logic issue: Status != LogLevelError conflates two separate domains

When g.Level is empty, the fallback is:

levelFromAllowed(g.Error == "" && g.Status != LogLevelError)

LogLevelError is "error" and Status uses the same string for a status outcome ("success", "error", "unknown"). A Status of "unknown" with no Error string returns LogLevelInfo, which is likely incorrect — unknown status should not be treated as a success.

Consider being explicit:

if g.Error != "" || g.Status == "error" || g.Status == "unknown" {
    return LogLevelError
}
return LogLevelInfo

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f22320f: EntryLevel now returns error whenever Status == "error", Error is non-empty, or Level == "error", matching processGatewayLogEntry's OR-based classification. This also removes the dead Status != LogLevelError comparison.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by ✂️ Ponytail Reviewer for #52107 · auto · 21.3 AIC · ⌖ 4.97 AIC · ⊞ 6.8K
Comment /ponytail to run again

Comment thread pkg/cli/log_entry.go
}
}
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

L1-168: yagni: entire file (LogEntry interface, 4 impls, FormatLogEntry, constants) has zero call sites anywhere in the repo. Delete until a real consumer needs generic log-entry handling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (341 new lines in pkg/cli/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/52107-logentry-interface-for-log-entry-structs.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn’t infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-52107: LogEntry Interface for Heterogeneous Log-Entry Structs

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

“AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say ‘I’ll deal with this later.’ Deferring decisions corroded my ability to think clearly.”

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 58.6 AIC · ⌖ 21.3 AIC · ⊞ 8.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on one correctness bug and two test coverage gaps.

📋 Key Themes & Highlights

Key Themes

  • Logic bug in GatewayLogEntry.EntryLevel: the g.Status != LogLevelError guard compares an HTTP-style status string against the severity constant "error"; the comparison is always true for HTTP statuses, making it dead code.
  • Missing test coverage: no case for an all-blank GatewayLogEntry message, and the zero-epoch behaviour of formatEpochTimestamp is undocumented.

Positive Highlights

  • ✅ Clean interface design: minimal surface, four orthogonal accessors, no leaking of struct internals.
  • ✅ Compile-time interface assertions (var _ LogEntry = ...) are exactly right here.
  • joinEntryMessage filtering out "-" placeholders is a nice defensive touch.
  • ✅ Table-driven tests with t.Parallel() throughout — good discipline.
  • ✅ No serialization breakage: only methods added to existing structs.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 47.7 AIC · ⌖ 6.79 AIC · ⊞ 7.1K
Comment /matt to run again

Comment thread pkg/cli/log_entry.go Outdated
// EntryLevel implements LogEntry.
func (g GatewayLogEntry) EntryLevel() string {
if g.Level != "" {
return g.Level

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] The fallback level logic compares g.Status (an HTTP-style string like "200") against the string constant LogLevelError ("error"). That comparison will always be true for any HTTP status, so the g.Status != LogLevelError guard is a no-op and the level is driven solely by g.Error == "".

💡 Suggested fix

Drop the dead Status check:

func (g GatewayLogEntry) EntryLevel() string {
    if g.Level != "" {
        return g.Level
    }
    return levelFromAllowed(g.Error == "")
}

Also add a test case with a non-empty Status and no Level to pin the expected behaviour.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f22320f — the dead Status comparison is gone and EntryLevel now compares Status against "error" directly, matching the real status vocabulary.

Comment thread pkg/cli/log_entry.go
}
}
return ""
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] GatewayLogEntry.EntryMessage silently returns an empty string when all four candidate fields (Message, Error, Event, Type) are blank. The test suite has no case for this path, so the caller gets an unexpectedly empty message string with no indication anything is wrong.

💡 Suggested test
{
    name: "gateway log entry with all-blank fields returns empty message",
    entry: GatewayLogEntry{Timestamp: "2024-01-12T10:00:00Z", Level: LogLevelInfo},
    expectedMessage: "",
},

Consider whether an empty message should be surfaced as a structured warning.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a test case for all-blank message fields in f22320f.

Comment thread pkg/cli/log_entry_test.go
}

func TestFormatEpochTimestampKeepsNonEpochValues(t *testing.T) {
t.Parallel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] The TestFormatEpochTimestampKeepsNonEpochValues test asserts assert.Empty(t, formatEpochTimestamp("")) — an empty input should return an empty string and the test is correct — but a zero-valued epoch string like "0" or "0.000" would be parsed as a valid epoch and formatted to "1970-01-01T00:00:00Z". Whether that is intentional isn't obvious; a test case would document the decision explicitly.

💡 Suggested addition
// Documents that a zero epoch is treated as a valid timestamp, not a placeholder.
assert.Equal(t, "1970-01-01T00:00:00Z", formatEpochTimestamp("0"))

If zero is considered a placeholder (like "-"), add the guard in formatEpochTimestamp.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a test in f22320f documenting that "0" is treated as a valid epoch, not a placeholder.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

🚨 Test Quality Score: 100/100 — Excellent

Analyzed 3 test(s): 3 design, 0 implementation, 1 violation.

📊 Metrics (3 tests)
Metric Value
Analyzed 3 (Go: 3, JS: 0)
✅ Design 3 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 3 (100%)
Duplicate clusters 0
Inflation No (test: 173 lines, prod: 168 lines, ratio: 1.0×)
🚨 Violations 1 (missing (go/redacted):build tag)
Test File Classification Issues
TestLogEntryInterfaceAccessors pkg/cli/log_entry_test.go:9 design_test / behavioral_contract / high_value assert messages missing descriptive context
TestFormatLogEntryIsGenericAcrossSources pkg/cli/log_entry_test.go:144 design_test / behavioral_contract / high_value assert messages missing descriptive context
TestFormatEpochTimestampKeepsNonEpochValues pkg/cli/log_entry_test.go:167 design_test / behavioral_contract / high_value assert messages missing descriptive context
⚠️ Flagged (1 hard violation)

pkg/cli/log_entry_test.goHard violation: missing (go/redacted):build tag on line 1.

All new Go test files must declare either (go/redacted):build !integration (unit) or (go/redacted):build integration (integration) as the very first line. Fix: add (go/redacted):build !integration as line 1 of this file.

Additionally, none of the assert.Equal calls include a message argument. Recommended: assert.Equal(t, tt.expectedTimestamp, tt.entry.EntryTimestamp(), "EntryTimestamp for %s", tt.name).

Verdict

failed. Hard violation: pkg/cli/log_entry_test.go is missing the mandatory (go/redacted):build tag on line 1. Behavioral test ratio: 0% implementation (threshold: 30%) — ratio passes, but the build-tag violation requires changes.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 46.8 AIC · ⌖ 6.83 AIC · ⊞ 7.6K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ Test Quality Sentinel: 100/100 (ratio passes) but hard violation: pkg/cli/log_entry_test.go is missing the mandatory (go/redacted):build tag on line 1. Review flagged tests in the comment above.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the open review feedback below, refresh the branch after changes, and use the pr-finisher skill before handing back.

Open review threads (newest reviewer feedback first):

No failed checks were reported in the compact candidate set for this run.
Please make sure the ADR is linked in the PR body and address the Test Quality Sentinel build-tag requirement before handing back.
Branch update was skipped because this PR is currently BLOCKED.

Generated by [👨🍳 PR Sous Chef](

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.87 AIC · ⌖ 5.49 AIC · ⊞ 8.5K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Triage: #52107

  • Category: refactor
  • Risk: medium
  • Priority score: 30/100 (impact 10/50, urgency 10/30, quality 10/20) → low
  • Recommended action: defer

Automated triage — see labels for machine-readable classification.

Generated by 🔧 PR Triage Agent · auto · 45 AIC · ⌖ 2.09 AIC · ⊞ 7.8K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot This PR looks ready for another finishing pass. Please refresh the branch, review any outstanding feedback, and run the pr-finisher skill before handing back to maintainers.

Focus areas:

  • Re-check for any unresolved review comments or stale concerns from automated reviewers.
  • Confirm the branch remains merge-ready after the refresh and summarize the current validation status in your reply.

Run context: https://github.com/github/gh-aw/actions/runs/31545290203

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 15.2 AIC · ⌖ 5.24 AIC · ⊞ 8.5K ·
Comment /souschef to run again

Copilot AI commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@copilot This PR looks ready for another finishing pass. Please refresh the branch, review any outstanding feedback, and run the pr-finisher skill before handing back to maintainers.

Focus areas:...

Branch is already up to date with main (merge commit fa8a121). All prior review threads have been addressed (f22320f) and replied to. The remaining unresolved thread ("yagni: delete this file") doesn't mention @copilot, so per instructions I'm not acting on it. Local validation: go build ./..., gofmt -l, targeted LogEntry tests, and make agent-report-progress (fmt/lint/impacted tests + full workflow recompilation) all pass with a clean working tree — nothing new to push.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Triage: PR #52107

  • Category: refactor | Risk: medium | Score: 38/100 (carry-over from prior run)
  • Breakdown: impact 15, urgency 6, quality 17
  • Recommended action: defer
  • CI: all checks green, mergeable_state=clean. Pure additive interface refactor (421/+0, 3 files) with no urgent driver — low business value relative to other open work; keep deferred as in the previous triage cycle.

Generated by 🔧 PR Triage Agent · auto · 44.5 AIC · ⌖ 2.66 AIC · ⊞ 7.8K ·

@pelikhan
pelikhan merged commit aefceee into main Aug 12, 2026
30 checks passed
@pelikhan
pelikhan deleted the copilot/deep-report-shared-base-type-log-entry-structs branch August 12, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] Give the 4 independent log-entry structs a shared base type

4 participants