diff --git a/pom.xml b/pom.xml index 990e38e..26c05fe 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ io.github.elf4j elf4j-engine - 2.0.11 + 3.0.0 jar elf4j-engine A stand-alone Java log engine implementing the ELF4J (Easy Logging Facade for Java) API diff --git a/src/main/java/elf4j/engine/NativeLoggerFactory.java b/src/main/java/elf4j/engine/NativeLoggerFactory.java index 4512698..edcc710 100644 --- a/src/main/java/elf4j/engine/NativeLoggerFactory.java +++ b/src/main/java/elf4j/engine/NativeLoggerFactory.java @@ -27,8 +27,8 @@ import elf4j.Level; import elf4j.Logger; -import elf4j.engine.service.DefaultLogService; import elf4j.engine.service.LogService; +import elf4j.engine.service.StoppableLogService; import elf4j.engine.util.StackTraceUtils; import elf4j.spi.LoggerFactory; import lombok.NonNull; @@ -76,7 +76,7 @@ public NativeLoggerFactory() { * the class that the API client uses to obtain access to a logger instance */ public NativeLoggerFactory(@NonNull Class serviceAccessClass) { - this(DEFAULT_LOGGER_SEVERITY_LEVEL, serviceAccessClass, new DefaultLogService()); + this(DEFAULT_LOGGER_SEVERITY_LEVEL, serviceAccessClass, new StoppableLogService()); } NativeLoggerFactory(@NonNull Level defaultLoggerLevel, diff --git a/src/main/java/elf4j/engine/configuration/LogServiceConfiguration.java b/src/main/java/elf4j/engine/configuration/LogServiceConfiguration.java index ec0f5ce..90bfb02 100644 --- a/src/main/java/elf4j/engine/configuration/LogServiceConfiguration.java +++ b/src/main/java/elf4j/engine/configuration/LogServiceConfiguration.java @@ -28,9 +28,6 @@ import elf4j.engine.NativeLogger; import elf4j.engine.writer.LogWriter; -import javax.annotation.Nullable; -import java.util.Properties; - /** * */ @@ -47,12 +44,4 @@ public interface LogServiceConfiguration { * writer and that configured for the logger's caller/owner class; otherwise, false. */ boolean isEnabled(NativeLogger nativeLogger); - - /** - * @param properties - * used to refresh the logging configuration. If null, only properties reloaded from the - * configuration file will be used. Otherwise, the specified properties will replace all current properties - * and configuration file is ignored. - */ - void refresh(@Nullable Properties properties); } diff --git a/src/main/java/elf4j/engine/configuration/Refreshable.java b/src/main/java/elf4j/engine/configuration/Refreshable.java new file mode 100644 index 0000000..938d555 --- /dev/null +++ b/src/main/java/elf4j/engine/configuration/Refreshable.java @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2023 Qingtian Wang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package elf4j.engine.configuration; + +import javax.annotation.Nullable; +import java.util.Properties; + +/** + * + */ +public interface Refreshable { + /** + * @param properties + * used to refresh the logging configuration. If null, only properties reloaded from the + * configuration file will be used. Otherwise, the specified properties will replace all current properties + * and configuration file is ignored. + */ + void refresh(@Nullable Properties properties); + + /** + * reloads from original source of properties + */ + default void refresh() { + this.refresh(null); + } +} \ No newline at end of file diff --git a/src/main/java/elf4j/engine/configuration/DefaultLogServiceConfiguration.java b/src/main/java/elf4j/engine/configuration/RefreshableLogServiceConfiguration.java similarity index 90% rename from src/main/java/elf4j/engine/configuration/DefaultLogServiceConfiguration.java rename to src/main/java/elf4j/engine/configuration/RefreshableLogServiceConfiguration.java index af2e882..c5facd4 100644 --- a/src/main/java/elf4j/engine/configuration/DefaultLogServiceConfiguration.java +++ b/src/main/java/elf4j/engine/configuration/RefreshableLogServiceConfiguration.java @@ -27,6 +27,7 @@ import elf4j.Level; import elf4j.engine.NativeLogger; +import elf4j.engine.service.LogServiceManager; import elf4j.engine.writer.LogWriter; import elf4j.util.InternalLogger; import lombok.ToString; @@ -40,7 +41,7 @@ * */ @ToString -public class DefaultLogServiceConfiguration implements LogServiceConfiguration { +public class RefreshableLogServiceConfiguration implements LogServiceConfiguration, Refreshable { private final Map loggerConfigurationCache = new ConcurrentHashMap<>(); private final PropertiesLoader propertiesLoader; private boolean noop; @@ -50,12 +51,13 @@ public class DefaultLogServiceConfiguration implements LogServiceConfiguration { /** * */ - public DefaultLogServiceConfiguration() { + public RefreshableLogServiceConfiguration() { this.propertiesLoader = new PropertiesLoader(); setRepositories(this.propertiesLoader.load()); + LogServiceManager.INSTANCE.register(this); } - DefaultLogServiceConfiguration(CallerLevelRepository callerLevelRepository, WriterRepository writerRepository) { + RefreshableLogServiceConfiguration(CallerLevelRepository callerLevelRepository, WriterRepository writerRepository) { this.propertiesLoader = new PropertiesLoader(); this.callerLevelRepository = callerLevelRepository; this.writerRepository = writerRepository; diff --git a/src/main/java/elf4j/engine/service/LogServiceManager.java b/src/main/java/elf4j/engine/service/LogServiceManager.java index 5699796..ea6a9c5 100644 --- a/src/main/java/elf4j/engine/service/LogServiceManager.java +++ b/src/main/java/elf4j/engine/service/LogServiceManager.java @@ -25,35 +25,60 @@ package elf4j.engine.service; +import elf4j.engine.configuration.Refreshable; + +import java.util.HashSet; import java.util.Properties; +import java.util.Set; /** * */ -public class LogServiceManager { - private LogServiceManager() { +public enum LogServiceManager { + /** + * + */ + INSTANCE; + + private final Set refreshables = new HashSet<>(); + private final Set stoppables = new HashSet<>(); + + /** + * @param refreshable + * added to be accessible for management + */ + public void register(Refreshable refreshable) { + this.refreshables.add(refreshable); } /** - * + * @param stoppable + * added to be accessible for management + */ + public void register(Stoppable stoppable) { + this.stoppables.add(stoppable); + } + + /** + * reloads properties source for each refreshable */ - public static void refreshConfiguration() { - refreshConfiguration(null); + public void refreshAll() { + refreshables.forEach(Refreshable::refresh); } /** * @param properties - * overriding properties for the new configuration, in addition to the reloaded properties from the - * configuration file + * if non-null, replaces current configuration with the specified properties, instead of reloading from the + * original properties source; otherwise, reloads the original properties source for each refreshable. */ - public static void refreshConfiguration(Properties properties) { - DefaultLogService.refreshConfiguration(properties); + public void refreshAll(Properties properties) { + refreshables.forEach(refreshable -> refreshable.refresh(properties)); } /** * */ - public static void shutdown() { - DefaultLogService.shutdown(); + public void shutdownAll() { + stoppables.forEach(Stoppable::stop); } } diff --git a/src/main/java/elf4j/engine/service/Stoppable.java b/src/main/java/elf4j/engine/service/Stoppable.java new file mode 100644 index 0000000..9159b9f --- /dev/null +++ b/src/main/java/elf4j/engine/service/Stoppable.java @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2023 Qingtian Wang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package elf4j.engine.service; + +/** + * + */ +public interface Stoppable { + /** + * + */ + void stop(); +} diff --git a/src/main/java/elf4j/engine/service/DefaultLogService.java b/src/main/java/elf4j/engine/service/StoppableLogService.java similarity index 89% rename from src/main/java/elf4j/engine/service/DefaultLogService.java rename to src/main/java/elf4j/engine/service/StoppableLogService.java index b6d811f..15c2046 100644 --- a/src/main/java/elf4j/engine/service/DefaultLogService.java +++ b/src/main/java/elf4j/engine/service/StoppableLogService.java @@ -26,41 +26,33 @@ package elf4j.engine.service; import elf4j.engine.NativeLogger; -import elf4j.engine.configuration.DefaultLogServiceConfiguration; import elf4j.engine.configuration.LogServiceConfiguration; +import elf4j.engine.configuration.RefreshableLogServiceConfiguration; import elf4j.engine.util.StackTraceUtils; import lombok.NonNull; import java.util.Objects; -import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * */ -public class DefaultLogService implements LogService { +public class StoppableLogService implements LogService, Stoppable { private final LogServiceConfiguration logServiceConfiguration; private final WriterThread writerThread; /** * */ - public DefaultLogService() { + public StoppableLogService() { this(ServiceConfigurationHolder.INSTANCE, WriterThreadHolder.INSTANCE); } - DefaultLogService(LogServiceConfiguration logServiceConfiguration, WriterThread writerThread) { + StoppableLogService(LogServiceConfiguration logServiceConfiguration, WriterThread writerThread) { this.logServiceConfiguration = logServiceConfiguration; this.writerThread = writerThread; - } - - static void refreshConfiguration(Properties properties) { - ServiceConfigurationHolder.INSTANCE.refresh(properties); - } - - static void shutdown() { - WriterThreadHolder.INSTANCE.shutdown(); + LogServiceManager.INSTANCE.register(this); } @Override @@ -99,6 +91,11 @@ public void log(NativeLogger nativeLogger, writerThread.execute(() -> logServiceConfiguration.getLogServiceWriter().write(logEntryBuilder.build())); } + @Override + public void stop() { + this.writerThread.shutdown(); + } + /** * */ @@ -125,7 +122,7 @@ public void execute(@NonNull Runnable command) { } private static class ServiceConfigurationHolder { - private static final LogServiceConfiguration INSTANCE = new DefaultLogServiceConfiguration(); + private static final LogServiceConfiguration INSTANCE = new RefreshableLogServiceConfiguration(); } private static class WriterThreadHolder { diff --git a/src/test/java/elf4j/engine/Main.java b/src/test/java/elf4j/engine/Main.java index 0e5867b..6680e3f 100644 --- a/src/test/java/elf4j/engine/Main.java +++ b/src/test/java/elf4j/engine/Main.java @@ -77,6 +77,6 @@ public static void main(String[] args) { .log("Not a practical example but the severity level is INFO"); MoreAwaitility.block(Duration.ofMillis(200), "Making sure console streams show up"); - LogServiceManager.shutdown(); + LogServiceManager.INSTANCE.shutdownAll(); } } diff --git a/src/test/java/elf4j/engine/configuration/DefaultLogServiceConfigurationTest.java b/src/test/java/elf4j/engine/configuration/StoppableLogServiceConfigurationTest.java similarity index 74% rename from src/test/java/elf4j/engine/configuration/DefaultLogServiceConfigurationTest.java rename to src/test/java/elf4j/engine/configuration/StoppableLogServiceConfigurationTest.java index d0dee0a..1574552 100644 --- a/src/test/java/elf4j/engine/configuration/DefaultLogServiceConfigurationTest.java +++ b/src/test/java/elf4j/engine/configuration/StoppableLogServiceConfigurationTest.java @@ -45,7 +45,7 @@ import static org.mockito.BDDMockito.then; @ExtendWith(MockitoExtension.class) -class DefaultLogServiceConfigurationTest { +class StoppableLogServiceConfigurationTest { @Mock CallerLevelRepository mockCallerLevelRepository; @Mock WriterRepository mockWriterRepository; @Mock LogWriter stubLogWriter; @@ -55,19 +55,19 @@ class DefaultLogServiceConfigurationTest { class isEnabled { @Test void cacheLoadFromReposOnlyOnce() { - DefaultLogServiceConfiguration defaultLoggingConfiguration = - new DefaultLogServiceConfiguration(mockCallerLevelRepository, mockWriterRepository); + RefreshableLogServiceConfiguration refreshableLogServiceConfiguration = + new RefreshableLogServiceConfiguration(mockCallerLevelRepository, mockWriterRepository); NativeLogger nativeLogger = new NativeLogger("test.owner.class.Name", Level.OFF, mockLogService); given(mockWriterRepository.getLogServiceWriter()).willReturn(stubLogWriter); given(stubLogWriter.getMinimumOutputLevel()).willReturn(Level.TRACE); given(mockCallerLevelRepository.getMinimumOutputLevel(nativeLogger)).willReturn(Level.TRACE); - defaultLoggingConfiguration.isEnabled(nativeLogger); + refreshableLogServiceConfiguration.isEnabled(nativeLogger); assertSame(stubLogWriter, mockWriterRepository.getLogServiceWriter()); then(mockCallerLevelRepository).should().getMinimumOutputLevel(nativeLogger); - defaultLoggingConfiguration.isEnabled(nativeLogger); + refreshableLogServiceConfiguration.isEnabled(nativeLogger); then(mockWriterRepository).shouldHaveNoMoreInteractions(); then(mockCallerLevelRepository).shouldHaveNoMoreInteractions(); @@ -78,20 +78,22 @@ void cacheLoadFromReposOnlyOnce() { class refresh { @Test void reload() { - DefaultLogServiceConfiguration defaultServiceConfiguration = new DefaultLogServiceConfiguration(); + RefreshableLogServiceConfiguration refreshableLogServiceConfiguration = + new RefreshableLogServiceConfiguration(); - defaultServiceConfiguration.refresh(null); + refreshableLogServiceConfiguration.refresh(null); - assertTrue(defaultServiceConfiguration.getLogServiceWriter() instanceof WriterGroup); + assertTrue(refreshableLogServiceConfiguration.getLogServiceWriter() instanceof WriterGroup); } @Test void replace() { - DefaultLogServiceConfiguration defaultServiceConfiguration = new DefaultLogServiceConfiguration(); + RefreshableLogServiceConfiguration refreshableLogServiceConfiguration = + new RefreshableLogServiceConfiguration(); - defaultServiceConfiguration.refresh(new Properties()); + refreshableLogServiceConfiguration.refresh(new Properties()); - assertTrue(defaultServiceConfiguration.getLogServiceWriter() instanceof StandardStreamsWriter); + assertTrue(refreshableLogServiceConfiguration.getLogServiceWriter() instanceof StandardStreamsWriter); } } } \ No newline at end of file diff --git a/src/test/java/elf4j/engine/service/DefaultLogServiceTest.java b/src/test/java/elf4j/engine/service/StoppableLogServiceTest.java similarity index 88% rename from src/test/java/elf4j/engine/service/DefaultLogServiceTest.java rename to src/test/java/elf4j/engine/service/StoppableLogServiceTest.java index 6cbae5c..ed924ad 100644 --- a/src/test/java/elf4j/engine/service/DefaultLogServiceTest.java +++ b/src/test/java/elf4j/engine/service/StoppableLogServiceTest.java @@ -46,7 +46,7 @@ import static org.mockito.Mockito.never; @ExtendWith(MockitoExtension.class) -class DefaultLogServiceTest { +class StoppableLogServiceTest { private static class StubWriterThread implements WriterThread { @Override @@ -62,13 +62,13 @@ public void execute(Runnable command) { @Nested class isEnabled { NativeLogger stubLogger; - DefaultLogService logService; + StoppableLogService logService; @Mock LogServiceConfiguration mockLogServiceConfiguration; @Mock WriterThread mockWriterThread; @Test void delegateToConfiguration() { - logService = new DefaultLogService(mockLogServiceConfiguration, mockWriterThread); + logService = new StoppableLogService(mockLogServiceConfiguration, mockWriterThread); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); logService.isEnabled(stubLogger); @@ -80,7 +80,7 @@ void delegateToConfiguration() { @Nested class log { NativeLogger stubLogger; - DefaultLogService logService; + StoppableLogService logService; @Mock LogServiceConfiguration mockLogServiceConfiguration; @Mock LogWriter mockLogWriter; @Mock WriterThread mockWriterThread; @@ -88,7 +88,7 @@ class log { @Test void async() { - logService = new DefaultLogService(mockLogServiceConfiguration, mockWriterThread); + logService = new StoppableLogService(mockLogServiceConfiguration, mockWriterThread); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true); given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter); @@ -100,7 +100,7 @@ void async() { @Test void callThreadRequired() { - logService = new DefaultLogService(mockLogServiceConfiguration, new StubWriterThread()); + logService = new StoppableLogService(mockLogServiceConfiguration, new StubWriterThread()); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogWriter.includeCallerThread()).willReturn(true); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true); @@ -116,7 +116,7 @@ void callThreadRequired() { @Test void callThreadNotRequired() { - logService = new DefaultLogService(mockLogServiceConfiguration, new StubWriterThread()); + logService = new StoppableLogService(mockLogServiceConfiguration, new StubWriterThread()); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogWriter.includeCallerThread()).willReturn(false); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true); @@ -130,7 +130,7 @@ void callThreadNotRequired() { @Test void callerDetailRequired() { - logService = new DefaultLogService(mockLogServiceConfiguration, new StubWriterThread()); + logService = new StoppableLogService(mockLogServiceConfiguration, new StubWriterThread()); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true); given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter); @@ -144,7 +144,7 @@ void callerDetailRequired() { @Test void callDetailNotRequired() { - logService = new DefaultLogService(mockLogServiceConfiguration, new StubWriterThread()); + logService = new StoppableLogService(mockLogServiceConfiguration, new StubWriterThread()); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true); given(mockLogWriter.includeCallerDetail()).willReturn(false); @@ -158,7 +158,7 @@ void callDetailNotRequired() { @Test void onlyLogWhenEnabled() { - logService = new DefaultLogService(mockLogServiceConfiguration, new StubWriterThread()); + logService = new StoppableLogService(mockLogServiceConfiguration, new StubWriterThread()); stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService); given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(false);