Skip to content

auto-instrument the std network clients when observability is on#451

Merged
kacy merged 2 commits into
mainfrom
feat/o11y-autoinstrument
Jul 17, 2026
Merged

auto-instrument the std network clients when observability is on#451
kacy merged 2 commits into
mainfrom
feat/o11y-autoinstrument

Conversation

@kacy

@kacy kacy commented Jul 17, 2026

Copy link
Copy Markdown
Owner

summary

phase 4 of the observability sprint: when tracing is active (after obs.init), the std http and grpc clients now create a client span, inject a W3C traceparent into the outbound request, and record RED metrics — with no span code in the app. a small pair of server-side helpers lets a handler record a server span in two lines.

every hook gates on trace.is_active() first, so the default (uninstrumented) program pays a single bool check and behaves byte-identically. no compiler change, no bootstrap-seed refresh — std only.

hooks

  • grpc Conn.unary — CLIENT span named after the full method, rpc.system/rpc.method attrs, traceparent injected into the request headers, span status from the grpc-status, and rpc_client_requests_total{rpc_method,grpc_status} + rpc_client_duration_ms{rpc_method}. the streaming opens propagate the current trace context via traceparent too.
  • http ClientRequest.send / send_tls_with_config — CLIENT span "HTTP <method>", http.request.method/http.url attrs, traceparent header, status from the response code (>= 400 or a transport error is an error), and http_client_requests_total{method,status} + http_client_duration_ms{method}.
  • http begin_server_span(req) / end_server_span(span, status) — new public helpers. begin extracts the inbound traceparent so the SERVER span joins the caller's trace; end sets status, ends, and records http_server_requests_total{method,route,status} + http_server_duration_ms{method,route}. usage is documented in the doc comment (two lines around a handler, since there is no server framework).

what was tested

  • pith test on std/net/grpc.pith (24), std/net/http.pith (23), std/net/http2/connection.pith (36) — all pass.
  • make run-examples (86 passed) and make run-regressions (218 passed) — proves off-by-default is transparent.
  • an end-to-end program: a traced http client call to a traced http server sharing one connection. drained spans confirmed the client and server spans share the same trace_id and the server span's parent_id equals the client span's span_id (traceparent propagated across the wire), and metrics.snapshot_text() showed both the client and server RED counters.

design notes

  • the hooks are additive wrapping: no existing signature or return value changed. send/send_tls_with_config/unary return exactly what they did.
  • db-driver spans were left as a follow-up to keep the change small and focused on the network clients.

kacy added 2 commits July 17, 2026 19:42
when tracing is active (after obs.init), the http and grpc clients now
create a client span, inject a W3C traceparent into the outbound request,
and record RED metrics — with no span code in the app. a small pair of
server-side helpers (begin_server_span / end_server_span) lets a handler
extract the inbound traceparent and record a server span in two lines.

every hook gates on trace.is_active() first, so an uninstrumented program
(the default) pays a single bool check and behaves byte-identically.

- grpc Conn.unary: CLIENT span named after the full method, rpc.* attrs,
  traceparent injected into the request headers, span status from the
  grpc-status, and rpc_client_requests_total / rpc_client_duration_ms.
  streaming opens propagate the current trace context too.
- http ClientRequest.send / send_tls_with_config: CLIENT span "HTTP
  <method>", http.* attrs, traceparent header, status from the response
  code, and http_client_requests_total / http_client_duration_ms.
- http begin_server_span / end_server_span: SERVER span joined to the
  inbound traceparent, plus http_server_requests_total /
  http_server_duration_ms.

tested: pith test on std/net/grpc.pith (24), std/net/http.pith (23), and
std/net/http2/connection.pith (36); make run-examples (86) and
run-regressions to prove off-by-default is transparent; and an
end-to-end program that runs a traced client call against a traced
server and confirms both spans share one trace id with the server span
parented under the client span, and that the RED counters incremented.
the auto-instrumentation makes std.net.grpc and std.net.http import
std.trace unconditionally. trace only borrowed log's id generators, but
importing log dragged its whole public surface — including a generic
`struct Field` — into every grpc/http consumer's compile graph. that
collided with the protogen tool's own `struct Field` (E219).

trace now generates ids straight from std.uuid, the same source log uses,
so the format is unchanged and correlation still lines up. log->trace is
the right direction anyway: a log record reads the current trace id, the
tracer never needs the logger.
@kacy
kacy force-pushed the feat/o11y-autoinstrument branch from 12f2fbd to 809771a Compare July 17, 2026 20:03
@kacy
kacy merged commit 208becb into main Jul 17, 2026
2 checks passed
@kacy
kacy deleted the feat/o11y-autoinstrument branch July 17, 2026 20:11
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.

1 participant