Skip to content

atenet-egress: log every failed request, sample successes at 1% - #2693

Merged
Haven Xia (HavenXia) merged 1 commit into
agent-substrate:mainfrom
ygao-g:egress-access-log-sampling
Oct 15, 2026
Merged

Haven Xia (HavenXia) merged 1 commit into
agent-substrate:mainfrom
ygao-g:egress-access-log-sampling

Conversation

@ygao-g

Copy link
Copy Markdown
Collaborator

Fixes #2685

The MITM and cleartext chains write one JSON access-log line per actor request. At 2,500 req/s on one replica that is 1.2 MB/s, and with the kubelet's default rotation only about 45 s of history survives, so a failure from a minute ago is already gone.

Both chains now log a request only when its status is 400 or above, when it carries a response flag, or as a 1% sample of the rest. At the same rate the log drops to 25 lines/s and the rotation window is over an hour, with every failure still recorded. The threshold and the sample rate are runtime keys, access_log.min_status and access_log.sample, so an operator can restore full logging at the admin port without a rollout. The log no longer counts successful requests per host; docs/metrics/substrate.yaml records that.

Testing: the manifest test compares each per-request log's filter against the expected YAML and asserts no other access log carries one. go test -race ./cmd/atenet/... and hack/verify/*.sh pass.

Prepared with help from an AI coding agent; reviewed and verified by the author.

@JeffLuoo Jeff Luo (JeffLuoo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this change. I have two comments to fix before merge. Both apply to the MITM chain and to the cleartext chain. The first one is about gRPC failures that the filter does not keep. The second one is about a sample decision that the actor can control.

filter:
or_filter:
filters:
- status_code_filter:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This filter does not keep gRPC failures. A gRPC error comes back as HTTP 200 with a non-zero grpc-status in the trailers, or in a trailers-only response. Envoy's local replies to a gRPC request also use :status 200. Such a request has no response flag, so only the 1% sample logs it. The grpc_status field in the log format then almost never shows a failure, and the claim "every failure still recorded" is not true for gRPC traffic.

Please add a gRPC branch to the or_filter on both chains (here and the cleartext chain at line 660), and update wantSampledLogFilter:

- and_filter:
    filters:
    - header_filter:
        header:
          name: content-type
          string_match: {prefix: application/grpc}
    - grpc_status_filter: {exclude: true, statuses: [OK]}

I limit the branch to gRPC requests on purpose. A grpc_status_filter alone can match plain HTTP 200 responses too. When a response has no grpc-status, Envoy maps the HTTP status to a gRPC code, and 200 maps to UNKNOWN. Please confirm the result with a request of each type.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. The MITM chain routes gRPC, so those errors only reached the log through the sample. In 9290a43 both chains carry your and_filter branch, wantSampledLogFilter pins it per leg, and the blind-spots entry mentions it. The old filter logged none of the three errors below.

Checked on Envoy 1.39.4 with this exact filter block and access_log.sample at 0, against a small h2c origin:

request response logged
gRPC, grpc-status: 5 in trailers 200 yes
gRPC, trailers-only grpc-status: 14 200 yes
gRPC to an Envoy direct_response: 403 (local reply) 200, grpc-status: 7 yes
gRPC OK, grpc-status: 0 in trailers 200 no
plain HTTP/1.1 GET, no grpc-status 200 no
{"ctype":"application/grpc","flags":"-","grpc_status":"NotFound","leg":"probe","path":"/svc/Err5","request_id":"1f2946d6-ca5d-4bbd-a4b8-218ffb1d50c9","status":200}
{"ctype":"application/grpc","flags":"-","grpc_status":"PermissionDenied","leg":"probe","path":"/svc/Denied","request_id":"32c3cae0-580d-4493-959e-bc0f83261eab","status":200}

op: GE
value: {default_value: 400, runtime_key: access_log.min_status}
- response_flag_filter: {}
- runtime_filter:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please set use_independent_randomness: true on this runtime_filter, and on the one in the cleartext chain at line 665. When the field is false (the default), Envoy takes the sample decision from x-request-id. The HCM does not set use_remote_address, so Envoy can keep an x-request-id that the actor sends. An actor that sends the same ID on each call, or that retries with one ID, then gets all of its successful requests logged or none of them. An actor can also pick an ID that keeps its successful requests out of the log. Independent sampling removes both problems, and the change is one line.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. In 9290a43 both runtime_filters set use_independent_randomness: true, and the test pins it per leg. Without the field, one fixed x-request-id decided all of an actor's successes at once.

Envoy 1.39.4, same filter block, access_log.sample raised to 50 with POST /runtime_modify?access_log.sample=50, then 200 plain GETs that all carry one fixed x-request-id:

x-request-id without the field with the field
0a1b2c3d-0000-4000-8000-000000000001 0 / 200 104 / 200, rerun 90 / 200
00000000-0000-4000-8000-000000000001 200 / 200 97 / 200

@JeffLuoo
Jeff Luo (JeffLuoo) added this pull request to the merge queue Oct 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 15, 2026
The MITM and cleartext chains wrote one access-log line per actor
request. At 2,500 req/s on one replica, with the kubelet's default
rotation, that keeps only 45 s of history.

Both chains now log a request only when its status is 400 or above,
when it is a gRPC request whose grpc-status is not OK, when it carries
a response flag, or as a 1% sample of the rest. The sample decision no
longer follows the actor's x-request-id.
@ygao-g
Yuan Gao (ygao-g) force-pushed the egress-access-log-sampling branch from 9290a43 to eaa1f19 Compare October 15, 2026 22:02
@HavenXia
Haven Xia (HavenXia) added this pull request to the merge queue Oct 15, 2026
Merged via the queue into agent-substrate:main with commit 84acd9a Oct 15, 2026
15 checks passed
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.

atenet-egress: which requests should the per-request access log keep?

4 participants