Skip to content

Disable Jansi in generated logback-spring.xml to fix console logging after DevTools restart#15694

Open
jamesfredley wants to merge 8 commits into
7.0.xfrom
fix/forge-logback-withjansi-devtools
Open

Disable Jansi in generated logback-spring.xml to fix console logging after DevTools restart#15694
jamesfredley wants to merge 8 commits into
7.0.xfrom
fix/forge-logback-withjansi-devtools

Conversation

@jamesfredley

@jamesfredley jamesfredley commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR disables Jansi in Forge-generated logback-spring.xml files to prevent console logging from dying after a Spring Boot DevTools restart. It preserves development console colors without Jansi by configuring bootRun in the Grails Gradle plugin, rather than generating that configuration into each end-user build.gradle.

Generated build.gradle files no longer include a bootRun ANSI snippet; the Grails Gradle plugin now applies the spring.output.ansi.console-available=true system property lazily to BootRun tasks.

Root cause

  • Jansi's AnsiConsole.systemInstall() globally replaces System.out/System.err and tracks installation with a static reference counter. That state 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.
  • Spring Boot renders ANSI colors via its own AnsiOutput and %clr converter, which does not require Jansi or global System.out replacement.

Change

  • Always generate <withJansi>false</withJansi> and remove the OS-based Jansi toggle in Logback, matching the base profile skeleton.
  • Configure spring.output.ansi.console-available=true on BootRun tasks in GrailsGradlePlugin with the existing lazy project.tasks.withType(BootRun).configureEach path.
  • Remove the previous generated tasks.matching { it.name == 'bootRun' } block from Forge's build.gradle template.
  • Add regression coverage that generated Logback config disables Jansi, generated build files do not inline the bootRun ANSI config, and the Grails Gradle plugin configures BootRun with 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.LogbackSpec

Fixes #15663

…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
Copilot AI review requested due to automatic review settings May 29, 2026 04:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 generated logback-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 jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks color logging on other OSs. I don't agree with this solution.

@bito-code-review

Copy link
Copy Markdown

The change explicitly sets <withJansi>false</withJansi> in the logback-spring.xml template, removing the previous logic that conditionally enabled Jansi based on the operating system. This modification is intended to prevent issues with console logging after a Spring Boot DevTools restart, as noted in the added test case in LogbackSpec.groovy.

grails-forge/grails-forge-core/src/main/java/org/grails/forge/feature/logging/template/logback.rocker.raw

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
-        <withJansi>@jansi</withJansi>
+        <withJansi>false</withJansi>

@jamesfredley

Copy link
Copy Markdown
Contributor Author

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

@jdaugherty

Copy link
Copy Markdown
Contributor

@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
@jamesfredley

Copy link
Copy Markdown
Contributor Author

The withJansi=false change is correct and doesn't cost us colors on Mac/Linux - I checked the Spring Boot 4 source to be sure.

Colors come from Spring Boot's %clr converter (AnsiOutput/ColorConverter), which the template already uses via ${CONSOLE_LOG_PATTERN}. That path is completely independent of Jansi:

  • In a real terminal, %clr colors render on Mac/Linux whether withJansi is true or false - the terminal interprets ANSI natively. Jansi only ever mattered for legacy Windows consoles.
  • Under ./gradlew bootRun, System.console() is null, so AnsiOutput's DETECT mode emits no colors regardless of Jansi. So the old withJansi=true gave us no colors under bootRun anyway - it only added the DevTools landmine.

So we lose nothing by dropping Jansi, and we can keep the Mac/Linux colors - by convention, even under bootRun - the proper way. Pushed a follow-up commit that adds this to the generated build.gradle:

tasks.matching { it.name == 'bootRun' }.configureEach {
    systemProperty 'spring.output.ansi.console-available', 'true'
}

spring.output.ansi.console-available=true tells Spring Boot's AnsiOutput to make the per-OS decision at runtime: colors on Linux/macOS and modern Windows terminals, and automatically off on legacy Windows consoles (no raw escape codes). It's scoped to bootRun so it never leaks ANSI into prod/CI logs, and it has none of Jansi's global System.out state - so no DevTools restart breakage on any platform. Net effect: better colors on Mac/Linux than the old Jansi toggle ever delivered, with the Windows landmine gone.

@jdaugherty

Copy link
Copy Markdown
Contributor

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.

@jdaugherty

Copy link
Copy Markdown
Contributor

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
@jamesfredley

Copy link
Copy Markdown
Contributor Author

Updated for the latest review feedback:

  • Removed the generated tasks.matching { it.name == 'bootRun' } block from Forge's build.gradle template.
  • Moved spring.output.ansi.console-available=true into GrailsGradlePlugin using the existing lazy project.tasks.withType(BootRun).configureEach configuration path.
  • Updated LogbackSpec to assert generated apps no longer inline the bootRun ANSI config.
  • Added a Grails Gradle plugin TestKit fixture/spec assertion that BootRun receives the Spring Boot console availability system property from the plugin.

Verified with:

  • ./gradlew :grails-gradle-plugins:test --tests org.grails.gradle.plugin.core.GrailsGradlePluginToolchainSpec
  • ./gradlew :grails-forge-core:test --tests org.grails.forge.feature.logging.LogbackSpec

@jamesfredley jamesfredley requested a review from jdaugherty June 24, 2026 23:56
@testlens-app

testlens-app Bot commented Jun 26, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 198eef0
▶️ Tests: 23594 executed
⚪️ Checks: 33/33 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

<withJansi>true</withJansi> breaks console logging after first reload in Grails 7.1.0

3 participants