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 f93f3284..7dacd5ec 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 @@ -205,8 +205,18 @@ interface N4Factory { * @return the logger name * @since 10.0.0 */ - @NonNull String name(); + @NonNull + String name(); + /** + * Returns the short name of this logger. + * + * @return the short logger name + * @since 10.0.0 + */ + @NonNull + String shortName(); + /** * Prints a trace message. * diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerFactory.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerFactory.java index 6f50649d..c865f165 100644 --- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerFactory.java +++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerFactory.java @@ -1,7 +1,5 @@ package javasabr.rlib.logger.api; -import java.io.Writer; - /** * Factory for creating {@link Logger} instances. * @@ -10,22 +8,22 @@ public interface LoggerFactory { /** - * Creates a logger with the specified name. + * Creates or gets a logger with the specified name. * * @param name the logger name * @return the logger instance * @since 10.0.0 */ - Logger make(String name); + Logger getLogger(String name); /** - * Creates a logger for the specified class. + * Creates or gets a logger for the specified class. * * @param type the class to create a logger for * @return the logger instance * @since 10.0.0 */ - Logger make(Class type); + Logger getLogger(Class type); /** * Returns the default logger. @@ -34,36 +32,12 @@ public interface LoggerFactory { * @since 10.0.0 */ Logger getDefault(); - - /** - * Adds a listener to receive log output. - * - * @param listener the listener to add - * @since 10.0.0 - */ - void addListener(LoggerListener listener); - - /** - * Removes a previously added listener. - * - * @param listener the listener to remove - * @since 10.0.0 - */ - void removeListener(LoggerListener listener); - - /** - * Adds a writer to receive log output. - * - * @param writer the writer to add - * @since 10.0.0 - */ - void addWriter(Writer writer); - + /** - * Removes a previously added writer. + * Returns the logger service. * - * @param writer the writer to remove + * @return the logger service * @since 10.0.0 */ - void removeWriter(Writer writer); + LoggerService getLoggerService(); } 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 22c94bd9..b019257d 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,11 +16,11 @@ @Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public enum LoggerLevel { - TRACE("TRACE", " ", false, false), - DEBUG("DEBUG", " ", false, false), - INFO("INFO", " ", true, true), - WARNING("WARN", " ", true, true), - ERROR("ERROR", " ", true, true); + TRACE("TRACE", " ", 0, false, false), + DEBUG("DEBUG", " ", 1, false, false), + INFO("INFO", " ", 2, true, true), + WARNING("WARN", " ", 3, true, true), + ERROR("ERROR", " ", 4, true, true); /** * The number of log levels. @@ -30,6 +30,7 @@ public enum LoggerLevel { String title; String offset; + int severity; boolean enabled; boolean forceFlush; diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerManager.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerManager.java index 86b0c745..859e03cd 100644 --- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerManager.java +++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerManager.java @@ -1,10 +1,9 @@ package javasabr.rlib.logger.api; -import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.util.Iterator; import java.util.ServiceLoader; -import javasabr.rlib.logger.api.impl.NullLoggerFactory; +import javasabr.rlib.logger.api.impl.NoOpsLoggerFactory; /** * Central manager for obtaining and configuring loggers. @@ -39,9 +38,9 @@ public class LoggerManager { if (implementation == null) { System.err.printf( - "ERROR: No any exist implementation of [%s], will be used null logger%n", + "ERROR: No any exist implementation of [%s], will be used no ops logger%n", LoggerFactory.class); - LOGGER_FACTORY = new NullLoggerFactory(); + LOGGER_FACTORY = new NoOpsLoggerFactory(); } else { try { LOGGER_FACTORY = implementation @@ -71,7 +70,7 @@ public static Logger getDefaultLogger() { * @since 10.0.0 */ public static Logger getLogger(Class cs) { - return LOGGER_FACTORY.make(cs); + return LOGGER_FACTORY.getLogger(cs); } /** @@ -82,57 +81,19 @@ public static Logger getLogger(Class cs) { * @since 10.0.0 */ public static Logger getLogger(String id) { - return LOGGER_FACTORY.make(id); + return LOGGER_FACTORY.getLogger(id); } /** - * Adds a listener to receive log output. + * Returns the logger service. * - * @param listener the listener to add + * @return the logger service * @since 10.0.0 */ - public static void addListener(LoggerListener listener) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.addListener(listener); - } - } - - /** - * Removes a previously added listener. - * - * @param listener the listener to remove - * @since 10.0.0 - */ - public static void removeListener(LoggerListener listener) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.removeListener(listener); - } + public static LoggerService getLoggerService() { + return LOGGER_FACTORY.getLoggerService(); } - - /** - * Adds a writer to receive log output. - * - * @param writer the writer to add - * @since 10.0.0 - */ - public static void addWriter(Writer writer) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.addWriter(writer); - } - } - - /** - * Removes a previously added writer. - * - * @param writer the writer to remove - * @since 10.0.0 - */ - public static void removeWriter(Writer writer) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.removeWriter(writer); - } - } - + /** * Configures the default setting for a log level. * @@ -141,9 +102,9 @@ public static void removeWriter(Writer writer) { * @since 10.0.0 */ public static void configureDefault(LoggerLevel level, boolean def) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.configureDefault(level, def); - } + LOGGER_FACTORY + .getLoggerService() + .configureDefault(level, def); } /** @@ -153,9 +114,9 @@ public static void configureDefault(LoggerLevel level, boolean def) { * @since 10.0.0 */ public static void removeDefault(LoggerLevel level) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.removeDefault(level); - } + LOGGER_FACTORY + .getLoggerService() + .removeDefault(level); } /** @@ -166,9 +127,9 @@ public static void removeDefault(LoggerLevel level) { * @since 10.0.0 */ public static void enable(Class cs, LoggerLevel level) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.enable(cs, level); - } + LOGGER_FACTORY + .getLoggerService() + .enable(cs, level); } /** @@ -179,8 +140,8 @@ public static void enable(Class cs, LoggerLevel level) { * @since 10.0.0 */ public static void disable(Class cs, LoggerLevel level) { - if (LOGGER_FACTORY instanceof LoggerService ls) { - ls.disable(cs, level); - } + LOGGER_FACTORY + .getLoggerService() + .disable(cs, level); } } diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerService.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerService.java index f98f0582..8b5d4595 100644 --- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerService.java +++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/LoggerService.java @@ -1,7 +1,5 @@ package javasabr.rlib.logger.api; -import java.io.Writer; - /** * Service interface for configuring logger behavior. * @@ -23,39 +21,7 @@ public interface LoggerService { * Indicates that the level is enabled. */ int ENABLED = 1; - - /** - * Adds a listener to receive log output. - * - * @param listener the listener to add - * @since 10.0.0 - */ - void addListener(LoggerListener listener); - - /** - * Removes a previously added listener. - * - * @param listener the listener to remove - * @since 10.0.0 - */ - void removeListener(LoggerListener listener); - - /** - * Adds a writer to receive log output. - * - * @param writer the writer to add - * @since 10.0.0 - */ - void addWriter(Writer writer); - - /** - * Removes a previously added writer. - * - * @param writer the writer to remove - * @since 10.0.0 - */ - void removeWriter(Writer writer); - + /** * Enables logging at the specified level for the class. * @@ -103,10 +69,10 @@ public interface LoggerService { /** * Writes a log message. * + * @param logger the logger * @param level the log level - * @param loggerName the logger name - * @param logMessage the message to write + * @param message the message to write * @since 10.0.0 */ - void write(LoggerLevel level, String loggerName, String logMessage); + void write(Logger logger, LoggerLevel level, String message); } 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/NoOpsLogger.java similarity index 74% rename from rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLogger.java rename to rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLogger.java index d6e51582..b26a685e 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/NoOpsLogger.java @@ -3,11 +3,16 @@ import javasabr.rlib.logger.api.Logger; import javasabr.rlib.logger.api.LoggerLevel; -public final class NullLogger implements Logger { +public final class NoOpsLogger implements Logger { @Override public String name() { - return "null"; + return "noops"; + } + + @Override + public String shortName() { + return "noops"; } @Override diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerFactory.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerFactory.java new file mode 100644 index 00000000..ce94ea12 --- /dev/null +++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerFactory.java @@ -0,0 +1,31 @@ +package javasabr.rlib.logger.api.impl; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerFactory; +import javasabr.rlib.logger.api.LoggerService; + +public class NoOpsLoggerFactory implements LoggerFactory { + + private static final NoOpsLogger NO_OPS_LOGGER = new NoOpsLogger(); + private static final LoggerService NO_OPS_LOGGER_SERVICE = new NoOpsLoggerService(); + + @Override + public Logger getLogger(String name) { + return NO_OPS_LOGGER; + } + + @Override + public Logger getLogger(Class type) { + return NO_OPS_LOGGER; + } + + @Override + public Logger getDefault() { + return NO_OPS_LOGGER; + } + + @Override + public LoggerService getLoggerService() { + return NO_OPS_LOGGER_SERVICE; + } +} diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerService.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerService.java new file mode 100644 index 00000000..4af091e4 --- /dev/null +++ b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NoOpsLoggerService.java @@ -0,0 +1,28 @@ +package javasabr.rlib.logger.api.impl; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; +import javasabr.rlib.logger.api.LoggerService; + +public class NoOpsLoggerService implements LoggerService { + + @Override + public void enable(Class cs, LoggerLevel level) {} + + @Override + public void disable(Class cs, LoggerLevel level) {} + + @Override + public void configureDefault(LoggerLevel level, boolean def) {} + + @Override + public void removeDefault(LoggerLevel level) {} + + @Override + public int enabled(LoggerLevel level) { + return 0; + } + + @Override + public void write(Logger logger, LoggerLevel level, String message) {} +} diff --git a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLoggerFactory.java b/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLoggerFactory.java deleted file mode 100644 index 686665b9..00000000 --- a/rlib-logger-api/src/main/java/javasabr/rlib/logger/api/impl/NullLoggerFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package javasabr.rlib.logger.api.impl; - -import java.io.Writer; -import javasabr.rlib.logger.api.Logger; -import javasabr.rlib.logger.api.LoggerFactory; -import javasabr.rlib.logger.api.LoggerListener; - -public class NullLoggerFactory implements LoggerFactory { - - private static final NullLogger NULL_LOGGER = new NullLogger(); - - @Override - public Logger make(String name) { - return NULL_LOGGER; - } - - @Override - public Logger make(Class type) { - return NULL_LOGGER; - } - - @Override - public Logger getDefault() { - return NULL_LOGGER; - } - - @Override - public void addListener(LoggerListener listener) {} - - @Override - public void addWriter(Writer writer) {} - - @Override - public void removeListener(LoggerListener listener) {} - - @Override - public void removeWriter(Writer writer) {} -} 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 74d72046..1a696ad0 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 @@ -24,6 +24,11 @@ public String name() { return "test"; } + @Override + public String shortName() { + return "test"; + } + @Override public boolean enabled(@NonNull LoggerLevel level) { return true; @@ -79,6 +84,11 @@ public String name() { return "test"; } + @Override + public String shortName() { + return "test"; + } + @Override public boolean enabled(@NonNull LoggerLevel level) { return true; @@ -142,6 +152,11 @@ public String name() { return "test"; } + @Override + public String shortName() { + return "test"; + } + @Override public boolean enabled(@NonNull LoggerLevel level) { return true; @@ -205,6 +220,11 @@ public String name() { return "test"; } + @Override + public String shortName() { + return "test"; + } + @Override public boolean enabled(@NonNull LoggerLevel level) { return true; @@ -268,6 +288,11 @@ public String name() { return "test"; } + @Override + public String shortName() { + return "test"; + } + @Override public boolean enabled(@NonNull LoggerLevel level) { return true; 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 48d59177..5e1a75c3 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 @@ -1,25 +1,41 @@ package javasabr.rlib.logger.impl; import java.util.Arrays; +import javasabr.rlib.collections.array.UnsafeArray; import javasabr.rlib.common.util.StringUtils; import javasabr.rlib.logger.api.Logger; import javasabr.rlib.logger.api.LoggerLevel; import javasabr.rlib.logger.api.LoggerService; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; /** * @author JavaSaBr */ -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +@FieldDefaults(level = AccessLevel.PROTECTED) public final class DefaultLogger implements Logger { - - int[] override; - String name; - LoggerService loggerService; - public DefaultLogger(String name, LoggerService loggerService) { + final int[] override; + final String name; + final String shortName; + final DefaultLoggerService loggerService; + + @Nullable + UnsafeArray traceConsumers; + @Nullable + UnsafeArray debugConsumers; + @Nullable + UnsafeArray infoConsumers; + @Nullable + UnsafeArray warnConsumers; + @Nullable + UnsafeArray errorConsumers; + + public DefaultLogger(String name, String shortName, DefaultLoggerService loggerService) { this.name = name; + this.shortName = shortName; this.loggerService = loggerService; this.override = new int[DefaultLoggerService.LOGGER_LEVELS.length]; Arrays.fill(override, LoggerService.NOT_CONFIGURE); @@ -30,6 +46,11 @@ public String name() { return name; } + @Override + public String shortName() { + return shortName; + } + @Override public boolean enabled(LoggerLevel level) { int value = override[level.ordinal()]; @@ -56,14 +77,14 @@ public void resetToDefault(LoggerLevel level) { @Override public void print(LoggerLevel level, String logMessage) { if (enabled(level)) { - loggerService.write(level, name, logMessage); + loggerService.write(this, level, logMessage); } } @Override public void print(LoggerLevel level, Throwable exception) { if (enabled(level)) { - loggerService.write(level, name, StringUtils.toString(exception)); + loggerService.write(this, level, StringUtils.toString(exception)); } } @@ -71,7 +92,28 @@ public void print(LoggerLevel level, Throwable exception) { public void print(LoggerLevel level, String message, Throwable exception) { if (enabled(level)) { String exceptionInfo = StringUtils.toString(exception); - loggerService.write(level, name, message + ": " + exceptionInfo); + loggerService.write(this, level, message + ": " + exceptionInfo); + } + } + + @Nullable + UnsafeArray resolvedConsumers(LoggerLevel level) { + return switch (level) { + case TRACE -> traceConsumers; + case DEBUG -> debugConsumers; + case INFO -> infoConsumers; + case WARNING -> warnConsumers; + case ERROR -> errorConsumers; + }; + } + + void saveResolvedConsumers(LoggerLevel level, UnsafeArray consumers) { + switch (level) { + case TRACE -> traceConsumers = consumers; + case DEBUG -> debugConsumers = consumers; + case INFO -> infoConsumers = consumers; + case WARNING -> warnConsumers = consumers; + case ERROR -> errorConsumers = consumers; } } } diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerFactory.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerFactory.java new file mode 100644 index 00000000..d532f2c3 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerFactory.java @@ -0,0 +1,38 @@ +package javasabr.rlib.logger.impl; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerFactory; +import javasabr.rlib.logger.api.LoggerService; +import javasabr.rlib.logger.impl.config.LoggerConfigResolver; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class DefaultLoggerFactory implements LoggerFactory { + + DefaultLoggerService defaultLoggerService; + + public DefaultLoggerFactory() { + this.defaultLoggerService = new DefaultLoggerService(LoggerConfigResolver.load()); + } + + @Override + public Logger getLogger(String name) { + return defaultLoggerService.getLogger(name); + } + + @Override + public Logger getLogger(Class type) { + return defaultLoggerService.getLogger(type); + } + + @Override + public Logger getDefault() { + return defaultLoggerService.getDefault(); + } + + @Override + public LoggerService getLoggerService() { + return defaultLoggerService; + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerService.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerService.java index 50b7115d..11c513e3 100644 --- a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerService.java +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/DefaultLoggerService.java @@ -1,22 +1,15 @@ package javasabr.rlib.logger.impl; -import static javasabr.rlib.common.util.ObjectUtils.notNull; - -import java.io.IOException; -import java.io.Writer; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.Arrays; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.ArrayIterationFunctions; -import javasabr.rlib.collections.array.MutableArray; +import java.util.function.Function; +import javasabr.rlib.collections.array.UnsafeArray; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; import javasabr.rlib.logger.api.Logger; -import javasabr.rlib.logger.api.LoggerFactory; import javasabr.rlib.logger.api.LoggerLevel; -import javasabr.rlib.logger.api.LoggerListener; import javasabr.rlib.logger.api.LoggerService; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.LoggerConfig; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; @@ -25,81 +18,65 @@ * * @author JavaSaBr */ -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class DefaultLoggerService implements LoggerFactory, LoggerService { - - static final LoggerLevel[] LOGGER_LEVELS = LoggerLevel.values(); - - ConcurrentMap loggers; - MutableArray listeners; - ArrayIterationFunctions listenerIterations; - MutableArray writers; - ArrayIterationFunctions writerIterations; - - Logger logger; - DateTimeFormatter timeFormatter; - int[] override; - - public DefaultLoggerService() { - this.loggers = new ConcurrentHashMap<>(); - this.logger = new DefaultLogger("", this); - this.timeFormatter = DateTimeFormatter.ofPattern("d.MM.yyyy HH:mm:ss:SSS"); - this.listeners = ArrayFactory.copyOnModifyArray(LoggerListener.class); - this.listenerIterations = listeners.iterations(); - this.writers = ArrayFactory.copyOnModifyArray(Writer.class); - this.writerIterations = writers.iterations(); - this.override = new int[LOGGER_LEVELS.length]; - Arrays.fill(override, NOT_CONFIGURE); - } +@FieldDefaults(level = AccessLevel.PROTECTED) +public class DefaultLoggerService implements LoggerService { - @Override - public void addListener(LoggerListener listener) { - listeners.add(listener); - } + public static final LoggerLevel[] LOGGER_LEVELS = LoggerLevel.values(); + public static final String ROOT_LOGGER_NAME = "ROOT"; - @Override - public void addWriter(Writer writer) { - writers.add(writer); + final LockableRefToRefDictionary loggers; + final Function loggerFactory = this::createNew; + + final Logger logger; + final int[] override; + + volatile LoggerConfig config; + + public DefaultLoggerService(LoggerConfig config) { + this.config = config; + this.loggers = DictionaryFactory.stampedLockBasedRefToRefDictionary(); + this.logger = getLogger(ROOT_LOGGER_NAME); + this.override = new int[LOGGER_LEVELS.length]; + Arrays.fill(override, NOT_CONFIGURE); } - @Override public Logger getDefault() { return logger; } - @Override - public Logger make(Class type) { - String simpleName = type.getSimpleName(); - Logger logger = loggers - .computeIfAbsent(simpleName, name -> new DefaultLogger(name, this)); - return notNull(logger); - } - - @Override - public Logger make(String name) { - Logger logger = loggers - .computeIfAbsent(name, str -> new DefaultLogger(str, this)); - return notNull(logger); + public DefaultLogger getLogger(Class type) { + long lock = loggers.writeLock(); + try { + return loggers.getOrCompute(type.getName(), loggerFactory); + } finally { + loggers.writeUnlock(lock); + } } - @Override - public void removeListener(LoggerListener listener) { - listeners.remove(listener); + public DefaultLogger getLogger(String name) { + long lock = loggers.writeLock(); + try { + return loggers.getOrCompute(name, loggerFactory); + } finally { + loggers.writeUnlock(lock); + } } - @Override - public void removeWriter(Writer writer) { - writers.remove(writer); + private DefaultLogger createNew(String name) { + String shortName = calculateShortName(name); + var created = new DefaultLogger(name, shortName, this); + config.configureLevels(created); + return created; } - + @Override public void enable(Class cs, LoggerLevel level) { - make(cs).overrideEnabled(level, true); + getLogger(cs).overrideEnabled(level, true); } @Override public void disable(Class cs, LoggerLevel level) { - make(cs).overrideEnabled(level, false); + getLogger(cs).overrideEnabled(level, false); } @Override @@ -118,46 +95,53 @@ public int enabled(LoggerLevel level) { } @Override - public void write(LoggerLevel level, String loggerName, String logMessage) { - var timestamp = timeFormatter.format(LocalDateTime.now()); - var resultMessage = level.title() - + level.offset() + ' ' - + timestamp + ' ' - + loggerName + ": " - + logMessage; - write(level, resultMessage); - } - - private void write(LoggerLevel level, String resultMessage) { - listenerIterations.forEach(resultMessage, LoggerListener::println); - writerIterations.forEach(resultMessage, DefaultLoggerService::append); - switch (level) { - case INFO, DEBUG -> System.out.println(resultMessage); - case ERROR, WARNING -> System.err.println(resultMessage); - } - if (!level.forceFlush()) { - return; + public void write(Logger logger, LoggerLevel level, String message) { + if (logger instanceof DefaultLogger defaultLogger) { + write(defaultLogger, level, message); + } else { + UnsafeArray consumers = config + .resolveConsumers(logger, level) + .asUnsafe(); + for (LogMessageConsumer consumer : consumers.wrapped()) { + //noinspection DataFlowIssue it's safe + consumer.consume(level, logger, message); + } } - listeners.forEach(LoggerListener::flush); - writers.forEach(DefaultLoggerService::flush); } - private static void append(Writer writer, String toWrite) { - try { - writer.append(toWrite); - writer.append('\n'); - } catch (IOException exception) { - //noinspection CallToPrintStackTrace - exception.printStackTrace(); + void write(DefaultLogger logger, LoggerLevel level, String message) { + UnsafeArray consumers = resolveConsumers(logger, level); + for (LogMessageConsumer consumer : consumers.wrapped()) { + //noinspection DataFlowIssue it's safe + consumer.consume(level, logger, message); } } - private static void flush(Writer writer) { - try { - writer.flush(); - } catch (IOException exception) { - //noinspection CallToPrintStackTrace - exception.printStackTrace(); + private UnsafeArray resolveConsumers(DefaultLogger logger, LoggerLevel level) { + UnsafeArray consumers = logger.resolvedConsumers(level); + if (consumers == null) { + consumers = config + .resolveConsumers(logger, level) + .asUnsafe(); + logger.saveResolvedConsumers(level, consumers); + } + return consumers; + } + + private static String calculateShortName(String name) { + String shortName = name; + int cutUntil = name.lastIndexOf('.'); + boolean isDotLastChar = cutUntil != -1 && cutUntil == shortName.length() - 1; + if (isDotLastChar && shortName.length() > 1) { + shortName = shortName.substring(0, shortName.length() - 1); + cutUntil = shortName.lastIndexOf('.'); + } + if (cutUntil != -1) { + shortName = shortName.substring(cutUntil + 1); + } + if (shortName.isEmpty()) { + shortName = name; } + return shortName; } } diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/FolderFileListener.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/FolderFileListener.java deleted file mode 100644 index fecd0269..00000000 --- a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/FolderFileListener.java +++ /dev/null @@ -1,72 +0,0 @@ -package javasabr.rlib.logger.impl; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import javasabr.rlib.logger.api.LoggerListener; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; - -/** - * @author JavaSaBr - */ -@FieldDefaults(level = AccessLevel.PROTECTED) -public class FolderFileListener implements LoggerListener { - - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyy-MM-dd_HH-mm-ss"); - - final Path folder; - - @Nullable - volatile Writer writer; - - public FolderFileListener(Path folder) { - if (!Files.isDirectory(folder)) { - throw new IllegalArgumentException("File:[%s] is not directory".formatted(folder)); - } - if (!Files.exists(folder)) { - try { - Files.createDirectories(folder); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - this.folder = folder; - } - - public Writer getOrCreateWriter() throws IOException { - var local = writer; - if (local == null) { - synchronized (this) { - local = writer; - if (local == null) { - var dateTime = LocalDateTime.now(); - var filename = TIME_FORMATTER.format(dateTime) + ".log"; - local = Files.newBufferedWriter(folder.resolve(filename), StandardCharsets.UTF_8); - this.writer = local; - return local; - } - } - } - return local; - } - - @Override - public void println(String text) { - try { - var writer = getOrCreateWriter(); - writer.append(text); - writer.append('\n'); - writer.flush(); - } catch (IOException exception) { - //noinspection CallToPrintStackTrace - exception.printStackTrace(); - } - } -} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageConsumer.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageConsumer.java new file mode 100644 index 00000000..c29c8f3c --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageConsumer.java @@ -0,0 +1,22 @@ +package javasabr.rlib.logger.impl.config; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; + +/** + * Consumer for processed log messages. + * + * @since 10.0.0 + */ +public interface LogMessageConsumer { + + /** + * Consumes a log message. + * + * @param level the log level + * @param logger the logger + * @param message the message + * @since 10.0.0 + */ + void consume(LoggerLevel level, Logger logger, String message); +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageRender.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageRender.java new file mode 100644 index 00000000..285430e9 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LogMessageRender.java @@ -0,0 +1,23 @@ +package javasabr.rlib.logger.impl.config; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; + +/** + * Renderer for log messages. + * + * @since 10.0.0 + */ +public interface LogMessageRender { + + /** + * Renders a log message. + * + * @param level the log level + * @param logger the logger + * @param message the message + * @return the rendered message + * @since 10.0.0 + */ + String render(LoggerLevel level, Logger logger, String message); +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfig.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfig.java new file mode 100644 index 00000000..833e8d43 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfig.java @@ -0,0 +1,31 @@ +package javasabr.rlib.logger.impl.config; + +import javasabr.rlib.collections.array.UnsafeArray; +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; + +/** + * Configuration contract for logger levels and message consumers. + * + * @since 10.0.0 + */ +public interface LoggerConfig { + + /** + * Configures enabled state for logger levels. + * + * @param logger the logger + * @since 10.0.0 + */ + void configureLevels(Logger logger); + + /** + * Resolves consumers for the logger and level. + * + * @param logger the logger + * @param level the log level + * @return the resolved consumers + * @since 10.0.0 + */ + UnsafeArray resolveConsumers(Logger logger, LoggerLevel level); +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigLoader.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigLoader.java new file mode 100644 index 00000000..e9bff195 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigLoader.java @@ -0,0 +1,27 @@ +package javasabr.rlib.logger.impl.config; + +import java.util.Optional; + +/** + * Loader of logger configuration. + * + * @since 10.0.0 + */ +public interface LoggerConfigLoader { + + /** + * Tries to load logger configuration. + * + * @return loaded configuration if available + * @since 10.0.0 + */ + Optional tryToLoad(); + + /** + * Returns loader order. + * + * @return the loader order + * @since 10.0.0 + */ + int order(); +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigResolver.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigResolver.java new file mode 100644 index 00000000..1ddf7a5b --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/LoggerConfigResolver.java @@ -0,0 +1,36 @@ +package javasabr.rlib.logger.impl.config; + +import java.util.Comparator; +import java.util.Optional; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayCollectors; +import javasabr.rlib.logger.impl.config.impl.DefaultLoggerConfigLoader; + +/** + * Resolver of logger configuration from available loaders. + * + * @since 10.0.0 + */ +public class LoggerConfigResolver { + + private static final Array LOADERS = Array + .of(new DefaultLoggerConfigLoader()) + .stream() + .sorted(Comparator.comparingInt(LoggerConfigLoader::order)) + .collect(ArrayCollectors.toArray(LoggerConfigLoader.class)); + + /** + * Loads logger configuration. + * + * @return the loaded logger configuration + * @since 10.0.0 + */ + public static LoggerConfig load() { + return LOADERS + .stream() + .map(LoggerConfigLoader::tryToLoad) + .flatMap(Optional::stream) + .findFirst() + .orElseThrow(); + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/ConsoleMessageConsumer.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/ConsoleMessageConsumer.java new file mode 100644 index 00000000..08780274 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/ConsoleMessageConsumer.java @@ -0,0 +1,25 @@ +package javasabr.rlib.logger.impl.config.impl; + +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.LogMessageRender; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class ConsoleMessageConsumer implements LogMessageConsumer { + + LogMessageRender logMessageRender; + + @Override + public void consume(LoggerLevel level, Logger logger, String message) { + String rendered = logMessageRender.render(level, logger, message); + switch (level) { + case INFO, DEBUG -> System.out.println(rendered); + case ERROR, WARNING -> System.err.println(rendered); + } + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfig.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfig.java new file mode 100644 index 00000000..36b7890f --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfig.java @@ -0,0 +1,160 @@ +package javasabr.rlib.logger.impl.config.impl; + +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.UnsafeArray; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; +import javasabr.rlib.logger.impl.DefaultLoggerService; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.LoggerConfig; + +public class DefaultLoggerConfig implements LoggerConfig { + + static final UnsafeArray EMPTY_CONSUMERS = Array + .empty(LogMessageConsumer.class) + .asUnsafe(); + + static final UnsafeArray TRACE_AND_LOWER_LEVELS = Array + .of(LoggerLevel.TRACE) + .asUnsafe(); + static final UnsafeArray DEBUG_AND_LOWER_LEVELS = Array + .of(LoggerLevel.DEBUG, LoggerLevel.TRACE) + .asUnsafe(); + static final UnsafeArray INFO_AND_LOWER_LEVELS = Array + .of(LoggerLevel.INFO, LoggerLevel.DEBUG, LoggerLevel.TRACE) + .asUnsafe(); + static final UnsafeArray WARN_AND_LOWER_LEVELS = Array + .of(LoggerLevel.WARNING, LoggerLevel.INFO, LoggerLevel.DEBUG, LoggerLevel.TRACE) + .asUnsafe(); + static final UnsafeArray ERROR_AND_LOWER_LEVELS = Array + .of(LoggerLevel.ERROR, LoggerLevel.WARNING, LoggerLevel.INFO, LoggerLevel.DEBUG, LoggerLevel.TRACE) + .asUnsafe(); + + static final UnsafeArray TRACE_AND_HIGHER_LEVELS = Array + .of(LoggerLevel.TRACE, LoggerLevel.DEBUG, LoggerLevel.INFO, LoggerLevel.WARNING, LoggerLevel.ERROR) + .asUnsafe(); + static final UnsafeArray DEBUG_AND_HIGHER_LEVELS = Array + .of(LoggerLevel.DEBUG, LoggerLevel.INFO, LoggerLevel.WARNING, LoggerLevel.ERROR) + .asUnsafe(); + static final UnsafeArray INFO_AND_HIGHER_LEVELS = Array + .of(LoggerLevel.INFO, LoggerLevel.WARNING, LoggerLevel.ERROR) + .asUnsafe(); + static final UnsafeArray WARN_AND_HIGHER_LEVELS = Array + .of(LoggerLevel.WARNING, LoggerLevel.ERROR) + .asUnsafe(); + static final UnsafeArray ERROR_AND_HIGHER_LEVELS = Array + .of(LoggerLevel.ERROR) + .asUnsafe(); + + public static final LoggerConsumersKey ROOT_TRACE_CONSUMERS_KEY = + new LoggerConsumersKey(DefaultLoggerService.ROOT_LOGGER_NAME, LoggerLevel.TRACE); + public static final LoggerConsumersKey ROOT_DEBUG_CONSUMERS_KEY = + new LoggerConsumersKey(DefaultLoggerService.ROOT_LOGGER_NAME, LoggerLevel.DEBUG); + public static final LoggerConsumersKey ROOT_INFO_CONSUMERS_KEY = + new LoggerConsumersKey(DefaultLoggerService.ROOT_LOGGER_NAME, LoggerLevel.INFO); + public static final LoggerConsumersKey ROOT_WARN_CONSUMERS_KEY = + new LoggerConsumersKey(DefaultLoggerService.ROOT_LOGGER_NAME, LoggerLevel.WARNING); + public static final LoggerConsumersKey ROOT_ERROR_CONSUMERS_KEY = + new LoggerConsumersKey(DefaultLoggerService.ROOT_LOGGER_NAME, LoggerLevel.ERROR); + + public static final RefToRefDictionary ENABLE_ALL_LEVELS = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.TRACE); + + final RefToRefDictionary loggerLevels; + final RefToRefDictionary> loggerConsumers; + + public DefaultLoggerConfig( + RefToRefDictionary loggerLevels, + RefToRefDictionary> loggerConsumers) { + this.loggerLevels = loggerLevels; + this.loggerConsumers = prepareLoggersConsumers(loggerConsumers); + } + + @Override + public void configureLevels(Logger logger) { + LoggerLevel targetLevel = loggerLevels.get(logger.name()); + if (targetLevel == null) { + targetLevel = loggerLevels.get(DefaultLoggerService.ROOT_LOGGER_NAME); + } + if (targetLevel == null) { + return; + } + for (LoggerLevel level : DefaultLoggerService.LOGGER_LEVELS) { + logger.overrideEnabled(level, false); + } + for (LoggerLevel level : resolveConfiguringLevels(targetLevel)) { + logger.overrideEnabled(level, true); + } + } + + @Override + public UnsafeArray resolveConsumers(Logger logger, LoggerLevel level) { + String loggerName = logger.name(); + UnsafeArray applicableLevels = resolveConsumerLevels(level); + for (LoggerLevel lookupLevel : applicableLevels.wrapped()) { + @SuppressWarnings("DataFlowIssue") + UnsafeArray consumers = loggerConsumers + .get(new LoggerConsumersKey(loggerName, lookupLevel)); + if (consumers != null) { + return consumers; + } + } + for (LoggerLevel lookupLevel : applicableLevels.wrapped()) { + @SuppressWarnings("DataFlowIssue") + UnsafeArray consumers = loggerConsumers + .get(resolveRootConsumersKey(lookupLevel)); + if (consumers != null) { + return consumers; + } + } + return EMPTY_CONSUMERS; + } + + public record LoggerConsumersKey(String loggerName, LoggerLevel level) {} + + private static UnsafeArray resolveConsumerLevels(LoggerLevel level) { + return switch (level) { + case TRACE -> TRACE_AND_LOWER_LEVELS; + case DEBUG -> DEBUG_AND_LOWER_LEVELS; + case INFO -> INFO_AND_LOWER_LEVELS; + case WARNING -> WARN_AND_LOWER_LEVELS; + case ERROR -> ERROR_AND_LOWER_LEVELS; + }; + } + + private static UnsafeArray resolveConfiguringLevels(LoggerLevel level) { + return switch (level) { + case TRACE -> TRACE_AND_HIGHER_LEVELS; + case DEBUG -> DEBUG_AND_HIGHER_LEVELS; + case INFO -> INFO_AND_HIGHER_LEVELS; + case WARNING -> WARN_AND_HIGHER_LEVELS; + case ERROR -> ERROR_AND_HIGHER_LEVELS; + }; + } + + private static LoggerConsumersKey resolveRootConsumersKey(LoggerLevel level) { + return switch (level) { + case TRACE -> ROOT_TRACE_CONSUMERS_KEY; + case DEBUG -> ROOT_DEBUG_CONSUMERS_KEY; + case INFO -> ROOT_INFO_CONSUMERS_KEY; + case WARNING -> ROOT_WARN_CONSUMERS_KEY; + case ERROR -> ROOT_ERROR_CONSUMERS_KEY; + }; + } + + private static RefToRefDictionary> prepareLoggersConsumers( + RefToRefDictionary> loggerConsumers) { + var tempDictionary = DictionaryFactory + .>mutableRefToRefDictionary(); + loggerConsumers.forEach((key, consumers) -> { + UnsafeArray trimmedCopy = Array + .copyOf(consumers) + .asUnsafe(); + tempDictionary.put(key, trimmedCopy); + }); + return tempDictionary.toReadOnly(); + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfigLoader.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfigLoader.java new file mode 100644 index 00000000..659f7e48 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/DefaultLoggerConfigLoader.java @@ -0,0 +1,32 @@ +package javasabr.rlib.logger.impl.config.impl; + +import java.util.Optional; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import javasabr.rlib.logger.api.LoggerLevel; +import javasabr.rlib.logger.impl.DefaultLoggerService; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.LoggerConfig; +import javasabr.rlib.logger.impl.config.LoggerConfigLoader; +import javasabr.rlib.logger.impl.config.impl.DefaultLoggerConfig.LoggerConsumersKey; + +public class DefaultLoggerConfigLoader implements LoggerConfigLoader { + + @Override + public Optional tryToLoad() { + RefToRefDictionary loggerLevels = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.INFO); + + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_INFO_CONSUMERS_KEY, + Array.of(new ConsoleMessageConsumer(new SimpleLogMessageRender()))); + + return Optional.of(new DefaultLoggerConfig(loggerLevels, loggerConsumers)); + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/SimpleLogMessageRender.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/SimpleLogMessageRender.java new file mode 100644 index 00000000..ea5705a7 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/SimpleLogMessageRender.java @@ -0,0 +1,29 @@ +package javasabr.rlib.logger.impl.config.impl; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import javasabr.rlib.logger.api.Logger; +import javasabr.rlib.logger.api.LoggerLevel; +import javasabr.rlib.logger.impl.config.LogMessageRender; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class SimpleLogMessageRender implements LogMessageRender { + + DateTimeFormatter timeFormatter; + + public SimpleLogMessageRender() { + this.timeFormatter = DateTimeFormatter.ofPattern("d.MM.yyyy HH:mm:ss:SSS"); + } + + @Override + public String render(LoggerLevel level, Logger logger, String message) { + var timestamp = timeFormatter.format(LocalDateTime.now()); + return level.title() + + level.offset() + ' ' + + timestamp + ' ' + + logger.shortName() + ": " + + message; + } +} diff --git a/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/package-info.java b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/package-info.java new file mode 100644 index 00000000..460658b0 --- /dev/null +++ b/rlib-logger-impl/src/main/java/javasabr/rlib/logger/impl/config/impl/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.rlib.logger.impl.config.impl; + +import org.jspecify.annotations.NullMarked; diff --git a/rlib-logger-impl/src/main/resources/META-INF/services/javasabr.rlib.logger.api.LoggerFactory b/rlib-logger-impl/src/main/resources/META-INF/services/javasabr.rlib.logger.api.LoggerFactory index 05a43c4d..9746aa58 100644 --- a/rlib-logger-impl/src/main/resources/META-INF/services/javasabr.rlib.logger.api.LoggerFactory +++ b/rlib-logger-impl/src/main/resources/META-INF/services/javasabr.rlib.logger.api.LoggerFactory @@ -1 +1 @@ -javasabr.rlib.logger.impl.DefaultLoggerService \ No newline at end of file +javasabr.rlib.logger.impl.DefaultLoggerFactory diff --git a/rlib-logger-impl/src/test/java/javasabr/rlib/logger/impl/DefaultLoggerTest.java b/rlib-logger-impl/src/test/java/javasabr/rlib/logger/impl/DefaultLoggerTest.java index 7ae43cc9..70f5c333 100644 --- a/rlib-logger-impl/src/test/java/javasabr/rlib/logger/impl/DefaultLoggerTest.java +++ b/rlib-logger-impl/src/test/java/javasabr/rlib/logger/impl/DefaultLoggerTest.java @@ -2,40 +2,26 @@ 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 java.util.ArrayList; +import java.util.List; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +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 javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.impl.DefaultLoggerConfig; +import javasabr.rlib.logger.impl.config.impl.DefaultLoggerConfig.LoggerConsumersKey; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; -@ResourceLock("LoggerListeners") class DefaultLoggerTest { - - 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); - - @BeforeEach - void prepare() { - LoggerManager.addListener(LOGGER_LISTENER); - LOGS_DATA_OPERATIONS - .inWriteLock(Collection::clear); - } + private final List receivedLogs = new ArrayList<>(); @AfterEach void cleanup() { - LOGS_DATA_OPERATIONS.inWriteLock(Collection::clear); - LoggerManager.removeListener(LOGGER_LISTENER); + receivedLogs.clear(); } @Test @@ -45,44 +31,316 @@ void shouldCreateDefaultLoggerImplementation() { } @Test - void shouldWriteDataToDefaultLoggerImplementation() { + void shouldCreateLoggerWithCorrectFullNameAndShortName() { + // when: + Logger logger1 = LoggerManager.getLogger(DefaultLoggerTest.class); + Logger logger2 = LoggerManager.getLogger("javasabr.rlib.logger.impl.DefaultLoggerTest2"); + + // then + assertThat(logger1.name()).isEqualTo("javasabr.rlib.logger.impl.DefaultLoggerTest"); + assertThat(logger1.shortName()).isEqualTo("DefaultLoggerTest"); + assertThat(logger2.name()).isEqualTo("javasabr.rlib.logger.impl.DefaultLoggerTest2"); + assertThat(logger2.shortName()).isEqualTo("DefaultLoggerTest2"); + + // when: + Logger logger3 = LoggerManager.getLogger("javasabr.rlib.logger.impl.DefaultLoggerTest3."); + + // then: + assertThat(logger3.name()).isEqualTo("javasabr.rlib.logger.impl.DefaultLoggerTest3."); + assertThat(logger3.shortName()).isEqualTo("DefaultLoggerTest3"); + } + + @Test + void shouldSendLogMessagesForAllLevels() { // given: - var logger = LoggerManager.getLogger(DefaultLoggerTest.class); - logger.overrideEnabled(LoggerLevel.DEBUG, true); - logger.overrideEnabled(LoggerLevel.WARNING, true); - logger.overrideEnabled(LoggerLevel.ERROR, true); - logger.overrideEnabled(LoggerLevel.INFO, true); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig( + DefaultLoggerConfig.ENABLE_ALL_LEVELS, + loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); // when: logger.print(LoggerLevel.ERROR, "test error data"); // then: - assertThat(LOGS_DATA.size()).isEqualTo(1); - assertThat(LOGS_DATA.get(0)).startsWith("ERROR "); - assertThat(LOGS_DATA.get(0)).endsWith("DefaultLoggerTest: test error data"); + assertThat(receivedLogs.size()).isEqualTo(1); + assertThat(receivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); // when: logger.print(LoggerLevel.WARNING, "test warn data 2"); // then: - assertThat(LOGS_DATA.size()).isEqualTo(2); - assertThat(LOGS_DATA.get(1)).startsWith("WARN "); - assertThat(LOGS_DATA.get(1)).endsWith("DefaultLoggerTest: test warn data 2"); + assertThat(receivedLogs.size()).isEqualTo(2); + assertThat(receivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); // when: logger.print(LoggerLevel.DEBUG, "test debug data 3"); // then: - assertThat(LOGS_DATA.size()).isEqualTo(3); - assertThat(LOGS_DATA.get(2)).startsWith("DEBUG "); - assertThat(LOGS_DATA.get(2)).endsWith("DefaultLoggerTest: test debug data 3"); + assertThat(receivedLogs.size()).isEqualTo(3); + assertThat(receivedLogs.get(2)).isEqualTo("DEBUG javasabr.rlib.logger.impl.DefaultLoggerTest test debug data 3"); // when: logger.print(LoggerLevel.INFO, "test info data 4"); // then: - assertThat(LOGS_DATA.size()).isEqualTo(4); - assertThat(LOGS_DATA.get(3)).startsWith("INFO "); - assertThat(LOGS_DATA.get(3)).endsWith("DefaultLoggerTest: test info data 4"); + assertThat(receivedLogs.size()).isEqualTo(4); + assertThat(receivedLogs.get(3)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 4"); + + // when: + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(5); + assertThat(receivedLogs.get(4)).isEqualTo("TRACE javasabr.rlib.logger.impl.DefaultLoggerTest test info data 5"); + } + + @Test + void shouldSendOnlyErrorLogMessages() { + // given: + var enabledLevels = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.ERROR); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig(enabledLevels, loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(1); + assertThat(receivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + + // when: + logger.print(LoggerLevel.WARNING, "test warn data 2"); + logger.print(LoggerLevel.INFO, "test info data 3"); + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(1); + } + + @Test + void shouldSendOnlyWarnAndHigherLogMessages() { + // given: + var enabledLevels = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.WARNING); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig(enabledLevels, loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(1); + assertThat(receivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + + // when: + logger.print(LoggerLevel.WARNING, "test warn data 2"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(2); + assertThat(receivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + + // when: + logger.print(LoggerLevel.INFO, "test info data 3"); + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(2); + } + + @Test + void shouldSendOnlyInfoAndHigherLogMessages() { + // given: + var enabledLevels = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.INFO); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig(enabledLevels, loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(1); + assertThat(receivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + + // when: + logger.print(LoggerLevel.WARNING, "test warn data 2"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(2); + assertThat(receivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + + // when: + logger.print(LoggerLevel.INFO, "test info data 3"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(3); + assertThat(receivedLogs.get(2)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 3"); + + // when: + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(3); + } + + @Test + void shouldSendOnlyDebugAndHigherLogMessages() { + // given: + var enabledLevels = RefToRefDictionary.of( + DefaultLoggerService.ROOT_LOGGER_NAME, + LoggerLevel.DEBUG); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig(enabledLevels, loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(1); + assertThat(receivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + + // when: + logger.print(LoggerLevel.WARNING, "test warn data 2"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(2); + assertThat(receivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + + // when: + logger.print(LoggerLevel.INFO, "test info data 3"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(3); + assertThat(receivedLogs.get(2)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 3"); + + // when: + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(4); + assertThat(receivedLogs.get(3)).isEqualTo("DEBUG javasabr.rlib.logger.impl.DefaultLoggerTest test debug data 4"); + + // when: + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(receivedLogs.size()).isEqualTo(4); + } + + @Test + void shouldSendLogMessageToCorrectConsumer1() { + // given: + List traceReceivedLogs = new ArrayList<>(); + List warnReceivedLogs = new ArrayList<>(); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> traceReceivedLogs.add(level + " " + logger.name() + " " + message)), + DefaultLoggerConfig.ROOT_WARN_CONSUMERS_KEY, + Array.of((level, logger, message) -> warnReceivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig( + DefaultLoggerConfig.ENABLE_ALL_LEVELS, + loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + logger.print(LoggerLevel.WARNING, "test warn data 2"); + logger.print(LoggerLevel.INFO, "test info data 3"); + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(warnReceivedLogs).hasSize(2); + assertThat(traceReceivedLogs).hasSize(3); + assertThat(warnReceivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + assertThat(warnReceivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + assertThat(traceReceivedLogs.get(0)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 3"); + assertThat(traceReceivedLogs.get(1)).isEqualTo("DEBUG javasabr.rlib.logger.impl.DefaultLoggerTest test debug data 4"); + assertThat(traceReceivedLogs.get(2)).isEqualTo("TRACE javasabr.rlib.logger.impl.DefaultLoggerTest test info data 5"); + } + + @Test + void shouldSendLogMessageToCorrectConsumer2() { + // given: + List debugReceivedLogs = new ArrayList<>(); + List errorReceivedLogs = new ArrayList<>(); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_DEBUG_CONSUMERS_KEY, + Array.of((level, logger, message) -> debugReceivedLogs.add(level + " " + logger.name() + " " + message)), + DefaultLoggerConfig.ROOT_ERROR_CONSUMERS_KEY, + Array.of((level, logger, message) -> errorReceivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig( + DefaultLoggerConfig.ENABLE_ALL_LEVELS, + loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + logger.print(LoggerLevel.WARNING, "test warn data 2"); + logger.print(LoggerLevel.INFO, "test info data 3"); + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(errorReceivedLogs).hasSize(1); + assertThat(debugReceivedLogs).hasSize(3); + assertThat(errorReceivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + assertThat(debugReceivedLogs.get(0)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + assertThat(debugReceivedLogs.get(1)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 3"); + assertThat(debugReceivedLogs.get(2)).isEqualTo("DEBUG javasabr.rlib.logger.impl.DefaultLoggerTest test debug data 4"); + } + + @Test + void shouldSendLogMessageToCorrectConsumer3() { + // given: + List traceReceivedLogs = new ArrayList<>(); + List infoReceivedLogs = new ArrayList<>(); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> traceReceivedLogs.add(level + " " + logger.name() + " " + message)), + DefaultLoggerConfig.ROOT_INFO_CONSUMERS_KEY, + Array.of((level, logger, message) -> infoReceivedLogs.add(level + " " + logger.name() + " " + message))); + var loggerService = new DefaultLoggerService(new DefaultLoggerConfig( + DefaultLoggerConfig.ENABLE_ALL_LEVELS, + loggerConsumers)); + var logger = loggerService.getLogger(DefaultLoggerTest.class); + + // when: + logger.print(LoggerLevel.ERROR, "test error data"); + logger.print(LoggerLevel.WARNING, "test warn data 2"); + logger.print(LoggerLevel.INFO, "test info data 3"); + logger.print(LoggerLevel.DEBUG, "test debug data 4"); + logger.print(LoggerLevel.TRACE, "test info data 5"); + + // then: + assertThat(infoReceivedLogs).hasSize(3); + assertThat(traceReceivedLogs).hasSize(2); + assertThat(infoReceivedLogs.get(0)).isEqualTo("ERROR javasabr.rlib.logger.impl.DefaultLoggerTest test error data"); + assertThat(infoReceivedLogs.get(1)).isEqualTo("WARN javasabr.rlib.logger.impl.DefaultLoggerTest test warn data 2"); + assertThat(infoReceivedLogs.get(2)).isEqualTo("INFO javasabr.rlib.logger.impl.DefaultLoggerTest test info data 3"); + assertThat(traceReceivedLogs.get(0)).isEqualTo("DEBUG javasabr.rlib.logger.impl.DefaultLoggerTest test debug data 4"); + assertThat(traceReceivedLogs.get(1)).isEqualTo("TRACE javasabr.rlib.logger.impl.DefaultLoggerTest test info data 5"); } } 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 index a9bdbc6a..412538d2 100644 --- 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 @@ -2,15 +2,15 @@ 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 java.util.ArrayList; +import java.util.List; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; 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 javasabr.rlib.logger.impl.DefaultLoggerService; +import javasabr.rlib.logger.impl.config.LogMessageConsumer; +import javasabr.rlib.logger.impl.config.impl.DefaultLoggerConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceAccessMode; @@ -21,30 +21,20 @@ @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); + private final List receivedLogs = new ArrayList<>(); + private Logger logger; @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); + RefToRefDictionary> loggerConsumers = RefToRefDictionary.of( + DefaultLoggerConfig.ROOT_TRACE_CONSUMERS_KEY, + Array.of((level, logger, message) -> receivedLogs.add(level + " " + logger.name() + " " + message))); + var defaultLoggerService = new DefaultLoggerService(new DefaultLoggerConfig( + DefaultLoggerConfig.ENABLE_ALL_LEVELS, + loggerConsumers)); + logger = defaultLoggerService.getLogger(Slf4jLoggerImplTest.class); } - + @Test void shouldReturnSlf4jLoggerImplFromLoggerFactory() { // when: @@ -68,82 +58,79 @@ void shouldReturnLoggerWithMatchingName() { @Test void shouldDelegateInfoMessageToRlibLogger() { // given: - rlibLogger.overrideEnabled(LoggerLevel.INFO, true); - var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class); + var slf4jLogger = new Slf4jLoggerImpl(logger); // when: slf4jLogger.info("hello from slf4j"); // then: - assertThat(LOGS_DATA.size()) + assertThat(receivedLogs.size()) .isEqualTo(1); - assertThat(LOGS_DATA.get(0)) - .endsWith("Slf4jLoggerImplTest: hello from slf4j"); + assertThat(receivedLogs.getFirst()) + .isEqualTo("INFO javasabr.rlib.logger.slf4j.impl.Slf4jLoggerImplTest hello from slf4j"); } @Test void shouldDelegateErrorWithExceptionToRlibLogger() { // given: - rlibLogger.overrideEnabled(LoggerLevel.ERROR, true); - var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class); + var slf4jLogger = new Slf4jLoggerImpl(logger); var exception = new RuntimeException("boom"); // when: slf4jLogger.error("error occurred", exception); // then: - assertThat(LOGS_DATA.size()) + assertThat(receivedLogs.size()) .isEqualTo(1); - assertThat(LOGS_DATA.get(0)) - .contains("Slf4jLoggerImplTest: error occurred") - .contains("RuntimeException: boom"); + assertThat(receivedLogs.getFirst()) + .startsWith("ERROR javasabr.rlib.logger.slf4j.impl.Slf4jLoggerImplTest error occurred: java.lang.RuntimeException: boom"); } @Test - void shouldNotDelegateDebugWhenDisabledByDefault() { + void shouldNotDelegateDebugWhenDisabled() { // given: - var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class); + logger.overrideEnabled(LoggerLevel.DEBUG, false); + var slf4jLogger = new Slf4jLoggerImpl(logger); // when: slf4jLogger.debug("should not appear"); // then: - assertThat(LOGS_DATA.size()) + assertThat(receivedLogs.size()) .isEqualTo(0); } @Test void shouldDelegateFormattedMessageToRlibLogger() { // given: - rlibLogger.overrideEnabled(LoggerLevel.INFO, true); - var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class); + logger.overrideEnabled(LoggerLevel.INFO, true); + var slf4jLogger = new Slf4jLoggerImpl(logger); // when: slf4jLogger.info("value is {}", 42); // then: - assertThat(LOGS_DATA.size()) + assertThat(receivedLogs.size()) .isEqualTo(1); - assertThat(LOGS_DATA.get(0)) - .endsWith("Slf4jLoggerImplTest: value is 42"); + assertThat(receivedLogs.getFirst()) + .endsWith("INFO javasabr.rlib.logger.slf4j.impl.Slf4jLoggerImplTest value is 42"); } @Test void shouldDelegateFormattedMessageWithTrailingExceptionToRlibLogger() { // given: - rlibLogger.overrideEnabled(LoggerLevel.ERROR, true); - - var slf4jLogger = LoggerFactory.getLogger(Slf4jLoggerImplTest.class); + logger.overrideEnabled(LoggerLevel.ERROR, true); + var slf4jLogger = new Slf4jLoggerImpl(logger); var exception = new RuntimeException("oops"); // when: slf4jLogger.error("failed with code {}", 500, exception); // then: - assertThat(LOGS_DATA.size()) + assertThat(receivedLogs.size()) .isEqualTo(1); - assertThat(LOGS_DATA.get(0)) - .contains("Slf4jLoggerImplTest: failed with code 500") + assertThat(receivedLogs.getFirst()) + .contains("ERROR javasabr.rlib.logger.slf4j.impl.Slf4jLoggerImplTest failed with code 500: java.lang.RuntimeException: oops") .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 aca1e099..c4c7198b 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 @@ -14,6 +14,11 @@ public String name() { return logger.getName(); } + @Override + public String shortName() { + return logger.getName(); + } + @Override public boolean enabled(LoggerLevel level) { return switch (level) { diff --git a/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLoggerFactory.java b/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLoggerFactory.java index 97a49704..2920dfce 100644 --- a/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLoggerFactory.java +++ b/rlib-logger-slf4j/src/main/java/javasabr/rlib/logger/slf4j/Slf4jLoggerFactory.java @@ -1,26 +1,39 @@ package javasabr.rlib.logger.slf4j; -import java.io.Writer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javasabr.rlib.logger.api.Logger; import javasabr.rlib.logger.api.LoggerFactory; -import javasabr.rlib.logger.api.LoggerListener; +import javasabr.rlib.logger.api.LoggerService; +import javasabr.rlib.logger.api.impl.NoOpsLoggerService; public class Slf4jLoggerFactory implements LoggerFactory { + private static final LoggerService NO_OPS_LOGGER_SERVICE = new NoOpsLoggerService(); + + private final ConcurrentMap loggers; private final Logger logger; public Slf4jLoggerFactory() { this.logger = new Slf4jLogger(org.slf4j.LoggerFactory.getLogger("")); + this.loggers = new ConcurrentHashMap<>(); } @Override - public Logger make(String name) { - return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)); + public Logger getLogger(String name) { + return loggers.computeIfAbsent( + name, + key -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key.toString()))); } @Override - public Logger make(Class type) { - return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(type)); + public Logger getLogger(Class type) { + return loggers.computeIfAbsent( + type, + key -> { + var clazz = (Class) key; + return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)); + }); } @Override @@ -29,22 +42,7 @@ public Logger getDefault() { } @Override - public void addListener(LoggerListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public void addWriter(Writer writer) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeListener(LoggerListener listener) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeWriter(Writer writer) { - throw new UnsupportedOperationException(); + public LoggerService getLoggerService() { + return NO_OPS_LOGGER_SERVICE; } }