Conversation
35cb224 to
80ecee4
Compare
Range-diff: stack/logging-abstraction (35cb224 -> 80ecee4)
Reproduce locally: |
80ecee4 to
02b6cb5
Compare
Range-diff: stack/logging-abstraction (80ecee4 -> 02b6cb5)
Reproduce locally: |
02b6cb5 to
63c2968
Compare
Range-diff: stack/logging-abstraction (02b6cb5 -> 63c2968)
Reproduce locally: |
63c2968 to
43c742c
Compare
Range-diff: stack/logging-abstraction (63c2968 -> 43c742c)
Reproduce locally: |
43c742c to
a8043ce
Compare
Range-diff: stack/logging-abstraction (43c742c -> a8043ce)
Reproduce locally: |
a8043ce to
c2ceb2a
Compare
c2ceb2a to
c04a380
Compare
c04a380 to
0f8673f
Compare
## 🥞 Stacked PR Use this [link](https://github.com/databricks/databricks-sdk-java/pull/740/files) to review incremental changes. - [**stack/logging-abstraction**](#740) [[Files changed](https://github.com/databricks/databricks-sdk-java/pull/740/files)] - [stack/logging-jul](#741) [[Files changed](https://github.com/databricks/databricks-sdk-java/pull/741/files/5156924f..c04a3808)] - [stack/logging-migration](#742) [[Files changed](https://github.com/databricks/databricks-sdk-java/pull/742/files/c04a3808..d5c03d4f)] --------- ## Summary Introduces a logging abstraction layer (`com.databricks.sdk.core.logging`) that decouples the SDK's internal logging from any specific backend. SLF4J remains the default. This PR contains only the abstraction and the SLF4J backend; the JUL backend and the call-site migration follow in stacked PRs. ## Why The SDK currently imports `org.slf4j.Logger` and `org.slf4j.LoggerFactory` directly in every class that logs. This hard coupling means users who embed the SDK in environments where SLF4J is impractical (e.g. BI tools with constrained classpaths) have no way to switch to an alternative logging backend like `java.util.logging`. We need an indirection layer that lets users swap the logging backend programmatically while keeping SLF4J as the zero-configuration default so that existing users don't have to change anything. The design follows the pattern established by SLF4J itself and [Netty's `InternalLoggerFactory`](https://netty.io/4.1/api/io/netty/util/internal/logging/InternalLoggerFactory.html): an `ILoggerFactory` interface that backends implement, a `LoggerFactory` utility class with static `getLogger` / `setDefault` methods, and a separate `Logger` abstract class that serves as a clean extension point for custom implementations. ## What changed ### Interface changes - **`Logger`** — new abstract class in `com.databricks.sdk.core.logging`. Defines the logging contract: `debug`, `info`, `warn`, `error` (each with plain-string, varargs, and `Supplier<String>` overloads). Users extend this to build custom loggers. - **`ILoggerFactory`** — new interface. Backends implement `createLogger(Class<?>)` and `createLogger(String)` to produce `Logger` instances. Users implement this to provide a fully custom logging backend. - **`LoggerFactory`** — new `final` utility class. Static methods `getLogger(Class<?>)` and `getLogger(String)` return loggers from the current default factory. `setDefault(ILoggerFactory)` overrides the backend — must be called before creating any SDK client. - **`Slf4jLoggerFactory`** — public concrete `ILoggerFactory` implementation with a singleton `INSTANCE`. This is the default. ### Behavioral changes None. SLF4J is the default backend and all logging calls pass through to `org.slf4j.Logger` exactly as before. Existing users see no difference. ### Internal changes - **`Slf4jLogger`** — package-private class that delegates all calls to an `org.slf4j.Logger`. Fully qualifies `org.slf4j.LoggerFactory` references to avoid collision with the SDK's `LoggerFactory`. - All new classes live in `com.databricks.sdk.core.logging`. ## How is this tested? - `LoggerFactoryTest` — verifies the default factory is SLF4J, that `setDefault(null)` is rejected, and that `getLogger(String)` works. - `Slf4jLoggerTest` — verifies `LoggerFactory.getLogger` returns the correct type, exercises all logging methods including varargs and trailing Throwable via a capturing Log4j appender that asserts on message content, level, and attached throwable. - Full test suite passes.
0f8673f to
e281114
Compare
Range-diff: main (0f8673f -> e281114)
Reproduce locally: |
|
If integration tests don't run automatically, an authorized user can run them manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
🥞 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.loggingis 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 publicILoggerFactoryimplementation with a singletonINSTANCE. 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 ajava.util.logging.Logger. Key implementation details:{}placeholders are substituted following the same semantics as SLF4J'sMessageFormatter.arrayFormat— trailing Throwables are unconditionally extracted and attached to theLogRecord, escaped\{}is rendered as literal{}, array arguments useArrays.deepToString, and null format strings return null.log(Level, String, Object[])method that constructs aLogRecordand walks the stack to set the correct source class/method, since JUL's automatic caller inference would otherwise attribute every record toJulLogger.debug→FINE,info→INFO,warn→WARNING,error→SEVERE.LoggerFactoryTest— addedsetDefaultSwitchesToJulandgetLoggerByNameWorksWithJultests.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 compareJulLogger.formatMessageandJulLogger.extractThrowabledirectly againstorg.slf4j.helpers.MessageFormatter.arrayFormatfor 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 viasetDefault.