[TUnit.Aspire] Forward Aspire resource logs into captured test output (opt-in)
Labels: enhancement, TUnit.Aspire
Problem
AspireFixture<TAppHost> captures OpenTelemetry today, but a resource's raw stdout/stderr is never surfaced to the test. Aspire does not pipe child-resource console output to the test process, so when something goes wrong inside a resource, the test author sees only resource state transitions — not why the resource misbehaved.
This bites hardest at startup: a resource that throws during boot frequently exits before its OTel exporter flushes, and host-builder/DI/config failures are written to stderr, not OTel. So OTel capture has a blind spot at the single most common Aspire-test failure — "a resource won't come up."
Real repro (Aspire 13.4.6, TUnit.Aspire 1.57.14)
A full-fleet E2E fixture timed out after 480s with a generic "Timed out waiting for the Aspire application to start." The actual cause was one service (chat) crashing ~40s into boot with an unhandled exception (exit 0xE0434352). Nothing about that exception reached the test output — diagnosing it required a hand-written throwaway harness that tapped ResourceLoggerService.WatchAsync from StartAsync.
Proposal
Add an opt-in option to forward every resource's logs into the fixture's captured output, subscribed from StartAsync (so logs of resources that later exit are still captured, because capture is live rather than fetched post-mortem).
// Option A: fixture option
public class MyFixture : AspireFixture<Projects.AppHost>
{
protected override AspireFixtureOptions Options => new() { ForwardResourceLogs = true };
}
// Option B: attribute
[CaptureResourceLogs] // all resources
[CaptureResourceLogs("chat", "api")] // subset
Behaviour:
- For each (selected) resource, subscribe
ResourceLoggerService.WatchAsync(name) right after the app starts.
- Write each line to the fixture's output writer (
TestContext.Current?.OutputWriter / Console) prefixed with the resource name, e.g. [chat] <line>.
- Because TUnit already captures per-test console output into the result/report, the resource logs then appear in the failing test's report for free — including for resources that crash mid-boot.
Reference implementation (proven to capture the crash the fixture missed)
var loggerService = app.Services.GetRequiredService<ResourceLoggerService>();
foreach (var resource in selectedResources)
{
_ = Task.Run(async () =>
{
await foreach (var batch in loggerService.WatchAsync(resource).WithCancellation(ct))
foreach (var line in batch)
context.OutputWriter.WriteLine($"[{resource}] {line}");
}, ct);
}
Acceptance criteria
- Opt-in (default off) — no behaviour change for existing users.
- Forwarded logs appear in the captured output of the test that owns the fixture lifecycle.
- Logs of a resource that exits during startup are still captured (live subscription, not post-mortem fetch).
- Resource-name prefixing; optional include/exclude filter.
Related
- Depends-on/foundation for the fail-fast-on-crash issue (attributed startup-failure messages).
- Removes the need for consumers to hand-roll
ResourceLoggerService dumps (which race and return "(no logs available)" for exited resources).
[TUnit.Aspire] Forward Aspire resource logs into captured test output (opt-in)
Labels:
enhancement,TUnit.AspireProblem
AspireFixture<TAppHost>captures OpenTelemetry today, but a resource's raw stdout/stderr is never surfaced to the test. Aspire does not pipe child-resource console output to the test process, so when something goes wrong inside a resource, the test author sees only resource state transitions — not why the resource misbehaved.This bites hardest at startup: a resource that throws during boot frequently exits before its OTel exporter flushes, and host-builder/DI/config failures are written to stderr, not OTel. So OTel capture has a blind spot at the single most common Aspire-test failure — "a resource won't come up."
Real repro (Aspire 13.4.6, TUnit.Aspire 1.57.14)
A full-fleet E2E fixture timed out after 480s with a generic "Timed out waiting for the Aspire application to start." The actual cause was one service (
chat) crashing ~40s into boot with an unhandled exception (exit0xE0434352). Nothing about that exception reached the test output — diagnosing it required a hand-written throwaway harness that tappedResourceLoggerService.WatchAsyncfromStartAsync.Proposal
Add an opt-in option to forward every resource's logs into the fixture's captured output, subscribed from
StartAsync(so logs of resources that later exit are still captured, because capture is live rather than fetched post-mortem).Behaviour:
ResourceLoggerService.WatchAsync(name)right after the app starts.TestContext.Current?.OutputWriter/ Console) prefixed with the resource name, e.g.[chat] <line>.Reference implementation (proven to capture the crash the fixture missed)
Acceptance criteria
Related
ResourceLoggerServicedumps (which race and return "(no logs available)" for exited resources).