diff --git a/core/src/main/java/io/jstach/rainbowgum/KeyValues.java b/core/src/main/java/io/jstach/rainbowgum/KeyValues.java index 01d9c833..55390c84 100644 --- a/core/src/main/java/io/jstach/rainbowgum/KeyValues.java +++ b/core/src/main/java/io/jstach/rainbowgum/KeyValues.java @@ -184,7 +184,8 @@ default void putAll(Map m) { * @return key values */ public static MutableKeyValues of(Map m) { - var kvs = of(m.size()); + int size = m.size(); + var kvs = of(size == 0 ? 4 : size); kvs.putAll(m); return kvs; } @@ -350,6 +351,9 @@ public int size() { } private int _next(int index) { + if (size == 0) { + return -1; + } var limit = threshold - 1; if (index >= limit) { return -1; diff --git a/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java index 332ab042..f0bc1c3f 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java @@ -64,7 +64,7 @@ private static String validateName(String name) { final class DefaultAppenderRegistry implements LogAppenderRegistry { /* - * The shit in here is a mess because auto configuration of appenders based on + * TODO The shit in here is a mess because auto configuration of appenders based on * properties is complicated particularly because we want to support Spring Boots * configuration OOB. */ diff --git a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java index 1bdea489..81004443 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java @@ -1,8 +1,13 @@ package io.jstach.rainbowgum; +import static io.jstach.rainbowgum.spi.RainbowGumServiceProvider.findProviders; + import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.ServiceLoader; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -11,6 +16,9 @@ import io.jstach.rainbowgum.LevelResolver.LevelConfig; import io.jstach.rainbowgum.LogConfig.ChangePublisher; import io.jstach.rainbowgum.LogProperties.PropertyGetter; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider.Configurator; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider.PropertiesProvider; /** * The configuration of a RainbowGum. In some other logging implementations this is called @@ -65,24 +73,6 @@ default LogOutput output(URI uri, String name) throws IOException { return outputRegistry().output(uri, name, properties()); } - /** - * Creates default log config backed by system properties. - * @return config. - */ - public static LogConfig of() { - return LogConfig.of(ServiceRegistry.of(), LogProperties.StandardProperties.SYSTEM_PROPERTIES); - } - - /** - * Creates config. - * @param registry service registry. - * @param properties properties. - * @return config. - */ - public static LogConfig of(ServiceRegistry registry, LogProperties properties) { - return new DefaultLogConfig(registry, properties); - } - /** * Creates a builder for making LogConfig. * @return builder. @@ -172,6 +162,10 @@ public static final class Builder { private @Nullable LogProperties logProperties; + private @Nullable ServiceLoader serviceLoader; + + private final List configurators = new ArrayList<>(); + /** * Default constructor */ @@ -183,7 +177,7 @@ private Builder() { * @param logProperties log properties. * @return this. */ - public Builder logProperties(LogProperties logProperties) { + public Builder properties(LogProperties logProperties) { this.logProperties = logProperties; return this; } @@ -199,19 +193,62 @@ public Builder serviceRegistry(ServiceRegistry serviceRegistry) { } /** - * Builds LogConfig using defaults on missing properties. + * Add to configure LogConfig and ServiceRegistry. + * @param configurator will run on build. + * @return this. + */ + public Builder configurator(RainbowGumServiceProvider.Configurator configurator) { + configurators.add(configurator); + return this; + } + + /** + * Sets the service loader to use for loading components that were not set. + * @param serviceLoader loader to use for missing components. + * @return this. + */ + public Builder serviceLoader(ServiceLoader serviceLoader) { + this.serviceLoader = serviceLoader; + return this; + } + + /** + * Builds LogConfig which will use the {@link ServiceLoader} if set to load + * missing components and if not set will use static defaults. * @return log config */ public LogConfig build() { ServiceRegistry serviceRegistry = this.serviceRegistry; LogProperties logProperties = this.logProperties; + var serviceLoader = this.serviceLoader; + var configurators = this.configurators; if (serviceRegistry == null) { serviceRegistry = ServiceRegistry.of(); } if (logProperties == null) { - logProperties = LogProperties.StandardProperties.SYSTEM_PROPERTIES; + if (serviceLoader != null) { + logProperties = provideProperties(serviceRegistry, serviceLoader); + } + else { + logProperties = LogProperties.StandardProperties.SYSTEM_PROPERTIES; + } + } + var config = new DefaultLogConfig(serviceRegistry, logProperties); + if (configurators.isEmpty() && serviceLoader != null) { + configurators = findProviders(serviceLoader, Configurator.class).toList(); } - return LogConfig.of(serviceRegistry, logProperties); + if (!configurators.isEmpty()) { + RainbowGumServiceProvider.Configurator.runConfigurators(configurators.stream(), config); + } + return config; + } + + private static LogProperties provideProperties(ServiceRegistry registry, + ServiceLoader loader) { + List props = findProviders(loader, PropertiesProvider.class) + .flatMap(s -> s.provideProperties(registry).stream()) + .toList(); + return LogProperties.of(props, LogProperties.StandardProperties.SYSTEM_PROPERTIES); } } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java index e7cfe05d..0267ddb1 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java @@ -63,12 +63,11 @@ public void register(String name, LogEncoder encoder) { @Override public LogEncoder provide(URI uri, String name, LogProperties properties) { String scheme = uri.getScheme(); - String path = uri.getPath(); - - if (scheme == null && path != null) { - return _encoder(uri, name, path); + if (scheme == null) { + throw new IllegalStateException("Encoder reference needs a URI with scheme. " + + "For example 'gelf' is not valid but 'gelf:///' is."); } - else if (scheme.equals("encoder") || scheme.equals("name")) { + if (scheme.equals("name")) { String _name = uri.getHost(); if (_name == null) { _name = name; @@ -85,7 +84,7 @@ else if (scheme.equals("encoder") || scheme.equals("name")) { private LogEncoder _encoder(URI uri, String name, String resolvedName) { return encoder(resolvedName).orElseThrow(() -> new NoSuchElementException( - "No encoder found. resolved = " + resolvedName + " name= " + name + ", uri=" + uri)); + "No encoder found. resolved='" + resolvedName + "', name='" + name + "', uri='" + uri + "'")); } @Override diff --git a/core/src/main/java/io/jstach/rainbowgum/LogProperties.java b/core/src/main/java/io/jstach/rainbowgum/LogProperties.java index 96600790..fdba1d85 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogProperties.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogProperties.java @@ -161,7 +161,7 @@ public interface LogProperties { * @apiNote the reason empty map is not returned for missing key is that it creates * ambiguity so null is returned when key is missing. */ - default Map mapOrNull(String key) { + default @Nullable Map mapOrNull(String key) { String s = valueOrNull(key); if (s == null) { return null; @@ -209,6 +209,8 @@ public final static class Builder { private Function function; + private Function renameKey = s -> s; + private Builder() { } @@ -243,6 +245,16 @@ public Builder function(Function function) { return this; } + /** + * Renames the key before it accesses function. + * @param renameKey rename key function. + * @return this. + */ + public Builder renameKey(Function renameKey) { + this.renameKey = renameKey; + return this; + } + /** * Parses a string as {@link Properties}. * @param properties properties as a string. @@ -272,15 +284,22 @@ public LogProperties build() { if (function == null) { throw new IllegalStateException("function is was not set"); } - return new DefaultLogProperties(function, description, order); + return new DefaultLogProperties(function, description, renameKey, order); } - private record DefaultLogProperties(Function func, String description, + private record DefaultLogProperties( // + Function func, // + String description, Function renameKey, // int order) implements LogProperties { @Override public @Nullable String valueOrNull(String key) { - return func.apply(key); + return func.apply(renameKey.apply(key)); + } + + @Override + public String description(String key) { + return renameKey.apply(key); } } @@ -315,6 +334,13 @@ Function parse(String content) { } + // /** + // * A log properties that can list all keys available. + // */ + // public interface ListableProperties extends LogProperties { + // public Set keys(); + // } + /** * LogProperties builder. * @return builder. diff --git a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java index 0e4ff178..876968d9 100644 --- a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java +++ b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java @@ -61,7 +61,7 @@ public Optional provide(LogConfig config) { } */ //@formatter:on -public interface RainbowGum extends AutoCloseable { +public sealed interface RainbowGum extends AutoCloseable, LogEventLogger { /** * Gets the currently statically bound RainbowGum. @@ -115,6 +115,18 @@ default void close() { router().close(); } + /** + * This append call is mainly for testing as it does not avoid making events that do + * not need to be made if no logging needs to be done. {@inheritDoc} + */ + @Override + default void log(LogEvent event) { + var r = router().route(event.loggerName(), event.level()); + if (r.isEnabled()) { + r.log(event); + } + } + /** * Use to build a custom {@link RainbowGum} which will use the {@link LogConfig} * provided by the service loader. @@ -127,7 +139,7 @@ public static Builder builder() { } /** - * Use to build a custom {@link RainbowGum}. + * Use to build a custom {@link RainbowGum} with supplied config. * @param config the config * @return builder. * @see #builder() @@ -136,6 +148,19 @@ public static Builder builder(LogConfig config) { return new Builder(config); } + /** + * Use to build a custom {@link RainbowGum} with supplied config. + * @param config consumer that has first argument as config builder. + * @return builder. + * @see #builder() + * @apiNote this method is for ergonomic fluent reasons. + */ + public static Builder builder(Consumer config) { + var b = LogConfig.builder(); + config.accept(b); + return builder(b.build()); + } + /** * RainbowGum Builder. */ @@ -143,7 +168,7 @@ public class Builder { private final LogConfig config; - private List routes = new ArrayList<>(); + private final List routes = new ArrayList<>(); private Builder(LogConfig config) { this.config = config; 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 4fa24905..21ada938 100644 --- a/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java +++ b/core/src/main/java/io/jstach/rainbowgum/spi/RainbowGumServiceProvider.java @@ -1,5 +1,6 @@ package io.jstach.rainbowgum.spi; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.ServiceLoader; @@ -49,17 +50,71 @@ public non-sealed interface ConfigProvider extends RainbowGumServiceProvider { /** * Called after {@link LogConfig} has been loaded to do various custom initialization * like registering {@link io.jstach.rainbowgum.LogOutput.OutputProvider}s. + *

+ * The configurator has the option to return false to indicate a retry + * needs to be made. This is poor mans way to handle dependency needs of one + * configurator needing another to run prior. * * @see LogConfig#outputRegistry() + * @see ServiceRegistry */ - public non-sealed interface Initializer extends RainbowGumServiceProvider { + @FunctionalInterface + public non-sealed interface Configurator extends RainbowGumServiceProvider { /** * Do adhoc initialization before RainbowGum is fully loaded. - * @param registry registry. * @param config config. + * @return true if all dependencies were found. + */ + boolean configure(LogConfig config); + + /** + * The default amount of passes made to resolve configurators. + */ + public static int CONFIGURATOR_PASSES = 4; + + /** + * Runs configurators. If a configurator returns false then it will be retried. + * The default of {@value #CONFIGURATOR_PASSES} passes will be tried. + * @param configurators stream of configurators. + * @param config config to use for registration. + * @throws IllegalStateException if there are configurators still returning + * false. */ - void initialize(ServiceRegistry registry, LogConfig config); + public static void runConfigurators(Stream configurators, + LogConfig config) { + runConfigurators(configurators, config, CONFIGURATOR_PASSES); + } + + /** + * Runs configurators. If a configurator returns false then it will be retried. + * @param configurators stream of configurators. + * @param config config to use for registration. + * @param passes number of times to retry configurator if it returns + * @throws IllegalStateException if there are configurators still returning + * false. + */ + public static void runConfigurators(Stream configurators, + LogConfig config, int passes) { + List unresolved = new ArrayList<>(configurators.toList()); + for (int i = 0; i < passes; i++) { + var it = unresolved.iterator(); + while (it.hasNext()) { + var c = it.next(); + if (c.configure(config)) { + it.remove(); + } + } + if (unresolved.isEmpty()) { + break; + } + } + if (!unresolved.isEmpty()) { + throw new IllegalStateException("Configurators could not find dependencies (returned false) after " + + passes + " passes. configurators = " + unresolved); + } + + } } @@ -89,7 +144,14 @@ public static RainbowGum defaults(LogConfig config) { } - private static Stream findProviders( + /** + * Finds service providers based on type. + * @param provider interface type. + * @param loader service loader to use. + * @param pt provider class. + * @return stream containing only provider of the given type. + */ + public static Stream findProviders( ServiceLoader loader, Class pt) { return loader.stream().flatMap(p -> { if (pt.isAssignableFrom(p.type())) { @@ -100,41 +162,13 @@ private static Stream findProviders( }); } - private static LogProperties provideProperties(ServiceRegistry registry, - ServiceLoader loader) { - List props = findProviders(loader, PropertiesProvider.class) - .flatMap(s -> s.provideProperties(registry).stream()) - .toList(); - return LogProperties.of(props, LogProperties.StandardProperties.SYSTEM_PROPERTIES); - } - - private static LogConfig provideConfig(ServiceLoader loader, ServiceRegistry registry, - LogProperties properties) { - return findProviders(loader, ConfigProvider.class).findFirst() - .map(s -> s.provideConfig(registry, properties)) - .orElseGet(() -> LogConfig.of(registry, properties)); - } - - /** - * Runs initializers based on config. - * @param loader service loader to use. - * @param config config before initializers have run. - */ - public static void runInitializers(ServiceLoader loader, LogConfig config) { - findProviders(loader, Initializer.class).forEach(c -> c.initialize(config.serviceRegistry(), config)); - } - /** * Creates config from service loader. * @param loader service loader. * @return config. */ public static LogConfig provideConfig(ServiceLoader loader) { - ServiceRegistry registry = ServiceRegistry.of(); - var properties = provideProperties(registry, loader); - var config = provideConfig(loader, registry, properties); - runInitializers(loader, config); - return config; + return LogConfig.builder().serviceLoader(loader).build(); } /** diff --git a/core/src/test/java/io/jstach/rainbowgum/LevelResolverTest.java b/core/src/test/java/io/jstach/rainbowgum/LevelResolverTest.java index 17d94b05..6ca66dfa 100644 --- a/core/src/test/java/io/jstach/rainbowgum/LevelResolverTest.java +++ b/core/src/test/java/io/jstach/rainbowgum/LevelResolverTest.java @@ -13,7 +13,7 @@ class LevelResolverTest { @Test void testResolveLevelString() { var m = Map.of("logging.level.io", "WARNING"); - LogConfig config = LogConfig.of(ServiceRegistry.of(), (k) -> m.get(k)); + LogConfig config = LogConfig.builder().properties((k) -> m.get(k)).build(); Level actual = config.levelResolver().levelOrNull("io"); Level expected = Level.WARNING; assertEquals(actual, expected); diff --git a/core/src/test/java/io/jstach/rainbowgum/LogConfigTest.java b/core/src/test/java/io/jstach/rainbowgum/LogConfigTest.java index 52ec0ff2..9a47baf9 100644 --- a/core/src/test/java/io/jstach/rainbowgum/LogConfigTest.java +++ b/core/src/test/java/io/jstach/rainbowgum/LogConfigTest.java @@ -13,7 +13,9 @@ class LogConfigTest { void test() { // System.setProperty("rainbowgum.log.stuff", "DEBUG"); - var config = LogConfig.of(ServiceRegistry.of(), Map.of("logging.level.stuff", "DEBUG")::get); + var config = LogConfig.builder() + .properties(Map.of("logging.level.stuff", "DEBUG")::get) + .build(); var resolver = config.levelResolver(); var actual = resolver.resolveLevel("stuff"); diff --git a/core/src/test/java/io/jstach/rainbowgum/RainbowGumTest.java b/core/src/test/java/io/jstach/rainbowgum/RainbowGumTest.java index 221346e0..c1ed05c4 100644 --- a/core/src/test/java/io/jstach/rainbowgum/RainbowGumTest.java +++ b/core/src/test/java/io/jstach/rainbowgum/RainbowGumTest.java @@ -30,7 +30,7 @@ public void testBuilder() throws Exception { @Test public void testLevelConfig() throws Exception { Map config = Map.of("logging.level.stuff", "" + Level.DEBUG.name()); - var gum = RainbowGum.builder(LogConfig.of(ServiceRegistry.of(), config::get)).build(); + var gum = RainbowGum.builder(b -> b.properties(config::get)).build(); var logger = gum.router().getLogger("stuff"); diff --git a/rainbowgum-avaje-config/src/main/java/io/jstach/rainbowgum/avaje/AvajePropertiesProvider.java b/rainbowgum-avaje-config/src/main/java/io/jstach/rainbowgum/avaje/AvajePropertiesProvider.java index be51bfbb..60a5670c 100644 --- a/rainbowgum-avaje-config/src/main/java/io/jstach/rainbowgum/avaje/AvajePropertiesProvider.java +++ b/rainbowgum-avaje-config/src/main/java/io/jstach/rainbowgum/avaje/AvajePropertiesProvider.java @@ -17,7 +17,7 @@ */ @ServiceProvider(RainbowGumServiceProvider.class) public class AvajePropertiesProvider - implements RainbowGumServiceProvider.PropertiesProvider, RainbowGumServiceProvider.Initializer { + implements RainbowGumServiceProvider.PropertiesProvider, RainbowGumServiceProvider.Configurator { /** * For service loader. @@ -33,13 +33,15 @@ public List provideProperties(ServiceRegistry registry) { } @Override - public void initialize(ServiceRegistry registry, LogConfig config) { + public boolean configure(LogConfig config) { + var registry = config.serviceRegistry(); var props = registry.findOrNull(AvajeProperties.class); if (props != null) { props.configuration.onChange(e -> { config.publisher().publish(); }); } + return true; } } 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 c5df1578..83e4e3f2 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 @@ -47,7 +47,8 @@ public final class $$builderName$$ { static final String $$propertyLiteral$$ = PROPERTY_PREFIX + "$$name$$"; $$/normal$$ $$/properties$$ - + + private final String propertyPrefix; $$#properties$$ $$#normal$$ final Property<$$typeWithAnnotation$$> $$propertyVar$$; @@ -79,6 +80,7 @@ public final class $$builderName$$ { $$^-first$$, $$/-first$$"$$name$$", $$name$$ $$/prefixParameters$$ ); + this.propertyPrefix = LogProperties.interpolateKey(PROPERTY_PREFIX, prefixParameters); $$#properties$$ $$#normal$$ $$propertyVar$$ = Property.builder() @@ -150,4 +152,12 @@ public void toProperties(java.util.function.BiConsumer consumer) $$/properties$$ } + /** + * The interpolated property prefix: {@value #PROPERTY_PREFIX}. + * @return resolved prefix which should end with a ".". + */ + public String propertyPrefix() { + return this.propertyPrefix; + } + } \ 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 92c56d2f..8dff6b69 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 @@ -3,7 +3,6 @@ import org.fusesource.jansi.AnsiConsole; import io.jstach.rainbowgum.LogConfig; -import io.jstach.rainbowgum.ServiceRegistry; import io.jstach.rainbowgum.LogOutput.OutputType; import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; import io.jstach.svc.ServiceProvider; @@ -12,7 +11,7 @@ * Jansi initializer. */ @ServiceProvider(RainbowGumServiceProvider.class) -public class JansiInitializer implements RainbowGumServiceProvider.Initializer { +public class JansiInitializer implements RainbowGumServiceProvider.Configurator { /** * Jansi disable property. @@ -26,12 +25,13 @@ public JansiInitializer() { } @Override - public void initialize(ServiceRegistry registry, LogConfig config) { + public boolean configure(LogConfig config) { if (installJansi(config)) { AnsiConsole.systemInstall(); config.formatterRegistry() .setFormatterForOutputType(OutputType.CONSOLE_OUT, () -> JansiLogFormatter.builder().build()); } + return true; } private boolean installJansi(LogConfig config) { diff --git a/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoder.java b/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoder.java index e2718ce3..8d3c6bdc 100644 --- a/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoder.java +++ b/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoder.java @@ -57,7 +57,7 @@ static GelfEncoder of(@LogConfigurable.PrefixParameter String name, String host, prettyPrint = prettyPrint == null ? false : prettyPrint; host = Objects.requireNonNull(host); // var _headers = KeyValues.of(convertHeaders(headers)); - var _headers = KeyValues.of(headers); + var _headers = KeyValues.of(headers == null ? Map.of() : headers); return new GelfEncoder(host, _headers, prettyPrint); } diff --git a/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoderConfigurator.java b/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoderConfigurator.java new file mode 100644 index 00000000..533435b4 --- /dev/null +++ b/rainbowgum-json/src/main/java/io/jstach/rainbowgum/json/encoder/GelfEncoderConfigurator.java @@ -0,0 +1,56 @@ +package io.jstach.rainbowgum.json.encoder; + +import java.net.URI; +import java.util.List; + +import io.jstach.rainbowgum.LogConfig; +import io.jstach.rainbowgum.LogEncoder; +import io.jstach.rainbowgum.LogEncoder.EncoderProvider; +import io.jstach.rainbowgum.LogProperties; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; +import io.jstach.rainbowgum.spi.RainbowGumServiceProvider.Configurator; +import io.jstach.svc.ServiceProvider; + +/** + * Adds Gelf Encoder to encoder registry. + */ +@ServiceProvider(RainbowGumServiceProvider.class) +public class GelfEncoderConfigurator implements Configurator { + + /** + * Default constructor for service loader. + */ + public GelfEncoderConfigurator() { + } + + @Override + public boolean configure(LogConfig config) { + config.encoderRegistry().register("gelf", new GelfEncoderProvider()); + return true; + } + + private static class GelfEncoderProvider implements EncoderProvider { + + @Override + public LogEncoder provide(URI uri, String name, LogProperties properties) { + GelfEncoderBuilder b = new GelfEncoderBuilder(name); + String prefix = b.propertyPrefix(); + String query = uri.getRawQuery(); + query = query == null ? "" : query; + var uriProperties = LogProperties.builder() + .fromURIQuery(query) + .renameKey(s -> s.replace(prefix, "")) + .build(); + LogProperties combined = LogProperties.of(List.of(uriProperties, properties)); + + String host = uri.getHost(); + if (host != null) { + b.host(host); + } + b.fromProperties(combined); + return b.build(); + } + + } + +} diff --git a/rainbowgum-json/src/main/java/module-info.java b/rainbowgum-json/src/main/java/module-info.java index 4e842a0b..447dc085 100644 --- a/rainbowgum-json/src/main/java/module-info.java +++ b/rainbowgum-json/src/main/java/module-info.java @@ -8,5 +8,9 @@ exports io.jstach.rainbowgum.json.encoder; requires transitive io.jstach.rainbowgum; + requires static io.jstach.svc; requires static org.eclipse.jdt.annotation; + + provides io.jstach.rainbowgum.spi.RainbowGumServiceProvider + with io.jstach.rainbowgum.json.encoder.GelfEncoderConfigurator; } \ No newline at end of file diff --git a/rainbowgum-json/src/test/java/io/jstach/rainbowgum/json/encoder/GelfEncoderTest.java b/rainbowgum-json/src/test/java/io/jstach/rainbowgum/json/encoder/GelfEncoderTest.java index 0795932f..084e1d62 100644 --- a/rainbowgum-json/src/test/java/io/jstach/rainbowgum/json/encoder/GelfEncoderTest.java +++ b/rainbowgum-json/src/test/java/io/jstach/rainbowgum/json/encoder/GelfEncoderTest.java @@ -1,6 +1,6 @@ package io.jstach.rainbowgum.json.encoder; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import java.time.Instant; import java.util.LinkedHashMap; @@ -8,8 +8,11 @@ import org.junit.jupiter.api.Test; +import io.jstach.rainbowgum.LogConfig; import io.jstach.rainbowgum.LogEvent; +import io.jstach.rainbowgum.LogProperties; import io.jstach.rainbowgum.PropertiesParser; +import io.jstach.rainbowgum.RainbowGum; import io.jstach.rainbowgum.output.ListLogOutput; class GelfEncoderTest { @@ -63,4 +66,39 @@ void testBuilder() { assertEquals(expected, message); } + @Test + void testFullLoad() throws Exception { + String properties = """ + logging.appender.console.encoder=gelf:/// + logging.encoder.console.host=localhost + logging.encoder.console.headers=header1\\=1 + logging.encoder.console.prettyPrint=true + """; + LogConfig config = LogConfig.builder() + .properties(LogProperties.builder().fromProperties(properties).build()) + .configurator(new GelfEncoderConfigurator()) + .build(); + try (var r = RainbowGum.builder(config).build().start()) { + Instant instant = Instant.ofEpochMilli(1); + LogEvent e = LogEvent.of(System.Logger.Level.INFO, "gelf", "hello", null).freeze(instant); + r.log(e); + } + } + + @Test + void testFullLoadUri() throws Exception { + String properties = """ + logging.appender.console.encoder=gelf://localhost/?prettyPrint=false + """; + LogConfig config = LogConfig.builder() + .properties(LogProperties.builder().fromProperties(properties).build()) + .configurator(new GelfEncoderConfigurator()) + .build(); + try (var r = RainbowGum.builder(config).build().start()) { + Instant instant = Instant.ofEpochMilli(1); + LogEvent e = LogEvent.of(System.Logger.Level.INFO, "gelf", "hello", null).freeze(instant); + r.log(e); + } + } + } diff --git a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java index 8a1acadb..a6249969 100644 --- a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java +++ b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQInitializer.java @@ -7,7 +7,6 @@ import io.jstach.rainbowgum.LogOutput; import io.jstach.rainbowgum.LogOutput.OutputProvider; import io.jstach.rainbowgum.LogProperties; -import io.jstach.rainbowgum.ServiceRegistry; import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; import io.jstach.svc.ServiceProvider; @@ -16,7 +15,7 @@ * {@value RabbitMQOutput#URI_SCHEME}. */ @ServiceProvider(RainbowGumServiceProvider.class) -public class RabbitMQInitializer implements RainbowGumServiceProvider.Initializer { +public class RabbitMQInitializer implements RainbowGumServiceProvider.Configurator { /** * Default constructor for service loader. @@ -25,8 +24,9 @@ public RabbitMQInitializer() { } @Override - public void initialize(ServiceRegistry registry, LogConfig config) { + public boolean configure(LogConfig config) { config.outputRegistry().register(RabbitMQOutput.URI_SCHEME, RabbitMQOutputProvider.INSTANCE); + return true; } private enum RabbitMQOutputProvider implements OutputProvider { diff --git a/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java index b021a2fc..09b9c50c 100644 --- a/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java +++ b/test/rainbowgum-test-rabbitmq/src/main/java/io/jstach/rainbowgum/test/rabbitmq/RabbitMQSetup.java @@ -25,8 +25,10 @@ public class RabbitMQSetup { // } public static void run(Map properties) { - var config = LogConfig.builder().logProperties(properties::get).build(); - RainbowGumServiceProvider.runInitializers(ServiceLoader.load(RainbowGumServiceProvider.class), config); + var config = LogConfig.builder() // + .serviceLoader(ServiceLoader.load(RainbowGumServiceProvider.class)) // + .properties(properties::get) + .build(); RainbowGum.set(() -> RainbowGum.builder(config).build()); RainbowGum.of(); // RainbowGum.set(() -> gum);