Skip to content

feat(difc): structured logging for every filtered item#2066

Merged
lpcox merged 10 commits intomainfrom
feat/difc-filtered-item-logging
Mar 17, 2026
Merged

feat(difc): structured logging for every filtered item#2066
lpcox merged 10 commits intomainfrom
feat/difc-filtered-item-logging

Conversation

@lpcox
Copy link
Collaborator

@lpcox lpcox commented Mar 17, 2026

Summary

Adds per-item structured logging when DIFC filtering removes objects from tool responses. This enables post-processing reports that show exactly what was filtered, why, and provide enough context to link back to the GitHub object.

Changes

  • FilteredItemDetail struct (resource.go): Pairs each filtered LabeledItem with the denial reason from the evaluator
  • FilterCollection (evaluator.go): Now captures EvaluationResult.Reason per denied item
  • difc_log.go (new): logFilteredItems() extracts metadata from raw JSON data and emits structured JSON log entries via the file logger
  • Phase 5 integration (unified.go): Calls logFilteredItems() whenever items are filtered
  • difc_log_test.go (new): Unit tests verifying the structured log output format, JSON validity, expected fields, and per-server/unified log routing

Log Entry Format

Each filtered item produces a JSON entry logged at [DIFC-FILTERED] prefix:

{
  "server_id": "github",
  "tool_name": "list_issues",
  "description": "issue:repo#42",
  "reason": "integrity too low",
  "secrecy_tags": ["private:org/repo"],
  "integrity_tags": ["none"],
  "author_association": "NONE",
  "author_login": "user",
  "html_url": "https://github.com/...",
  "number": "42"
}

Entries are written to both per-server log files and the unified mcp-gateway.log for easy grepping.

Testing

  • All existing tests updated for the FilteredItemDetail type change
  • New tests in difc_log_test.go assert that logFilteredItems emits valid JSON with the [DIFC-FILTERED] prefix, writes to both per-server and unified logs, and handles edge cases (nil labels, non-map data, empty filtered list, multiple items)
  • make agent-finished passes (format, build, lint, unit + integration tests)

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

Add per-item structured logging when DIFC filtering removes objects from
tool responses. Each filtered item is logged as a JSON entry containing:

- server_id, tool_name, description (guard label)
- secrecy_tags, integrity_tags (DIFC labels)
- reason (human-readable denial reason from evaluator)
- author_association, author_login, html_url, number, sha (GitHub metadata)

Implementation:
- FilteredItemDetail struct pairs each filtered LabeledItem with its
  denial reason from the evaluator
- FilterCollection now captures EvaluationResult.Reason per denied item
- New difc_log.go in server package extracts metadata from raw JSON data
  and logs structured JSON entries via the file logger
- Entries logged at INFO level with [DIFC-FILTERED] prefix for grep

This enables post-processing reports that show exactly what was filtered,
why, and provide enough context to link back to the GitHub object.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 17, 2026 21:51
@lpcox
Copy link
Collaborator Author

lpcox commented Mar 17, 2026

@pelikhan will mint a new gateway when this merges

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

Adds structured, per-item audit logging when DIFC filtering removes objects from tool responses, enabling post-processing/reporting on what was denied and why.

Changes:

  • Introduces difc.FilteredItemDetail to retain denial reasons alongside filtered LabeledItems.
  • Extends Evaluator.FilterCollection to capture EvaluationResult.Reason per denied item.
  • Adds internal/server/difc_log.go and integrates per-item structured logging from Phase 5 filtering in UnifiedServer.

Reviewed changes

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

