Skip to content

Add --diagnostics: a per-request diagnostic stream for all output formats #268

Description

@FrankRay78

Summary

Add a --diagnostics switch that writes low-level detail about how a speed test ran to a stream separate from the result, in every output format. The result on stdout is unchanged whether or not the switch is present, so a user can capture diagnostics alongside piped CSV or JSON without corrupting it.

The goal is that a single run with --diagnostics produces everything needed to raise a good bug report — in particular, why individual requests failed, which the code currently discards.

This is the second of two issues that replace #256. It depends on the switch surface settled by #267.

Motivation

Diagnosing #245 — upload silently reporting 0 bps against HTTPS-migrated Ookla servers — took four released versions, a dozen screenshots and a hand-patched debug build to find something one line of diagnostic output would have named immediately.

The information exists and is thrown away. In OoklaSpeedtest.cs:340-356, every per-request exception — transport error, TLS failure, timeout, non-success HTTP status — is caught, converted into an increment of failedCount, and discarded. #222 added those counts, which is what made the symptom visible at all; this issue is about making the cause visible too.

The switch this replaces was no help. --verbosity Debug affected only the default console writer, so --csv and --json — the modes intended for scripting and for pasting into a bug report — had no diagnostics at all, and what it did add was a little extra prose about the result rather than information about how the test ran.

NetPace writes nothing to stderr today; everything goes through a single console. Introducing a second output stream is the substantive part of the Console-side work, and the test host does not yet capture one.

Users & jobs

A user hitting a network problem they cannot explain, who wants to attach something useful to a bug report. They need one run, with one extra switch, to produce a complete picture — not a back-and-forth of screenshots.

A maintainer triaging that report. They need to see which server was used and why it was chosen, which requests failed and for what reason, and enough environment detail to reproduce, without asking for a second run.

A script author already piping --csv or --json into another tool, who wants to keep a diagnostic trail on the side without touching their parsing.

Capability

A separate diagnostic stream

When --diagnostics is present, NetPace writes detail about the run to a stream distinct from the one carrying the result, so the two can be captured and redirected independently:

netpace --json --diagnostics > result.json 2> diagnostics.log

The result stream is byte-identical with and without the switch, in every output format. Nothing about the speed test result changes; the switch only adds a second stream.

When the switch is absent, nothing is written to the diagnostic stream.

What the diagnostics contain

Enough to reconstruct the run without asking the user for anything else:

  • When the test ran, and how NetPace was invoked.
  • What NetPace was running as and on — version, runtime, operating system.
  • Which server was used, and how it came to be chosen: auto-selected by latency, taken first from the list, or specified by the user.
  • Which of the latency, download and upload tests ran.
  • For each request: which request it was, when it happened, what it targeted, whether it succeeded or the reason it failed, how many bytes moved, and how long it took.

Every request gets its own record — none are dropped or summarised. A run issues between 2 requests on the fastest profile and roughly 456 on the heaviest, so a complete log is at most a few hundred lines; trimming would save almost nothing and risks discarding the single failed request that explains the problem.

The failure reason is the point of the exercise. A run against a server that rejects every upload must produce diagnostics from which the rejection cause is identifiable — that is the #245 case, and it is the acceptance bar for this feature.

Interactive use

Running with --diagnostics at a terminal must not corrupt the live progress display. There is direct evidence from #245 that naive interleaving does exactly that: temporary instrumentation shredded the progress bar into Uploading ---- 13%ly closed by the remote host...

Out of scope

  • Writing diagnostics to a file path given as an option value is not in this issue. Stream redirection already covers it.
  • Diagnostics for anything other than a speed test run — server listing, help, version — are not in this issue.
  • Changing what the counts in the normal result output show is not in this issue; Surface Transfer Failures #222 settled that.
  • Credential redaction is not in this issue. No CLI option carries a secret today, and diagnostics record the invocation and per-request URLs rather than effective settings, so there is nothing to leak. See Open questions for the tripwire that would change that.
  • Retiring --verbosity and adding --minimal are not in this issue; that is Retire --verbosity in favour of a --minimal output-format switch #267.

