Skip to content

fix(logger): attribute parallel logs to the correct test under go test -json#1873

Merged
james00012 merged 4 commits into
gruntwork-io:mainfrom
james00012:fix/json-log-attribution-1871
Jul 24, 2026
Merged

fix(logger): attribute parallel logs to the correct test under go test -json#1873
james00012 merged 4 commits into
gruntwork-io:mainfrom
james00012:fix/json-log-attribution-1871

Conversation

@james00012

@james00012 james00012 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #1871.

What

The default Terratest logger wrote formatted lines directly to os.Stdout. Under go test -json, the JSON runner cannot attribute a raw stdout write to a specific parallel test, so it tags each line with whichever test was active. The result is output where the Test field does not match the line it carries, which makes the JSON stream unusable for de-interleaving parallel tests. PR #1467 only added a mutex to stop garbled lines; attribution was never addressed.

Change

Two commits:

  1. logger: DoLog now routes through t.Log when a *testing.T is available and the destination is stdout. The framework then attributes each line to the correct test, including under t.Parallel(). Behavior is otherwise preserved:

    • t.Log streams immediately under -v on Go 1.14+, so the always on streaming rationale in the doc comment still holds.
    • An explicit non stdout writer (for example a bytes.Buffer) is always honored.
    • If the test has already completed, it falls back to a direct stdout write instead of panicking.
  2. logger/parser: routing through t.Log indents each line and prepends the framework file.go:NN: decoration, which broke terratest_log_parser de-interleaving of parallel non JSON output (every line rolled up to the last test resumed via === CONT). The indented lines still embed their owning test name, so the parser now detects that format and extracts the name directly. The un-indented path (stdout fallback) is unchanged.

Verification

  • Real terraform e2e: two t.Parallel() tests each running terraform.InitAndApplyContext against a random_integer + time_sleep config (mirrors the reporter repro, no cloud needed). Before: 134 of 188 terratest log lines misattributed. After: 0 of 190.
  • Parallel subtests under -json: 0 of 24 misattributed.
  • Completed-test path falls back to stdout without panicking; non *testing.T loggers still stream to stdout at column 0; *testing.B routes through b.Log.
  • Parser de-interleaving of real post-fix -v output verified end to end via SpawnParsers; existing fixture based parser tests still pass.
  • Full modules/core suite green under -race; go build across the workspace, go vet, and gofmt clean.

Notes for review

  • Routing through t.Log means Go prepends its own file:line: decoration, so a line now carries both Go and Terratest prefixes. I kept the Terratest prefix intact to avoid dropping the test name and timestamp. Happy to trim the redundancy if you prefer.
  • The switch cannot be gated to JSON only, since go test -v -json is indistinguishable from -v at runtime, so it applies whenever a real *testing.T is present.

Summary by CodeRabbit

  • Bug Fixes
    • Improved routing of Terratest stdout output through t.Log when available to keep parallel go test -json attribution correct.
    • Enhanced log parsing to reliably associate indented Terratest “applying” lines with the owning test and de-interleave mixed parallel output.
  • Documentation
    • Clarified how indented Terratest “applying” output is attributed to the correct test.
  • Tests
    • Added new coverage for indented Terratest log detection, test-name extraction (including subtests), and correct splitting of t.Log-routed output.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Logger output now uses per-test logging for stdout when a compatible testing sink exists. Parser logic recognizes indented Terratest output and attributes interleaved parallel-test logs to the correct test. Tests cover routing, fallback behavior, parsing, and de-interleaving.

Changes

Testing-aware logger routing and attribution

Layer / File(s) Summary
Testing sink routing and attribution
modules/core/logger/logger.go, modules/core/logger/logger_test.go
DoLog routes stdout through compatible testing sinks with panic-safe fallback, Logf marks helpers, and tests verify sink routing, explicit writers, and stdout fallback behavior.
Indented log parsing and de-interleaving
modules/core/logger/parser/parser.go, modules/core/logger/parser/terratest_log_line_test.go
Parser helpers recognize indented Terratest lines, extract test names, and verify that interleaved parallel-test output is written to the correct per-test logs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GoTest
  participant DoLog
  participant TestingT
  participant Parser
  participant TestLog
  GoTest->>DoLog: emit test log
  DoLog->>TestingT: route stdout through Log
  TestingT-->>Parser: test-attributed output
  Parser->>TestLog: write line to owning test log
