Skip to content

fix(logging): use one logback-spring.xml and silence logback status from main() - #193

Merged
epugh merged 1 commit into
apache:mainfrom
adityamparikh:fix/idiomatic-logging
Sep 11, 2026
Merged

epugh merged 1 commit into
apache:mainfrom
adityamparikh:fix/idiomatic-logging

Conversation

@adityamparikh

@adityamparikh adityamparikh commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Problem

After #189 the logging setup had three coupled pieces and a test whose only job was to keep them coupled:

Piece Job
logback.xml a NopStatusListener and nothing else, so logback's own pre-Spring init stays off stdout in the native image
logback-spring.xml the real configuration, with the <springProfile> appenders
logging.config=classpath:logback-spring.xml in application.properties steer Boot past logback.xml, which initializeWithConventions() would otherwise stop at, never loading the -spring variant

That is not how Boot expects to be configured. The reference documentation asks for one file, resolved by convention, and reserves logging.config for pointing at an external file. Two classpath files disambiguated by a property is a workaround, and LoggingConfigurationTest existed to stop anyone from "cleaning it up" — which #104 had in fact done in a stray commit that would now break native STDIO.

Change

The only thing logback.xml did was install a status listener before Boot exists. Logback has a first-class knob for exactly that, the logback.statusListenerClass system property, and I verified the ordering in logback 1.5.32 (Boot 3.5.14's BOM): ContextInitializer.autoConfig() runs checkVersions() (the source of the native-only |-WARN), then StatusListenerConfigHelper.installIfAsked() (reads the property), and LogbackServiceProvider calls StatusPrinter.printInCaseOfErrorsOrWarnings() only when StatusUtil.contextHasStatusListener() is false. So the listener does not need to exist before the WARN, only before the print, and the property is read in between.

main()                          sets logback.statusListenerClass (unless already set)
  └─ SpringApplication.<clinit> first LoggerFactory touch
       └─ ContextInitializer     checkVersions() → |-WARN (native only)
                                 installIfAsked() → NopStatusListener   ← property read here
                                 BasicConfigurator (no logback.xml)
       └─ LogbackServiceProvider contextHasStatusListener() == true → nothing printed
  └─ LoggingApplicationListener  logback-spring.xml by convention → <springProfile> appenders
  • Main.main() sets the property as its first statement. An operator's explicit -Dlogback.statusListenerClass=...OnConsoleStatusListener wins, so logback itself can still be debugged.
  • logback.xml deleted.
  • logging.config removed from application.properties. LOGGING_CONFIG in the environment still works as Boot's normal override for an external file.
  • SolrNativeHints no longer registers logback.xml; the logback-spring.xml hint stays as belt-and-braces for the non-AOT path.
  • LoggingConfigurationTest now pins the convention instead of the coupling: no standard-location logback file on the classpath, no logging.config in application.properties, and Main installs a listener that StatusListenerConfigHelper honours. Written first and watched fail on main before the change.
  • AGENTS.md, dev-docs/graalvm-native-image.md, docs/security/stdio.md and the keycloak.md troubleshooting section (which still described the pre-fix(logging): load logback-spring.xml without breaking native stdout #189 "no HTTP logs" state) updated.

logging.pattern.console= in application-stdio.properties is untouched: it is the idiom Spring AI documents for STDIO servers and does not depend on any of the above.

Verification

Check Result
LoggingConfigurationTest on main before the change 2 of 5 fail (logback.xml present, logging.config set), compile error for the missing Main API
./gradlew build (JDK 25) 403 tests, 0 failures, 7 skipped; McpClientStdioIntegrationTest (JVM java -jar over MCP STDIO) 40/40
./gradlew dockerIntegrationTest -Pnative (native STDIO image driven over MCP STDIO) BUILD SUCCESSFUL in 8m 46s; freshly built solr-mcp:1.0.0-SNAPSHOT-native-stdio (GraalVM CE 25.0.2, Paketo): DockerImageMcpClientStdioIntegrationTest 40/40, DockerImageStdioIntegrationTest 4/4

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wh7SJkZhL1uuK7pYc3SLk8

…rom main()

Spring Boot expects a single logging configuration resolved by convention.
Since apache#189 the repo shipped two (logback.xml holding only a NopStatusListener,
logback-spring.xml holding the <springProfile> appenders) plus a
logging.config property whose only purpose was to steer Boot past the first
file, which initializeWithConventions() would otherwise stop at, never loading
the -spring variant. A test existed solely to keep the three coupled.

The early file did one job: install a status listener before Boot exists, so
that logback's own initialization in a native image (where checkVersions()
always raises a |-WARN) does not print its status list to stdout, corrupting
the MCP STDIO stream. Logback's logback.statusListenerClass system property
does exactly that: ContextInitializer.autoConfig() reads it in
installIfAsked() after checkVersions(), and LogbackServiceProvider skips
StatusPrinter.printInCaseOfErrorsOrWarnings() whenever a listener is
installed. Main.main() now sets it as its first statement, unless an operator
already set it on the command line.

- delete logback.xml and the logback.xml native resource hint
- drop logging.config from application.properties; LOGGING_CONFIG in the
  environment still overrides as Boot's normal external-file mechanism
- LoggingConfigurationTest now pins the convention: no standard-location
  logback file on the classpath, no logging.config, and Main installs a
  listener StatusListenerConfigHelper honours
- update AGENTS.md, the native-image dev doc, docs/security/stdio.md and the
  keycloak.md troubleshooting section that still described the pre-apache#189 state

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wh7SJkZhL1uuK7pYc3SLk8
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
@adityamparikh

Copy link
Copy Markdown
Contributor Author

Native verification done: ./gradlew dockerIntegrationTest -Pnative — BUILD SUCCESSFUL in 8m 46s on a freshly built solr-mcp:1.0.0-SNAPSHOT-native-stdio (GraalVM CE 25.0.2). DockerImageMcpClientStdioIntegrationTest 40/40 and DockerImageStdioIntegrationTest 4/4, so the native STDIO stream stays clean with logback.xml gone and the status listener set from main(). PR description updated with the same numbers.

@epugh

epugh commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Fixing main!

@epugh
epugh merged commit b4ffe18 into apache:main Sep 11, 2026
adityamparikh added a commit to adityamparikh/solr-mcp that referenced this pull request Sep 11, 2026
Takes the single-logback-spring.xml convention from apache#193 and carries the
Boot 4 empty-console-pattern fix across it:

- AGENTS.md, docs/security/stdio.md: keep apache#193's wording but describe the
  empty logging.pattern.console as something the stdio profile must not set,
  with the Spring Framework 7 context-pausing rationale.
- LoggingConfigurationTest moved to the top-level package in apache#193; the
  stdioProfileDoesNotExportAnEmptyConsolePattern guard is re-homed there and
  the old config/ copy is dropped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQhKFCmP75K71UV7CiYTqB
Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
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