Skip to content

Add java.util.logging backend#741

Draft
mihaimitrea-db wants to merge 2 commits intomainfrom
mihaimitrea-db/stack/logging-jul
Draft

Add java.util.logging backend#741
mihaimitrea-db wants to merge 2 commits intomainfrom
mihaimitrea-db/stack/logging-jul

Conversation

@mihaimitrea-db
Copy link
Contributor

@mihaimitrea-db mihaimitrea-db commented Mar 26, 2026

🥞 Stacked PR

Use this link to review incremental changes.


Summary

Adds a java.util.logging (JUL) backend for the logging abstraction introduced in PR #740. Users who cannot or prefer not to use SLF4J can switch to JUL with a single line before creating any SDK client.

Why

SLF4J is the right default for most users, but some environments (BI tools, embedded runtimes, minimal deployments) either don't ship an SLF4J binding or make it difficult to configure one. In those cases, the JDK's built-in java.util.logging is the only logging framework guaranteed to be available.

Without a JUL backend, users in these environments would get silent NOP logging (if SLF4J has no binding) or would have to shim their own adapter. Providing a first-party JUL backend that they can activate with LoggerFactory.setDefault(JulLoggerFactory.INSTANCE) removes that friction.

What changed

Interface changes

  • JulLoggerFactory — new public concrete LoggerFactory subclass with a singleton INSTANCE. Activating JUL is one line: LoggerFactory.setDefault(JulLoggerFactory.INSTANCE).

Behavioral changes

None. The default backend is still SLF4J. JUL is only used when explicitly opted into.

Internal changes

  • JulLogger — package-private class that delegates to a java.util.logging.Logger. Key implementation details:
    • SLF4J-parity formatting: {} placeholders are substituted following the same semantics as SLF4J's MessageFormatter.arrayFormat — trailing Throwables are unconditionally extracted and attached to the LogRecord, escaped \{} is rendered as literal {}, array arguments use Arrays.deepToString, and null format strings return null.
    • Caller inference: All log calls go through a single log(Level, String, Object[]) method that constructs a LogRecord and walks the stack to set the correct source class/method, since JUL's automatic caller inference would otherwise attribute every record to JulLogger.
    • Level mapping: debugFINE, infoINFO, warnWARNING, errorSEVERE.
  • LoggerFactory Javadoc — updated the usage example to reference JulLoggerFactory.
  • LoggerFactoryTest — added setDefaultSwitchesToJul and getLoggerByNameWorksWithJul tests.

How is this tested?

  • JulLoggerTest — 12 tests covering placeholder formatting, trailing Throwable extraction, null/empty args, and end-to-end level mapping through all log methods.
  • LoggingParityTest — 8 tests that compare JulLogger.formatMessage and JulLogger.extractThrowable directly against org.slf4j.helpers.MessageFormatter.arrayFormat for the same inputs, covering: single Throwable arg, trailing Throwable beyond placeholders, no-placeholder Throwable, non-Throwable args, multi-arg with Throwable, array rendering, escaped placeholders, and null format strings.
  • LoggerFactoryTest — 2 additional tests for JUL factory switching via setDefault.
  • Full test suite (1140 tests) passes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant