Demote granular internal logs from Debug to Trace level#5116
Conversation
Per-item scheduling, hook building, and assembly loading logs are too verbose for Debug and better suited to Trace. High-level flow and configuration logs remain at Debug.
There was a problem hiding this comment.
Code Review
Overall this is a clean, well-scoped PR that improves the signal-to-noise ratio at Debug level. The separation between Debug (flow-level) and Trace (per-item detail) is a meaningful improvement for users who run with --log-level debug.
Concern: Assembly load failures demoted to Trace
The four Failed to load ... messages in InfrastructureGenerator.cs (and the generated snapshot files) have been moved to Trace:
// Before
LogDebug("[ModuleInitializer:...] Failed to load ...: " + ex.Message);
// After
LogTrace("[ModuleInitializer:...] Failed to load ...: " + ex.Message);I'd argue these should stay at Debug. The success path (Assembly initialized) is absolutely right to move to Trace — it's noise. But a failure deserves more visibility. A user who runs --log-level debug to investigate a test discovery problem would no longer see these. They'd need to know to enable Trace first, which is a significant discoverability hurdle.
The PR description itself notes this pattern: "Keeps high-level flow logs... at Debug". An assembly load failure is effectively a flow event (it affects what tests are discovered), not a per-item detail.
Suggested approach: Keep Failed to load at Debug, move only Loading assembly containing and Assembly initialized to Trace.
Inconsistency: Missing IsTraceEnabled guards in HookDelegateBuilder
ConstraintKeyScheduler and TestScheduler correctly guard their trace calls:
if (_logger.IsTraceEnabled)
await _logger.LogTraceAsync($"Starting test {test.TestId} with constraint keys: {string.Join(...)}");But HookDelegateBuilder.cs does not:
// No guard — string interpolation always runs
await _logger.LogTraceAsync($"Creating delegate for {hookTypeName} hook: {hook.Name}");
await _logger.LogTraceAsync($"Built {hooks.Count} {hookTypeName} hook delegate(s)");The existing LogAsync base method short-circuits on IsEnabled, so correctness is fine — but the string interpolation always allocates even when Trace is off. The guards in the other files exist specifically to avoid this. The PR adds IsTraceEnabled precisely to enable this pattern; it should be applied consistently.
Minor: TestGroupingService summary block has no guard
The 7-call summary block:
await _logger.LogTraceAsync("═══ Test Grouping Summary ═══").ConfigureAwait(false);
await _logger.LogTraceAsync($" Parallel (no constraints): {parallelTests.Count} tests").ConfigureAwait(false);
// ... 5 more callsCould be wrapped in a single guard to avoid 7 async overhead calls when Trace is disabled:
if (_logger.IsTraceEnabled)
{
await _logger.LogTraceAsync("═══ Test Grouping Summary ═══").ConfigureAwait(false);
// ...
}The individual string interpolations here are cheap (just integer property accesses), so this is low priority — but it would be consistent with the guard pattern used elsewhere and GroupTestsByConstraintsAsync is on the hot path.
What's good
- The two-tier Debug/Trace separation is clearly reasoned and the PR description maps it well.
- Adding to is the right way to enable efficient guard checks — it mirrors the existing pattern symmetrically.
- Snapshot files correctly updated across all 4 target frameworks.
- The per-test constraint key and scheduling logs are absolutely right to move to Trace — they'd produce O(N) lines per test run at Debug level.
There was a problem hiding this comment.
Code Review
Overall this is a clean, well-scoped PR that improves the signal-to-noise ratio at Debug level. The separation between Debug (flow-level) and Trace (per-item detail) is a meaningful improvement for users who run with --log-level debug.
Concern: Assembly load failures demoted to Trace
The four Failed to load ... messages in InfrastructureGenerator.cs (and the generated snapshot files) have been moved to Trace:
// Before
LogDebug("[ModuleInitializer:...] Failed to load ...: " + ex.Message);
// After
LogTrace("[ModuleInitializer:...] Failed to load ...: " + ex.Message);I'd argue these should stay at Debug. The success path (Assembly initialized) is absolutely right to move to Trace — it's noise. But a failure deserves more visibility. A user who runs --log-level debug to investigate a test discovery problem would no longer see these. They'd need to know to enable Trace first, which is a significant discoverability hurdle.
The PR description itself notes this pattern: "Keeps high-level flow logs... at Debug". An assembly load failure is effectively a flow event (it affects what tests are discovered), not a per-item detail.
Suggested approach: Keep Failed to load at Debug, move only Loading assembly containing and Assembly initialized to Trace.
Inconsistency: Missing IsTraceEnabled guards in HookDelegateBuilder
ConstraintKeyScheduler and TestScheduler correctly guard their trace calls:
if (_logger.IsTraceEnabled)
await _logger.LogTraceAsync($"Starting test {test.TestId} with constraint keys: {string.Join(...)}");But HookDelegateBuilder.cs does not:
// No guard — string interpolation always runs
await _logger.LogTraceAsync($"Creating delegate for {hookTypeName} hook: {hook.Name}");
await _logger.LogTraceAsync($"Built {hooks.Count} {hookTypeName} hook delegate(s)");The existing LogAsync base method short-circuits on IsEnabled, so correctness is fine — but the string interpolation always allocates even when Trace is off. The guards in the other files exist specifically to avoid this. The PR adds IsTraceEnabled precisely to enable this pattern; it should be applied consistently.
Minor: TestGroupingService summary block has no guard
The 7-call summary block:
await _logger.LogTraceAsync("=== Test Grouping Summary ===").ConfigureAwait(false);
await _logger.LogTraceAsync($" Parallel (no constraints): {parallelTests.Count} tests").ConfigureAwait(false);
// ... 5 more callsCould be wrapped in a single guard to avoid 7 async overhead calls when Trace is disabled:
if (_logger.IsTraceEnabled)
{
await _logger.LogTraceAsync("=== Test Grouping Summary ===").ConfigureAwait(false);
// ...
}The individual string interpolations here are cheap (just integer property accesses), so this is low priority — but it would be consistent with the guard pattern used elsewhere, and GroupTestsByConstraintsAsync is on the hot path.
What's good
- The two-tier Debug/Trace separation is clearly reasoned and the PR description maps it well.
- Adding
IsTraceEnabledtoTUnitFrameworkLoggeris the right way to enable efficient guard checks — it mirrors the existingIsDebugEnabledpattern symmetrically. - Snapshot files correctly updated across all 4 target frameworks.
- The per-test constraint key and scheduling logs are absolutely right to move to Trace — they would produce O(N) lines per test run at Debug level, which was the real noise problem.
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.19.16 to 1.19.22. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.19.22 <!-- Release notes generated using configuration in .github/release.yml at v1.19.22 --> ## What's Changed ### Other Changes * Remove redundant prefixes from tracing timeline spans by @thomhurst in thomhurst/TUnit#5111 * Render span tags and events on separate lines by @thomhurst in thomhurst/TUnit#5113 * Demote granular internal logs from Debug to Trace level by @thomhurst in thomhurst/TUnit#5116 ### Dependencies * chore(deps): update tunit to 1.19.16 by @thomhurst in thomhurst/TUnit#5109 **Full Changelog**: thomhurst/TUnit@v1.19.16...v1.19.22 Commits viewable in [compare view](thomhurst/TUnit@v1.19.16...v1.19.22). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
IsTraceEnabledproperty toTUnitFrameworkLoggerfor guard checksFiles changed
IsTraceEnabledproperty.verified.txtfiles updated to matchTest plan
TUnit.Enginebuilds with 0 warningsTUnit.Core.SourceGeneratorbuilds with 0 warnings