feat(egress): add global log option for full request/response logging
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:
@@ -70,6 +70,12 @@ class Route:
|
||||
inbound_detectors: tuple[str, ...] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Config:
|
||||
routes: tuple[Route, ...]
|
||||
log: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Decision:
|
||||
action: str # "forward" or "block"
|
||||
@@ -334,6 +340,29 @@ def load_routes(text: str) -> tuple[Route, ...]:
|
||||
return parse_routes(payload)
|
||||
|
||||
|
||||
def parse_config(payload: object) -> "Config":
|
||||
"""Parse a full egress config payload (top-level log flag + routes)."""
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("routes payload: top-level must be an object")
|
||||
payload_dict: dict[str, object] = typing.cast(dict[str, object], payload)
|
||||
|
||||
log_raw: object = payload_dict.get("log", False)
|
||||
if not isinstance(log_raw, bool):
|
||||
raise ValueError("routes payload: 'log' must be true or false")
|
||||
|
||||
routes = parse_routes(payload)
|
||||
return Config(routes=routes, log=log_raw)
|
||||
|
||||
|
||||
def load_config(text: str) -> "Config":
|
||||
"""Parse YAML text → Config (routes + log flag)."""
|
||||
try:
|
||||
payload = parse_yaml_subset(text)
|
||||
except YamlSubsetError as e:
|
||||
raise ValueError(f"routes payload: invalid YAML: {e}") from e
|
||||
return parse_config(payload)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Match evaluation
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -535,6 +564,7 @@ def scan_inbound(
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Config",
|
||||
"Decision",
|
||||
"HeaderMatch",
|
||||
"MatchEntry",
|
||||
@@ -544,8 +574,10 @@ __all__ = [
|
||||
"decide",
|
||||
"evaluate_matches",
|
||||
"is_git_push_request",
|
||||
"load_config",
|
||||
"load_routes",
|
||||
"match_route",
|
||||
"parse_config",
|
||||
"parse_routes",
|
||||
"scan_inbound",
|
||||
"scan_outbound",
|
||||
|
||||
Reference in New Issue
Block a user