Skip to content

Support custom Log4j2 configurations - #149

Merged
alexander-yevsyukov merged 10 commits into
masterfrom
custom-log4j2-formatting
Aug 25, 2026
Merged

Support custom Log4j2 configurations#149
alexander-yevsyukov merged 10 commits into
masterfrom
custom-log4j2-formatting

Conversation

@alexander-yevsyukov

@alexander-yevsyukovalexander-yevsyukov commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

The Log4j2 backend threw IllegalStateException ("Unable to format a message for the configuration: ...") from LogEvents.toLog4jLogEvent whenever the current Log4j2 configuration was not DefaultConfiguration — i.e., whenever an application provides its own log4j2.xml. This made the backend unusable in any configured application (reported from delivery-server, which packages log4j2.xml in its fat JAR).

The fix

Following the upstream Flogger approach: with a user-provided configuration, only the literal log message becomes the Log4j2 message. The metadata keeps traveling in the log event's context data map (populated by createContextMap()), where a user-defined layout renders it, e.g., via %X. Under DefaultConfiguration, whose hard-wired layout ignores context data, the metadata is still appended to the message itself in the [CONTEXT ... ] form, as before. Spine's LogData carries no template arguments (the Kotlin lambda API evaluates messages before the backend sees them), so Flogger's BaseMessageFormatter branch reduces to the literal message.

The refactoring

The configuration check now reads the configuration of the context owning the logger that renders the event (logger.context.configuration) instead of the statically looked-up LoggerContext.getContext(false). This makes the branch decision consistent with the rendering context in multi-context deployments (per-webapp or OSGi context selectors) and replaces a per-event stack-walking global lookup with a field read. This is a deliberate, documented divergence from the original Flogger implementation. The conversion functions became internal extensions — LogData.toLog4jEvent(logger) and LogData.toLog4jEvent(logger, error); the previous public functions had no external usages.

Testing

  • New Log4j2CustomConfigSpec runs against a private, started LoggerContext (no global-state mutation): plain message without the [CONTEXT ... ] suffix, metadata via the context data map, the cause via LogEvent.thrown, and a multi-context test — two backends in one JVM, each formatting per the configuration of its own context.
  • Shared test helpers extracted into given/TestLoggers.kt.
  • :log4j2-backend:test: 41 tests green; full build and dokkaGenerate pass.
  • End-to-end smoke check: with the artifacts published to Maven Local, a Java app using WithLogging and the actual delivery-serverlog4j2.xml on the classpath (XmlConfiguration) logs per the pattern with no exception.

Note for consumers

With a custom configuration, %msg now carries only the log message. To render Spine metadata (tags, key-value context), add %X (e.g., %notEmpty{ [%X]}) to the pattern layout.

Additional changes riding along