Show a summary per file
File Description
internal/server/unified.go Calls new per-item logging helper when Phase 5 filtering removes items.
internal/server/difc_log.go New structured log entry builder + per-item logging implementation.
internal/difc/resource.go Adds FilteredItemDetail and updates filtered collection type to store details.
internal/difc/evaluator.go Populates FilteredItemDetail.Reason for denied items during filtering.
internal/difc/resource_test.go Updates unit tests to reflect the new filtered item detail structure.

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

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +31 to +45
func logFilteredItems(serverID, toolName string, filtered *difc.FilteredCollectionLabeledData) {
for _, detail := range filtered.Filtered {
entry := buildFilteredItemLogEntry(serverID, toolName, detail)

entryJSON, err := json.Marshal(entry)
if err != nil {
logger.LogWarnWithServer(serverID, "difc",
"[DIFC-FILTERED] %s | %s | description=%s | reason=%s (json marshal failed: %v)",
serverID, toolName, entry.Description, entry.Reason, err)
continue
}

logger.LogInfoWithServer(serverID, "difc",
"[DIFC-FILTERED] %s", string(entryJSON))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Contributor

Choose a reason for hiding this comment

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

Added internal/server/difc_log_test.go (commit 79fe8e7) with 8 tests covering the structured logging path:

  • TestLogFilteredItems_EmitsValidJSONWithExpectedFields — initializes real FileLogger + ServerFileLogger, calls logFilteredItems, then reads both the per-server log and mcp-gateway.log, parses the JSON from the [DIFC-FILTERED] lines, and asserts every expected field (server_id, tool_name, description, reason, secrecy_tags, integrity_tags, author_login, author_association, html_url, number)
  • TestLogFilteredItems_MultipleItems — verifies one log line per filtered item
  • TestLogFilteredItems_EmptyFiltered — verifies no output when the filtered list is empty
  • TestBuildFilteredItemLogEntry_WithNilLabels — nil labels don't panic; raw data fields are still extracted
  • TestBuildFilteredItemLogEntry_ExtractAuthorLogin_UserObject/AuthorObject — exercises both user.login and author.login paths
  • TestBuildFilteredItemLogEntry_ExtractNumberField_JsonNumber — exercises the json.Number branch in extractNumberField
  • TestBuildFilteredItemLogEntry_NonMapData — non-map item data doesn't panic

lpcox and others added 3 commits March 17, 2026 14:57
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ng format (dash)

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
lpcox and others added 2 commits March 17, 2026 15:02
… format (#2067)

`TestPlaywrightMCPServer/ToolsRegistered` was failing because the test
contained stale hard-coded expectations that no longer match the current
`mcp/playwright` image behavior.

## Changes

- **Tool count**: Updated expected count from `22` → `21` to match the
tools the current playwright image actually registers
- **Tool name separator**: Updated from triple-underscore
(`playwright___browser_close`) → dash (`playwright-browser_close`) to
match the current gateway naming convention

```go
// Before
if !strings.Contains(stderrStr, "Registered 22 tools from playwright") &&
    !strings.Contains(stderrStr, "Registered tool: playwright___browser_close") {

// After
if !strings.Contains(stderrStr, "Registered 21 tools from playwright") &&
    !strings.Contains(stderrStr, "Registered tool: playwright-browser_close") {
```

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> Fix the failing GitHub Actions workflow integration-test
> Analyze the workflow logs, identify the root cause of the failure, and
implement a fix.
> Job ID: 67483962994
> Job URL:
https://github.com/github/gh-aw-mcpg/actions/runs/23218086781/job/67483962994


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

📱 Kick off Copilot coding agent tasks wherever you are with [GitHub
Mobile](https://gh.io/cca-mobile-docs), available on iOS and Android.
- TestLogFilteredItems_EmitsValidJSONWithExpectedFields: initializes real
  file/server loggers, calls logFilteredItems, then parses the emitted JSON
  from both per-server and unified logs, verifying the [DIFC-FILTERED]
  prefix and all expected fields (server_id, tool_name, description, reason,
  secrecy_tags, integrity_tags, author_login, author_association, html_url, number)
- TestLogFilteredItems_MultipleItems: verifies one log line per filtered item
- TestLogFilteredItems_EmptyFiltered: verifies no output when filtered list is empty
- TestBuildFilteredItemLogEntry_WithNilLabels: nil labels don't panic, metadata still extracted
- TestBuildFilteredItemLogEntry_ExtractAuthorLogin_UserObject: exercises user.login path
- TestBuildFilteredItemLogEntry_ExtractAuthorLogin_AuthorObject: exercises author.login path
- TestBuildFilteredItemLogEntry_ExtractNumberField_JsonNumber: exercises json.Number path
- TestBuildFilteredItemLogEntry_NonMapData: non-map item data doesn't panic

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

Copilot AI commented Mar 17, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

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-build2585400861/b332/launcher.test /tmp/go-build2585400861/b332/launcher.test -test.testlogfile=/tmp/go-build2585400861/b332/testlog.txt -test.paniconexit0 -test.timeout=10m0s conf�� ache/go/1.25.8/x64/src/runtime/c-D ternal/fips140/edwards25519/doc.GOAMD64_v1 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3763330709/b328/launcher.test /tmp/go-build3763330709/b328/launcher.test -test.testlogfile=/tmp/go-build3763330709/b328/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2585400861/b317/config.test /tmp/go-build2585400861/b317/config.test -test.testlogfile=/tmp/go-build2585400861/b317/testlog.txt -test.paniconexit0 -test.timeout=10m0s conf�� 1295621/b144/_pk-p user.email x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3763330709/b313/config.test /tmp/go-build3763330709/b313/config.test -test.testlogfile=/tmp/go-build3763330709/b313/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true tes.crt 1bb60643138b78ae7f4ce14e92b0afe42f8 /usr/sbin/bash b2a83be36036c113dfb6c81ba53daabc9dd/log.json 7a8ae78f71d2f4470.5 cb43afabdbc44342f81518b4a348cf4c--root c35/log.json /usr�� --root d5525ad61677fa7c23fc4bd799dd3b80ff062805a04289bcc44 erd-shim-runc-v2 d5525ad61677fa7c/check (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build2585400861/b332/launcher.test /tmp/go-build2585400861/b332/launcher.test -test.testlogfile=/tmp/go-build2585400861/b332/testlog.txt -test.paniconexit0 -test.timeout=10m0s conf�� ache/go/1.25.8/x64/src/runtime/c-D ternal/fips140/edwards25519/doc.GOAMD64_v1 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3763330709/b328/launcher.test /tmp/go-build3763330709/b328/launcher.test -test.testlogfile=/tmp/go-build3763330709/b328/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build2585400861/b332/launcher.test /tmp/go-build2585400861/b332/launcher.test -test.testlogfile=/tmp/go-build2585400861/b332/testlog.txt -test.paniconexit0 -test.timeout=10m0s conf�� ache/go/1.25.8/x64/src/runtime/c-D ternal/fips140/edwards25519/doc.GOAMD64_v1 x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3763330709/b328/launcher.test /tmp/go-build3763330709/b328/launcher.test -test.testlogfile=/tmp/go-build3763330709/b328/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build2585400861/b341/mcp.test /tmp/go-build2585400861/b341/mcp.test -test.testlogfile=/tmp/go-build2585400861/b341/testlog.txt -test.paniconexit0 -test.timeout=10m0s conf�� 1295621/b168/_pkg_.a Khsb/03Es1Kl01eb4t_E5Khsb x_amd64/vet (dns block)
    • Triggering command: /tmp/go-build3763330709/b337/mcp.test /tmp/go-build3763330709/b337/mcp.test -test.testlogfile=/tmp/go-build3763330709/b337/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true dmti2mps3 y ache/go/1.25.8/xjson ntime.v2.task/mobash 77ad926b292df3de--norc fec4510176aac2bf--noprofile ache/go/1.25.8/x64/pkg/tool/linu/run/containerd/io.containerd.runtime.v2.task/moby/a4bd7bd1db3e981b1fcd6417d9c0898e97c62c88201fd0780cd9221a8a7af6c8 /usr�� ntime.v2.task/moby/61f600c867859/run/containerd/io.containerd.runtime.v2.task/moby/a4bd7bd1db3e9docker /var/run/docker/runtime-runc/moba4bd7bd1db3e981b1fcd6417d9c0898e97c62c88201fd0780cd9221a8a7af6c8inspect by/a4bd7bd1db3e981b1fcd6417d9c0898e97c62c88201fd0780cd9221a8a7af6c8/log.json 53a6f56ce4d78660/usr/bin/runc.original --log-format json (dns block)

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

Copilot AI and others added 3 commits March 17, 2026 22:50
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
- [x] Identify lint failure: `internal/server/difc_log_test.go` was not
gofmt-formatted
- [x] Run `gofmt -w` to fix formatting
- [x] Verify `make lint` passes

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> Fix the failing GitHub Actions workflow lint
> Analyze the workflow logs, identify the root cause of the failure, and
implement a fix.
> Job ID: 67489714827
> Job URL:
https://github.com/github/gh-aw-mcpg/actions/runs/23218548623/job/67489714827


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
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.

3 participants