Build report: structured JSON report with per-mojo log capture - #12695
Build report: structured JSON report with per-mojo log capture#12695gnodet wants to merge 1 commit into
Conversation
bae1db9 to
5a5af1e
Compare
602fffb to
056dcf0
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
056dcf0 to
a1ee585
Compare
e64db31 to
de8044a
Compare
a1ee585 to
0f31ce2
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8083c89 to
170bd72
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170bd72 to
af0cc72
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8baa65a to
02ac855
Compare
af0cc72 to
3658983
Compare
gnodet
left a comment
There was a problem hiding this comment.
Well-designed build report feature with clean API/impl separation and comprehensive tests. A few issues noted below.
Also noted:
- The architecture is solid: clean API interfaces in maven-api-core, record-based implementations in maven-core, EventSpy pattern for automatic discovery, thread-based log routing for parallel-build safety, atomic file writes with symlink swap.
- The PR correctly depends on PR #12694 (logging foundation) — should not be merged until #12694 lands.
- No test for the
captureLogEventrouting logic (mojo-level vs module-level vs build-level buffers). This is the core routing mechanism and warrants at least one test exercising the dispatch.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of gnodet
| * Maximum number of log events captured per scope (mojo, module, or build). | ||
| * Beyond this, events are dropped and a truncation notice is appended. | ||
| */ | ||
| static final int MAX_LOG_EVENTS_PER_SCOPE = 500; |
There was a problem hiding this comment.
The Javadoc says "Beyond this, events are dropped and a truncation notice is appended" but captureLogEvent silently drops events without appending any truncation notice. Either append a synthetic LogEvent indicating truncation (e.g. with level WARN and message "... N events truncated"), or update the Javadoc to say events are silently dropped.
| sb.append('}'); | ||
| } | ||
|
|
||
| private static void writeModule(StringBuilder sb, ModuleReport module, int indent) { |
There was a problem hiding this comment.
The hasMore parameter is unused (annotated @SuppressWarnings("unused")) and all call sites pass true. If trailing comma control is no longer needed, the parameter should be removed to reduce confusion.
| private static void writeModule(StringBuilder sb, ModuleReport module, int indent) { | |
| private static void writeNullableField(StringBuilder sb, int indent, String key, String value) { |
gnodet
left a comment
There was a problem hiding this comment.
Well-designed build report feature with clean API/impl separation, solid thread safety, and comprehensive tests. A few issues worth addressing:
Confirmed findings (verified independently):
-
[Medium]
BuildReportCollector.java— The Javadoc onMAX_LOG_EVENTS_PER_SCOPEstates "events are dropped and a truncation notice is appended," butcaptureLogEventsilently drops events without ever appending a truncation notice. Either implement the truncation notice (e.g., append a synthetic LogEvent like "... N events truncated") or correct the Javadoc to say events are silently dropped. -
[Low]
BuildReportJsonWriter.java— ThewriteNullableFieldmethod has an unusedboolean hasMoreparameter annotated with@SuppressWarnings("unused"). The parameter is never read and the method always emits a trailing comma regardless. Remove it to avoid confusion. -
[Low]
BuildReportJsonWriter.java—writeProblemusessb.lastIndexOf(",\n")to remove trailing commas (searches entire buffer backwards), whilewriteLogEventuses the dedicatedremoveTrailingCommahelper (checks only last two characters). UseremoveTrailingCommaconsistently — it's safer sincelastIndexOfcould theoretically match an earlier,\nif future refactors change field order.
This review was generated by an AI agent (Claude Code) and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
| /** | ||
| * Maximum number of log events captured per scope (mojo, module, or build). | ||
| * Beyond this, events are dropped and a truncation notice is appended. | ||
| */ |
There was a problem hiding this comment.
[Medium] The Javadoc here says "events are dropped and a truncation notice is appended," but captureLogEvent (below) silently drops events when the limit is reached — no truncation notice is ever appended.
Either implement the truncation notice (e.g., append a synthetic LogEvent like "... N events truncated") or correct this Javadoc to say events are silently dropped.
| } | ||
|
|
||
| private static void writeNullableField( | ||
| StringBuilder sb, int indent, String key, String value, @SuppressWarnings("unused") boolean hasMore) { |
There was a problem hiding this comment.
[Low] The hasMore parameter is annotated @SuppressWarnings("unused") and is indeed never read — the method always emits a trailing comma regardless of its value. Consider removing it:
| StringBuilder sb, int indent, String key, String value, @SuppressWarnings("unused") boolean hasMore) { | |
| private static void writeNullableField(StringBuilder sb, int indent, String key, String value) { |
gnodet
left a comment
There was a problem hiding this comment.
Well-structured addition of a JSON build report feature with clean API design, good test coverage (17 tests), and defensive error handling. The zero-dependency JSON writer is appropriate for Maven's philosophy. A few design items:
Medium severity:
-
No opt-out mechanism (
BuildReportCollector.java): The collector is unconditionally active for every build — everymvninvocation writes a JSON file to disk with no system property to disable it. Consider adding-Dmaven.build.report.skip=truefor environments where this is undesirable (read-only filesystems, embedded invocations, CI runners). -
MojoSkipped events not handled (
BuildReportCollector.java):ExecutionEvent.Type.MojoSkipped(fired e.g. when a mojo requires online mode but Maven is offline) silently vanishes from the report. Inconsistent withProjectSkippedwhich IS handled. Skipped mojos should be tracked withBuildStatus.SKIPPED.
Low severity:
-
Failure timestamp inaccuracy (
BuildReportCollector.javaline 1110):failureTimestampis set toMonotonicClock.now()at report-assembly time, not at actual failure time. The mojo's timing data does capture the real timing — worth documenting in theFailureReport.timestamp()Javadoc. -
Inconsistent trailing comma removal (
BuildReportJsonWriter.java):writeProblemusessb.lastIndexOf(",\n")which searches backwards through the entire buffer, whilewriteLogEventuses the more robustremoveTrailingComma(sb)which checks only the end. Consider usingremoveTrailingCommaconsistently. -
Unused
hasMoreparameter (BuildReportJsonWriter.javaline 1553): Annotated@SuppressWarnings("unused")and never referenced. Either use it to control comma behavior or remove it. -
~70 lines of duplicated test helpers:
BuildReportCollectorTestandBuildReportIntegrationTestshare identicalcreateProject,createSession,createMojoExecution, andcreateEventmethods. Consider extracting to a shared test utility.
The API design (immutable interfaces in maven-api-core, record implementations in maven-core, @Experimental markers) follows Maven's established patterns. Thread safety approach is sound. The atomic file write + symlink pattern is well-implemented with proper fallbacks.
This review was generated by an AI agent (Claude Code) and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
3658983 to
c51ee87
Compare
02ac855 to
812a842
Compare
c51ee87 to
b26db14
Compare
812a842 to
84568d2
Compare
- BuildReport/ModuleReport/MojoReport/FailureReport API interfaces - BuildReportCollector EventSpy tracks lifecycle events and captures log output - BuildReportJsonWriter zero-dependency JSON serializer with stable field order - Thread-based log routing for parallel builds via ConcurrentHashMap - Atomic writes with timestamped filename and build-report-latest.json symlink
b26db14 to
b6ea5b4
Compare
gnodet
left a comment
There was a problem hiding this comment.
Well-structured build report feature with clean API/impl separation, solid thread safety, and good test coverage. The core architecture (EventSpy-based collector, ConcurrentHashMap log routing, atomic file write with symlink swap) is sound. The 271-file diff is mostly site doc conversions and dependency bumps from rebasing — the actual feature is ~15 new files.
Nine items to address before the chain merges to master:
1. NPE risk: null logger-name guard removed in MavenJulHandler (high)
In MavenJulHandler.java line ~124, the null-to-empty-string guard for record.getLoggerName() was removed. JUL spec explicitly allows null logger names, and LoggerFactory.getLogger(null) will throw NPE/IllegalArgumentException. Restore the guard or move the LoggerFactory.getLogger() call inside the else branch.
2. No opt-out mechanism for BuildReportCollector (medium)
The collector is unconditionally active (@Singleton @Named extending AbstractEventSpy) — every mvn invocation incurs the overhead with no way to disable it. Read-only filesystems, embedded invocations, and CI runners that don't want report files have no escape hatch. Consider a system property like -Dmaven.build.report.skip=true.
3. MojoSkipped events not handled (medium)
BuildReportCollector.onEvent() handles MojoStarted/MojoSucceeded/MojoFailed but not MojoSkipped. Skipped mojos silently vanish from the report. This is inconsistent with ProjectSkipped which IS handled.
4. Unconditional StackWalker overhead (medium)
DefaultLog.withMetadata() now calls StackWalker.walk() unconditionally for every Log API call (~1-5μs per log statement). The previous hasReportCapture() guard was removed. If an opt-out is added (finding #2), the conditional StackWalker should be restored.
5. Javadoc/code mismatch on truncation (medium)
MAX_LOG_EVENTS_PER_SCOPE Javadoc says "events are dropped and a truncation notice is appended," but the code silently drops events beyond the limit without appending a notice. Align the docs or implement the truncation notice.
6-9. Lower-severity items:
BuildReportJsonWriter.writeNullableFieldhas unusedboolean hasMoreparameter with@SuppressWarnings("unused")— remove itwriteProblemusessb.lastIndexOf(",\n")(O(n) search) whilewriteLogEventuses the dedicatedremoveTrailingCommahelper (O(1)) — use the helper consistentlyfailureTimestampis set at report-assembly time (MonotonicClock.now()inbuildReport()), not at actual failure time — could derive from the failedMojoTimingBuildReportCollectorTestandBuildReportIntegrationTestshare ~70 lines of identical helper methods — consider extracting to a shared test utility
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
Summary
Part 2 of the logging feature chain (depends on #12694 — logging foundation).
Adds a structured build report that captures per-module and per-mojo execution results, timing, log events, and failures as a JSON file at the end of every build.
15 files changed, ~2700 insertions — focused on report data model, collection, and serialization.
What's in this PR
BuildReport,BuildStatus,ModuleReport,MojoReport,FailureReportDefaultBuildReport,DefaultModuleReport,DefaultMojoReport,DefaultFailureReportBuildReportCollectorEventSpythat tracks lifecycle events and captures log output viaLogEventSink, routing to mojo/module/build-level buffersBuildReportJsonWriterKey design decisions
BuildReportCollectoris a@Named @Singletonthat extendsAbstractEventSpy, discovered automatically — no wiring changes neededConcurrentHashMap<Long, String>(thread ID → mojo/project key) to associate log events with the correct scope in parallel buildsLogEventSink(4-arg) independently from the existingLogSink(5-arg) used byProjectBuildLogAppender— no interference with console outputbuild-report-latest.jsonsymlinkonSessionEndedwraps report generation in try-catch so report failures never crash the buildWhat's NOT in this PR (deferred to later PRs)
--warning-modeCLI flag — Warning mode, diagnostic collector, BuilderProblem enrichments #12698--console=plain/rich/machine) — Console modes: --console=plain/rich/verbose/machine #12697mvnlogviewer tool — mvnlog: build log viewer, integration tests, script routing #12699PR chain
mvnlogviewerTest plan
🤖 Generated with Claude Code