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