feat(egress): add global log option for full request/response logging
lint / lint (push) Failing after 1m25s
test / unit (pull_request) Successful in 29s
test / integration (pull_request) Successful in 44s

Adds a top-level `log: true` option to the egress config that logs the
full request (method, path, headers, body) and response (status, headers,
body) for every forwarded connection as JSON lines on stderr.

Wire format: `log: true` at the root of routes.yaml, parsed into the new
`Config` dataclass alongside `routes`. The sidecar addon switches from
`self.routes` to `self.config` and writes `_log_request` / `_log_response`
JSON lines when `self.config.log` is set.

Manifest: `egress.log: true` in bottle YAML flows through `EgressConfig.Log`
→ `Egress.prepare()` → `egress_render_routes(..., log=)` → routes.yaml.
`EgressPlan` also carries the flag for introspection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 13:59:48 -04:00
parent e82bbb587f
commit e72247a1b5
7 changed files with 183 additions and 17 deletions
+9 -3
View File
@@ -349,6 +349,7 @@ def _parse_dlp_block(
@dataclass(frozen=True)
class EgressConfig:
routes: tuple[EgressRoute, ...] = ()
Log: bool = False
@classmethod
def from_dict(cls, bottle_name: str, raw: object) -> "EgressConfig":
@@ -367,10 +368,15 @@ class EgressConfig:
for i, entry in enumerate(routes_list)
)
validate_egress_routes(bottle_name, routes)
log_raw = d.get("log", False)
if not isinstance(log_raw, bool):
raise ManifestError(
f"bottle '{bottle_name}' egress.log must be true or false"
)
for k in d:
if k != "routes":
if k not in ("routes", "log"):
raise ManifestError(
f"bottle '{bottle_name}' egress has unknown key {k!r}; "
f"only 'routes' is accepted"
f"accepted keys are 'routes', 'log'"
)
return cls(routes=routes)
return cls(routes=routes, Log=log_raw)