diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/Logger.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/Logger.java
index 5fe3ecd8..f93f3284 100644
--- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/Logger.java
+++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/Logger.java
@@ -198,7 +198,36 @@ interface N4Factory {
@NonNull
String make(A arg1, B arg2, C arg3, D arg4);
}
+
+ /**
+ * Returns the name of this logger.
+ *
+ * @return the logger name
+ * @since 10.0.0
+ */
+ @NonNull String name();
+ /**
+ * Prints a trace message.
+ *
+ * @param message the message to print
+ * @since 10.0.0
+ */
+ default void trace(@NonNull String message) {
+ print(LoggerLevel.TRACE, message);
+ }
+
+ /**
+ * Prints a trace message with an exception.
+ *
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ default void trace(@NonNull String message, @NonNull Throwable exception) {
+ print(LoggerLevel.TRACE, message, exception);
+ }
+
/**
* Prints a debug message.
*
@@ -209,6 +238,17 @@ default void debug(@NonNull String message) {
print(LoggerLevel.DEBUG, message);
}
+ /**
+ * Prints a debug message with an exception.
+ *
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ default void debug(@NonNull String message, @NonNull Throwable exception) {
+ print(LoggerLevel.DEBUG, message, exception);
+ }
+
default void debug(int arg1, @NonNull IntFactory factory) {
print(LoggerLevel.DEBUG, arg1, factory);
}
@@ -261,6 +301,21 @@ default void debug(A arg1, int arg2, int arg3, @NonNull N1Int2Factory fac
default void error(@NonNull String message) {
print(LoggerLevel.ERROR, message);
}
+
+ default void error(@NonNull Throwable exception) {
+ print(LoggerLevel.ERROR, exception);
+ }
+
+ /**
+ * Prints an error message with an exception.
+ *
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ default void error(@NonNull String message, @NonNull Throwable exception) {
+ print(LoggerLevel.ERROR, message, exception);
+ }
default void error(A arg1, @NonNull N1Factory factory) {
print(LoggerLevel.ERROR, arg1, factory);
@@ -310,14 +365,21 @@ default void error(A arg1, B arg2, C arg3, D arg4, @NonNull N4Facto
print(LoggerLevel.ERROR, arg1, arg2, arg3, arg4, factory);
}
- default void error(@NonNull Throwable exception) {
- print(LoggerLevel.ERROR, exception);
- }
-
default void info(@NonNull String message) {
print(LoggerLevel.INFO, message);
}
+ /**
+ * Prints an info message with an exception.
+ *
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ default void info(@NonNull String message, @NonNull Throwable exception) {
+ print(LoggerLevel.INFO, message, exception);
+ }
+
default void info(int arg1, @NonNull IntFactory factory) {
print(LoggerLevel.INFO, arg1, factory);
}
@@ -399,6 +461,17 @@ default void warn(@NonNull String message) {
print(LoggerLevel.WARNING, message);
}
+ /**
+ * Prints a warning message with an exception.
+ *
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ default void warn(@NonNull String message, @NonNull Throwable exception) {
+ print(LoggerLevel.WARNING, message, exception);
+ }
+
@Deprecated(forRemoval = true)
default void warning(@NonNull String message) {
print(LoggerLevel.WARNING, message);
@@ -582,6 +655,16 @@ default void warning(
void print(@NonNull LoggerLevel level, @NonNull Throwable exception);
+ /**
+ * Prints a message with an exception at the specified level.
+ *
+ * @param level the logger level
+ * @param message the message to print
+ * @param exception the exception to print
+ * @since 10.0.0
+ */
+ void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception);
+
default void print(@NonNull LoggerLevel level, A arg1, @NonNull N1Factory factory) {
if (enabled(level)) {
print(level, factory.make(arg1));
diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerLevel.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerLevel.java
index 71863c28..22c94bd9 100644
--- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerLevel.java
+++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerLevel.java
@@ -16,8 +16,9 @@
@Accessors(fluent = true)
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public enum LoggerLevel {
- INFO("INFO", " ", true, true),
+ TRACE("TRACE", " ", false, false),
DEBUG("DEBUG", " ", false, false),
+ INFO("INFO", " ", true, true),
WARNING("WARN", " ", true, true),
ERROR("ERROR", " ", true, true);
diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLogger.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLogger.java
index 3e50da07..d6e51582 100644
--- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLogger.java
+++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLogger.java
@@ -4,10 +4,18 @@
import javasabr.rlib.logger.api.LoggerLevel;
public final class NullLogger implements Logger {
+
+ @Override
+ public String name() {
+ return "null";
+ }
@Override
public void print(LoggerLevel level, String message) {}
@Override
public void print(LoggerLevel level, Throwable exception) {}
+
+ @Override
+ public void print(LoggerLevel level, String message, Throwable exception) {}
}
diff --git a/rlib-logger-api/src/test/java/javasabr/rlib/logger/api/LoggerTest.java b/rlib-logger-api/src/test/java/javasabr/rlib/logger/api/LoggerTest.java
index 044e4ba4..74d72046 100644
--- a/rlib-logger-api/src/test/java/javasabr/rlib/logger/api/LoggerTest.java
+++ b/rlib-logger-api/src/test/java/javasabr/rlib/logger/api/LoggerTest.java
@@ -10,6 +10,49 @@
public class LoggerTest {
+ @Test
+ @DisplayName("should print trace message and trace message with exception")
+ void shouldPrintTraceMethods() {
+ // given:
+ var messages = new ArrayList();
+ var exception = new RuntimeException("trace-error");
+
+ Logger logger = new Logger() {
+
+ @Override
+ public String name() {
+ return "test";
+ }
+
+ @Override
+ public boolean enabled(@NonNull LoggerLevel level) {
+ return true;
+ }
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message) {
+ messages.add(level + "_" + message);
+ }
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull Throwable exception) {
+ }
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception) {
+ messages.add(level + "_" + message + "_" + exception.getMessage());
+ }
+ };
+
+ // when:
+ logger.trace("msg1");
+ logger.trace("msg2", exception);
+
+ // then:
+ assertThat(messages)
+ .isEqualTo(List.of("TRACE_msg1", "TRACE_msg2_trace-error"));
+ }
+
@Test
@DisplayName("should print all debug methods")
void shouldPrintAllDebugMethods() {
@@ -31,6 +74,11 @@ void shouldPrintAllDebugMethods() {
Logger logger = new Logger() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
@Override
public boolean enabled(@NonNull LoggerLevel level) {
return true;
@@ -44,6 +92,10 @@ public void print(@NonNull LoggerLevel level, @NonNull String message) {
@Override
public void print(@NonNull LoggerLevel level, @NonNull Throwable exception) {
}
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception) {
+ }
};
// when:
@@ -85,6 +137,11 @@ void shouldPrintAllInfoMethods() {
Logger logger = new Logger() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
@Override
public boolean enabled(@NonNull LoggerLevel level) {
return true;
@@ -98,6 +155,10 @@ public void print(@NonNull LoggerLevel level, @NonNull String message) {
@Override
public void print(@NonNull LoggerLevel level, @NonNull Throwable exception) {
}
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception) {
+ }
};
// when:
@@ -139,6 +200,11 @@ void shouldPrintAllWarningMethods() {
Logger logger = new Logger() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
@Override
public boolean enabled(@NonNull LoggerLevel level) {
return true;
@@ -152,6 +218,10 @@ public void print(@NonNull LoggerLevel level, @NonNull String message) {
@Override
public void print(@NonNull LoggerLevel level, @NonNull Throwable exception) {
}
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception) {
+ }
};
// when:
@@ -193,6 +263,11 @@ void shouldPrintAllErrorMethods() {
Logger logger = new Logger() {
+ @Override
+ public String name() {
+ return "test";
+ }
+
@Override
public boolean enabled(@NonNull LoggerLevel level) {
return true;
@@ -206,6 +281,10 @@ public void print(@NonNull LoggerLevel level, @NonNull String message) {
@Override
public void print(@NonNull LoggerLevel level, @NonNull Throwable exception) {
}
+
+ @Override
+ public void print(@NonNull LoggerLevel level, @NonNull String message, @NonNull Throwable exception) {
+ }
};
// when:
diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLogger.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLogger.java
index 8b7d14e5..48d59177 100644
--- a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLogger.java
+++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLogger.java
@@ -13,9 +13,7 @@
*/
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
public final class DefaultLogger implements Logger {
-
- private static final LoggerLevel[] VALUES = LoggerLevel.values();
-
+
int[] override;
String name;
LoggerService loggerService;
@@ -27,6 +25,11 @@ public DefaultLogger(String name, LoggerService loggerService) {
Arrays.fill(override, LoggerService.NOT_CONFIGURE);
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public boolean enabled(LoggerLevel level) {
int value = override[level.ordinal()];
@@ -63,4 +66,12 @@ public void print(LoggerLevel level, Throwable exception) {
loggerService.write(level, name, StringUtils.toString(exception));
}
}
+
+ @Override
+ public void print(LoggerLevel level, String message, Throwable exception) {
+ if (enabled(level)) {
+ String exceptionInfo = StringUtils.toString(exception);
+ loggerService.write(level, name, message + ": " + exceptionInfo);
+ }
+ }
}
diff --git a/rlib-logger-slf4j-impl/build.gradle b/rlib-logger-slf4j-impl/build.gradle
new file mode 100644
index 00000000..b809f887
--- /dev/null
+++ b/rlib-logger-slf4j-impl/build.gradle
@@ -0,0 +1,11 @@
+plugins {
+ id("configure-java")
+ id("configure-publishing")
+}
+
+dependencies {
+ api projects.rlibLoggerApi
+ api libs.slf4j.api
+
+ testImplementation projects.rlibLoggerImpl
+}
diff --git a/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/SLF4JServiceProviderImpl.java b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/SLF4JServiceProviderImpl.java
new file mode 100644
index 00000000..e3b1a1d0
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/SLF4JServiceProviderImpl.java
@@ -0,0 +1,47 @@
+package javasabr.rlib.logger.slf4j.impl;
+
+import org.jspecify.annotations.Nullable;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.IMarkerFactory;
+import org.slf4j.helpers.BasicMarkerFactory;
+import org.slf4j.helpers.NOPMDCAdapter;
+import org.slf4j.spi.MDCAdapter;
+import org.slf4j.spi.SLF4JServiceProvider;
+
+public class SLF4JServiceProviderImpl implements SLF4JServiceProvider {
+
+ public static final String REQUESTED_API_VERSION = "2.0.17";
+
+ private final IMarkerFactory markerFactory = new BasicMarkerFactory();
+ private final MDCAdapter mdcAdapter = new NOPMDCAdapter();
+ @Nullable
+ private ILoggerFactory loggerFactory;
+
+ @Override
+ public ILoggerFactory getLoggerFactory() {
+ if (loggerFactory == null) {
+ throw new IllegalStateException("SLF4JServiceProviderImpl is not initialized yet");
+ }
+ return loggerFactory;
+ }
+
+ @Override
+ public IMarkerFactory getMarkerFactory() {
+ return markerFactory;
+ }
+
+ @Override
+ public MDCAdapter getMDCAdapter() {
+ return mdcAdapter;
+ }
+
+ @Override
+ public String getRequestedApiVersion() {
+ return REQUESTED_API_VERSION;
+ }
+
+ @Override
+ public void initialize() {
+ this.loggerFactory = new Slf4jLoggerFactoryImpl();
+ }
+}
diff --git a/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerFactoryImpl.java b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerFactoryImpl.java
new file mode 100644
index 00000000..dd2843ef
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerFactoryImpl.java
@@ -0,0 +1,18 @@
+package javasabr.rlib.logger.slf4j.impl;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javasabr.rlib.logger.api.LoggerManager;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+
+public class Slf4jLoggerFactoryImpl implements ILoggerFactory {
+
+ private final ConcurrentMap loggers = new ConcurrentHashMap<>();
+
+ @Override
+ public Logger getLogger(String name) {
+ return loggers
+ .computeIfAbsent(name, requested -> new Slf4jLoggerImpl(LoggerManager.getLogger(requested)));
+ }
+}
diff --git a/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImpl.java b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImpl.java
new file mode 100644
index 00000000..d2169d07
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImpl.java
@@ -0,0 +1,419 @@
+package javasabr.rlib.logger.slf4j.impl;
+
+import javasabr.rlib.logger.api.Logger;
+import javasabr.rlib.logger.api.LoggerLevel;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Marker;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
+
+@RequiredArgsConstructor
+public class Slf4jLoggerImpl implements org.slf4j.Logger {
+
+ private final Logger logger;
+
+ @Override
+ public String getName() {
+ return logger.name();
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return logger.enabled(LoggerLevel.TRACE);
+ }
+
+ @Override
+ public void trace(String msg) {
+ logger.print(LoggerLevel.TRACE, msg);
+ }
+
+ @Override
+ public void trace(String format, Object arg) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void trace(String format, Object arg1, Object arg2) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void trace(String format, Object... arguments) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void trace(String msg, Throwable exception) {
+ logger.print(LoggerLevel.TRACE, msg, exception);
+ }
+
+ @Override
+ public boolean isTraceEnabled(Marker marker) {
+ return logger.enabled(LoggerLevel.TRACE);
+ }
+
+ @Override
+ public void trace(Marker marker, String msg) {
+ logger.print(LoggerLevel.TRACE, msg);
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object arg) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object arg1, Object arg2) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String format, Object... arguments) {
+ if (isTraceEnabled()) {
+ traceImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void trace(Marker marker, String msg, Throwable exception) {
+ logger.trace(msg, exception);
+ }
+
+ private void traceImpl(FormattingTuple formatted) {
+ if (formatted.getThrowable() != null) {
+ logger.print(LoggerLevel.TRACE, formatted.getMessage(), formatted.getThrowable());
+ } else {
+ logger.print(LoggerLevel.TRACE, formatted.getMessage());
+ }
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return logger.enabled(LoggerLevel.DEBUG);
+ }
+
+ @Override
+ public void debug(String msg) {
+ logger.print(LoggerLevel.DEBUG, msg);
+ }
+
+ @Override
+ public void debug(String format, Object arg) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void debug(String format, Object arg1, Object arg2) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void debug(String format, Object... arguments) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void debug(String msg, Throwable exception) {
+ logger.print(LoggerLevel.DEBUG, msg, exception);
+ }
+
+ @Override
+ public boolean isDebugEnabled(Marker marker) {
+ return logger.enabled(LoggerLevel.DEBUG);
+ }
+
+ @Override
+ public void debug(Marker marker, String msg) {
+ logger.print(LoggerLevel.DEBUG, msg);
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object arg) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object arg1, Object arg2) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String format, Object... arguments) {
+ if (isDebugEnabled()) {
+ debugImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void debug(Marker marker, String msg, Throwable exception) {
+ logger.print(LoggerLevel.DEBUG, msg, exception);
+ }
+
+ private void debugImpl(FormattingTuple formatted) {
+ if (formatted.getThrowable() != null) {
+ logger.print(LoggerLevel.DEBUG, formatted.getMessage(), formatted.getThrowable());
+ } else {
+ logger.print(LoggerLevel.DEBUG, formatted.getMessage());
+ }
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return logger.enabled(LoggerLevel.INFO);
+ }
+
+ @Override
+ public void info(String msg) {
+ logger.print(LoggerLevel.INFO, msg);
+ }
+
+ @Override
+ public void info(String format, Object arg) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void info(String format, Object arg1, Object arg2) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void info(String format, Object... arguments) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void info(String msg, Throwable exception) {
+ logger.print(LoggerLevel.INFO, msg, exception);
+ }
+
+ @Override
+ public boolean isInfoEnabled(Marker marker) {
+ return logger.enabled(LoggerLevel.INFO);
+ }
+
+ @Override
+ public void info(Marker marker, String msg) {
+ logger.print(LoggerLevel.INFO, msg);
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object arg) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object arg1, Object arg2) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void info(Marker marker, String format, Object... arguments) {
+ if (isInfoEnabled()) {
+ infoImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void info(Marker marker, String msg, Throwable exception) {
+ logger.print(LoggerLevel.INFO, msg, exception);
+ }
+
+ private void infoImpl(FormattingTuple formatted) {
+ if (formatted.getThrowable() != null) {
+ logger.print(LoggerLevel.INFO, formatted.getMessage(), formatted.getThrowable());
+ } else {
+ logger.print(LoggerLevel.INFO, formatted.getMessage());
+ }
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return logger.enabled(LoggerLevel.WARNING);
+ }
+
+ @Override
+ public void warn(String msg) {
+ logger.print(LoggerLevel.WARNING, msg);
+ }
+
+ @Override
+ public void warn(String format, Object arg) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void warn(String format, Object arg1, Object arg2) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void warn(String format, Object... arguments) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void warn(String msg, Throwable exception) {
+ logger.print(LoggerLevel.WARNING, msg, exception);
+ }
+
+ @Override
+ public boolean isWarnEnabled(Marker marker) {
+ return logger.enabled(LoggerLevel.WARNING);
+ }
+
+ @Override
+ public void warn(Marker marker, String msg) {
+ logger.print(LoggerLevel.WARNING, msg);
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object arg) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object arg1, Object arg2) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void warn(Marker marker, String format, Object... arguments) {
+ if (isWarnEnabled()) {
+ warnImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void warn(Marker marker, String msg, Throwable exception) {
+ logger.print(LoggerLevel.WARNING, msg, exception);
+ }
+
+ private void warnImpl(FormattingTuple formatted) {
+ if (formatted.getThrowable() != null) {
+ logger.print(LoggerLevel.WARNING, formatted.getMessage(), formatted.getThrowable());
+ } else {
+ logger.print(LoggerLevel.WARNING, formatted.getMessage());
+ }
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return logger.enabled(LoggerLevel.ERROR);
+ }
+
+ @Override
+ public void error(String msg) {
+ logger.print(LoggerLevel.ERROR, msg);
+ }
+
+ @Override
+ public void error(String format, Object arg) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void error(String format, Object arg1, Object arg2) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void error(String format, Object... arguments) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void error(String msg, Throwable exception) {
+ logger.print(LoggerLevel.ERROR, msg, exception);
+ }
+
+ @Override
+ public boolean isErrorEnabled(Marker marker) {
+ return logger.enabled(LoggerLevel.ERROR);
+ }
+
+ @Override
+ public void error(Marker marker, String msg) {
+ logger.print(LoggerLevel.ERROR, msg);
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object arg) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.format(format, arg));
+ }
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object arg1, Object arg2) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.format(format, arg1, arg2));
+ }
+ }
+
+ @Override
+ public void error(Marker marker, String format, Object... arguments) {
+ if (isErrorEnabled()) {
+ errorImpl(MessageFormatter.arrayFormat(format, arguments));
+ }
+ }
+
+ @Override
+ public void error(Marker marker, String msg, Throwable exception) {
+ logger.print(LoggerLevel.ERROR, msg, exception);
+ }
+
+ private void errorImpl(FormattingTuple formatted) {
+ if (formatted.getThrowable() != null) {
+ logger.print(LoggerLevel.ERROR, formatted.getMessage(), formatted.getThrowable());
+ } else {
+ logger.print(LoggerLevel.ERROR, formatted.getMessage());
+ }
+ }
+}
diff --git a/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/package-info.java b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/package-info.java
new file mode 100644
index 00000000..0f43b229
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/main/java/javasabr/rlib/logger/slf4j/impl/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package javasabr.rlib.logger.slf4j.impl;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/rlib-logger-slf4j-impl/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/rlib-logger-slf4j-impl/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
new file mode 100644
index 00000000..6236bf9b
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider
@@ -0,0 +1 @@
+javasabr.rlib.logger.slf4j.impl.SLF4JServiceProviderImpl
diff --git a/rlib-logger-slf4j-impl/src/test/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImplTest.java b/rlib-logger-slf4j-impl/src/test/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImplTest.java
new file mode 100644
index 00000000..a9bdbc6a
--- /dev/null
+++ b/rlib-logger-slf4j-impl/src/test/java/javasabr/rlib/logger/slf4j/impl/Slf4jLoggerImplTest.java
@@ -0,0 +1,149 @@
+package javasabr.rlib.logger.slf4j.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+import javasabr.rlib.collections.array.ArrayFactory;
+import javasabr.rlib.collections.array.LockableArray;
+import javasabr.rlib.collections.operation.LockableOperations;
+import javasabr.rlib.logger.api.Logger;
+import javasabr.rlib.logger.api.LoggerLevel;
+import javasabr.rlib.logger.api.LoggerListener;
+import javasabr.rlib.logger.api.LoggerManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.ResourceAccessMode;
+import org.junit.jupiter.api.parallel.ResourceLock;
+import org.slf4j.LoggerFactory;
+
+@ResourceLock("LoggerListeners")
+@ResourceLock(value = "RLibLoggerOverrides", mode = ResourceAccessMode.READ_WRITE)
+class Slf4jLoggerImplTest {
+
+ private static final LockableArray LOGS_DATA = ArrayFactory
+ .stampedLockBasedArray(String.class);
+ private static final LockableOperations> LOGS_DATA_OPERATIONS =
+ LOGS_DATA.operations();
+ private static final LoggerListener LOGGER_LISTENER = text -> LOGS_DATA_OPERATIONS
+ .inWriteLock(text, Collection::add);
+
+ private final Logger rlibLogger = LoggerManager.getLogger(Slf4jLoggerImplTest.class);
+
+ @BeforeEach
+ void prepare() {
+ LoggerManager.addListener(LOGGER_LISTENER);
+ LOGS_DATA_OPERATIONS.inWriteLock(Collection::clear);
+ }
+
+ @AfterEach
+ void cleanup() {
+ for (var level : LoggerLevel.values()) {
+ rlibLogger.resetToDefault(level);
+ }
+ LOGS_DATA_OPERATIONS.inWriteLock(Collection::clear);
+ LoggerManager.removeListener(LOGGER_LISTENER);
+ }
+
+ @Test
+ void shouldReturnSlf4jLoggerImplFromLoggerFactory() {
+ // when:
+ var logger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+
+ // then:
+ assertThat(logger)
+ .isInstanceOf(Slf4jLoggerImpl.class);
+ }
+
+ @Test
+ void shouldReturnLoggerWithMatchingName() {
+ // when:
+ var logger = LoggerFactory.getLogger("my.logger.name");
+
+ // then:
+ assertThat(logger.getName())
+ .isEqualTo("my.logger.name");
+ }
+
+ @Test
+ void shouldDelegateInfoMessageToRlibLogger() {
+ // given:
+ rlibLogger.overrideEnabled(LoggerLevel.INFO, true);
+ var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+
+ // when:
+ slf4jLogger.info("hello from slf4j");
+
+ // then:
+ assertThat(LOGS_DATA.size())
+ .isEqualTo(1);
+ assertThat(LOGS_DATA.get(0))
+ .endsWith("Slf4jLoggerImplTest: hello from slf4j");
+ }
+
+ @Test
+ void shouldDelegateErrorWithExceptionToRlibLogger() {
+ // given:
+ rlibLogger.overrideEnabled(LoggerLevel.ERROR, true);
+ var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+ var exception = new RuntimeException("boom");
+
+ // when:
+ slf4jLogger.error("error occurred", exception);
+
+ // then:
+ assertThat(LOGS_DATA.size())
+ .isEqualTo(1);
+ assertThat(LOGS_DATA.get(0))
+ .contains("Slf4jLoggerImplTest: error occurred")
+ .contains("RuntimeException: boom");
+ }
+
+ @Test
+ void shouldNotDelegateDebugWhenDisabledByDefault() {
+ // given:
+ var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+
+ // when:
+ slf4jLogger.debug("should not appear");
+
+ // then:
+ assertThat(LOGS_DATA.size())
+ .isEqualTo(0);
+ }
+
+ @Test
+ void shouldDelegateFormattedMessageToRlibLogger() {
+ // given:
+ rlibLogger.overrideEnabled(LoggerLevel.INFO, true);
+ var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+
+ // when:
+ slf4jLogger.info("value is {}", 42);
+
+ // then:
+ assertThat(LOGS_DATA.size())
+ .isEqualTo(1);
+ assertThat(LOGS_DATA.get(0))
+ .endsWith("Slf4jLoggerImplTest: value is 42");
+ }
+
+ @Test
+ void shouldDelegateFormattedMessageWithTrailingExceptionToRlibLogger() {
+ // given:
+ rlibLogger.overrideEnabled(LoggerLevel.ERROR, true);
+
+ var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class);
+ var exception = new RuntimeException("oops");
+
+ // when:
+ slf4jLogger.error("failed with code {}", 500, exception);
+
+ // then:
+ assertThat(LOGS_DATA.size())
+ .isEqualTo(1);
+ assertThat(LOGS_DATA.get(0))
+ .contains("Slf4jLoggerImplTest: failed with code 500")
+ .contains("RuntimeException: oops");
+ }
+}
diff --git a/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLogger.java b/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLogger.java
index c3729313..aca1e099 100644
--- a/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLogger.java
+++ b/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLogger.java
@@ -9,33 +9,52 @@ public class Slf4jLogger implements Logger {
private final org.slf4j.Logger logger;
+ @Override
+ public String name() {
+ return logger.getName();
+ }
+
@Override
public boolean enabled(LoggerLevel level) {
return switch (level) {
- case INFO -> logger.isInfoEnabled();
+ case TRACE -> logger.isTraceEnabled();
case DEBUG -> logger.isDebugEnabled();
- case ERROR -> logger.isErrorEnabled();
+ case INFO -> logger.isInfoEnabled();
case WARNING -> logger.isWarnEnabled();
+ case ERROR -> logger.isErrorEnabled();
};
}
@Override
public void print(LoggerLevel level, String message) {
switch (level) {
- case INFO -> logger.info(message);
+ case TRACE -> logger.trace(message);
case DEBUG -> logger.debug(message);
- case ERROR -> logger.error(message);
+ case INFO -> logger.info(message);
case WARNING -> logger.warn(message);
+ case ERROR -> logger.error(message);
}
}
@Override
public void print(LoggerLevel level, Throwable exception) {
switch (level) {
- case INFO -> logger.info(exception.getMessage(), exception);
+ case TRACE -> logger.trace(exception.getMessage(), exception);
case DEBUG -> logger.debug(exception.getMessage(), exception);
- case ERROR -> logger.error(exception.getMessage(), exception);
+ case INFO -> logger.info(exception.getMessage(), exception);
case WARNING -> logger.warn(exception.getMessage(), exception);
+ case ERROR -> logger.error(exception.getMessage(), exception);
+ }
+ }
+
+ @Override
+ public void print(LoggerLevel level, String message, Throwable exception) {
+ switch (level) {
+ case TRACE -> logger.trace(message, exception);
+ case DEBUG -> logger.debug(message, exception);
+ case INFO -> logger.info(message, exception);
+ case WARNING -> logger.warn(message, exception);
+ case ERROR -> logger.error(message, exception);
}
}
}
diff --git a/settings.gradle b/settings.gradle
index b5affd2b..ed51c8af 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,4 +21,5 @@ include ':rlib-reusable'
include ':rlib-reference'
include ':rlib-concurrent'
include ':rlib-eventbus'
+include ':rlib-logger-slf4j-impl'
include ':test-coverage'
diff --git a/test-coverage/build.gradle b/test-coverage/build.gradle
index ac2ca37b..307e3b7a 100644
--- a/test-coverage/build.gradle
+++ b/test-coverage/build.gradle
@@ -14,6 +14,7 @@ dependencies {
jacocoAggregation projects.rlibLoggerApi
jacocoAggregation projects.rlibLoggerImpl
jacocoAggregation projects.rlibLoggerSlf4j
+ jacocoAggregation projects.rlibLoggerSlf4jImpl
jacocoAggregation projects.rlibMail
jacocoAggregation projects.rlibNetwork
jacocoAggregation projects.rlibPluginSystem