Skip to content

Phase 7c: SDK-free observability + protocol-agnostic model example - #18

Merged
aaylward merged 5 commits into
mainfrom
claude/smithy-cpp-generator-plan-fpeqzt
Jul 7, 2026
Merged

aaylward merged 5 commits into
mainfrom
claude/smithy-cpp-generator-plan-fpeqzt

Conversation

@aaylward

@aaylward aaylward commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

The observability slice discussed in review: enrich the existing hooks so real backends (including OpenTelemetry) plug in cleanly, add W3C Trace Context propagation — and keep the core SDK-free. The optional //runtime:otel adapter is recorded in the PLAN as post-0.1.0 (opentelemetry-cpp's protobuf/gRPC dependency tree stays out of the dep-light core; hook shapes stabilize in production first).

Addendum (second commit): the consumer example now demonstrates the upstream protocol-agnostic vision, and fixes a runner bug it exposed — see the last section.

Operation names in observations

  • HttpResponse gains a non-wire operation annotation; Router::Add takes the operation name and stamps it after dispatch (both protocol generators pass it, goldens regenerated).
  • RequestObservation now reports operation — per-operation metrics and logs without touching generated code. Dispatch failures (404/405) report it empty.

Trace correlation

  • RequestObservation.trace_parent carries the incoming traceparent header verbatim for log correlation.
  • smithy/http/trace_context.h: ParseTraceparent (strict lowercase hex, zero-id and version-ff rejection, future-version tolerance), FormatTraceparent, GenerateTraceContext, GenerateSpanId.
  • smithy::PropagateTraceContext() client interceptor: sets traceparent on every attempt that lacks one; pass a current-context source to join the application's active trace, omit for fresh sampled roots.

Client attempt observation

smithy::ObserveAttempts(callback) — the client-side counterpart of the server's Observe: one callback per HTTP attempt with method/target/attempt/status (−1 + message on transport errors), retries visible via attempt > 1.

Tests

  • Trace-context unit tests: parse/format round-trips, malformed/zero-id/version rejections, future-version tolerance, generated-id validity and uniqueness.
  • Weather e2e: PropagateTraceContext + ObserveAttempts on the generated client against Observe on the generated server — the served observation reports operation GetCity and a parseable sampled traceparent; an unrouted request reports 404 with an empty operation.

Protocol-agnostic model example (upstream Smithy vision)

  • examples/bazel-consumer/model/todo.smithy no longer declares a protocol: @http traits describe HTTP semantics abstractly, and overlay files (model/bindings/restjson1.smithy, model/bindings/rpcv2cbor.smithy) bind the service with apply.
  • The consumer generates the same acme.todo#Todo service twice, side by side — restJson1 under acme::todo, rpcv2Cbor under acme::todo::cbor — with an integration test proving identical handler semantics over the second wire protocol.
  • Runner bug fix: --model was documented repeatable but only the last flag won, so base-model-plus-overlay could never assemble ("Trait applied to unknown shape"). The runner now imports every --model file, matching what the Bazel rule already passed.
  • docs/quickstart.md teaches the overlay flow (the inline-trait shortcut still works).

Docs

Production guide gains an Observability section with the explicit OpenTelemetry stance; runtime overview, quickstart, and PLAN updated.

Validation

  • bazel test //... — 45/45 green locally, plus ASan+UBSan (Beast targets CI-verified as usual)
  • out-of-tree consumer module green: both protocol bindings generate and pass
  • regeneration byte-stable; clang-format/clang-tidy/buildifier clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf

claude added 2 commits July 7, 2026 13:04
Completes the observability story short of a telemetry-SDK adapter
(recorded in the PLAN as an optional post-0.1.0 //runtime:otel target;
opentelemetry-cpp's protobuf/gRPC tree stays out of the dep-light core):

- HttpResponse gains a non-wire `operation` annotation; the router
  stamps it from a new Router::Add operation-name argument (both
  protocol generators pass the Smithy operation name), so middleware
  outside the generated router can label by operation.

- RequestObservation now carries `operation` and `trace_parent` (the
  incoming W3C traceparent header, verbatim) alongside
  method/target/status/duration — log correlation and per-operation
  metrics without touching generated code. Dispatch failures (404/405)
  report an empty operation.

- smithy/http/trace_context.h: W3C Trace Context helpers —
  ParseTraceparent (strict hex, zero-id and version-ff rejection,
  future-version tolerance), FormatTraceparent, GenerateTraceContext,
  GenerateSpanId.

- smithy/client/observability.h: ObserveAttempts (one callback per HTTP
  attempt: method/target/attempt/status or transport error — the
  client-side metrics hook) and PropagateTraceContext (sets traceparent
  on attempts that lack one; optional current-context source joins the
  application's active trace, otherwise fresh sampled roots).

- Weather e2e: PropagateTraceContext + ObserveAttempts on the generated
  client against Observe on the generated server — the observation
  reports operation "GetCity" and a parseable sampled traceparent; an
  unrouted request reports 404 with an empty operation.

- docs: production-guide Observability section (including the explicit
  OpenTelemetry stance), runtime overview, PLAN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf
Demonstrates the upstream Smithy vision in the quick start: the todo
model no longer declares a protocol — @http traits describe HTTP
semantics abstractly, and small overlay files bind the service to a
concrete protocol with `apply`. The consumer now generates the same
acme.todo#Todo service twice, side by side: restJson1 under acme::todo
and rpcv2Cbor under acme::todo::cbor, with an integration test proving
identical handler semantics over the second wire protocol.

Fixes the runner bug this exposed: --model was documented as repeatable
but only the last flag won, so a base model plus overlay could never
assemble ("Trait applied to unknown shape"). The runner now imports
every --model file, matching the Bazel rule, which already passed one
flag per file in srcs.

docs/quickstart.md teaches the overlay flow (and notes the inline-trait
shortcut still works).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf
@aaylward aaylward changed the title Phase 7c: SDK-free observability — enriched hooks + W3C Trace Context Phase 7c: SDK-free observability + protocol-agnostic model example Jul 7, 2026
claude added 3 commits July 7, 2026 13:17
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf
The greeter model the rule tests generate against is now
protocol-agnostic, with a greeter_restjson1.smithy `apply` overlay —
so every smithy_cpp_*_library rule test exercises multi-file model
assembly on every CI run. Adds an analysis-test assertion that each
srcs entry becomes its own --model flag on the generator action, and a
functional greeter_roundtrip_test proving the overlay-bound service's
generated client and server work together in-process (this is the
in-tree counterpart of the consumer example's overlay flow, and runs
in the main bazel job on all four OS configs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf
Bug-shaking coverage for the painful spots, plus the missing REST-path
showcase:

- Weather gains GetReport, the first greedy-label (@http uri
  "/reports/{reportPath+}") example operation. E2e proves greedy
  labels keep embedded slashes and decode percent-escapes segment by
  segment (spaces, %, ?, #, &, UTF-8).

- Hostile plain-label round-trips: CityId values inside its @pattern
  charset echo back exactly through encode -> route -> decode (the
  handler error message is the oracle, including consecutive/leading/
  trailing spaces); URI-delimiter and UTF-8 values outside the pattern
  are rejected by generated validation with a clean 400 — never a
  crash, mis-route, or mangled echo.

- Paginator drives a token full of URI-hostile characters
  ("a b&c=d?e+f/g%h#i") through the output-body -> input-query round
  trip and the server verifies it arrives byte-identical.

- Full-stack layering: FlakyTransport under retry x bearer_token x
  PropagateTraceContext x ObserveAttempts against
  Chain(RequireBearerAuth, Observe) — client hooks see all three
  attempts (-1, -1, 200), the server sees exactly one authorized,
  traced request labeled GetCity.

- @httpApiKeyAuth(in: "query") wire test pins both target-append
  branches ('?' with no query string, '&' after one) and that the key
  itself is percent-encoded ("k&e y" -> "k%26e%20y").

- Cafe's OrderCoffee is now @requestCompression(["gzip"]): wire tests
  pin no-compression under the threshold, compression at threshold 0
  and past the 10 KiB default, and that the payload gunzips back to
  the exact CBOR (clientToken preserved byte-for-byte).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjaNFwBZxoHdqagvq8ycQf
@aaylward
aaylward merged commit ac7c53a into main Jul 7, 2026
10 checks passed
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.

2 participants