Loading

Suggested reviewers: denis256, yhakbar

Poem

Logs find their test,
Parallel lines settle down,
Stdout keeps fallback.
Helpers mark the trail,
JSON tells the tale.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the core fix: attributing parallel Terratest logs to the correct test under go test -json.
Description check ✅ Passed The description is detailed and tied to the issue, though it does not follow the repository template sections like TODOs, Release Notes, or Migration Guide.
Linked Issues check ✅ Passed The code changes address #1871 by routing stdout logs through testing.T and updating the parser to recover the correct test name.
Out of Scope Changes check ✅ Passed The added tests, parser helpers, and logger plumbing all support the same logging-attribution fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@james00012
james00012 marked this pull request as ready for review July 18, 2026 17:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
modules/core/logger/parser/parser.go (1)

59-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider supporting non-Test prefixes like Benchmark or Fuzz.

Currently, the regex specifically looks for the Test prefix. While this is consistent with the existing heuristic lower down in parser.go, it means that logs from Terratest instances running inside Benchmark, Fuzz, or Example functions will be missed by this parser branch.

Since the timestamp signature (\d{4}-\d{2}-\d{2}T) is extremely strict and unlikely to cause false positives, you could safely broaden the capture group to match any non-whitespace string, ensuring future-proofing for other Go test frameworks.

