diff --git a/core/src/main/java/io/jstach/rainbowgum/Defaults.java b/core/src/main/java/io/jstach/rainbowgum/Defaults.java index 90ff37c4..bcd1fd4e 100644 --- a/core/src/main/java/io/jstach/rainbowgum/Defaults.java +++ b/core/src/main/java/io/jstach/rainbowgum/Defaults.java @@ -1,150 +1,17 @@ package io.jstach.rainbowgum; -import java.net.URI; -import java.util.EnumMap; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.eclipse.jdt.annotation.Nullable; - -import io.jstach.rainbowgum.LogAppender.ThreadSafeLogAppender; -import io.jstach.rainbowgum.LogOutput.OutputType; -import io.jstach.rainbowgum.LogProperties.Property; -import io.jstach.rainbowgum.format.StandardEventFormatter; -import io.jstach.rainbowgum.publisher.BlockingQueueAsyncLogPublisher; - /** * Static defaults that should probably be in the config class. * * @author agentgt */ -public class Defaults { +final class Defaults { static final String SHUTDOWN = "#SHUTDOWN#"; - /** - * Default async buffer size. - */ - public static final int ASYNC_BUFFER_SIZE = 1024; - - private static final ReentrantReadWriteLock staticLock = new ReentrantReadWriteLock(); - - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - private static CopyOnWriteArrayList shutdownHooks = new CopyOnWriteArrayList<>(); - - // we do not need the newer VarHandle because there is only one of these guys - private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false); - - private final EnumMap> formatters = new EnumMap<>(OutputType.class); - - private final LogProperties properties; - - private static final Property defaultsAppenderBufferProperty = Property.builder() - .map(s -> Boolean.parseBoolean(s)) - .orElse(false) - .build(LogProperties.concatKey("defaults.appender.buffer")); - - static final Property fileProperty = Property.builder().map(URI::new).build(LogProperties.FILE_PROPERTY); - - static final Property> outputProperty = Property.builder() - .map(p -> Stream.of(p.split(",")).filter(s -> !s.trim().isEmpty()).toList()) - .build(LogProperties.OUTPUT_PROPERTY); - - Defaults(LogProperties logProperties) { - this.properties = logProperties; - } - - static Function threadSafeAppender = (appender) -> { - return new LockingLogAppender(appender); - }; - - LogPublisher.AsyncLogPublisher asyncPublisher(List appenders, int bufferSize) { - return BlockingQueueAsyncLogPublisher.of(appenders, bufferSize); - } - - LogAppender logAppender(LogOutput output, @Nullable LogEncoder encoder) { - if (encoder == null) { - encoder = LogEncoder.of(formatterForOutputType(output.type())); - } - ; - return defaultsAppenderBufferProperty.get(properties).value() ? new BufferLogAppender(output, encoder) - : new DefaultLogAppender(output, encoder); - } + // static final Property fileProperty = + // Property.builder().map(URI::new).build(LogProperties.FILE_PROPERTY); static final String NEW_LINE = "\n"; - /** - * Associates a default formatter with a specific output type - * @param outputType output type to use for finding best default formatter. - * @return formatter for output type. - */ - public LogFormatter formatterForOutputType(OutputType outputType) { - lock.readLock().lock(); - try { - var formatter = formatters.get(outputType); - if (formatter == null) { - return StandardEventFormatter.builder().build(); - } - return formatter.get(); - } - finally { - lock.readLock().unlock(); - } - } - - /** - * Sets a default formatter for a specific output type. - * @param outputType output type. - * @param formatter formatter. - */ - public void setFormatterForOutputType(OutputType outputType, Supplier formatter) { - lock.writeLock().lock(); - try { - formatters.put(outputType, formatter); - } - finally { - lock.writeLock().unlock(); - } - } - - static void addShutdownHook(AutoCloseable hook) { - staticLock.writeLock().lock(); - try { - if (shutdownHookRegistered.compareAndSet(false, true)) { - var thread = new Thread(() -> { - runShutdownHooks(); - }); - thread.setName("rainbowgum-shutdown"); - Runtime.getRuntime().addShutdownHook(thread); - } - shutdownHooks.add(hook); - } - finally { - staticLock.writeLock().unlock(); - } - } - - private static void runShutdownHooks() { - /* - * We do not lock here since we are in the shutdown thread luckily shutdownHooks - * is thread safe - */ - for (var hook : shutdownHooks) { - try { - hook.close(); - } - catch (Exception e) { - MetaLog.error(Defaults.class, e); - } - } - // Help the GC or whatever final cleanup is going on - shutdownHooks.clear(); - } - } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java index fac11c62..8c693810 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java @@ -1,13 +1,16 @@ package io.jstach.rainbowgum; +import java.net.URI; import java.util.List; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import org.eclipse.jdt.annotation.Nullable; import io.jstach.rainbowgum.LogAppender.AbstractLogAppender; import io.jstach.rainbowgum.LogAppender.ThreadSafeLogAppender; import io.jstach.rainbowgum.LogEncoder.Buffer; +import io.jstach.rainbowgum.LogProperties.Property; /** * Appenders are guaranteed to be written synchronously much like an actor in actor @@ -143,10 +146,26 @@ public AppenderProvider build() { var output = this.output; var encoder = this.encoder; return config -> { - return config.defaults().logAppender(output, encoder); + return logAppender(config, output, encoder); }; } + private static final Property defaultsAppenderBufferProperty = Property.builder() + .map(s -> Boolean.parseBoolean(s)) + .orElse(false) + .build(LogProperties.concatKey("defaults.appender.buffer")); + + private static LogAppender logAppender(LogConfig config, LogOutput output, @Nullable LogEncoder encoder) { + var formatterRegistry = config.formatterRegistry(); + var properties = config.properties(); + if (encoder == null) { + encoder = LogEncoder.of(formatterRegistry.formatterForOutputType(output.type())); + } + + return defaultsAppenderBufferProperty.get(properties).value() ? new BufferLogAppender(output, encoder) + : new DefaultLogAppender(output, encoder); + } + } @Override @@ -167,7 +186,7 @@ public static ThreadSafeLogAppender of(LogAppender appender) { if (appender instanceof ThreadSafeLogAppender lo) { return lo; } - return Defaults.threadSafeAppender.apply(appender); + return DefaultLogAppender.threadSafeAppender.apply(appender); } } @@ -317,6 +336,12 @@ public void close() { */ class DefaultLogAppender extends AbstractLogAppender implements ThreadSafeLogAppender { + static Function threadSafeAppender = (appender) -> { + return new LockingLogAppender(appender); + }; + + static final Property fileProperty = Property.builder().map(URI::new).build(LogProperties.FILE_PROPERTY); + protected final ReentrantLock lock = new ReentrantLock(); public DefaultLogAppender(LogOutput output, LogEncoder encoder) { diff --git a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java index 2bba9794..902a7c45 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java @@ -43,10 +43,10 @@ default PropertyValue get(Property property) { public LevelConfig levelResolver(); /** - * Special defaults. Internal for now. - * @return defaults. + * Registered formatters. + * @return formatter registry. */ - public Defaults defaults(); + public LogFormatterRegistry formatterRegistry(); /** * Output provider that uses URI to find output. @@ -224,18 +224,17 @@ final class DefaultLogConfig implements LogConfig { private final LevelConfig levelResolver; - private final Defaults defaults; - private final ChangePublisher publisher; private final LogOutputRegistry outputRegistry; + private final LogFormatterRegistry formatterRegistry; + public DefaultLogConfig(ServiceRegistry registry, LogProperties properties) { super(); this.registry = registry; this.properties = properties; this.levelResolver = new ConfigLevelResolver(properties); - this.defaults = new Defaults(properties); this.publisher = new AbstractChangePublisher() { @Override protected LogConfig _config() { @@ -243,6 +242,7 @@ protected LogConfig _config() { } }; this.outputRegistry = LogOutputRegistry.of(); + this.formatterRegistry = LogFormatterRegistry.of(); } @Override @@ -255,11 +255,6 @@ public LevelConfig levelResolver() { return this.levelResolver; } - @Override - public Defaults defaults() { - return defaults; - } - @Override public ServiceRegistry registry() { return this.registry; @@ -275,4 +270,9 @@ public LogOutputRegistry outputRegistry() { return this.outputRegistry; } + @Override + public LogFormatterRegistry formatterRegistry() { + return this.formatterRegistry; + } + } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogFormatterRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogFormatterRegistry.java new file mode 100644 index 00000000..fb015f0f --- /dev/null +++ b/core/src/main/java/io/jstach/rainbowgum/LogFormatterRegistry.java @@ -0,0 +1,79 @@ +package io.jstach.rainbowgum; + +import java.util.EnumMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +import io.jstach.rainbowgum.LogOutput.OutputType; +import io.jstach.rainbowgum.format.StandardEventFormatter; + +/** + * Formatters that are registered based on output type. + */ +public sealed interface LogFormatterRegistry permits DefaultLogFormatterRegistry { + + /** + * Creates a log formatter registry. + * @return new created log formatter registry. + */ + public static LogFormatterRegistry of() { + return new DefaultLogFormatterRegistry(); + } + + /** + * Associates a default formatter with a specific output type + * @param outputType output type to use for finding best default formatter. + * @return formatter for output type. + */ + public LogFormatter formatterForOutputType(OutputType outputType); + + /** + * Sets a default formatter for a specific output type. + * @param outputType output type. + * @param formatter formatter. + */ + public void setFormatterForOutputType(OutputType outputType, Supplier formatter); + +} + +final class DefaultLogFormatterRegistry implements LogFormatterRegistry { + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + private final EnumMap> formatters = new EnumMap<>(OutputType.class); + + /** + * Associates a default formatter with a specific output type + * @param outputType output type to use for finding best default formatter. + * @return formatter for output type. + */ + public LogFormatter formatterForOutputType(OutputType outputType) { + lock.readLock().lock(); + try { + var formatter = formatters.get(outputType); + if (formatter == null) { + return StandardEventFormatter.builder().build(); + } + return formatter.get(); + } + finally { + lock.readLock().unlock(); + } + } + + /** + * Sets a default formatter for a specific output type. + * @param outputType output type. + * @param formatter formatter. + */ + public void setFormatterForOutputType(OutputType outputType, Supplier formatter) { + lock.writeLock().lock(); + try { + formatters.put(outputType, formatter); + } + finally { + lock.writeLock().unlock(); + } + } + +} diff --git a/core/src/main/java/io/jstach/rainbowgum/LogLifecycle.java b/core/src/main/java/io/jstach/rainbowgum/LogLifecycle.java index d7745193..3c91225e 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogLifecycle.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogLifecycle.java @@ -1,5 +1,9 @@ package io.jstach.rainbowgum; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantReadWriteLock; + /** * A component that has a start and stop. */ @@ -14,3 +18,48 @@ public interface LogLifecycle extends AutoCloseable { public void close(); } + +final class ShutdownManager { + + private static final ReentrantReadWriteLock staticLock = new ReentrantReadWriteLock(); + + private static CopyOnWriteArrayList shutdownHooks = new CopyOnWriteArrayList<>(); + + // we do not need the newer VarHandle because there is only one of these guys + private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false); + + static void addShutdownHook(AutoCloseable hook) { + staticLock.writeLock().lock(); + try { + if (shutdownHookRegistered.compareAndSet(false, true)) { + var thread = new Thread(() -> { + runShutdownHooks(); + }); + thread.setName("rainbowgum-shutdown"); + Runtime.getRuntime().addShutdownHook(thread); + } + shutdownHooks.add(hook); + } + finally { + staticLock.writeLock().unlock(); + } + } + + private static void runShutdownHooks() { + /* + * We do not lock here since we are in the shutdown thread luckily shutdownHooks + * is thread safe + */ + for (var hook : shutdownHooks) { + try { + hook.close(); + } + catch (Exception e) { + MetaLog.error(Defaults.class, e); + } + } + // Help the GC or whatever final cleanup is going on + shutdownHooks.clear(); + } + +} diff --git a/core/src/main/java/io/jstach/rainbowgum/LogOutputRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogOutputRegistry.java index 82c05501..016692cf 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogOutputRegistry.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogOutputRegistry.java @@ -6,13 +6,17 @@ import java.nio.channels.FileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import io.jstach.rainbowgum.LogProperties.Property; /** * Register output providers by URI scheme. */ -public interface LogOutputRegistry extends LogOutputProvider { +public sealed interface LogOutputRegistry extends LogOutputProvider permits DefaultOutputRegistry { /** * Register a provider by {@link URI#getScheme() scheme}. @@ -31,7 +35,11 @@ public static LogOutputRegistry of() { } -class DefaultOutputRegistry implements LogOutputRegistry { +final class DefaultOutputRegistry implements LogOutputRegistry { + + static final Property> outputProperty = Property.builder() + .map(p -> Stream.of(p.split(",")).filter(s -> !s.trim().isEmpty()).toList()) + .build(LogProperties.OUTPUT_PROPERTY); private final Map providers = new ConcurrentHashMap<>(); diff --git a/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java b/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java index 8b6ce2cf..b6fd6812 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogPublisher.java @@ -3,6 +3,7 @@ import java.util.List; import io.jstach.rainbowgum.LogAppender.ThreadSafeLogAppender; +import io.jstach.rainbowgum.publisher.BlockingQueueAsyncLogPublisher; /** * Publishers push logs to appenders either synchronously or asynchronously. @@ -97,14 +98,19 @@ public static AsyncLogPublisher.Builder builder() { */ public static class Builder extends AbstractBuilder { - private int bufferSize = Defaults.ASYNC_BUFFER_SIZE; + /** + * Default async buffer size. + */ + public static final int ASYNC_BUFFER_SIZE = 1024; + + private int bufferSize = ASYNC_BUFFER_SIZE; private Builder() { } /** * Sets buffer size. Typically means how many events can be queued up. Default - * is {@link Defaults#ASYNC_BUFFER_SIZE}. + * is {@value #ASYNC_BUFFER_SIZE}. * @param bufferSize buffer size. * @return this. */ @@ -115,7 +121,7 @@ public AsyncLogPublisher.Builder bufferSize(int bufferSize) { public PublisherProvider build() { int bufferSize = this.bufferSize; - return (config, appenders) -> config.defaults().asyncPublisher(appenders, bufferSize); + return (config, appenders) -> asyncPublisher(appenders, bufferSize); } @Override @@ -123,6 +129,11 @@ protected AsyncLogPublisher.Builder self() { return this; } + private static LogPublisher.AsyncLogPublisher asyncPublisher(List appenders, + int bufferSize) { + return BlockingQueueAsyncLogPublisher.of(appenders, bufferSize); + } + } } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogRouter.java b/core/src/main/java/io/jstach/rainbowgum/LogRouter.java index b43eb272..ea97d5d6 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogRouter.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogRouter.java @@ -93,10 +93,10 @@ public boolean isEnabled() { /** * Root router is a router that has child routers. */ - public sealed interface RootRouter extends LogRouter permits InternalRootRouter { + sealed interface RootRouter extends LogRouter permits InternalRootRouter { /** - * Level resolver to find levels for a long name. + * Level resolver to find levels for a log name. * @return level resolver. */ public LevelResolver levelResolver(); @@ -240,7 +240,7 @@ Router build() { List appenders = new ArrayList<>(this.appenders); if (appenders.isEmpty()) { appenders.add(LogAppender.builder().output(LogOutput.ofStandardOut()).build()); - config.get(Defaults.fileProperty) // + config.get(DefaultLogAppender.fileProperty) // .map(u -> config.output(u, "")) // .map(o -> { return LogAppender.builder().output(o).build(); @@ -248,9 +248,9 @@ Router build() { .optional() // .ifPresent(appenders::add); - List outputs = config.get(Defaults.outputProperty).value(List.of()); + List outputs = config.get(DefaultOutputRegistry.outputProperty).value(List.of()); for (String o : outputs) { - Property outputProperty = output(Defaults.outputProperty, o); + Property outputProperty = output(DefaultOutputRegistry.outputProperty, o); LogOutput output = outputProperty.map(u -> config.output(u, o)) .get(config.properties()) .value(); diff --git a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java index 652e4bf1..690fdedc 100644 --- a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java +++ b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java @@ -248,7 +248,7 @@ static void set(Supplier rainbowGumSupplier) { private static void start(RainbowGum gum) { Objects.requireNonNull(gum); - Defaults.addShutdownHook(gum); + ShutdownManager.addShutdownHook(gum); InternalRootRouter.setRouter(gum.router()); gum.start(); } diff --git a/core/src/main/java/io/jstach/rainbowgum/publisher/BlockingQueueAsyncLogPublisher.java b/core/src/main/java/io/jstach/rainbowgum/publisher/BlockingQueueAsyncLogPublisher.java index 36019eff..cee40e26 100644 --- a/core/src/main/java/io/jstach/rainbowgum/publisher/BlockingQueueAsyncLogPublisher.java +++ b/core/src/main/java/io/jstach/rainbowgum/publisher/BlockingQueueAsyncLogPublisher.java @@ -178,7 +178,6 @@ public void unmaskInterruptFlag() { Thread.currentThread().interrupt(); } catch (SecurityException se) { - // addError("Failed to interrupt current thread", se); } } } diff --git a/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java b/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java index 4b7d3597..cdc410fa 100644 --- a/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java +++ b/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java @@ -7,7 +7,6 @@ import org.eclipse.jdt.annotation.Nullable; -import io.jstach.rainbowgum.Defaults; import io.jstach.rainbowgum.LogConfig; import io.jstach.rainbowgum.LogOutputProvider; import io.jstach.rainbowgum.LogProperties; @@ -53,7 +52,6 @@ public non-sealed interface ConfigProvider extends RainbowGumServiceProvider { * like registering {@link LogOutputProvider}s. * * @see LogConfig#outputRegistry() - * @see Defaults#formatterForOutputType(io.jstach.rainbowgum.LogOutput.OutputType) */ public non-sealed interface Initializer extends RainbowGumServiceProvider { diff --git a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java index dc395192..911b2e17 100644 --- a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java +++ b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java @@ -131,19 +131,17 @@ public final class $$builderName$$ { } /** - * Turns the builder into java.util.Properties like Map - * @return properties. + * Turns the builder into java.util.Properties like Map skipping values that are null. + * @param consumer apply is called where first arg is key and second is value. */ - public java.util.Map asProperties() { - java.util.Map m = new java.util.LinkedHashMap<>(); + public void toProperties(java.util.function.BiConsumer consumer) { $$#properties$$ $$#normal$$ if (this.$$name$$ != null) { - m.put($$propertyVar$$.key(), String.valueOf(this.$$name$$)); + consumer.accept($$propertyVar$$.key(), String.valueOf(this.$$name$$)); } $$/normal$$ $$/properties$$ - return m; } } \ No newline at end of file diff --git a/rainbowgum-jansi/src/main/java/io/jstach/rainbowgum/jansi/JansiInitializer.java b/rainbowgum-jansi/src/main/java/io/jstach/rainbowgum/jansi/JansiInitializer.java index 7284a712..92c56d2f 100644 --- a/rainbowgum-jansi/src/main/java/io/jstach/rainbowgum/jansi/JansiInitializer.java +++ b/rainbowgum-jansi/src/main/java/io/jstach/rainbowgum/jansi/JansiInitializer.java @@ -29,7 +29,7 @@ public JansiInitializer() { public void initialize(ServiceRegistry registry, LogConfig config) { if (installJansi(config)) { AnsiConsole.systemInstall(); - config.defaults() + config.formatterRegistry() .setFormatterForOutputType(OutputType.CONSOLE_OUT, () -> JansiLogFormatter.builder().build()); } } diff --git a/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java b/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java index 3d4a1dcb..acd7ce89 100644 --- a/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java +++ b/test/rainbowgum-test-rabbitmq/src/test/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetupTest.java @@ -1,6 +1,7 @@ package io.jstach.rainbowgum.test.rabbitmq; import java.net.URI; +import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.AfterAll; @@ -33,9 +34,10 @@ void testMain() { // b.port(rabbit.getAmqpPort()); b.uri(URI.create(rabbit.getAmqpUrl())); b.declareExchange(true); - Map properties = b.asProperties(); + Map properties = new LinkedHashMap<>(); properties.put(LogProperties.OUTPUT_PROPERTY, "amqp"); properties.put(LogProperties.OUTPUT_PROPERTY + ".amqp", "amqp:///"); + b.toProperties(properties::put); System.out.println(properties); System.out.println(rabbit.getAmqpUrl()); // LoggerFactoryFriend.reset();