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..f22060265 --- /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(myCustomFactory);
+ * 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/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..e8a7d6a6c
--- /dev/null
+++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/logging/LoggerFactoryTest.java
@@ -0,0 +1,33 @@
+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 getLoggerByNameWorks() {
+ Logger logger = LoggerFactory.getLogger("com.example.Test");
+ assertNotNull(logger);
+ logger.info("getLoggerByNameWorks test message");
+ }
+}
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();
+ }
+
+}