From 1029297d86b1ba90f26a2e5c8e19fde1edb70d54 Mon Sep 17 00:00:00 2001 From: James Fredley Date: Fri, 29 May 2026 00:05:29 -0400 Subject: [PATCH] Disable Jansi in generated logback-spring.xml to fix console logging after DevTools restart Grails Forge generated logback-spring.xml with true 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 false, matching the base profile skeleton. Fixes #15663 Assisted-by: claude-code:claude-opus-4.8 --- .../org/grails/forge/feature/logging/Logback.java | 10 +--------- .../feature/logging/template/logback.rocker.raw | 4 ++-- .../forge/feature/logging/LogbackSpec.groovy | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/Logback.java b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/Logback.java index 23e340a951d..6c5c273bd18 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/Logback.java +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/Logback.java @@ -20,7 +20,6 @@ import jakarta.inject.Singleton; import org.grails.forge.application.ApplicationType; -import org.grails.forge.application.OperatingSystem; import org.grails.forge.application.generator.GeneratorContext; import org.grails.forge.build.dependencies.Dependency; import org.grails.forge.feature.DefaultFeature; @@ -61,17 +60,10 @@ public boolean shouldApply(ApplicationType applicationType, Options options, Set @Override public void apply(GeneratorContext generatorContext) { - OperatingSystem operatingSystem = generatorContext.getOperatingSystem(); - boolean jansi = false; - - if (operatingSystem != OperatingSystem.WINDOWS) { - jansi = true; - } - String projectName = generatorContext.getProject().getName(); String packageName = generatorContext.getProject().getPackageName(); - generatorContext.addTemplate("loggingConfig", new RockerTemplate("grails-app/conf/logback-spring.xml", logback.template(projectName, packageName, jansi))); + generatorContext.addTemplate("loggingConfig", new RockerTemplate("grails-app/conf/logback-spring.xml", logback.template(projectName, packageName))); generatorContext.addDependency(Dependency.builder() .groupId("org.apache.grails") .artifactId("grails-logging") diff --git a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw index 14243f809c8..62413c310cd 100644 --- a/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw +++ b/grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw @@ -17,14 +17,14 @@ specific language governing permissions and limitations under the License. *@ -@args (String projectName, String packageName, boolean jansi) +@args (String projectName, String packageName) - @jansi + false ${CONSOLE_LOG_THRESHOLD} diff --git a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/logging/LogbackSpec.groovy b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/logging/LogbackSpec.groovy index 3df3a2acc2e..4bb8d0a5bc1 100644 --- a/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/logging/LogbackSpec.groovy +++ b/grails-forge/grails-forge-core/src/test/groovy/org/grails/forge/feature/logging/LogbackSpec.groovy @@ -52,4 +52,18 @@ class LogbackSpec extends ApplicationContextSpec implements CommandOutputFixture where: applicationType << ApplicationType.values().toList() } + + @Unroll + void "test logback-spring.xml disables Jansi for #applicationType application"() { + when: + def output = generate(applicationType, new Options(DevelopmentReloading.DEVTOOLS)) + + then: "Jansi is disabled so its global System.out replacement does not break console logging after a Spring Boot DevTools restart (issue #15663)" + def logback = output.get("grails-app/conf/logback-spring.xml") + logback.contains("false") + !logback.contains("true") + + where: + applicationType << ApplicationType.values().toList() + } }