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 defaultFactory = new AtomicReference<>(); + + /** Returns a logger for the given class, using the current default factory. */ + public static Logger getLogger(Class type) { + return getDefault().newInstance(type); + } + + /** Returns a logger with the given name, using the current default factory. */ + public static Logger getLogger(String name) { + return getDefault().newInstance(name); + } + + /** + * Overrides the logging backend used by the SDK. + * + *

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(); + } + +}