💡 Proposed tweak to generalize the capture group
-	regexIndentedTerratestLog = regexp.MustCompile(`^\s+\S+:\d+:\s+(Test[^\s]*)\s+\d{4}-\d{2}-\d{2}T`)
+	regexIndentedTerratestLog = regexp.MustCompile(`^\s+\S+:\d+:\s+([^\s]+)\s+\d{4}-\d{2}-\d{2}T`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/core/logger/parser/parser.go` around lines 59 - 63, Update
regexIndentedTerratestLog to capture any non-whitespace test identifier instead
of requiring the identifier to begin with “Test,” while preserving the existing
indentation, file decoration, and RFC3339 timestamp constraints so Benchmark,
Fuzz, and Example logs are parsed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/core/logger/parser/parser.go`:
- Around line 123-128: Update GetTestNameFromIndentedTerratestLogLine to check
that FindStringSubmatch returns a match containing the capture group before
accessing m[1]. Return the function’s safe empty-string fallback when the input
does not match, while preserving the existing extracted test name for valid
matches.

---

Nitpick comments:
In `@modules/core/logger/parser/parser.go`:
- Around line 59-63: Update regexIndentedTerratestLog to capture any
non-whitespace test identifier instead of requiring the identifier to begin with
“Test,” while preserving the existing indentation, file decoration, and RFC3339
timestamp constraints so Benchmark, Fuzz, and Example logs are parsed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 53800129-1659-4ad7-b381-c595ad5d7907

📥 Commits

Reviewing files that changed from the base of the PR and between 6767013 and 37daae3.

📒 Files selected for processing (2)
  • modules/core/logger/parser/parser.go
  • modules/core/logger/parser/terratest_log_line_test.go

Comment on lines +123 to +128
// GetTestNameFromIndentedTerratestLogLine extracts the test name embedded in a terratest log line emitted through
// t.Log. See regexIndentedTerratestLog.
func GetTestNameFromIndentedTerratestLogLine(text string) string {
m := regexIndentedTerratestLog.FindStringSubmatch(text)
return m[1]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a bounds check to prevent potential panics.

Because this function is exported, it can be called by other packages with arbitrary strings. If the input string doesn't match the regex, FindStringSubmatch returns nil, and indexing m[1] will cause a runtime panic.

Even though it's called safely within this file's switch block, adding a quick bounds check makes the function robust for any future use. (Note: The other exported helpers in this file share this vulnerability and could also benefit from similar checks.)

🛡️ Proposed fix
 func GetTestNameFromIndentedTerratestLogLine(text string) string {
 	m := regexIndentedTerratestLog.FindStringSubmatch(text)
-	return m[1]
+	if len(m) > 1 {
+		return m[1]
+	}
+	return ""
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// GetTestNameFromIndentedTerratestLogLine extracts the test name embedded in a terratest log line emitted through
// t.Log. See regexIndentedTerratestLog.
func GetTestNameFromIndentedTerratestLogLine(text string) string {
m := regexIndentedTerratestLog.FindStringSubmatch(text)
return m[1]
}
// GetTestNameFromIndentedTerratestLogLine extracts the test name embedded in a terratest log line emitted through
// t.Log. See regexIndentedTerratestLog.
func GetTestNameFromIndentedTerratestLogLine(text string) string {
m := regexIndentedTerratestLog.FindStringSubmatch(text)
if len(m) > 1 {
return m[1]
}
return ""
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/core/logger/parser/parser.go` around lines 123 - 128, Update
GetTestNameFromIndentedTerratestLogLine to check that FindStringSubmatch returns
a match containing the capture group before accessing m[1]. Return the
function’s safe empty-string fallback when the input does not match, while
preserving the existing extracted test name for valid matches.

@james00012
james00012 force-pushed the fix/json-log-attribution-1871 branch from 37daae3 to f341866 Compare July 18, 2026 17:45
Comment thread modules/core/logger/parser/parser.go Outdated
// and prepends its own "file.go:NN: " decoration, e.g. " apply_test.go:42: TestFoo 2006-01-02T15:04:05Z07:00
// caller.go:7: message". It captures the embedded test name so parallel output can still be de-interleaved. The
// "TestName <RFC3339 timestamp>" signature is what distinguishes a terratest line from an ordinary t.Log line.
regexIndentedTerratestLog = regexp.MustCompile(`^\s+\S+:\d+:\s+(Test[^\s]*)\s+\d{4}-\d{2}-\d{2}T`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: We could use 'Test\S*' here instead of 'Test[^\s]*' to match the style used in other areas. Not a blocker though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@james00012
james00012 force-pushed the fix/json-log-attribution-1871 branch from f341866 to 58d655a Compare July 21, 2026 01:21
@james00012
james00012 enabled auto-merge (squash) July 21, 2026 01:32
…t -json

The default Terratest logger wrote formatted lines straight to os.Stdout. For
parallel tests, go test -json cannot tell which test a raw stdout write belongs
to, so it tags each line with whichever test was active, mixing up the output of
parallel tests (issue gruntwork-io#1871).

When a *testing.T is available, DoLog now routes the line through t.Log so the
framework attributes it to the correct test, including under t.Parallel(). t.Log
still streams immediately under -v on Go 1.14+, and an explicit non-stdout writer
is still honored. If the test has already completed, it falls back to a direct
stdout write instead of panicking.
Routing logs through t.Log (previous commit) indents each line and prepends the
testing framework's "file.go:NN: " decoration, so terratest_log_parser's
un-indented "Test"-prefix heuristic no longer matched and every parallel line
fell through to the previousTestName rollup. Both parallel tests resume up front
via === CONT and never pause again, so that rollup attributed all output to the
last test resumed.

The indented lines still embed their owning test name, so detect that format and
extract the name directly, restoring correct de-interleaving. The existing
un-indented case still handles the stdout fallback path. Adds unit tests for the
new matcher and an end-to-end SpawnParsers test over interleaved t.Log output.
@james00012
james00012 force-pushed the fix/json-log-attribution-1871 branch from 802c30c to 6c6132a Compare July 21, 2026 01:49
@gcagle3
gcagle3 self-requested a review July 24, 2026 09:47
@james00012
james00012 merged commit 74e48a2 into gruntwork-io:main Jul 24, 2026
12 checks passed
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.

Parallel logging attributes output to the wrong test

2 participants