From 58fb9e1acafdfdaeaa86943898d01a616846f465 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:40:00 +0100 Subject: [PATCH 01/13] refactor: Unify config namespaces --- penna-api/src/main/java/module-info.java | 3 +-- .../java/penna/api/{configv2 => config}/ConfigToLogger.java | 4 +--- .../src/main/java/penna/api/{configv2 => config}/Manager.java | 4 +--- .../main/java/penna/api/{configv2 => config}/Provider.java | 2 +- .../src/main/java/penna/api/{configv2 => config}/Storage.java | 4 +--- ...{penna.api.configv2.Provider => penna.api.config.Provider} | 0 6 files changed, 5 insertions(+), 12 deletions(-) rename penna-api/src/main/java/penna/api/{configv2 => config}/ConfigToLogger.java (92%) rename penna-api/src/main/java/penna/api/{configv2 => config}/Manager.java (98%) rename penna-api/src/main/java/penna/api/{configv2 => config}/Provider.java (97%) rename penna-api/src/main/java/penna/api/{configv2 => config}/Storage.java (93%) rename penna-yaml-config/src/main/resources/META-INF/services/{penna.api.configv2.Provider => penna.api.config.Provider} (100%) diff --git a/penna-api/src/main/java/module-info.java b/penna-api/src/main/java/module-info.java index db23771..6d4edfc 100644 --- a/penna-api/src/main/java/module-info.java +++ b/penna-api/src/main/java/module-info.java @@ -1,4 +1,4 @@ -import penna.api.configv2.Provider; +import penna.api.config.Provider; /** * Penna API is a thin set of classes and records that are common-ground between the {@code penna.core} project @@ -13,5 +13,4 @@ exports penna.api.models; exports penna.api.config; - exports penna.api.configv2; } \ No newline at end of file diff --git a/penna-api/src/main/java/penna/api/configv2/ConfigToLogger.java b/penna-api/src/main/java/penna/api/config/ConfigToLogger.java similarity index 92% rename from penna-api/src/main/java/penna/api/configv2/ConfigToLogger.java rename to penna-api/src/main/java/penna/api/config/ConfigToLogger.java index 4f1d2bc..b405f55 100644 --- a/penna-api/src/main/java/penna/api/configv2/ConfigToLogger.java +++ b/penna-api/src/main/java/penna/api/config/ConfigToLogger.java @@ -1,6 +1,4 @@ -package penna.api.configv2; - -import penna.api.config.Config; +package penna.api.config; /** * Class exists to contextually bind a configuration object to a logger by its name. diff --git a/penna-api/src/main/java/penna/api/configv2/Manager.java b/penna-api/src/main/java/penna/api/config/Manager.java similarity index 98% rename from penna-api/src/main/java/penna/api/configv2/Manager.java rename to penna-api/src/main/java/penna/api/config/Manager.java index e7dee89..2ee969a 100644 --- a/penna-api/src/main/java/penna/api/configv2/Manager.java +++ b/penna-api/src/main/java/penna/api/config/Manager.java @@ -1,6 +1,4 @@ -package penna.api.configv2; - -import penna.api.config.Config; +package penna.api.config; import java.util.Objects; import java.util.ServiceLoader; diff --git a/penna-api/src/main/java/penna/api/configv2/Provider.java b/penna-api/src/main/java/penna/api/config/Provider.java similarity index 97% rename from penna-api/src/main/java/penna/api/configv2/Provider.java rename to penna-api/src/main/java/penna/api/config/Provider.java index 7aae2fc..f83ec39 100644 --- a/penna-api/src/main/java/penna/api/configv2/Provider.java +++ b/penna-api/src/main/java/penna/api/config/Provider.java @@ -1,4 +1,4 @@ -package penna.api.configv2; +package penna.api.config; /** * This interface allows for additional configuration providers to interact with the logger setup. diff --git a/penna-api/src/main/java/penna/api/configv2/Storage.java b/penna-api/src/main/java/penna/api/config/Storage.java similarity index 93% rename from penna-api/src/main/java/penna/api/configv2/Storage.java rename to penna-api/src/main/java/penna/api/config/Storage.java index 6554f79..6a80794 100644 --- a/penna-api/src/main/java/penna/api/configv2/Storage.java +++ b/penna-api/src/main/java/penna/api/config/Storage.java @@ -1,6 +1,4 @@ -package penna.api.configv2; - -import penna.api.config.Config; +package penna.api.config; /** * A Config Storage defines a class that stores the configurations for all logs in the hierarchy. diff --git a/penna-yaml-config/src/main/resources/META-INF/services/penna.api.configv2.Provider b/penna-yaml-config/src/main/resources/META-INF/services/penna.api.config.Provider similarity index 100% rename from penna-yaml-config/src/main/resources/META-INF/services/penna.api.configv2.Provider rename to penna-yaml-config/src/main/resources/META-INF/services/penna.api.config.Provider From e00abf8bd38e16c54dd8f7ac65c1661d0447d7b1 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:41:08 +0100 Subject: [PATCH 02/13] refactor: Use virtual threads --- .../main/java/penna/core/internals/Clock.java | 2 +- .../penna/core/internals/ThreadCreator.java | 24 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 penna-core/src/main/java/penna/core/internals/ThreadCreator.java diff --git a/penna-core/src/main/java/penna/core/internals/Clock.java b/penna-core/src/main/java/penna/core/internals/Clock.java index daddd15..e280610 100644 --- a/penna-core/src/main/java/penna/core/internals/Clock.java +++ b/penna-core/src/main/java/penna/core/internals/Clock.java @@ -52,7 +52,7 @@ private Clock() {} * increment the clock every X nanoseconds as defined by {@link Clock#REFRESH_RATE} and after every * Y iterations, as defined by {@link Clock#PRECISION}, we sync back with the system clock. */ - private static final Thread clockThread = ThreadCreator.newThread("penna-clock-ticker", () -> { + private static final Thread clockThread = Thread.ofVirtual().name("penna-clock-ticker").start(() -> { while(!Thread.currentThread().isInterrupted()) { var ts = timestamp.incrementAndGet(); diff --git a/penna-core/src/main/java/penna/core/internals/ThreadCreator.java b/penna-core/src/main/java/penna/core/internals/ThreadCreator.java deleted file mode 100644 index cf270a7..0000000 --- a/penna-core/src/main/java/penna/core/internals/ThreadCreator.java +++ /dev/null @@ -1,24 +0,0 @@ -package penna.core.internals; - - -/** - * The thread creator is a utility class that allows us to create worker threads in penna. - * They have a few properties in common such as being daemon threads and belonging to the same {@link ThreadGroup} - *
- * @see Thread#setDaemon(boolean) - */ -public final class ThreadCreator { - private ThreadCreator() {} - private static final ThreadGroup pennaThreadGroup = new ThreadGroup(Thread.currentThread().getThreadGroup(), "penna"); - - public static Thread newThread(String name, Runnable target){ - var thread = new Thread(pennaThreadGroup, target); - - // We want to be able to identify the thread - thread.setName(name); - - // Thread has to be a daemon thread in order for us to be able to close the JVM while it still runs. - thread.setDaemon(true); - return thread; - } -} From 68a7f6e3fc1c0c55436c891f04f2c2d237baa5d4 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:42:12 +0100 Subject: [PATCH 03/13] refactor: Remove runtime sink rebinding It should not be possible to rebind the sink in runtime. For tests, we still allow injecting the sinks, but not through a manager piece. Instead, the Object pool itself replaces the old objects with newly created ones with the supplied sink. --- .../core/internals/LogUnitContextPool.java | 14 +++- .../penna/core/logger/guard/LevelGuard.java | 4 +- .../main/java/penna/core/sink/CoreSink.java | 71 +++++++++++-------- .../java/penna/core/sink/InternalSink.java | 3 + .../java/penna/core/sink/NonStandardSink.java | 3 - .../main/java/penna/core/sink/ProxySink.java | 23 ------ .../src/main/java/penna/core/sink/Sink.java | 2 +- .../java/penna/core/sink/SinkManager.java | 26 ------- .../core/slf4j/PennaServiceProvider.java | 13 +--- .../internals/TestContextPoolManager.java | 13 ++++ .../java/penna/core/logger/CoreSinkTests.java | 6 +- .../internals/TestContextPoolManager.java | 14 ++++ .../java/penna/core/logger/LoggerTests.java | 8 +-- .../test/java/penna/core/sink/TestSink.java | 2 +- 14 files changed, 95 insertions(+), 107 deletions(-) create mode 100644 penna-core/src/main/java/penna/core/sink/InternalSink.java delete mode 100644 penna-core/src/main/java/penna/core/sink/NonStandardSink.java delete mode 100644 penna-core/src/main/java/penna/core/sink/ProxySink.java delete mode 100644 penna-core/src/main/java/penna/core/sink/SinkManager.java create mode 100644 penna-core/src/propertyTesting/java/penna/core/internals/TestContextPoolManager.java create mode 100644 penna-core/src/test/java/penna/core/internals/TestContextPoolManager.java diff --git a/penna-core/src/main/java/penna/core/internals/LogUnitContextPool.java b/penna-core/src/main/java/penna/core/internals/LogUnitContextPool.java index 71a9900..009d3d7 100644 --- a/penna-core/src/main/java/penna/core/internals/LogUnitContextPool.java +++ b/penna-core/src/main/java/penna/core/internals/LogUnitContextPool.java @@ -1,11 +1,14 @@ package penna.core.internals; +import org.jetbrains.annotations.VisibleForTesting; import penna.core.logger.LogUnitContext; import penna.core.models.PennaLogEvent; -import penna.core.sink.SinkManager; +import penna.core.sink.CoreSink; +import penna.core.sink.Sink; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; /** * This Object Pool ensures each thread will have access to a single pooled {@link LogUnitContext}, @@ -26,7 +29,7 @@ public final class LogUnitContextPool { private final LogUnitContext[] objectGroup; private LogUnitContext leafObject(int index) { - return new LogUnitContext(this, index, SinkManager.Instance.get(), new PennaLogEvent()); + return new LogUnitContext(this, index, CoreSink.getSink(), new PennaLogEvent()); } public LogUnitContextPool() { @@ -39,6 +42,13 @@ public LogUnitContextPool() { } } + @VisibleForTesting + void refillThePool(Supplier sinkSupplier) { + for (int i = 0; i < objectGroup.length; i++) { + objectGroup[i] = new LogUnitContext(this, i, sinkSupplier.get(), objectGroup[i].logEvent()); + } + } + public void release(int index) { locks[index].unlock(); } diff --git a/penna-core/src/main/java/penna/core/logger/guard/LevelGuard.java b/penna-core/src/main/java/penna/core/logger/guard/LevelGuard.java index b088488..785e7eb 100644 --- a/penna-core/src/main/java/penna/core/logger/guard/LevelGuard.java +++ b/penna-core/src/main/java/penna/core/logger/guard/LevelGuard.java @@ -1,6 +1,7 @@ package penna.core.logger.guard; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.event.Level; import org.slf4j.spi.LoggingEventBuilder; import org.slf4j.spi.NOPLoggingEventBuilder; @@ -20,7 +21,8 @@ public sealed interface LevelGuard permits DebugLevelGuard, ErrorLevelGuard, InfoLevelGuard, NOPGuard, TraceLevelGuard, WarnLevelGuard { final class Shared { - private static final LogUnitContextPool logUnits = new LogUnitContextPool(); + @VisibleForTesting + public static final LogUnitContextPool logUnits = new LogUnitContextPool(); } final class FromConfig { diff --git a/penna-core/src/main/java/penna/core/sink/CoreSink.java b/penna-core/src/main/java/penna/core/sink/CoreSink.java index 06fc40a..08f08f4 100644 --- a/penna-core/src/main/java/penna/core/sink/CoreSink.java +++ b/penna-core/src/main/java/penna/core/sink/CoreSink.java @@ -179,7 +179,7 @@ private void writeThrowable(final Throwable throwable, LogConfig config, int ini } } - private void writeMap(LogConfig config, final Map map) throws IOException { + private void writeMap(LogConfig config, final Map map) throws IOException { jsonGenerator.checkSpace(4); jsonGenerator.openObject(); for (var key : map.keySet()) { @@ -194,7 +194,7 @@ private void writeMap(LogConfig config, final Map map) throws IOException { jsonGenerator.writeSep(); } - private void writeArray(LogConfig config, final List lst) throws IOException { + private void writeArray(LogConfig config, final List lst) throws IOException { jsonGenerator.openArray(); for (Object o : lst) { writeObject(config, o); @@ -214,35 +214,44 @@ private void writeArray(LogConfig config, final Object... lst) throws IOExceptio } private void writeObject(LogConfig config, final Object object) throws IOException { - if (object instanceof Throwable throwable) { - config.filter.reset(); - writeThrowable(throwable, config, 0); - } else if (object instanceof Map map) { - writeMap(config, map); - } else if (object instanceof List lst) { - writeArray(config, lst); - } else if (object instanceof Object[] lst) { - writeArray(config, lst); - } else if (object instanceof String str) { - jsonGenerator.checkSpace(str.length()); - jsonGenerator.writeString(str); - } else if (object instanceof Long num) { - // Long.MIN_SIZE will yield 20 chars - jsonGenerator.checkSpace(20); - jsonGenerator.writeNumber(num); - } else if (object instanceof Integer num) { - jsonGenerator.checkSpace(11); - jsonGenerator.writeNumber(num); - } else if (object instanceof Float num) { - jsonGenerator.checkSpace(32); - jsonGenerator.writeNumber(num); - } else if (object instanceof Double num) { - jsonGenerator.checkSpace(64); - jsonGenerator.writeNumber(num); - } else { - var str = object.toString(); - jsonGenerator.checkSpace(str.length()); - jsonGenerator.writeString(str); + + switch (object) { + case Throwable throwable -> { + config.filter.reset(); + writeThrowable(throwable, config, 0); + } + case Map map -> writeMap(config, map); + case List lst -> writeArray(config, lst); + case Object[] lst -> writeArray(config, lst); + case String str -> { + jsonGenerator.checkSpace(str.length()); + jsonGenerator.writeString(str); + } + case Long num -> { + // Long.MIN_SIZE will yield 20 chars + jsonGenerator.checkSpace(20); + jsonGenerator.writeNumber(num); + } + case Integer num -> { + jsonGenerator.checkSpace(11); + jsonGenerator.writeNumber(num); + } + case Float num -> { + jsonGenerator.checkSpace(32); + jsonGenerator.writeNumber(num); + } + case Double num -> { + jsonGenerator.checkSpace(64); + jsonGenerator.writeNumber(num); + } + case null -> { + jsonGenerator.writeNull(); + } + default -> { + var str = object.toString(); + jsonGenerator.checkSpace(str.length()); + jsonGenerator.writeString(str); + } } } diff --git a/penna-core/src/main/java/penna/core/sink/InternalSink.java b/penna-core/src/main/java/penna/core/sink/InternalSink.java new file mode 100644 index 0000000..54e6d6c --- /dev/null +++ b/penna-core/src/main/java/penna/core/sink/InternalSink.java @@ -0,0 +1,3 @@ +package penna.core.sink; + +public non-sealed interface InternalSink extends Sink {} diff --git a/penna-core/src/main/java/penna/core/sink/NonStandardSink.java b/penna-core/src/main/java/penna/core/sink/NonStandardSink.java deleted file mode 100644 index fd2feeb..0000000 --- a/penna-core/src/main/java/penna/core/sink/NonStandardSink.java +++ /dev/null @@ -1,3 +0,0 @@ -package penna.core.sink; - -public non-sealed interface NonStandardSink extends Sink {} diff --git a/penna-core/src/main/java/penna/core/sink/ProxySink.java b/penna-core/src/main/java/penna/core/sink/ProxySink.java deleted file mode 100644 index bd67041..0000000 --- a/penna-core/src/main/java/penna/core/sink/ProxySink.java +++ /dev/null @@ -1,23 +0,0 @@ -package penna.core.sink; - -import penna.core.models.PennaLogEvent; - -import java.io.IOException; - -public final class ProxySink implements Sink { - - private Sink realImpl; - - public ProxySink(Sink realImpl) { - this.realImpl = realImpl; - } - - void replaceImpl(Sink newImpl) { - this.realImpl = newImpl; - } - - @Override - public void write(PennaLogEvent logEvent) throws IOException { - this.realImpl.write(logEvent); - } -} diff --git a/penna-core/src/main/java/penna/core/sink/Sink.java b/penna-core/src/main/java/penna/core/sink/Sink.java index 45859fe..fc19891 100644 --- a/penna-core/src/main/java/penna/core/sink/Sink.java +++ b/penna-core/src/main/java/penna/core/sink/Sink.java @@ -4,6 +4,6 @@ import java.io.IOException; -public sealed interface Sink permits CoreSink, ProxySink, NonStandardSink { +public sealed interface Sink permits CoreSink, InternalSink { void write(PennaLogEvent logEvent) throws IOException; } \ No newline at end of file diff --git a/penna-core/src/main/java/penna/core/sink/SinkManager.java b/penna-core/src/main/java/penna/core/sink/SinkManager.java deleted file mode 100644 index fd9f87f..0000000 --- a/penna-core/src/main/java/penna/core/sink/SinkManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package penna.core.sink; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -public final class SinkManager { - private Supplier factory = CoreSink::new; - private final List proxies = new ArrayList<>(); - - public static final SinkManager Instance = new SinkManager(); - - private SinkManager() {} - - public void replace(Supplier factory) { - this.factory = factory; - - proxies.forEach(proxy -> proxy.replaceImpl(factory.get())); - } - - public ProxySink get() { - var proxy = new ProxySink(factory.get()); - proxies.add(proxy); - return proxy; - } -} diff --git a/penna-core/src/main/java/penna/core/slf4j/PennaServiceProvider.java b/penna-core/src/main/java/penna/core/slf4j/PennaServiceProvider.java index 3e8141c..0074c26 100644 --- a/penna-core/src/main/java/penna/core/slf4j/PennaServiceProvider.java +++ b/penna-core/src/main/java/penna/core/slf4j/PennaServiceProvider.java @@ -5,12 +5,7 @@ import org.slf4j.helpers.BasicMarkerFactory; import org.slf4j.spi.MDCAdapter; import org.slf4j.spi.SLF4JServiceProvider; -import penna.api.configv2.Manager; -import penna.core.sink.NonStandardSink; -import penna.core.sink.SinkManager; - -import java.util.Optional; -import java.util.ServiceLoader; +import penna.api.config.Manager; public final class PennaServiceProvider implements SLF4JServiceProvider { @@ -46,14 +41,8 @@ public String getRequestedApiVersion() { return REQUESTED_API_VERSION; } - private Optional> getOverridingSink() { - ServiceLoader sinkProvider = ServiceLoader.load(NonStandardSink.class); - return sinkProvider.stream().findFirst(); - } - @Override public void initialize() { - getOverridingSink().ifPresent(nonStandardSinkProvider -> SinkManager.Instance.replace(nonStandardSinkProvider::get)); PennaLoggerFactory pennaLoggerFactory = PennaLoggerFactory.getInstance(); Manager.Factory.initialize(pennaLoggerFactory); diff --git a/penna-core/src/propertyTesting/java/penna/core/internals/TestContextPoolManager.java b/penna-core/src/propertyTesting/java/penna/core/internals/TestContextPoolManager.java new file mode 100644 index 0000000..402d835 --- /dev/null +++ b/penna-core/src/propertyTesting/java/penna/core/internals/TestContextPoolManager.java @@ -0,0 +1,13 @@ +package penna.core.internals; + +import penna.core.logger.guard.LevelGuard; +import penna.core.sink.Sink; + +import java.util.function.Supplier; + +public class TestContextPoolManager { + private TestContextPoolManager() {} + public static void replace(Supplier replacementSupplier) { + LevelGuard.Shared.logUnits.refillThePool(replacementSupplier); + } +} diff --git a/penna-core/src/propertyTesting/java/penna/core/logger/CoreSinkTests.java b/penna-core/src/propertyTesting/java/penna/core/logger/CoreSinkTests.java index a1c002c..05d7f05 100644 --- a/penna-core/src/propertyTesting/java/penna/core/logger/CoreSinkTests.java +++ b/penna-core/src/propertyTesting/java/penna/core/logger/CoreSinkTests.java @@ -8,11 +8,11 @@ import org.slf4j.event.Level; import penna.api.config.Config; import penna.api.models.LogField; +import penna.core.internals.TestContextPoolManager; import penna.core.models.KeyValuePair; import penna.core.models.LogConfig; import penna.core.models.PennaLogEvent; import penna.core.sink.CoreSink; -import penna.core.sink.SinkManager; import java.io.File; import java.io.FileOutputStream; @@ -154,7 +154,7 @@ void validJsonMessage(@ForAll("fields") LogField[] fields) throws IOException { File testFile = File.createTempFile("valid-message", ".json"); FileOutputStream fos = new FileOutputStream(testFile); - SinkManager.Instance.replace(() -> new CoreSink(fos)); + TestContextPoolManager.replace(() -> new CoreSink(fos)); Config config = Config.getDefault().replaceFields(fields); LoggerStorage cache = new LoggerStorage(); @@ -184,7 +184,7 @@ void validGenericMessage( File testFile = File.createTempFile("valid-message", ".json"); FileOutputStream fos = new FileOutputStream(testFile); - SinkManager.Instance.replace(() -> new CoreSink(fos)); + TestContextPoolManager.replace(() -> new CoreSink(fos)); Config config = Config.getDefault().replaceFields(fields); LoggerStorage cache = new LoggerStorage(); diff --git a/penna-core/src/test/java/penna/core/internals/TestContextPoolManager.java b/penna-core/src/test/java/penna/core/internals/TestContextPoolManager.java new file mode 100644 index 0000000..2463cb9 --- /dev/null +++ b/penna-core/src/test/java/penna/core/internals/TestContextPoolManager.java @@ -0,0 +1,14 @@ +package penna.core.internals; + +import penna.core.logger.guard.LevelGuard; +import penna.core.sink.Sink; +import penna.core.sink.TestSink; + +import java.util.function.Supplier; + +public class TestContextPoolManager { + private TestContextPoolManager() {} + public static void replace(Supplier replacementSupplier) { + LevelGuard.Shared.logUnits.refillThePool(replacementSupplier); + } +} diff --git a/penna-core/src/test/java/penna/core/logger/LoggerTests.java b/penna-core/src/test/java/penna/core/logger/LoggerTests.java index 5078442..f15ab66 100644 --- a/penna-core/src/test/java/penna/core/logger/LoggerTests.java +++ b/penna-core/src/test/java/penna/core/logger/LoggerTests.java @@ -7,10 +7,10 @@ import org.slf4j.MDC; import org.slf4j.Marker; import org.slf4j.MarkerFactory; +import penna.core.internals.TestContextPoolManager; import penna.core.logger.guard.InfoLevelGuard; import penna.core.sink.CoreSink; import penna.core.sink.Sink; -import penna.core.sink.SinkManager; import penna.core.sink.TestSink; import java.io.File; @@ -50,7 +50,7 @@ void can_write_log_messages() { AtomicInteger counter = new AtomicInteger(0); Sink checker = new TestSink(mle -> counter.getAndIncrement()); - SinkManager.Instance.replace(() -> checker); + TestContextPoolManager.replace(() -> checker); PennaLogger pennaLogger = cache.getOrCreate("test"); @@ -85,7 +85,7 @@ void markers_are_kept() { } }); - SinkManager.Instance.replace(() -> checker); + TestContextPoolManager.replace(() -> checker); PennaLogger pennaLogger = cache.getOrCreate("test"); Assertions.assertEquals(InfoLevelGuard.singleton(), pennaLogger.levelGuard); @@ -110,7 +110,7 @@ void everything_added_to_the_log_is_present_in_the_message() throws IOException File testFile = File.createTempFile("valid-message", ".json"); FileOutputStream fos = new FileOutputStream(testFile); - SinkManager.Instance.replace(() -> new CoreSink(fos)); + TestContextPoolManager.replace(() -> new CoreSink(fos)); MDC.put("key", "value"); logger.atInfo() diff --git a/penna-core/src/test/java/penna/core/sink/TestSink.java b/penna-core/src/test/java/penna/core/sink/TestSink.java index a800e67..55df40e 100644 --- a/penna-core/src/test/java/penna/core/sink/TestSink.java +++ b/penna-core/src/test/java/penna/core/sink/TestSink.java @@ -5,7 +5,7 @@ import java.io.IOException; import java.util.function.Consumer; -public final class TestSink implements NonStandardSink { +public final class TestSink implements InternalSink { final Consumer consumer; From f86902ad8f559b0cd266e7e3f9934ae375ce7746 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:45:08 +0100 Subject: [PATCH 04/13] refactor: Use line separator directly --- penna-core/src/main/java/penna/core/internals/DirectJson.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/penna-core/src/main/java/penna/core/internals/DirectJson.java b/penna-core/src/main/java/penna/core/internals/DirectJson.java index 94a76b0..9416035 100644 --- a/penna-core/src/main/java/penna/core/internals/DirectJson.java +++ b/penna-core/src/main/java/penna/core/internals/DirectJson.java @@ -16,7 +16,7 @@ public final class DirectJson implements Closeable { private static final int INITIAL_BUFFER_SIZE = 2 * 1024; private int highWatermark = (int) Math.ceil(INITIAL_BUFFER_SIZE * 0.8); - private static final byte[] LINE_BREAK = System.getProperty("line.separator").getBytes(StandardCharsets.UTF_8); + private static final byte[] LINE_BREAK = System.lineSeparator().getBytes(StandardCharsets.UTF_8); private static final byte QUOTE = '"'; private static final byte ENTRY_SEP = ':'; private static final byte KV_SEP = ','; From 3768911548e2336ccc906947be338c56cbfd0c66 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:45:21 +0100 Subject: [PATCH 05/13] refactor: Make it safe to serialize --- .../main/java/penna/core/internals/store/StringTreeMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/penna-core/src/main/java/penna/core/internals/store/StringTreeMap.java b/penna-core/src/main/java/penna/core/internals/store/StringTreeMap.java index 3393c82..a60a494 100644 --- a/penna-core/src/main/java/penna/core/internals/store/StringTreeMap.java +++ b/penna-core/src/main/java/penna/core/internals/store/StringTreeMap.java @@ -1,5 +1,6 @@ package penna.core.internals.store; +import java.io.Serial; import java.util.Map; import java.util.TreeMap; @@ -13,6 +14,9 @@ */ public final class StringTreeMap extends TreeMap implements StringMap { + @Serial + private static final long serialVersionUID = 23827L; + public StringTreeMap() { super(); } From 1eacac05278fef11507c194e8d566baa8b8d2235 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:45:50 +0100 Subject: [PATCH 06/13] refactor: Remove unnecessary locks and checks --- .../java/penna/core/logger/LoggerStorage.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/penna-core/src/main/java/penna/core/logger/LoggerStorage.java b/penna-core/src/main/java/penna/core/logger/LoggerStorage.java index 6e749c4..82d8bef 100644 --- a/penna-core/src/main/java/penna/core/logger/LoggerStorage.java +++ b/penna-core/src/main/java/penna/core/logger/LoggerStorage.java @@ -60,12 +60,12 @@ public static Node create(String component, Config config) { public final Lock lock = new ReentrantLock(); - void setConfigAndUpdateRecursively(Config baseConfig) { + void setConfigAndUpdateRecursively(@NotNull Config baseConfig) { lock.lock(); try { configRef = baseConfig; - if (loggerRef != null && baseConfig != null) { + if (loggerRef != null) { loggerRef.updateConfig(baseConfig); } } finally { @@ -74,14 +74,18 @@ void setConfigAndUpdateRecursively(Config baseConfig) { if (children[1] != null) {children[1].updateRecursively(baseConfig);} } - void updateRecursively(Config baseConfig) { + void updateRecursively(@NotNull Config baseConfig) { lock.lock(); try { if (configRef != null) { + // TODO: This might be a little problematic. Needs to be investigated further + // A Configuration object might need a stamp so we can differentiate + // a valid replacement (i.e. a newer version is updating old data) from an + // invalid one. configRef = baseConfig; } - if (loggerRef != null && baseConfig != null) { + if (loggerRef != null) { loggerRef.updateConfig(baseConfig); } } finally { @@ -184,12 +188,6 @@ public Config getConfig(@NotNull String prefix) { } public void replaceConfig(@NotNull Config newConfig) { - root.lock.lock(); - try { - root.configRef = newConfig; - } finally { - root.lock.unlock(); - } root.updateRecursively(newConfig); } } \ No newline at end of file From 7d38f650f09f98dc3a8df8d7880c1bdf2a53c780 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:46:15 +0100 Subject: [PATCH 07/13] refactor: Rename imports --- penna-core/src/main/java/module-info.java | 9 --------- .../main/java/penna/core/slf4j/PennaLoggerFactory.java | 4 ++-- penna-yaml-config/src/main/java/module-info.java | 4 ++-- .../main/java/penna/config/yaml/YamlConfigProvider.java | 4 ++-- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/penna-core/src/main/java/module-info.java b/penna-core/src/main/java/module-info.java index 2672671..fd2471c 100644 --- a/penna-core/src/main/java/module-info.java +++ b/penna-core/src/main/java/module-info.java @@ -1,19 +1,10 @@ import penna.core.slf4j.PennaServiceProvider; module penna.core { - uses penna.core.sink.NonStandardSink; // Depends explicitly on the SLF4J api requires org.slf4j; requires transitive penna.api; // Exposes a service provider for SLF4j provides org.slf4j.spi.SLF4JServiceProvider with PennaServiceProvider; - - - // Penna subprojects also have access to minilog - // when/if breaking apart from slf4j, expose the full logger - exports penna.core.minilog to penna.config.yaml; - exports penna.core.sink to penna.dev; - exports penna.core.slf4j to penna.dev; - exports penna.core.models to penna.dev; } diff --git a/penna-core/src/main/java/penna/core/slf4j/PennaLoggerFactory.java b/penna-core/src/main/java/penna/core/slf4j/PennaLoggerFactory.java index d86dd86..0dac447 100644 --- a/penna-core/src/main/java/penna/core/slf4j/PennaLoggerFactory.java +++ b/penna-core/src/main/java/penna/core/slf4j/PennaLoggerFactory.java @@ -3,8 +3,8 @@ import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import penna.api.config.Config; -import penna.api.configv2.ConfigToLogger; -import penna.api.configv2.Storage; +import penna.api.config.ConfigToLogger; +import penna.api.config.Storage; import penna.core.logger.LoggerStorage; diff --git a/penna-yaml-config/src/main/java/module-info.java b/penna-yaml-config/src/main/java/module-info.java index 852ef7c..481b291 100644 --- a/penna-yaml-config/src/main/java/module-info.java +++ b/penna-yaml-config/src/main/java/module-info.java @@ -1,9 +1,9 @@ +import penna.api.config.Provider; import penna.config.yaml.YamlConfigProvider; module penna.config.yaml { requires org.slf4j; requires transitive penna.api; - requires penna.core; // (Optional) support for Jackson requires static com.fasterxml.jackson.databind; @@ -15,5 +15,5 @@ // (Optional) support for Snakeyaml Engine requires static org.snakeyaml.engine.v2; - provides penna.api.configv2.Provider with YamlConfigProvider; + provides Provider with YamlConfigProvider; } diff --git a/penna-yaml-config/src/main/java/penna/config/yaml/YamlConfigProvider.java b/penna-yaml-config/src/main/java/penna/config/yaml/YamlConfigProvider.java index ff52363..fed6adc 100644 --- a/penna-yaml-config/src/main/java/penna/config/yaml/YamlConfigProvider.java +++ b/penna-yaml-config/src/main/java/penna/config/yaml/YamlConfigProvider.java @@ -1,7 +1,7 @@ package penna.config.yaml; -import penna.api.configv2.Manager; -import penna.api.configv2.Provider; +import penna.api.config.Manager; +import penna.api.config.Provider; import penna.config.yaml.parsers.Parser; import java.io.IOException; From fa7958a654454c9cd88ded6181c9f944d2e025df Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:46:52 +0100 Subject: [PATCH 08/13] docs: Update README --- README.md | 75 ++++++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 76dc49f..86594a3 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,13 @@ In order to use it, add it to the [build manager of your preference](https://mvn ```groovy // gradle -runtimeOnly 'com.hkupty.penna:penna-core:0.7.0' +runtimeOnly 'com.hkupty.penna:penna-core:0.8.0' // Penna doesn't have any strict dependencies aside from slf4j. -implementation 'org.slf4j:slf4j-api:2.0.9' +implementation 'org.slf4j:slf4j-api:2.0.12' ``` -:warning: Note that Penna is built targeting JVM 17+. +:warning: Note that Penna is built targeting JVM 21+. By default, you will get log level `INFO` enabled as well as the following fields: - `timestamp` @@ -58,68 +58,37 @@ By default, you will get log level `INFO` enabled as well as the following field - `data` (slf4j's 2.0 `.addKeyValue()`) - `throwable` -Penna has support for logging also a `Counter` to each message, individually marking each message with a monotonically increasing -`long` counting from process startup, but that is disabled by default. - If you want to configure it, Penna provides a separate convenience library for configuring your log levels in yaml files: ```yaml # resources/penna.yaml - -# This is for configuring the root level -penna: - # We don't need to set level because by default it is set to INFO - fields: - # Not that it matter for json, but the key-value pairs below will be rendered in this order. - # So, for human readability in the console, one can tweak the position of the fields: - - level - - logger - - thread - - data - - mdc - - markers - - message - - throwable - loggers: - # This map will match the logger with the same literal name and all its children loggers, so - # com.mycompany.myapp as well as com.mycompany.myapp.controllers.MyGreatController and so on.. - com.mycompany.myapp: - level: debug - # There's no need to set fields here since it will inherit from the root logger - com.vendor.noisylib: - level: warn - fields: - # USE WITH CAUTION! You can opt to remove/add fields to the message in different loggers - # In this example, we're removing `thread`, `data`, `mdc` and `markers` and adding the `counter` field. - # This means the log messages from `com.vendor.noisylib` will be rendered differently. - - level - - logger - - message - - throwable +--- +# Since version 0.8, penna-yaml-config supports setting up a file watcher +# so any updates to this file will be reflected immediately +watch: true +loggers: + # All the loggers under `com.yourapp` will be configured to debug level. + com.yourapp: { level: debug } + org.noisylibrary: { level: warn } ``` If you want to use [penna-yaml-config](penna-yaml-config/README.md), you have to add it as a dependency: ```groovy -runtimeOnly 'com.hkupty.penna:penna-yaml-config:0.7.0' +runtimeOnly 'com.hkupty.penna:penna-yaml-config:0.8.0' -// We have to add a yaml parser to the classpath for `penna-yaml-config` to work properly. -// Currently we only support `jackson-dataformat-yaml`, but we plan on adding support for other libraries. -runtimeOnly 'com.fasterxml.jackson.core:jackson-core:2.14.2' -runtimeOnly 'com.fasterxml.jackson.core:jackson-databind:2.14.2' -runtimeOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.2' -``` +// penna-yaml-config is a thin layer and uses a yaml parsing libray under the hood. +// You can chose among jackson, snakeyaml (yaml 1.1) or snakeyaml engine (yaml 1.2) -## Test logs +// Jackson +runtimeOnly 'com.fasterxml.jackson.core:jackson-core:2.17.0' +runtimeOnly 'com.fasterxml.jackson.core:jackson-databind:2.17.0' +runtimeOnly 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.0' -Starting from Penna 0.7, Penna offers a `penna-dev` module which reformats the log to standard formatted strings instead of json. -By adding `penna-dev` to your test runtime it will automatically bind the new logger on top of `penna-core` - -```groovy -// penna-core is still needed as a runtime dependency -runtimeOnly 'com.hkupty.penna:penna-core:0.7.0' +// Snakeyaml +runtimeOnly 'org.yaml:snakeyaml:2.2' -// penna-dev should only be added to the test runtime -testRuntimeOnly 'com.hkupty.penna:penna-dev:0.7.0' +// Snakeyaml engine +runtimeOnly 'org.snakeyaml:snakeyaml-engine:2.7' ``` ## Principles From 17b9f1f11691275980851bb560c8d85be8a19a99 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 22:47:04 +0100 Subject: [PATCH 09/13] chore: Bump version for rc release --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0566460..fbb9ac4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.7.2 +version=0.8.0-rc1 From 799bf333445b6517b42bd6d94d1885724e081da7 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 23:19:14 +0100 Subject: [PATCH 10/13] www: Add release page --- www/content/blog/0.8-release.md | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 www/content/blog/0.8-release.md diff --git a/www/content/blog/0.8-release.md b/www/content/blog/0.8-release.md new file mode 100644 index 0000000..d0d0ab1 --- /dev/null +++ b/www/content/blog/0.8-release.md @@ -0,0 +1,42 @@ +--- +title: "0.8 Release" +date: 2024-03-20T22:48:35+01:00 +description: "JDK 21+ support, configuration improvements and more" +--- + +Almost two seasons ago, Penna the 0.7 branch began and now, 289 commits later, a new and even more exciting chapter begins. Version 0.8 is soon released with exciting features and a great deal of improvement! + + + +## A breath of fresh JDK + +Following the recent release of JDK 22, Penna 0.8 is bumping its JDK requirement to 21. Why? Well, we should stick to LTS versions as our requirement baselines and we shouldn't wait long with old versions. +Furthermore, so many fantastic features were released since JDK 17 that it doesn't make sense to refrain from using them, right? + +## Improved MDC implementation + +Continuing on the MDC work, a new and more flexible structure is in place. It is very well performing, but even more importantly, it fixes a few bugs that have shown up in the previous implementation. + +## Configuration restructuring + +Up until this version, the configuration logic would follow the principle of a providing an interface with a dynamically discoverable concrete implementation which would be bound to the internal logger storage. + +This had a few flaws, caused dummy implementations to exist and provided very little to no benefit. + +This new version improves significantly the structure by splitting the responsibilities between the configuration manager and the configuration providers. + +Consider subscribing to my [polar page](https://polar.sh/hkupty/) where I'll post an in-depth explanation of this refactoring if you're interested. + +## Development mode is gone + +The short-lived module, `penna-dev` is removed as of this version. +It had two potential benefits, which were providing more readable logs in development, but that is much better handled by tools like [fblog](https://github.com/brocode/fblog). +Also, it allowed for tests to change configuration during runtime, which is now much better handled by the new configuration code. + +## Final words + +It has been really a fantastic journey and I can only thank the experience so far. Penna is still a very small and unknown project, sure, but it brings me so much joy while still pushing me to be a better developer and maintainer. +I can only hope one day this project will bring others as much benefit, either through a good developer experience, through its performance characteristics or through the lightweight nature of this project. +Open source software and more broadly, the open wide web filled with knowledge has allowed me to have a career, sustain my family and live a relatively good life. I wish Penna helps me repay all that. + +Back to Penna, I don't know what will come of exciting during this 0.8.x branch. Hopefully we'll (finally) land the native marker support. If you have any suggestions or thoughts, don't refrain from [opening an issue](https://github.com/hkupty/penna/issues) or [starting a discussion](https://github.com/hkupty/penna/discussions). I'd really appreciate your feedback! From 38c379867f53fadffad210ffdda72671d9996c5f Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 23:19:34 +0100 Subject: [PATCH 11/13] docs: Remove old todo --- penna-yaml-config/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/penna-yaml-config/README.md b/penna-yaml-config/README.md index 0a09925..3279298 100644 --- a/penna-yaml-config/README.md +++ b/penna-yaml-config/README.md @@ -3,9 +3,4 @@ Given `penna` is designed for apps running in [kubernetes](https://kubernetes.io), we believe that yaml is a more natural configuration format than xml, so it should be convenient to set yaml files in k8s. -## Roadmap - -- 1.0 -- [x] read `penna.yaml`; -- [ ] allow for a separate yaml file for test; -- [ ] watch `penna.yaml` file for re-configuring the app upon app update; +This library contains a simple layer for providing file-based configuration on yaml. From 7e0b389712cc2771f96502477565a8ffaad2f760 Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 23:19:50 +0100 Subject: [PATCH 12/13] docs: Add CHANGELOG entries --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ebf99..bffa93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,25 @@ the PATCH component is omitted when its value is `0`. ## Unreleased +### `penna-api` + +#### Changed + +- Replace configuration interfaces([#89](https://github.com/hkupty/penna/pull/89)) + + ### `penna-core` #### Changed - Replace internal MDC storage implementation([#83](https://github.com/hkupty/penna/pull/83)) +- Remove ad-hoc configuration mechanism([#89](https://github.com/hkupty/penna/pull/89)) + +### `penna-yaml-config` + +#### Changed + +- Restructure configuration; implement new interface([#89](https://github.com/hkupty/penna/pull/89)) ## 0.7.2 From 093b7b6ad1d5f6698f32b4a8713f1223fc762f7d Mon Sep 17 00:00:00 2001 From: Henry Kupty Date: Wed, 20 Mar 2024 23:22:36 +0100 Subject: [PATCH 13/13] docs: Add more CHANGELOG entries --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bffa93c..6937d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,14 @@ the PATCH component is omitted when its value is `0`. - Replace configuration interfaces([#89](https://github.com/hkupty/penna/pull/89)) - ### `penna-core` #### Changed - Replace internal MDC storage implementation([#83](https://github.com/hkupty/penna/pull/83)) - Remove ad-hoc configuration mechanism([#89](https://github.com/hkupty/penna/pull/89)) +- Remove sink proxy([#94](https://github.com/hkupty/penna/pull/94)) +- Minor cleanups and adjusts for JDK21 ([#94](https://github.com/hkupty/penna/pull/94)) ### `penna-yaml-config`