-
Notifications
You must be signed in to change notification settings - Fork 1
Integration ProxyTap Extension
The Kronikol.Extensions.ProxyTap package is Kronikol's first out-of-process capture component. A ProxyTap is a transparent HTTP tee: it listens on a port, forwards every request byte-for-byte to the real service, and records a copy of each exchange as a Kronikol request/response pair attributed to the running test. Nothing inside the services needs to change.
Use it when the system under test is polyglot, third-party or legacy — services you cannot add TestTrackingMessageHandler / TestTrackingContextMiddleware to — and you still want per-test sequence diagrams of every hop.
Two capture topologies. Kronikol's classic model (topology A) instruments the system under test: handlers and middleware inside the backend record the calls. The proxy-tap model (topology B) leaves the backend untouched: a browser or test fixture stamps identity headers, they ride the real request through the services, and a tap on each hop is the sink. Both produce the same
RequestResponseLogentries and the same reports.
dotnet add package Kronikol.Extensions.ProxyTapNo ASP.NET Core host is required — the tap is built on HttpListener and binds localhost without URL ACLs.
browser/test ──► tap :8082 ──► graphql :8081 ──► tap :9192 ──► data-insights :9091 ──► ...
│ records │ records
▼ ▼
RequestResponseLogger / NDJSON file (IRequestResponseSink)
For every exchange the tap:
-
Resolves the test identity from the inbound request —
test-tracking-current-test-name/test-tracking-current-test-id(the same headersTestTrackingContextMiddlewarereads), then any configured fallback headers, then (by default) the W3Ctraceparenttrace id. The traceparent fallback is what makes browser-driven suites work: the test mints the trace, every browser request carries it, and downstream hops that drop the custom headers still carrytraceparent. -
Re-injects the four correlation headers (
test-tracking-current-test-name,-current-test-id,-caller-name,-trace-id) on the forwarded request when missing, so attribution survives a hop that would otherwise drop them. -
Forwards the original bytes (hop-by-hop headers handled;
Hostrewritten to the target). When anActivityListeneris attached to theKronikol.ProxyTapsource it emits a server span (parented on the inboundtraceparent) and a client span, and re-parents the forwardedtraceparenton the client span — the downstream service then nests under the tap in your distributed trace. Without a listener it is fully transparent. - Responds first, records second — capture decodes (gzip/deflate/br) and caps a copy of both bodies after the response has been written back, so it never sits on the request path.
-
Redacts secrets at capture —
authorization,proxy-authorization,cookie,set-cookie,x-api-key, … are replaced with[REDACTED](or dropped) before the entry reaches any sink. This is the security boundary: the secret never enters the in-memory store,TestRunReport.json, or an NDJSON file. (ExcludedHeadersonly hides headers in the diagram — see Capture-Time-Redaction.) -
Logs a
Requestand aResponseentry (sharedTraceId/RequestResponseId,ServiceName/CallerNamefrom the options, status, timestamps,ActivityTraceId/ActivitySpanId) to the configured sink.
Requests with no resolvable identity (health probes, warm-ups) are forwarded but not captured unless CaptureUnattributedRequests is set.
using Kronikol.Extensions.ProxyTap;
await using var tap = new ProxyTap(new ProxyTapOptions
{
ListenPort = 8082,
ForwardBaseUri = new Uri("http://localhost:8081"),
CallerName = "web", // the participant that dials the tap
ServiceName = "graphql", // the participant the tap forwards to
});
await tap.StartAsync();
// Point the caller at :8082 instead of :8081 — it sees identical traffic.Then generate reports exactly as you would for in-process tracking (ReportGenerator.CreateStandardReportsWithDiagrams(features, start, end, options)); every tapped call renders as an arrow between web and graphql in the scenario whose Id equals the resolved test id.
services.AddProxyTapTestTracking(o =>
{
o.ListenPort = 8082;
o.ForwardBaseUri = new Uri("http://localhost:8081");
o.CallerName = "web";
o.ServiceName = "graphql";
});One IHostedService per tap starts it with the host and stops it on shutdown. Call it once per hop (a ProxyTap per listen → forward pair); the taps are resolvable as IEnumerable<ProxyTap>.
-
.NET + Playwright — use Integration Playwright (
browser.NewTrackedContextAsync(identity)): the four headers plus atraceparentland on every browser request. -
Any other client — send the headers named in
Kronikol.Constants.TestTrackingHttpHeaders, or just a W3Ctraceparentwhose trace id you use as the scenario id. -
In-process HttpClient —
TestTrackingMessageHandleralready stamps them.
| Option | Default | Meaning |
|---|---|---|
ListenPort / ListenHost
|
— / localhost
|
Where the tap listens. |
ForwardBaseUri |
— | The real service (scheme + host + port). |
CallerName / ServiceName
|
— | The two participants this hop joins. |
DependencyCategory / CallerDependencyCategory
|
null |
A DependencyCategories value for shape/colour (e.g. BigQuery, AI). |
CaptureBodies / BodyCapBytes
|
true / 65536
|
Capture bodies; truncate decoded text beyond the cap (…truncated (N chars total)). |
HeaderPolicy |
AllExceptSecrets |
All, AllExceptSecrets, Whitelist (+ HeaderWhitelist), None. |
SecretDenylist / DropSecretHeaders / RedactedValue
|
CaptureRedaction.DefaultSecretHeaders / false / [REDACTED]
|
Capture-time secret handling. |
ReinjectCorrelation |
true |
Re-stamp missing test-tracking-* headers on the forwarded request. |
IdentityFromTraceparent |
true |
Use the inbound traceparent trace id as the test id when no id header is present. |
TestNameHeaderFallbacks / TestIdHeaderFallbacks
|
empty | Extra headers to read name/id from (legacy header names). |
IdentityResolver |
null |
Custom (headers, traceparent) → (name, id)? override. |
FallbackTestName |
Unknown |
Name when only an id is known (the tests file / kronikol ingest normalises it). |
CaptureUnattributedRequests |
false |
Capture requests with no identity (under FallbackTestName). |
FallbackTestId |
null |
With CaptureUnattributedRequests, use this fixed id for all unattributed traffic (one "outside any test" scenario) instead of a fresh id per request. |
Sink |
RequestResponseLoggerSink.Instance |
Where entries go; combine with NdjsonInteractionWriter via CompositeRequestResponseSink. |
Phase |
Unknown |
TestPhase stamped on entries. |
EmitActivities / SynthesizeTraceparent
|
true / true
|
Spans on ProxyTap.ActivitySource; mint a traceparent when the caller sent none. |
ForwardTimeout / ConnectTimeout
|
200 s / 5 s | Upstream deadlines. |
Log / Name
|
null |
Diagnostics callback and display name. |
RequestResponseLogger is a process-wide static store, so a tap running in the same process as report generation (a test host, an orchestrator) can log directly — the default sink. A tap running in another process (a sidecar, a polyglot harness) cannot reach that store: give it an NdjsonInteractionWriter sink and replay the file with kronikol ingest (see Ingesting External Captures). Use both at once to get the live in-process report and a replayable artifact:
using var file = new NdjsonInteractionWriter(".logs/taps/web-graphql.ndjson");
o.Sink = new CompositeRequestResponseSink(RequestResponseLoggerSink.Instance, file);Polling and retry traffic produces long runs of identical arrows. Enable collapsing on the report: ReportConfigurationOptions.CollapseConsecutiveIdenticalCalls = true (+ CollapseThreshold, MaxArrowsPerDiagram) — see Report Configuration. A scenario whose id matched no tapped call shows an explicit No interactions captured marker rather than an empty section.
- HTTP/1.1 only (the
HttpListenerfront end); HTTP/2 or gRPC-over-h2c hops need a different tee. - The tap is a participant in the trace: latencies you see include the tap's own (small) overhead.
- The tap cannot repair a downstream service that resets trace context on its own (e.g. a Java agent starting a fresh root for an outbound call); such legs need the service to forward the id — see the generic alternative (header re-injection) before patching anything.
Getting Started
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
- Integration Playwright
Uninstrumentable / polyglot backends
- Integration ProxyTap Extension
- Integration TcpTap Extension
- Integration Otlp Extension
- Exporting to OpenTelemetry
- Ingesting External Captures
- Integration Cucumber Messages
- Capture-Time Redaction
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Search Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Querying Reports
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference