Disable Jansi in generated logback-spring.xml to fix console logging after DevTools restart#15694
Disable Jansi in generated logback-spring.xml to fix console logging after DevTools restart#15694jamesfredley wants to merge 8 commits into
Conversation
…after DevTools restart Grails Forge generated logback-spring.xml with <withJansi>true</withJansi> on non-Windows operating systems. Jansi's AnsiConsole.systemInstall() globally replaces System.out/System.err and tracks installation with a static reference counter that lives in the Spring Boot DevTools base classloader, so it survives restarts. Logback's ConsoleAppender calls systemInstall() on start() but never calls systemUninstall() on stop(). After the first DevTools restart the old appender closes the shared Jansi stream while the counter stays above zero, so systemInstall() is a no-op and the new appender writes to a closed, stale stream. Console logging then silently stops, which matches the reported behavior (logging works at startup, dies after the first reload). Spring Boot does not enable Jansi in its own logback defaults; it renders ANSI colors via its own AnsiOutput and the %clr converter, which works without globally replacing System.out. There is therefore no benefit to enabling Jansi, so this removes the OS-based toggle and always generates <withJansi>false</withJansi>, matching the base profile skeleton. Fixes #15663 Assisted-by: claude-code:claude-opus-4.8
There was a problem hiding this comment.
Pull request overview
This PR updates Grails Forge’s generated Logback configuration to disable Jansi consistently, addressing console logging loss after Spring Boot DevTools restarts and aligning Forge output with the base profile skeleton.
Changes:
- Removes OS-dependent Jansi toggling from the Logback feature.
- Hard-codes
<withJansi>false</withJansi>in the generatedlogback-spring.xml. - Adds regression coverage across all application types for the generated Logback config.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/Logback.java |
Removes OS lookup and passes the simplified template arguments. |
grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw |
Emits <withJansi>false</withJansi> directly in generated Logback config. |
grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/logging/LogbackSpec.groovy |
Adds a regression test asserting generated configs disable Jansi. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
jdaugherty
left a comment
There was a problem hiding this comment.
I disagree with this PR. It breaks the color logging. We should only be disabling jansi if devtools is selected as the reloading, we shouldn't remove it by default.
| OperatingSystem operatingSystem = generatorContext.getOperatingSystem(); | ||
| boolean jansi = false; | ||
|
|
||
| if (operatingSystem != OperatingSystem.WINDOWS) { |
There was a problem hiding this comment.
This breaks color logging on other OSs. I don't agree with this solution.
|
The change explicitly sets grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw |
|
@jdaugherty we should look into why Spring Boot deliberately does not enable Jansi in its own logback defaults. This has been a landmine for Grails for multiple versions. Color is nice, but not at the cost that has been paid, and this time this is Mac/Linux too. The PR that updates Jansi for CLI, might improve this situation. No sure. |
Windows is horrible with utf-8 / coloring. It's the only platform that has this issue. For why Spring Boot doesn't use that default, it doesn't have a lot of defaults. That doesn't stop us from having convention over configuration. This should be a requirement of Linux/Mac OS |
Disabling Jansi in the generated logback-spring.xml stops Jansi's global System.out replacement from breaking console logging after a Spring Boot DevTools restart. Spring Boot renders console colors through its own %clr converter / AnsiOutput, independent of Jansi, so disabling Jansi does not remove colors in a real terminal. Under ./gradlew bootRun, System.console() is null, so AnsiOutput's DETECT mode emits no colors regardless of Jansi. Set spring.output.ansi.console-available=true on the generated bootRun task so AnsiOutput makes a per-OS decision in development: colors on Linux/macOS and modern Windows terminals, and disabled on legacy Windows consoles to avoid escape-code noise, with none of Jansi's global side effects. Add a LogbackSpec regression test asserting the generated build.gradle emits the bootRun systemProperty for every application type. Assisted-by: claude-code:claude-4.8-opus
|
The Colors come from Spring Boot's
So we lose nothing by dropping Jansi, and we can keep the Mac/Linux colors - by convention, even under tasks.matching { it.name == 'bootRun' }.configureEach {
systemProperty 'spring.output.ansi.console-available', 'true'
}
|
|
fyi: matching is non-lazy gradle. We should only configure the task. Ideally this would be done the gradle plugin and not in end user apps. |
|
I confirmed jansi only benefits windows and only added color support for older versions. I am inclinded to remove it since @jamesfredley is the requester. Lets fix the color config in the gradle plugin and then we can merge. |
Configure BootRun tasks from the Grails Gradle plugin so Forge-generated build.gradle files no longer inline the console ANSI system property. Assisted-by: opencode:gpt-5.5
|
Updated for the latest review feedback:
Verified with:
|
✅ All tests passed ✅🏷️ Commit: 198eef0 Learn more about TestLens at testlens.app. |
Summary
This PR disables Jansi in Forge-generated
logback-spring.xmlfiles to prevent console logging from dying after a Spring Boot DevTools restart. It preserves development console colors without Jansi by configuringbootRunin the Grails Gradle plugin, rather than generating that configuration into each end-userbuild.gradle.Generated
build.gradlefiles no longer include abootRunANSI snippet; the Grails Gradle plugin now applies thespring.output.ansi.console-available=truesystem property lazily toBootRuntasks.Root cause
AnsiConsole.systemInstall()globally replacesSystem.out/System.errand tracks installation with a static reference counter. That state lives in the Spring Boot DevTools base classloader, so it survives restarts.ConsoleAppendercallssystemInstall()onstart()but never callssystemUninstall()onstop(). After the first DevTools restart, the old appender closes the shared Jansi stream while the counter stays above zero, sosystemInstall()is a no-op and the new appender writes to a closed, stale stream. Console logging then silently stops.AnsiOutputand%clrconverter, which does not require Jansi or globalSystem.outreplacement.Change
<withJansi>false</withJansi>and remove the OS-based Jansi toggle inLogback, matching the base profile skeleton.spring.output.ansi.console-available=trueonBootRuntasks inGrailsGradlePluginwith the existing lazyproject.tasks.withType(BootRun).configureEachpath.tasks.matching { it.name == 'bootRun' }block from Forge'sbuild.gradletemplate.bootRunANSI config, and the Grails Gradle plugin configuresBootRunwith the Spring Boot console availability property.Verification
./gradlew :grails-gradle-plugins:test --tests org.grails.gradle.plugin.core.GrailsGradlePluginToolchainSpec./gradlew :grails-forge-core:test --tests org.grails.forge.feature.logging.LogbackSpecFixes #15663