Skip to content

Console modes: --console=plain/rich/verbose/machine - #13180

Open
gnodet wants to merge 6 commits into
masterfrom
feature/console-modes
Open

gnodet wants to merge 6 commits into
masterfrom
feature/console-modes

Conversation

@gnodet

@gnodet gnodet commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

⚠️ This PR is stacked on #12695. Review only this commit: 14521d5

Summary

Part 3 of the logging feature chain. Depends on #12695 (build report).

Adds the --console CLI flag with four output modes:

  • plain — compact one-line-per-module output, ideal for CI (auto-selected in CI environments)
  • rich — JLine status bar with live reactor progress (auto-selected on interactive TTYs)
  • verbose — full mojo-level output, current Maven 4.0 default behavior
  • machine — JSON lines: one typed JSON object per lifecycle event, designed for piping to external tools

Auto-detection (--console=auto, the default): CI → plain, interactive TTY → rich, otherwise → verbose.

Files changed (17 files)

Area Files
API Options.java (+console())
Event loggers PlainExecutionEventLogger, RichBuildEventListener, RichExecutionEventLogger, MachineBuildEventListener, MachineExecutionEventLogger
CLI wiring CommonsCliOptions, LayeredOptions, LookupInvoker (preliminary interactive detection), MavenInvoker (console mode switch + transfer listener)
Existing logger ExecutionEventLogger (version info on failure, linear reactor summary)
Tests 5 test classes with full coverage

PR chain

# PR Feature
1 #12694 Logging foundation
2 #12695 Build report
3 This PR Console modes
4 #12698 Warning mode + diagnostics
5 #12699 mvnlog viewer
6 #12702 Structured problems pipeline
7 #12714 TRACE level migration

Test plan

  • mvn verify -pl api/maven-api-cli,impl/maven-cli -am — all tests pass
  • CI validation

@gnodet

gnodet commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

One format worth considering for the machine-readable console mode: logfmt.

level=INFO  build=a1b2c3 module=my-app execution=maven-compiler-plugin:compile:default phase=compile ts=1234567890123 msg="Compiling 42 source files"
level=ERROR build=a1b2c3 module=my-app execution=maven-compiler-plugin:compile:default phase=compile ts=1234567890456 msg="Compilation failure"
level=INFO  build=a1b2c3 module=my-app execution=maven-compiler-plugin:compile:default result=FAILURE duration_ms=1823 msg="Mojo finished"

It sits nicely between plain text (human-readable) and JSON lines (fully structured): parseable by Loki, Vector, Grafana out of the box, but still legible with a plain cat.

The interesting property: with build + module + execution as grouping keys, the full hierarchy (modules → executions → log events + timing + results) can be reconstructed from the flat stream by any consumer.

Could be offered as a --console=logfmt option, or considered as the canonical machine format if we want to converge on a single representation.

@gnodet
gnodet force-pushed the feature/build-report branch 8 times, most recently from 7eaa538 to 869e497 Compare September 19, 2026 04:55

@gnodet-bot gnodet-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.

Console modes review — overall this is solid. Three specific issues worth addressing before merge.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

…enhancements

- Add LogEvent interface (maven-api-core) with projectId/mojoId context fields
- Add DefaultLogEvent record (maven-core) implementing LogEvent
- Add MavenJulHandler (maven-logging): bridge JUL→SLF4J for plugin logging
- Enhance DefaultLog (maven-core): carry LOG_API_METADATA for mojo log capture
- Add ProjectBuildLogAppender (maven-core): MDC-aware log sink feeding LogEvent stream
- Suppress JLine terminal-init DEBUG logs before activateLogging in quiet mode
- Gate StackWalker behind hasReportCapture() for zero overhead in normal builds
- Fix warn(Supplier<String>, Throwable) incorrectly calling logger.info()
- Add @PARAM tags on trace(Supplier) overloads; fix sequenceNumber() @return javadoc
- Fix LogEvent.message() @return javadoc copy-paste from formattedMessage()
- Strengthen logApiMetadataIsClearedAfterCall() test to exercise the remove() path
@gnodet
gnodet force-pushed the feature/build-report branch 3 times, most recently from cbc7623 to f656ea9 Compare September 21, 2026 05:53
- Add BuildReport, ModuleReport, MojoReport, FailureReport, BuildStatus API types (maven-api-core)
- Add BuildEnvironment API type and Session.buildEnvironment() accessor
- Implement BuildReportCollector in maven-core: captures per-mojo log output, timing, failures
- Implement BuildReportJsonWriter: writes structured JSON report to file at build end
- Integrate into MavenInvoker and DefaultMavenExecutionRequest
- Extend MavenSimpleLogger and MavenJulHandler with LogSink support for log capture
- Add SessionStub.buildEnvironment() stub for maven-testing
- Add BuildReportCollectorTest, BuildReportJsonWriterTest, BuildReportIntegrationTest
- Handle MojoSkipped events in BuildReportCollector
- Fix ConcurrentModificationException in buildReport() log buffer iteration
@gnodet
gnodet force-pushed the feature/build-report branch from 11205e6 to 92a187f Compare September 21, 2026 07:44
@gnodet
gnodet force-pushed the feature/console-modes branch from 7b65dd4 to 14521d5 Compare September 21, 2026 08:41
@gnodet
gnodet changed the base branch from feature/build-report to master September 21, 2026 08:41

