Repository navigation
feat(client): optional tracing spans (feature tracing, off by default) - #11
Merged
Merged
Conversation
With the feature on, each request through the pooled Client is a span tree, target `may_minihttp::client`: - http_client_request (INFO), the whole request until the body is read or the response dropped: http.request.method, server.address, server.port, url.scheme, http.response.status_code, error.type, http.request.resend_count, may_minihttp.connection_reused; - http_client_connect (INFO), DNS + connect + TLS of a new connection; - http_client_write, http_client_wait (time to first byte) and http_client_body (DEBUG); - DEBUG events pool_checkout (hit/miss/wait) and pool_evict (expired/closed). No paths, query strings or headers are recorded. The parent is the span given to RequestBuilder::parent_span, else Span::current() at send. RequestBuilder::request_span hands the client a caller's span to use as the request span instead: the client records the fields that span declares and opens its phase spans under it, so a caller with its own per-request client span gets one span, not two. When the request span is disabled, no phase spans or events are created (they would be orphan roots). The client never enters a span: a may coroutine can yield mid-request and resume on another thread, so spans get an explicit parent and close on drop. With the feature off there is no tracing dependency, the macros expand to nothing and the span fields are zero-sized. Also: ClientErrorKind::as_str(); HttpClient::send_request split into crate-private write_request/read_response so the write and the wait are timed separately; bench_client honours BENCH_TRACING=info|debug when built with the feature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y3TSRmEZAYU6RBUXhkn7WF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
This adds an optional
tracingfeature (off by default) that instruments the pooledClient. With the feature off, nothing is compiled in andtracingis not a dependency: the macros expand to nothing and the span fields are zero-sized. There is no OpenTelemetry dependency in either case. Atracing-opentelemetrysubscriber, or any other subscriber, turns these spans into whatever it exports.With the feature on, each request is a span tree. The target is
may_minihttp::client.http_client_requesthttp.request.method,server.address,server.port,url.scheme,http.response.status_code,error.type,http.request.resend_count,may_minihttp.connection_reusedhttp_client_connectserver.address,server.port,error.typehttp_client_writehttp_client_waithttp.response.status_codehttp_client_bodyhttp.response.body.size(buffered)It also emits DEBUG events:
pool_checkout(result=hit/miss/wait) andpool_evict(reason=expired/closed,count). Paths, query strings and headers are never recorded.Parenting, and why spans are never entered. A may coroutine can yield in the middle of a request and resume on another thread. An entered span would then be "current" for whatever else runs on the first thread. The client therefore creates every span with an explicit parent and closes it by dropping it, which is all an exporter needs for the tree and the timing. The parent is chosen as follows:
RequestBuilder::parent_span(&span)makesspanthe parent.Span::current()at send time. That lookup is skipped when nothing records INFO.RequestBuilder::request_span(&span)hands the client a caller's span to use as the request span. The client records the fields that span declares and opens its phase spans under it. A caller with its own per-request client span (its own name, kind and route) then gets one span per request, not two nested ones.Also in this PR:
ClientErrorKind::as_str()returns stable snake_case names (a metric label orerror.type).HttpClient::send_requestis split into the crate-privatewrite_requestandread_response, so the write and the wait are timed separately.send_requestbehaves as before.bench_clienthonoursBENCH_TRACING=info|debugwhen built with the feature.Overhead
bench_client poolagainstbench_serveron loopback, 16 coroutines × 4000 requests, ms02. Seven interleaved rounds, medians shown. The client's CPU time per request comes from/usr/bin/timeand is quantised at 0.16 µs per request; it is steadier than req/s on this shared host (load 19–23).tracing-subscriberregistry at INFO (request span; connect only on a miss)The "feature on, no subscriber" row was measured before
Span::current()was skipped when nothing records INFO. This PR includes that skip.Tests
tests/client_tracing.rs(required-features = ["tracing"]), using a recordingtracing-subscriberlayer:Span::current()as the default parent;error.typeon the connect and request spans;cargo fmt --check, clippy (default-D warnings; all features, all targets), and the full suite with all features (320 passed) and with default features, on Rust 1.99.0.🤖 Generated with Claude Code
https://claude.ai/code/session_01Y3TSRmEZAYU6RBUXhkn7WF