Besides the backend work, this branch carries routine housekeeping:

  • A config submodule float, which brings the config-distributed updates: buildSrc/** dependency objects, the Gradle wrapper (9.6.1 → 9.7.1), the wrapper-validation workflow, .gitignore, and IDE settings. Per this organization's convention, config-distributed files are reviewed in the config repository, not here.
  • Regenerated dependency/license reports under docs/dependencies/.
  • Unused-import cleanups in build.gradle.kts and SimpleMessageFormatter.kt.
  • The version bump 2.0.0-SNAPSHOT.423.424 per the versioning policy.

🤖 Generated with Claude Code

alexander-yevsyukovand others added 9 commits August 25, 2026 18:40
`toLog4jLogEvent()` no longer throws `IllegalStateException` when the
current configuration is not `DefaultConfiguration`, e.g., when an
application supplies its own `log4j2.xml`.
Following the upstream Flogger approach, with a custom configuration only
the literal log message becomes the Log4j2 message. The metadata is
carried by the context data map of the log event, where a user-defined
layout can render it, e.g., via `%X`. Under `DefaultConfiguration`,
whose hard-wired layout ignores context data, the metadata is still
appended to the message itself.
New `Log4j2CustomConfigSpec` covers the custom-configuration branch.
Test helpers shared by the specs are extracted into `given/TestLoggers.kt`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Log4j2 backend selects the message-formatting strategy by checking
whether the current configuration is `DefaultConfiguration`. This check
now reads the configuration of the context owning the logger which
renders the event, instead of the statically looked-up
`LoggerContext.getContext(false)`.
This makes the decision consistent with the rendering context in
multi-context deployments (per-webapp or OSGi context selectors), and
replaces a stack-walking global lookup on the hot path with a field
read. This is a deliberate divergence from the original Flogger
implementation, documented in the code.
The conversion functions become `internal` extensions on `LogData`:
`LogData.toLog4jEvent(logger)` and `LogData.toLog4jEvent(logger, error)`.
`Log4j2CustomConfigSpec` now runs against a private `LoggerContext`
instead of mutating the global configuration, and gains a test proving
that two backends in the same JVM each format per the configuration of
their own context.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Replace `requireNonNull(value)` followed by `value!!` with Kotlin's
`requireNotNull(value)`, which smart-casts the value.
- Drop a `@Suppress("NAME_SHADOWING")` which no longer suppresses
anything.
Both were flagged as pre-existing nits by the Kotlin review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CopilotAI lite review requested due to automatic review settings August 25, 2026 18:48
@alexander-yevsyukovalexander-yevsyukov moved this to 🏗 In progress in v2.0Aug 25, 2026
@alexander-yevsyukovalexander-yevsyukov moved this from 🏗 In progress to In Review in v2.0Aug 25, 2026

CopilotAI 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 the Log4j2 backend to work correctly when an application provides a non-DefaultConfiguration (e.g., via log4j2.xml), avoiding runtime failures and ensuring metadata is still available via Log4j2 context data. In addition, the PR includes a substantial set of build/dependency-reporting and dependency/version/tooling updates.

Changes:

  • Log4j2 backend: format %msg as the literal message under custom configurations, while keeping metadata in LogEvent context data (and preserving the existing [CONTEXT ...] suffix behavior under DefaultConfiguration).
  • Tests: add coverage for custom Log4j2 configuration and multi-context behavior; extract shared test logger helpers.
  • Build/dependencies/tooling: version bumps, dependency-reporting changes (POM generation), dependency coordinate updates, and Gradle wrapper/workflow churn.

Reviewed changes

Copilot reviewed 41 out of 46 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
version.gradle.ktsBumps the published snapshot version.
logging/src/commonMain/kotlin/io/spine/logging/backend/SimpleMessageFormatter.ktRemoves an unused import (no functional change).
gradle/wrapper/gradle-wrapper.propertiesUpdates the Gradle wrapper distribution URL.
docs/dependencies/pom.xmlUpdates the documented/generated dependency POM (versions/coordinates changed).
buildSrc/src/test/kotlin/io/spine/gradle/report/pom/PomGeneratorIgTest.ktAdds Gradle TestKit-based functional coverage for generatePom.
buildSrc/src/test/kotlin/io/spine/gradle/report/pom/DependencyWriterSpec.ktUpdates dependency writer tests to use resolved-version collection.
buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ResolvedVersions.ktIntroduces per-project resolved-version collection and persistence.
buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.ktWires resolved versions into POM XML writing.
buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.ktMakes generatePom depend on per-project resolved-version collectors.
buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.ktRefactors dependency collection to take per-project resolved versions.
buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.ktAdjusts path constants usage and opts license report task out of build cache.
buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.ktUpdates Testcontainers coordinates/version and adds PostgreSQL module.
buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.ktBumps Validation version; removes some artifacts.
buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.ktUpdates ToolBase version and splits artifacts; deprecates old all-in-one artifact.
buildSrc/src/main/kotlin/io/spine/dependency/local/Time.ktBumps Spine Time version.
buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoTap.ktBumps ProtoTap version.
buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.ktRemoves ModelCompiler dependency definition.
buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.ktBumps Logging dependency version reference.
buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.ktRefactors artifact naming and bumps CoreJvmCompiler versions.
buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.ktBumps CoreJvm version.
buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.ktBumps fallback versions; renames fat CLI artifact; removes CLI API reference.
buildSrc/src/main/kotlin/io/spine/dependency/local/Change.ktBumps Spine Change version.
buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.ktBumps BaseTypes version.
buildSrc/src/main/kotlin/io/spine/dependency/local/Base.ktBumps Spine Base versions.
buildSrc/src/main/kotlin/io/spine/dependency/lib/Roaster.ktRemoves outdated comment about Java 11 compatibility.
buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.ktBumps Palantir Java Format version.
buildSrc/src/main/kotlin/io/spine/dependency/lib/Log4j2.ktBumps Log4j2 version; adds SLF4J 2 bridge coordinate.
buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.ktIntroduces a dedicated Jackson 2.x BOM/dependency set.
buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.ktSwitches Jackson definitions to 3.x (tools.jackson) and updates module declarations.
buildSrc/src/main/kotlin/io/spine/dependency/boms/Boms.ktPoints optional Jackson BOM to Jackson 2.x BOM (via JacksonV2).
buildSrc/src/main/kotlin/BuildExtensions.ktAdds helper to exclude JetBrains annotations from published deps.
buildSrc/build.gradle.ktsClarifies buildSrc Jackson pin rationale; configures TestKit and classpath injection.
build.gradle.ktsRemoves ToolBase forcing and an exclusion entry.
backends/log4j2-backend/src/test/kotlin/io/spine/logging/backend/log4j2/Log4j2LoggerBackendSpec.ktRefactors test logger creation to shared helpers.
backends/log4j2-backend/src/test/kotlin/io/spine/logging/backend/log4j2/Log4j2CustomConfigSpec.ktAdds tests for custom Log4j2 configuration behavior + multi-context check.
backends/log4j2-backend/src/test/kotlin/io/spine/logging/backend/log4j2/given/TestLoggers.ktExtracts shared test logger/appender utilities.
backends/log4j2-backend/src/main/kotlin/io/spine/logging/backend/log4j2/LogEvents.ktImplements configuration-aware message formatting and refactors conversion helpers to LogData extensions.
backends/log4j2-backend/src/main/kotlin/io/spine/logging/backend/log4j2/Log4j2LoggerBackend.ktUpdates backend to use the new LogData.toLog4jEvent(...) conversions.
.idea/kotlinc.xmlRemoves IDE-local Kotlin compiler settings file.
.gitignoreClarifies .idea/kotlinc.xml should remain ignored.
.github/workflows/gradle-wrapper-validation.ymlRemoves Gradle wrapper validation workflow.
.agents/tasks/log4j2-context-binding.mdAdds an internal task note/document for the Log4j2 context-binding change.
.agents/tasks/custom-log4j2-formatting.mdAdds an internal task note/document for custom Log4j2 formatting support.
Files not reviewed (1)
  • .idea/kotlinc.xml: Generated file
Suppressed comments (1)

backends/log4j2-backend/src/main/kotlin/io/spine/logging/backend/log4j2/Log4j2LoggerBackend.kt:51

  • Same as above: avoid logger.get() here; log via the logger API directly to avoid relying on the internal get() contract.
 logger.get().log(badData.toLog4jEvent(logger, error))

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadversion.gradle.kts
@codecov

codecovBot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 68.67%. Comparing base (584faa2) to head (9b5ecbc).

Additional details and impacted files
@@ Coverage Diff @@## master #149 +/- ##
============================================
+ Coverage 67.80% 68.67% +0.87% - Complexity 464 470 +6 
============================================
Files 109 109 Lines 2578 2586 +8 Branches 403 403 ============================================
+ Hits 1748 1776 +28 + Misses 696 675 -21 - Partials 134 135 +1 
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Codecov flagged the error-conversion overload
`LogData.toLog4jEvent(logger, error)` as uncovered patch lines; the
review had also noted the missing `handleError` coverage as
a pre-existing gap. The new tests verify that a malformed log statement
is re-targeted to `WARN` when its level is below `WARNING`, keeps its
level otherwise, carries the error as `thrown`, and describes both
the error and the original message.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexander-yevsyukov
alexander-yevsyukov merged commit 1ca1eee into masterAug 25, 2026
9 checks passed
@alexander-yevsyukov
alexander-yevsyukov deleted the custom-log4j2-formatting branch August 25, 2026 23:13
@github-project-automationgithub-project-automationBot moved this from In Review to ✅ Done in v2.0Aug 25, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

3 participants

@alexander-yevsyukov@armiol