@gnodet-bot gnodet-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.

Console modes + build report review — the three findings from the previous review (PR #12697 / old SHA 7b65dd40) are all addressed in this resubmission. One new concurrency issue found, and one pre-existing concern persists.

Addressed from prior review:

  • MachineExecutionEventLogger mojo key now includes groupId — module collision in multi-module builds fixed
  • BUILD FAILURE header correctly at logger.error() in logResult()
  • currentVisitedProjectCount is now AtomicInteger in MachineExecutionEventLogger

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Console modes re-review (SHA 14521d59) — prior findings #1 (mojoKey collision) and #2 (BUILD FAILURE level in ExecutionEventLogger) are fixed. Three new issues in the newly introduced classes.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Console modes re-review (SHA 67d50688) — all four findings from the prior review are addressed. One nit remains unresolved.

Addressed from prior review:

  • RichBuildEventListener.completedProjects race — fixed: volatile intfinal AtomicInteger with .incrementAndGet() / .get() throughout
  • ✅ Per-module FAILURE lines in ExecutionEventLogger logged at wrong level — fixed: if (buildSummary instanceof BuildFailure) logger.error(...) branch added
  • PlainExecutionEventLogger BUILD FAILURE logged at INFO — fixed: session.getResult().hasExceptions() check added, tests updated
  • PlainExecutionEventLogger.volatile int currentVisitedProjectCount — fixed: volatile dropped (lock provides the memory ordering)
  • ✅ Unknown --console= value silent fallthrough — fixed: warning now logged via context.logger.warn()

One missed fix:

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet force-pushed the feature/console-modes branch from 67d5068 to 3487903 Compare September 21, 2026 13:26

@gnodet-bot gnodet-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.

Re-review after 3487903335 ("fix review: RichBuildEventListener race, logger.error on failures, unknown --console warn, drop volatile")

All four findings from the previous CHANGES_REQUESTED reviews are addressed:

Finding Status
completedProjects++ race in RichBuildEventListener ✅ Fixed — volatile intAtomicInteger, incrementAndGet() used throughout
Per-module FAILURE lines logged at INFO in ExecutionEventLogger.logReactorSummary ✅ Fixed — BuildFailure instanceof check now routes to logger.error()
BUILD FAILURE logged at INFO in PlainExecutionEventLogger.logResult ✅ Fixed — session.getResult().hasExceptions() check now routes to logger.error()
Silent fallthrough for unknown --console= values in MavenInvoker.determineConsoleMode ✅ Fixed — unknown values now emit a warn(); "auto" falls through silently as before
Misleading volatile on currentVisitedProjectCount in PlainExecutionEventLogger ✅ Fixed — volatile dropped

One nit from the prior inline comment (already posted on this SHA) remains open:
RichExecutionEventLogger.java:85private volatile int currentVisitedProjectCount still has the misleading volatile modifier. The field is only ever mutated inside synchronized (this) blocks, and the lock already provides the required happens-before. The volatile is redundant and signals incorrect intent to future readers — same fix as applied to PlainExecutionEventLogger.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Console modes re-review (SHA a39b4c2e) — the remaining nit from the previous review is addressed.

Addressed:

  • volatile removed from currentVisitedProjectCount in RichExecutionEventLogger — correct. The field is only written inside synchronized (this) (line 258: number = ++currentVisitedProjectCount) and assigned single-threadedly at session start (line 147). The JMM happens-before guarantee from the monitor makes volatile redundant and misleading (it implied an unsynchronized read path that does not exist).

All prior findings from all previous reviews are addressed. The PR is ready.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet force-pushed the feature/console-modes branch from a39b4c2 to 0855915 Compare September 21, 2026 16:36

@gnodet-bot gnodet-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.

Console modes re-review (SHA 08559154) — re-reviewing the delta since the prior APPROVE (a39b4c2e).

Delta since last review:

  • ✅ Trailing newline added to 7 files (MachineBuildEventListener.java, LayeredOptions.java, LookupInvoker.java, 4 test files) — trivial formatting fix, no logic change.
  • WorkspaceParentProjectBuilderTest.java (new, 141 lines) — solid test for workspace parent basedir handling. Correctly overrides getWorkspaceReader() (the hook exists on master since #13212). Two scenarios well-covered: parent from project-local repository gets no basedir, parent that is a session project keeps its basedir. Test structure is clean — @TempDir, proper POM helpers, correct use of assertNull/assertEquals. No issues.
  • domtripVersion 1.6.0 → 1.7.0, jlineVersion 4.4.3 → 4.4.5 — both are minor bug-fix releases with no breaking API changes. JLine 4.4.5 includes fixes for FFM SIGQUIT handler, ColorPalette OSC 4 fallback, ReDoS-vulnerable regex, and paginated layout defects. domtrip 1.7.0 is from the maveniverse project (same maintainer). Both bumps are safe.
  • .worktrees/** added to RAT exclusion — correct, avoids false license-header hits on git worktree admin files.

All prior findings from the review chain remain addressed. APPROVED.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Console modes re-review (SHA f6bb3306) — single commit since the prior APPROVE (08559154).

Delta: Restores getWorkspaceReader() hook in AbstractCoreMavenComponentTestCase.

  • getWorkspaceReader() returns null by default — DefaultSessionBuilder.setWorkspaceReader(null) is a plain field assignment, no NPE risk.
  • SessionBuilder.setWorkspaceReader(WorkspaceReader) exists in resolver 2.0.23 — the call site is valid.
  • WorkspaceParentProjectBuilderTest.getWorkspaceReader() override (already in-tree) is now wired into initRepoSession(), which is the whole point of this commit.
  • ✅ Javadoc on the new method is correct and sufficient.

Clean fix. APPROVE.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Console modes re-review (SHA 48f67cdb) — single commit since the prior APPROVE (f6bb3306).

Delta: Restores isProjectPom() helper and its use in resolveProjectSource() in DefaultProjectBuilder.

  • isProjectPom(Path pomFile) — logic is correct. pomFile == null guard handles Artifact.getPath() returning null before resolution. projects == null guard matches the existing pattern at line 371 (defensive for UT environments). filter(Objects::nonNull) is appropriate even though the list is @Nonnull. The use of normalize() is more correct than the existing line 371 equivalent.
  • localProject = isWorkspaceRepo && isProjectPom(pomArtifact.getPath()) — restores the original invariant: only POMs belonging to actual session projects get a basedir. Fixes WorkspaceParentProjectBuilderTest.parentFromProjectLocalRepositoryHasNoBasedir.
  • ✅ No new behavior introduced — pure restoration of code accidentally dropped in the squash commit.

Clean fix, no issues.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

- Add --console=plain/rich/verbose/machine option to select output mode
- plain: compact one-line-per-module output with structured summary
- rich: JLine status bar with live reactor progress and mojo tracking
- verbose: full mojo-level output (Maven 4.0 default behaviour)
- machine: JSON lines stream of lifecycle events for CI/LLM/IDE tooling
- MachineBuildEventListener: structured JSON log/transfer events
- MachineExecutionEventLogger: JSON lifecycle events (build/module/mojo)
- RichBuildEventListener: JLine terminal status bar with parallel progress
- RichExecutionEventLogger: minimal per-module logger for rich mode
- PlainExecutionEventLogger: compact logger for plain mode
- BUILD FAILURE logged at ERROR level; per-module FAILURE lines at ERROR
- Unknown --console values emit a warning and fall back to auto
- mojoKey uses groupId to avoid collision in multi-module builds
- Use AtomicInteger for thread-safe counters (completedProjects, etc.)
- Apply spotless formatting to all modified files
…estCase

The console-modes squash removed the getWorkspaceReader() extension point
that was added by 25be7b8, breaking the compilation of
WorkspaceParentProjectBuilderTest which overrides that method.
…acts from getting a basedir

The console-modes squash accidentally removed the isProjectPom() helper and
the associated check in resolveProjectSource(). This caused any POM served
by the workspace reader (including those from the project-local repository)
to be treated as a local checkout with a basedir, breaking the invariant that
only actual session projects should have a basedir.

Restore the original logic: localProject = isWorkspaceRepo && isProjectPom().

Fixes WorkspaceParentProjectBuilderTest.parentFromProjectLocalRepositoryHasNoBasedir.
…g output

When running on GitHub Actions, CI=true is inherited by child Maven
processes spawned by the IT framework. With the new console-modes feature,
CI detection causes auto mode to select plain, which suppresses mojo-start
banners ([INFO] --- plugin:version:goal @ module) and 'Reactor Summary'
lines that many ITs rely on.

Fix: call removeCIEnvironmentVariables() in AbstractMavenIntegrationTestCase.
newVerifier() so all ITs run in verbose mode by default (the same output
format they were written against). ITs that test CI-specific behaviour
can opt back in with verifier.setEnvironmentVariable("CI", "true").

Also add --console=verbose to MavenITmng8655SettingsParserTest.coreRealmParserReadsBootstrapSettings
which uses ForkedMavenExecutor directly (bypasses the Verifier) and
inherits the CI env from the parent process.

@gnodet-bot gnodet-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.

Console modes re-review (SHA 905812cd) — single commit since the prior APPROVE (48f67cdb).

Delta: Isolates ITs from the CI environment so CI → plain auto-detection does not suppress output that ITs rely on.

  • AbstractMavenIntegrationTestCase.newVerifier()removeCIEnvironmentVariables() called in the base method. Verified against all six CI detectors: GenericCIDetector (CI="false"), GithubCIDetector (GITHUB_ACTIONS=""), TravisCIDetector (TRAVIS=""), CircleCIDetector (CIRCLECI=""), JenkinsCIDetector (WORKSPACE=""), TeamcityCIDetector (TEAMCITY_VERSION="") — every detector is correctly neutralized.
  • MavenITmng8655SettingsParserTest.coreRealmParserReadsBootstrapSettings() — adds --console=verbose to the ForkedMavenExecutor call (which bypasses newVerifier() and thus the base-class fix). Correct: verbose mode uses ExecutionEventLogger which emits the Building bootstrap-settings 0.1 line the assertion checks.
  • ✅ Existing CI-aware ITs: tests that called removeCIEnvironmentVariables() individually (mng4829, mng6240, mng4461) are now redundant but harmless. No regression.

All prior findings remain resolved. Clean delta.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet
gnodet force-pushed the feature/console-modes branch from 905812c to 512629a Compare September 21, 2026 19:25

@gnodet-bot gnodet-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.

Console modes re-review (SHA 512629a5) — re-reviewing the delta since the prior APPROVE (905812cd).

Context: The branch was force-pushed. The new HEAD collapses several prior commits into a different squash. Comparing the PR diff vs master (the only merge-relevant delta), the sole net-new code change is the JUL-level clamping in LookupInvoker.

Items that appeared to regress vs 905812cd but are not merge regressions:

  • jlineVersion 4.4.5 → 4.4.3, domtripVersion 1.7.0 → 1.6.0, .worktrees/** RAT exclude absent — these were squashed out of the PR branch, but all three are already on master via independent commits (ebb415b5, 77944f80, 18806b1a45). They will survive the merge.
  • WorkspaceParentProjectBuilderTest not in PR branch — was added+deleted within the feature branch squash. master has it independently via c3b52889c3. Merge will keep master's version.

New JUL-level clamping in LookupInvoker (configureLogging + activateLogging):

configureLogging (quiet mode): MavenJulHandler.install() sets Level.ALL internally, then setLevel(Level.SEVERE) immediately overrides it. These two calls are synchronous in a single thread — zero event-processing window between them. Level.SEVERE blocks JUL events before they reach publish(), preventing the SLF4J-bootstrap reentrancy flood that Level.ALL would cause while SLF4J is still pre-activated. Correct.

activateLogging: post-activate() JUL root level is set to mirror the SLF4J effective level (quiet→SEVERE, verbose→ALL, default→INFO). The isInstalled() guard prevents double-install. The three-way mapping is correct.

The fix correctly closes the race window described in the comment: between configureLogging and activateLogging, createTerminal() runs and JLine emits JUL events. In quiet mode those events were leaking before this change. Setting Level.SEVERE at source (JUL root) before createTerminal() is the right fix — the isLevelEnabled() check in MavenJulHandler.publish() would not suffice because newly created SLF4J loggers briefly see the default INFO level during the reentrancy window.

IT coverage via MavenITmng4387QuietLoggingTest (on master, already exercises quiet-mode output expectations). The change is narrowly scoped and well-explained. APPROVE.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

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.

2 participants