Skip to content

fix(logging): route Tomcat JULI/catalina logs to console + dotcms.log in container (#36075)#36076

Open
wezell wants to merge 2 commits into
mainfrom
issue-36075-catalina-logs-to-console
Open

fix(logging): route Tomcat JULI/catalina logs to console + dotcms.log in container (#36075)#36076
wezell wants to merge 2 commits into
mainfrom
issue-36075-catalina-logs-to-console

Conversation

@wezell

@wezell wezell commented Jun 9, 2026

Copy link
Copy Markdown
Member

Proposed Changes

Fixes #36075

In the container, only dotCMS application logs (log4j2) reached the container log stream. Tomcat's internal java.util.logging (JULI / "catalina") logs — org.apache.catalina.*, org.apache.coyote.* — went to a separate catalina.<date>.log and did not surface in docker logs / kubectl logs.

This PR bridges raw java.util.logging into log4j2 via log4j-jul's Log4jBridgeHandler, so everything except access logs is unified to console (stdout) + dotcms.log.

What changed

  • bom/logging/pom.xml — add managed log4j-jul dependency (${log4j.version} = 2.23.1).
  • dotCMS/pom.xml — declare log4j-jul (provided) and copy its jar into the Tomcat log4j2/lib folder (already on the JVM classpath via setenv.sh).
  • OVERRIDE/.../tomcat/conf/logging.properties — replace the JULI AsyncFileHandler (catalina.<date>.log) + ConsoleHandler with org.apache.logging.log4j.jul.Log4jBridgeHandler.

Why this approach

  • log4j-appserver's TomcatLogger (already bundled) only routes the org.apache.juli.logging.Log facade. Code logging directly via java.util.logging was unbridged — this adds that bridge.
  • Log4jBridgeHandler is the least-invasive option: it keeps Tomcat's ClassLoaderLogManager and avoids any -Djava.util.logging.manager JVM-arg ordering risk vs catalina.sh.
  • A single shared log4j2 context (BasicAsyncLoggerContextSelector, set in setenv.sh) means anything reaching log4j2 lands in the existing Console (stdout) + dotcms.log appenders.

Access logs unchanged

Tomcat's AccessLogValve writes its own file and bypasses both JULI and the juli facade, so access logs remain a separate file by design.

Testing / Verification

Built the arm64 image and ran it against Postgres; observed all four acceptance criteria:

  • ✅ Tomcat-internal catalina logs now on container stdout (log4j2 console format):
    14:00:58.573  INFO  startup.VersionLoggerListener - Server version name: Apache Tomcat/9.0.118
    14:00:58.667  INFO  http11.Http11Nio2Protocol - Initializing ProtocolHandler ["http-nio2-8080"]
    14:00:58.800  INFO  core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.118]
    
  • ✅ Same catalina logs also present in dotcms.log (81 Tomcat-internal lines).
  • No catalina.<date>.log file produced.
  • ✅ Access log remains separate (dotcms_access.<date>.log).

🤖 Generated with Claude Code

… in container (#36075)

In the container, only dotCMS application logs (log4j2) reached the container
log stream; Tomcat's internal java.util.logging (JULI / catalina) logs went to
a separate catalina.<date>.log and did not surface.

Bridge raw java.util.logging into log4j2 via log4j-jul's Log4jBridgeHandler so
everything except access logs is unified to console (stdout) + dotcms.log:

- bom/logging/pom.xml: add managed log4j-jul dependency.
- dotCMS/pom.xml: declare log4j-jul (provided) and copy its jar into the
  Tomcat log4j2/lib folder (already on the JVM classpath via setenv.sh).
- OVERRIDE tomcat/conf/logging.properties: replace the AsyncFileHandler
  (catalina.<date>.log) and ConsoleHandler with Log4jBridgeHandler.

Access logs (AccessLogValve) bypass JULI and remain a separate file by design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @wezell's task in 1m 55s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze for issues
  • Post review

Clean. Two things worth a second look:

1. propagateLevels = true during early JVM startup

logging.properties is read by Tomcat's ClassLoaderLogManager very early — before the Log4j2 BasicAsyncLoggerContextSelector (set in setenv.sh) is fully warmed up. With propagateLevels = true, Log4jBridgeHandler calls back into Log4j2 on every log call to sync JUL levels. If the async context hasn't initialized yet, the handler falls back to ALL, which is safe, but it means the very first Tomcat startup messages may appear at a different effective level than configured.

Not a correctness bug — the library handles the race gracefully — but if someone ever reports noisy startup logs for a brief window, this is where to look.

2. BOM scope inconsistency

In bom/logging/pom.xml, log4j-appserver (the companion artifact that routes the Juli facade) has scope: runtime, while the new log4j-jul has scope: provided. Both are Tomcat-level jars deployed to log4j2/lib, not the webapp. The provided scope for log4j-jul is more correct. The inconsistency won't cause a runtime problem because dotCMS/pom.xml explicitly overrides both to provided, but any future module that imports the BOM and doesn't override would get log4j-appserver bundled into its WAR unexpectedly.

Worth aligning log4j-appserver to provided in the BOM at some point (separate from this PR, since it's pre-existing).

Not a concern (confirming the PR's claim): Double-logging between log4j-appserver's TomcatLogger and Log4jBridgeHandler is not possible. TomcatLogger routes org.apache.juli.logging.Log facade calls directly to Log4j2, bypassing JUL handlers entirely. The two paths don't overlap.

The jar lives on Tomcat's classpath, not the webapp classpath, so
provided is the correct scope. Using runtime caused double-loading in
modules that inherit the BOM without overriding the scope themselves.

https://claude.ai/code/session_0137tNu61fbsugvh9KxeQLkB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Tomcat/catalina (JULI) logs do not reach container stdout — only dotCMS app logs are visible in container logs

2 participants