Acceptance criteria

  • For every output format, the result stream produced with --diagnostics is byte-identical to the result stream produced by the same invocation without it.
  • With --diagnostics, the result stream and the diagnostic stream can be redirected to different destinations, and each contains only its own content.
  • Without --diagnostics, no diagnostic content is produced on any stream.
  • --diagnostics produces diagnostics in every output format, including the compact, CSV and JSON formats.
  • Diagnostics from a run identify when the test ran, how NetPace was invoked, the NetPace version, the runtime and the operating system.
  • Diagnostics identify the server used and which selection route chose it.
  • Diagnostics identify which of the latency, download and upload tests ran.
  • Diagnostics record each request individually, including whether it succeeded and — where it did not — a reason attributable to that request.
  • Given a run in which every request of a test fails, the diagnostics allow the failure cause to be identified without re-running. (Regression bar for Upload Redirect 307 #245.)
  • Running interactively with --diagnostics leaves the live progress display intact and readable.
  • --help lists --diagnostics; README's --help block and USER_GUIDE describe it, including a redirection example.

Both streams are verified against committed snapshots per the testing conventions in CLAUDE.md. Timestamps must come from the existing IClock stub rather than wall-clock time, or the snapshots become non-deterministic.


Technical notes

Where it lives

  • The substantive work is in NetPace.Core, not the Console. Per-request detail does not currently exist to be rendered — the catch block at OoklaSpeedtest.cs:340-356 increments a counter and discards the exception. Core has to surface these events before the Console can format them; the formatting is the easy half.
  • The existing IProgress<SpeedTestProgress> channel is one candidate carrier, but that is a suggestion, not a decision — see Open questions.
  • Console side: NetPace resolves a single IAnsiConsole from DI throughout Program.cs. A second, error-stream console has to be introduced and threaded to wherever diagnostics are emitted.

Constraints / assumptions

  • Test seam is a prerequisite, not a detail. CommandLineTestHost registers one TestConsole and returns a single Output string. There is no way to capture or snapshot a second stream today. Until that is extended, none of this is testable, and Constitution Principle I is non-negotiable — so this is the first thing to build, before any production code.
  • Do not write to the diagnostic stream inside the measured window. Console I/O per request risks perturbing the numbers being measured, and is the mechanism behind the shredded progress bar in Upload Redirect 307 #245. Buffer and flush after the progress display completes.
  • Keep it trim-safe. NetPace targets AOT-trimmable builds; environment and version detail must be gathered without runtime reflection.
  • A full run issues many requests. A per-request record for every one of them may make the diagnostic stream large — see Open questions.

Unknowns

  • Surfacing per-request events will change the NetPace.Core public surface. The maintainer has approved that change in principle, so CLAUDE.md's "discuss public-API changes first" gate is satisfied for this issue. What remains is the shape of the change and its semver consequence for NuGet consumers, settled by the carrier decision below.

Open questions / future work

  • Carrier for per-request events in Core — recommendation posted, awaiting ratification. The design comment recommends a second IProgress<RequestDiagnostic> on new overloads, chosen because three of the four output writers pass no progress reporter and --diagnostics must work in every format. Stays a MINOR bump. Ratify before implementing.
  • Failure reason on the wire. DecidedRequestDiagnostic carries the reason as a string plus the exception type name, rather than the Exception itself, keeping exception objects off the public NuGet surface.
  • Diagnostic record format. Decided — one key=value record per line. See the decision comment below.
  • Volume. Decided — every request gets a record, none dropped or summarised. See Capability.
  • Credential redaction — deferred, with a tripwire. Out of scope here because nothing NetPace records today can carry a secret. Revisit the moment either becomes true: a CLI option is added that takes a credential, or diagnostics start reporting effective settings or profile state (OoklaSpeedtestSettings.ProxyCredential is where a proxy password actually lives). Until then there is no secret in the output to redact.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureEnd user feature

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions