diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ApiClient.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ApiClient.java index 6507d1eb6..244b4bba4 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ApiClient.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ApiClient.java @@ -25,8 +25,8 @@ import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; /** * Simplified REST API client with retries, JSON POJO SerDe through Jackson and exception POJO diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java index 7e67ca72e..014c55dc6 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/AzureCliCredentialsProvider.java @@ -7,8 +7,8 @@ import com.databricks.sdk.support.InternalApi; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class AzureCliCredentialsProvider implements CredentialsProvider { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java index 58aaf7655..d7cfa06c2 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java @@ -18,8 +18,8 @@ import java.util.Arrays; import java.util.List; import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class CliTokenSource implements TokenSource { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ConfigLoader.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ConfigLoader.java index ae531ffc0..f1a830c94 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ConfigLoader.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/ConfigLoader.java @@ -13,8 +13,8 @@ import org.apache.commons.configuration2.INIConfiguration; import org.apache.commons.configuration2.SubnodeConfiguration; import org.apache.commons.configuration2.ex.ConfigurationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class ConfigLoader { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java index b312e1a3e..60050c7fc 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java @@ -10,8 +10,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.util.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class DatabricksCliCredentialsProvider implements CredentialsProvider { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java index b2b0d75e4..82bcae379 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksConfig.java @@ -19,8 +19,8 @@ import java.time.Duration; import java.util.*; import org.apache.http.HttpMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; public class DatabricksConfig { private static final Logger LOG = LoggerFactory.getLogger(DatabricksConfig.class); diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java index 99716890f..10fcc2173 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DefaultCredentialsProvider.java @@ -5,8 +5,8 @@ import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; /** * The DefaultCredentialsProvider is the primary authentication handler for the Databricks SDK. It diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleCredentialsCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleCredentialsCredentialsProvider.java index 463d2bab9..92bb4c01e 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleCredentialsCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleCredentialsCredentialsProvider.java @@ -12,8 +12,8 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class GoogleCredentialsCredentialsProvider implements CredentialsProvider { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleIdCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleIdCredentialsProvider.java index 376d691c5..52f9a73a2 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleIdCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/GoogleIdCredentialsProvider.java @@ -10,8 +10,8 @@ import com.google.auth.oauth2.ImpersonatedCredentials; import java.io.IOException; import java.util.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class GoogleIdCredentialsProvider implements CredentialsProvider { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/NotebookNativeCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/NotebookNativeCredentialsProvider.java index ef6571e61..10966f4d7 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/NotebookNativeCredentialsProvider.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/NotebookNativeCredentialsProvider.java @@ -4,8 +4,8 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; /** * A CredentialsProvider that uses the API token from the command context to authenticate. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/commons/CommonsHttpClient.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/commons/CommonsHttpClient.java index b1e9ca61a..9bf8233ad 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/commons/CommonsHttpClient.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/commons/CommonsHttpClient.java @@ -35,8 +35,8 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi public class CommonsHttpClient implements HttpClient { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java index cf0c81853..eb81aba20 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java @@ -6,8 +6,8 @@ import com.databricks.sdk.support.InternalApi; import java.util.HashMap; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.databricks.sdk.core.logging.Logger; +import com.databricks.sdk.core.logging.LoggerFactory; @InternalApi abstract class AbstractErrorMapper { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/JulLogger.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/JulLogger.java new file mode 100644 index 000000000..c31fafc99 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/JulLogger.java @@ -0,0 +1,193 @@ +package com.databricks.sdk.core.logging; + +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** Delegates logging calls to a {@code java.util.logging.Logger}, translating SLF4J conventions. */ +class JulLogger extends Logger { + + private static final String LOGGING_PACKAGE = "com.databricks.sdk.core.logging."; + + private final java.util.logging.Logger delegate; + + private JulLogger(java.util.logging.Logger delegate) { + this.delegate = delegate; + } + + static Logger create(Class> type) { + return create(type.getName()); + } + + static Logger create(String name) { + java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger(name); + return new JulLogger(julLogger); + } + + @Override + public boolean isDebugEnabled() { + return delegate.isLoggable(Level.FINE); + } + + @Override + public void debug(String msg) { + log(Level.FINE, msg, null); + } + + @Override + public void debug(String format, Object... args) { + log(Level.FINE, format, args); + } + + @Override + public void info(String msg) { + log(Level.INFO, msg, null); + } + + @Override + public void info(String format, Object... args) { + log(Level.INFO, format, args); + } + + @Override + public void warn(String msg) { + log(Level.WARNING, msg, null); + } + + @Override + public void warn(String format, Object... args) { + log(Level.WARNING, format, args); + } + + @Override + public void error(String msg) { + log(Level.SEVERE, msg, null); + } + + @Override + public void error(String format, Object... args) { + log(Level.SEVERE, format, args); + } + + private void log(Level level, String format, Object[] args) { + if (!delegate.isLoggable(level)) { + return; + } + Throwable thrown = (args != null) ? extractThrowable(format, args) : null; + String message = (args != null) ? formatMessage(format, args) : format; + LogRecord record = new LogRecord(level, message); + record.setLoggerName(delegate.getName()); + if (thrown != null) { + record.setThrown(thrown); + } + inferCaller(record); + delegate.log(record); + } + + /** + * Sets the source class and method on a {@link LogRecord} by walking the call stack to find the + * first frame outside this logging package. + * + *
JUL normally infers caller information automatically by scanning the stack for the first + * frame after its own {@code java.util.logging.Logger} methods. Because {@code JulLogger} wraps + * the JUL logger, that automatic inference stops at {@code JulLogger} or its helper methods + * instead of reaching the actual SDK class that initiated the log call. Without this correction, + * every log record would be attributed to {@code JulLogger}, making JUL output useless for + * identifying the real call site. + */ + private static void inferCaller(LogRecord record) { + StackTraceElement[] stack = new Throwable().getStackTrace(); + for (StackTraceElement frame : stack) { + if (!frame.getClassName().startsWith(LOGGING_PACKAGE)) { + record.setSourceClassName(frame.getClassName()); + record.setSourceMethodName(frame.getMethodName()); + return; + } + } + } + + /** + * Replaces SLF4J-style {@code {}} placeholders with argument values, matching the semantics of + * SLF4J's {@code MessageFormatter.arrayFormat}: + * + *
Use this when SLF4J is not desirable: + * + *
{@code
+ * LoggerFactory.setDefault(JulLoggerFactory.INSTANCE);
+ * }
+ */
+public class JulLoggerFactory extends LoggerFactory {
+
+ public static final JulLoggerFactory INSTANCE = new JulLoggerFactory();
+
+ @Override
+ protected Logger newInstance(Class> type) {
+ return JulLogger.create(type);
+ }
+
+ @Override
+ protected Logger newInstance(String name) {
+ return JulLogger.create(name);
+ }
+}
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Logger.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Logger.java
new file mode 100644
index 000000000..6f41da426
--- /dev/null
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Logger.java
@@ -0,0 +1,28 @@
+package com.databricks.sdk.core.logging;
+
+/**
+ * Logging contract used throughout the SDK.
+ *
+ * Extend this class to provide a custom logging implementation, then register it via a custom + * {@link LoggerFactory} subclass and {@link LoggerFactory#setDefault}. + */ +public abstract class Logger { + + public abstract boolean isDebugEnabled(); + + public abstract void debug(String msg); + + public abstract void debug(String format, Object... args); + + public abstract void info(String msg); + + public abstract void info(String format, Object... args); + + public abstract void warn(String msg); + + public abstract void warn(String format, Object... args); + + public abstract void error(String msg); + + public abstract void error(String format, Object... args); +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/LoggerFactory.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/LoggerFactory.java new file mode 100644 index 000000000..c0d000e6f --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/LoggerFactory.java @@ -0,0 +1,59 @@ +package com.databricks.sdk.core.logging; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Creates and configures {@link Logger} instances for the SDK. + * + *
By default, logging goes through SLF4J. Users can override the backend programmatically + * before creating any SDK client: + * + *
{@code
+ * LoggerFactory.setDefault(JulLoggerFactory.INSTANCE);
+ * WorkspaceClient ws = new WorkspaceClient();
+ * }
+ *
+ * Extend this class to provide a fully custom logging backend.
+ */
+public abstract class LoggerFactory {
+
+ private static final AtomicReference Must be called before creating any SDK client or calling {@link #getLogger}. Loggers
+ * already obtained will not be affected by subsequent calls.
+ */
+ public static void setDefault(LoggerFactory factory) {
+ if (factory == null) {
+ throw new IllegalArgumentException("LoggerFactory must not be null");
+ }
+ defaultFactory.set(factory);
+ }
+
+ static LoggerFactory getDefault() {
+ LoggerFactory f = defaultFactory.get();
+ if (f != null) {
+ return f;
+ }
+ defaultFactory.compareAndSet(null, Slf4jLoggerFactory.INSTANCE);
+ return defaultFactory.get();
+ }
+
+ /** Creates a new logger for the given class. */
+ protected abstract Logger newInstance(Class> type);
+
+ /** Creates a new logger with the given name. */
+ protected abstract Logger newInstance(String name);
+}
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLogger.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLogger.java
new file mode 100644
index 000000000..267702963
--- /dev/null
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLogger.java
@@ -0,0 +1,64 @@
+package com.databricks.sdk.core.logging;
+
+/** Delegates all logging calls to an SLF4J {@code Logger}. */
+class Slf4jLogger extends Logger {
+
+ private final org.slf4j.Logger delegate;
+
+ private Slf4jLogger(org.slf4j.Logger delegate) {
+ this.delegate = delegate;
+ }
+
+ static Logger create(Class> type) {
+ return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(type));
+ }
+
+ static Logger create(String name) {
+ return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name));
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return delegate.isDebugEnabled();
+ }
+
+ @Override
+ public void debug(String msg) {
+ delegate.debug(msg);
+ }
+
+ @Override
+ public void debug(String format, Object... args) {
+ delegate.debug(format, args);
+ }
+
+ @Override
+ public void info(String msg) {
+ delegate.info(msg);
+ }
+
+ @Override
+ public void info(String format, Object... args) {
+ delegate.info(format, args);
+ }
+
+ @Override
+ public void warn(String msg) {
+ delegate.warn(msg);
+ }
+
+ @Override
+ public void warn(String format, Object... args) {
+ delegate.warn(format, args);
+ }
+
+ @Override
+ public void error(String msg) {
+ delegate.error(msg);
+ }
+
+ @Override
+ public void error(String format, Object... args) {
+ delegate.error(format, args);
+ }
+}
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLoggerFactory.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLoggerFactory.java
new file mode 100644
index 000000000..c34687b02
--- /dev/null
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/logging/Slf4jLoggerFactory.java
@@ -0,0 +1,17 @@
+package com.databricks.sdk.core.logging;
+
+/** A {@link LoggerFactory} backed by SLF4J. This is the default. */
+public class Slf4jLoggerFactory extends LoggerFactory {
+
+ public static final Slf4jLoggerFactory INSTANCE = new Slf4jLoggerFactory();
+
+ @Override
+ protected Logger newInstance(Class> type) {
+ return Slf4jLogger.create(type);
+ }
+
+ @Override
+ protected Logger newInstance(String name) {
+ return Slf4jLogger.create(name);
+ }
+}
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/AzureServicePrincipalCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/AzureServicePrincipalCredentialsProvider.java
index eca52808d..a5820239a 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/AzureServicePrincipalCredentialsProvider.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/AzureServicePrincipalCredentialsProvider.java
@@ -6,8 +6,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* Adds refreshed Azure Active Directory (AAD) Service Principal OAuth tokens to every request,
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/CachedTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/CachedTokenSource.java
index 6f3d63ec5..dae0d7351 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/CachedTokenSource.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/CachedTokenSource.java
@@ -6,8 +6,8 @@
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* An OAuth TokenSource which can be refreshed.
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Consent.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Consent.java
index 19619d127..96e11c865 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Consent.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Consent.java
@@ -21,8 +21,8 @@
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* Consent provides a mechanism to retrieve an authorization code and exchange it for an OAuth token
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java
index 026197e12..0a8ea2598 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/DatabricksOAuthTokenSource.java
@@ -9,8 +9,8 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* Implementation of TokenSource that handles OAuth token exchange for Databricks authentication.
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java
index afc40024e..d80be284d 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/EndpointTokenSource.java
@@ -7,8 +7,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* Represents a token source that exchanges a control plane token for an endpoint-specific dataplane
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java
index 1d8b48dac..884fb91f4 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/ExternalBrowserCredentialsProvider.java
@@ -12,8 +12,8 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* A {@code CredentialsProvider} which implements the Authorization Code + PKCE flow by opening a
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/FileTokenCache.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/FileTokenCache.java
index b1805c131..b2ae78d7f 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/FileTokenCache.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/FileTokenCache.java
@@ -8,8 +8,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/** A TokenCache implementation that stores tokens as plain files. */
@InternalApi
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentials.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentials.java
index 34a1878d1..482b671f9 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentials.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentials.java
@@ -6,8 +6,8 @@
import com.databricks.sdk.support.InternalApi;
import java.io.Serializable;
import java.util.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* An implementation of RefreshableTokenSource implementing the refresh_token OAuth grant type.
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentialsTokenSource.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentialsTokenSource.java
index 3c42e0490..7b42b441a 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentialsTokenSource.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/SessionCredentialsTokenSource.java
@@ -6,8 +6,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* TokenSource that handles OAuth token refresh for SessionCredentials.
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenEndpointClient.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenEndpointClient.java
index 8931ec162..90b6e96b8 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenEndpointClient.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/TokenEndpointClient.java
@@ -14,8 +14,8 @@
import java.util.Map;
import java.util.Objects;
import org.apache.http.HttpHeaders;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* Client for interacting with an OAuth token endpoint.
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/retry/NonIdempotentRequestRetryStrategy.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/retry/NonIdempotentRequestRetryStrategy.java
index db4740700..a63e1eb17 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/retry/NonIdempotentRequestRetryStrategy.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/retry/NonIdempotentRequestRetryStrategy.java
@@ -6,8 +6,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* This class is used to determine if a non-idempotent request should be retried. We essentially
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/OSUtils.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/OSUtils.java
index 66da25cfa..2d8ccf8f2 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/OSUtils.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/OSUtils.java
@@ -7,8 +7,8 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
/**
* OSUtils is an interface that provides utility methods for determining the current operating
diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/mixin/ClustersExt.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/mixin/ClustersExt.java
index dc86da730..68ce33d4d 100644
--- a/databricks-sdk-java/src/main/java/com/databricks/sdk/mixin/ClustersExt.java
+++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/mixin/ClustersExt.java
@@ -13,8 +13,8 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.databricks.sdk.core.logging.Logger;
+import com.databricks.sdk.core.logging.LoggerFactory;
public class ClustersExt extends ClustersAPI {
private static final Logger LOG = LoggerFactory.getLogger(ClustersExt.class);
diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/JulLoggerTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/JulLoggerTest.java
new file mode 100644
index 000000000..ddaba64ad
--- /dev/null
+++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/JulLoggerTest.java
@@ -0,0 +1,85 @@
+package com.databricks.sdk.core.logging;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+public class JulLoggerTest {
+
+ @Test
+ void formatMessageNoPlaceholders() {
+ assertEquals("hello world", JulLogger.formatMessage("hello world", new Object[] {}));
+ }
+
+ @Test
+ void formatMessageNullArgs() {
+ assertEquals("hello", JulLogger.formatMessage("hello", (Object[]) null));
+ }
+
+ @Test
+ void formatMessageSinglePlaceholder() {
+ assertEquals("hello world", JulLogger.formatMessage("hello {}", new Object[] {"world"}));
+ }
+
+ @Test
+ void formatMessageMultiplePlaceholders() {
+ assertEquals("a=1, b=2", JulLogger.formatMessage("a={}, b={}", new Object[] {"1", "2"}));
+ }
+
+ @Test
+ void formatMessageTrailingThrowableExcluded() {
+ Exception ex = new RuntimeException("boom");
+ String result = JulLogger.formatMessage("Failed at {}: {}", new Object[] {"host", "msg", ex});
+ assertEquals("Failed at host: msg", result);
+ }
+
+ @Test
+ void formatMessageThrowableIsAlwaysExcluded() {
+ Exception ex = new RuntimeException("boom");
+ String result = JulLogger.formatMessage("Error: {}", new Object[] {ex});
+ assertEquals("Error: {}", result);
+ }
+
+ @Test
+ void extractThrowableWhenTrailing() {
+ Exception ex = new RuntimeException("boom");
+ Throwable result = JulLogger.extractThrowable("Failed: {}", new Object[] {"msg", ex});
+ assertSame(ex, result);
+ }
+
+ @Test
+ void extractThrowableNullWhenNotTrailing() {
+ assertNull(JulLogger.extractThrowable("a={}, b={}", new Object[] {"1", "2"}));
+ }
+
+ @Test
+ void extractThrowableAlwaysWhenLastArgIsThrowable() {
+ Exception ex = new RuntimeException("boom");
+ assertSame(ex, JulLogger.extractThrowable("Error: {}", new Object[] {ex}));
+ }
+
+ @Test
+ void extractThrowableNullArgs() {
+ assertNull(JulLogger.extractThrowable("msg", (Object[]) null));
+ }
+
+ @Test
+ void extractThrowableEmptyArgs() {
+ assertNull(JulLogger.extractThrowable("msg", new Object[] {}));
+ }
+
+ @Test
+ void julLoggerLevelMapping() {
+ Logger logger = JulLogger.create(JulLoggerTest.class);
+ assertNotNull(logger);
+ logger.debug("debug msg");
+ logger.debug("debug {} {}", "a", "b");
+ logger.info("info msg");
+ logger.info("info {}", "val");
+ logger.warn("warn msg");
+ logger.warn("warn {}", "val");
+ logger.error("error msg");
+ logger.error("error {}", "val");
+ logger.error("error {} failed", "op", new RuntimeException("cause"));
+ }
+}
diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggerFactoryTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggerFactoryTest.java
new file mode 100644
index 000000000..652c26d7e
--- /dev/null
+++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggerFactoryTest.java
@@ -0,0 +1,49 @@
+package com.databricks.sdk.core.logging;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+public class LoggerFactoryTest {
+
+ @AfterEach
+ void resetFactory() {
+ LoggerFactory.setDefault(Slf4jLoggerFactory.INSTANCE);
+ }
+
+ @Test
+ void defaultFactoryIsSLF4J() {
+ Logger logger = LoggerFactory.getLogger(LoggerFactoryTest.class);
+ assertNotNull(logger);
+ logger.info("LoggerFactory defaultFactoryIsSLF4J test message");
+ }
+
+ @Test
+ void setDefaultRejectsNull() {
+ assertThrows(IllegalArgumentException.class, () -> LoggerFactory.setDefault(null));
+ }
+
+ @Test
+ void setDefaultSwitchesToJul() {
+ LoggerFactory.setDefault(JulLoggerFactory.INSTANCE);
+ Logger logger = LoggerFactory.getLogger(LoggerFactoryTest.class);
+ assertNotNull(logger);
+ logger.info("setDefaultSwitchesToJul test message via JUL");
+ }
+
+ @Test
+ void getLoggerByNameWorks() {
+ Logger logger = LoggerFactory.getLogger("com.example.Test");
+ assertNotNull(logger);
+ logger.info("getLoggerByNameWorks test message");
+ }
+
+ @Test
+ void getLoggerByNameWorksWithJul() {
+ LoggerFactory.setDefault(JulLoggerFactory.INSTANCE);
+ Logger logger = LoggerFactory.getLogger("com.example.Test");
+ assertNotNull(logger);
+ logger.info("getLoggerByNameWorksWithJul test message");
+ }
+}
diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggingParityTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggingParityTest.java
new file mode 100644
index 000000000..495e3d2a6
--- /dev/null
+++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggingParityTest.java
@@ -0,0 +1,88 @@
+package com.databricks.sdk.core.logging;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.slf4j.helpers.MessageFormatter;
+import org.slf4j.helpers.FormattingTuple;
+
+/**
+ * Verifies that JulLogger's placeholder formatting and Throwable extraction produce the same
+ * results as SLF4J's {@link MessageFormatter#arrayFormat}, so the two backends behave identically
+ * for any given Logger call.
+ */
+public class LoggingParityTest {
+
+ @Test
+ void singleThrowableArgIsExtractedNotFormatted() {
+ Exception ex = new RuntimeException("boom");
+ Object[] args = {ex};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("Error: {}", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("Error: {}", args));
+ assertEquals(slf4j.getThrowable(), JulLogger.extractThrowable("Error: {}", args));
+ }
+
+ @Test
+ void trailingThrowableBeyondPlaceholders() {
+ Exception ex = new RuntimeException("boom");
+ Object[] args = {"op", ex};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("Error: {} failed", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("Error: {} failed", args));
+ assertEquals(slf4j.getThrowable(), JulLogger.extractThrowable("Error: {} failed", args));
+ }
+
+ @Test
+ void noPlaceholdersTrailingThrowable() {
+ Exception ex = new RuntimeException("boom");
+ Object[] args = {ex};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("Something broke", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("Something broke", args));
+ assertEquals(slf4j.getThrowable(), JulLogger.extractThrowable("Something broke", args));
+ }
+
+ @Test
+ void nonThrowableArgsNoExtraction() {
+ Object[] args = {"a", "b"};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("{} {}", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("{} {}", args));
+ assertEquals(slf4j.getThrowable(), JulLogger.extractThrowable("{} {}", args));
+ }
+
+ @Test
+ void multipleArgsWithTrailingThrowable() {
+ Exception ex = new RuntimeException("boom");
+ Object[] args = {"host", 8080, ex};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("Connect to {}:{}", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("Connect to {}:{}", args));
+ assertEquals(slf4j.getThrowable(), JulLogger.extractThrowable("Connect to {}:{}", args));
+ }
+
+ @Test
+ void arrayArgumentRenderedLikeSlf4j() {
+ Object[] args = {new String[] {"a", "b"}};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("arr {}", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("arr {}", args));
+ }
+
+ @Test
+ void escapedPlaceholderRenderedLikeSlf4j() {
+ Object[] args = {"x"};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat("escaped \\{} {}", args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage("escaped \\{} {}", args));
+ }
+
+ @Test
+ void nullFormatRenderedLikeSlf4j() {
+ Object[] args = {"x"};
+ FormattingTuple slf4j = MessageFormatter.arrayFormat(null, args);
+
+ assertEquals(slf4j.getMessage(), JulLogger.formatMessage(null, args));
+ }
+}
diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/Slf4jLoggerTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/Slf4jLoggerTest.java
new file mode 100644
index 000000000..817e583dd
--- /dev/null
+++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/Slf4jLoggerTest.java
@@ -0,0 +1,31 @@
+package com.databricks.sdk.core.logging;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+public class Slf4jLoggerTest {
+
+ @Test
+ void createReturnsSlf4jLogger() {
+ Logger logger = Slf4jLogger.create(Slf4jLoggerTest.class);
+ assertNotNull(logger);
+ assertTrue(logger instanceof Slf4jLogger);
+ }
+
+ @Test
+ void slf4jLoggerOperations() {
+ Logger logger = Slf4jLogger.create(Slf4jLoggerTest.class);
+ logger.debug("debug");
+ logger.debug("debug {}", "arg");
+ logger.info("info");
+ logger.info("info {}", "arg");
+ logger.warn("warn");
+ logger.warn("warn {}", "arg");
+ logger.error("error");
+ logger.error("error {}", "arg");
+ logger.error("error {} failed", "op", new RuntimeException("cause"));
+ logger.isDebugEnabled();
+ }
+
+}