Skip to content

[APMSVLS-501] feat(bottlecap): add bottlecap-test-mode binary#1216

Draft
lucaspimentel wants to merge 1 commit intolpimentel/bottlecap-test-modefrom
lpimentel/bottlecap-testmode-binary
Draft

[APMSVLS-501] feat(bottlecap): add bottlecap-test-mode binary#1216
lucaspimentel wants to merge 1 commit intolpimentel/bottlecap-test-modefrom
lpimentel/bottlecap-testmode-binary

Conversation

@lucaspimentel
Copy link
Copy Markdown
Member

Stacked on #1201. Review #1201 first; this PR's diff is purely additive once that lands.

Overview

Adds a second [[bin]] target, bottlecap-test-mode, that runs the APM trace-processing surface as a long-lived HTTP server with no AWS Lambda Extension lifecycle. Reuses TraceAgent, FlushingService, and the trace/stats/proxy flushers built by bottlecap::startup::build_trace_agent (introduced in #1201); the only Lambda-binary code it duplicates is init_ustr and enable_logging_subsystem (~20 lines, called out in the design doc).

Endpoints on 127.0.0.1:8126:

  • /v0.4/traces, /v0.5/traces, /v0.6/stats, /info — unchanged from the Lambda binary's TraceAgent router.
  • POST /flush — new, registered by a FlushRouterExtension impl attached via TraceAgent::with_router_extension(...) (the seam from [APMSVLS-501] refactor(bottlecap): preparatory work for "test-mode" binary #1201). Calls FlushingService::flush_blocking_final() and returns 204 No Content for deterministic harness-driven flushing.

Configuration: same DD_* env vars as the Lambda binary. Notable inputs:

  • DD_APM_DD_URL — redirects trace intake (the parity harness points this at the fake-intake from [APMSVLS-497][APMSVLS-498] test: add fake-intake for APM payload-level tests #1194).
  • DD_SERVERLESS_FLUSH_STRATEGY — opt-in periodic flush ticker, decoupled from managed-instance mode.
  • DD_TESTMODE_FUNCTION_ARN — overrides the stub ARN used for tag generation (defaults to arn:aws:lambda:us-east-1:000000000000:function:testmode).

API key is hardcoded to "stub-key" (no secrets resolver path); the parity harness fake-intake ignores auth.

Feature gating

Gated behind the test-mode cargo feature via required-features = ["test-mode"], the same feature that gates InvocationProcessorHandle::noop() (added in #1201). The binary is therefore not built in default or fips builds, including by cargo build --workspace. Build/run with:

cargo build --bin bottlecap-test-mode --features test-mode
cargo run   --bin bottlecap-test-mode --features test-mode

CI will need a clippy/build step that passes --features test-mode to keep this binary green going forward; happy to add that as a follow-up if reviewers prefer.

Shutdown ordering

signal::ctrl_c() cancels the shutdown token first (drives axum's graceful_shutdown so any in-flight /v0.4/traces request drains through the trace aggregator), then runs flush_blocking_final(). The periodic flush task selects on the same token so it doesn't leak when the listener stops.

Why a second binary

The overlap between Lambda-mode and test-mode is small (~20 lines: init_ustr, logging, config load), and the rest is intentionally different (no telemetry listener, no LWA, no logs agent, no proxy, no DogStatsD UDP, no event-bus-driven lifecycle). A second [[bin]] makes the test-mode surface explicit and structural, enforced by the compiler instead of by a runtime branch. Rejected alternatives (env-var branch, auto-detect, CLI flag, single binary with mode gating) are in the design doc.

Design doc: lucas-pimentel/docs/bottlecap-test-mode.md (local; happy to land it in-repo if reviewers prefer).

Testing

  • cargo check --bin bottlecap-test-mode --features test-mode
  • cargo clippy --workspace --all-targets --features default -- -D warnings (existing surface unchanged)
  • cargo clippy --workspace --all-targets --features default,test-mode -- -D warnings
  • cargo fmt --all -- --check
  • Manual smoke test against a local fake intake on :8200:
    • POST /v0.4/traces (real msgpack span) → 200, buffered, periodic flush fired at 2s, intake received POST /api/v0.2/traces (485 bytes, DD-API-KEY: stub-key).
    • POST /flush → 204.
    • POST /v0.5/traces (malformed) → 500 (correct rejection from the existing v0.5 deserializer).
    • GET /info → 200 with the standard endpoints list.
    • SIGINT → TRACE_AGENT | Shutdown signal received, shutting downAggregator service stopped → clean exit.
    • Span dedup verified: same trace_id+span_id sent twice, second dropped by the existing dedup service.

No new unit tests in this PR. The seam (RouterExtension) and the no-op handle (InvocationProcessorHandle::noop()) are both covered by tests added in #1201; end-to-end coverage for test-mode lands as part of the parity harness (#1194 and the future apm-agent-parity-rs repo).

🤖 Generated with Claude Code

A second [[bin]] target that runs the APM trace-processing surface as a
long-lived HTTP server with no Lambda lifecycle. Listens on
127.0.0.1:8126 and exposes the standard tracer endpoints
(/v0.4/traces, /v0.5/traces, /v0.6/stats, /info) plus POST /flush for
deterministic harness-driven flushing. Configured by the same DD_* env
vars the Lambda binary reads. Optional periodic flushing via
DD_SERVERLESS_FLUSH_STRATEGY (decoupled from managed-instance mode).

Gated behind the `test-mode` cargo feature (required-features), so it is
not built in default or fips builds. Build with
`cargo build --bin bottlecap-test-mode --features test-mode`.

Intended for the cross-agent parity harness (APMSVLS-496) and for local
dev workflows that need a tracer endpoint without standing up a Lambda.

APMSVLS-501

🤖 Co-Authored-By: Claude Code <noreply@anthropic.com>
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