diff --git a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java index 57411b85..a9bcafd1 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogAppender.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogAppender.java @@ -1,5 +1,6 @@ package io.jstach.rainbowgum; +import java.net.URI; import java.util.List; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -84,6 +85,22 @@ public static LogAppender of(List appenders) { return new CompositeLogAppender(appenders.toArray(new LogAppender[] {})); } + /** + * Finds an appender based on URI. + */ + public interface AppenderProvider { + + /** + * Loads an appender from a URI. + * @param uri uri. + * @param name name of appender. + * @param config config to access properties and other components like encoders. + * @return appender. + */ + LogAppender provide(URI uri, String name, LogConfig config); + + } + /** * Builder for creating standard appenders. *

@@ -120,6 +137,16 @@ public Builder output(Provider output) { return this; } + /** + * Sets output. + * @param output output. + * @return builder. + */ + public Builder output(LogOutput output) { + this.output = Provider.of(output); + return this; + } + /** * Sets formatter as encoder. * @param formatter formatter to be converted to encoder. @@ -127,7 +154,7 @@ public Builder output(Provider output) { * @see LogEncoder#of(LogFormatter) */ public Builder formatter(LogFormatter formatter) { - this.encoder = LogEncoder.of(formatter); + this.encoder = Provider.of(LogEncoder.of(formatter)); return this; } @@ -138,7 +165,7 @@ public Builder formatter(LogFormatter formatter) { * @see LogEncoder#of(LogFormatter) */ public Builder formatter(LogFormatter.EventFormatter formatter) { - this.encoder = LogEncoder.of(formatter); + this.encoder = Provider.of(LogEncoder.of(formatter)); return this; } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java index f0bc1c3f..cbef3ed5 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogAppenderRegistry.java @@ -4,32 +4,26 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.Nullable; -import io.jstach.rainbowgum.LogConfig.Provider; +import io.jstach.rainbowgum.LogAppender.AppenderProvider; import io.jstach.rainbowgum.LogProperties.Property; /** * Register appenders by name. */ -public sealed interface LogAppenderRegistry permits DefaultAppenderRegistry { +public sealed interface LogAppenderRegistry extends AppenderProvider permits DefaultAppenderRegistry { /** - * Finds an appender by name. - * @param name name of the appender. - * @return appender provider to be used for creating the appender. + * Register a provider by {@link URI#getScheme() scheme}. + * @param scheme URI scheme to match for. + * @param provider provider for scheme. */ - Optional> appender(String name); - - /** - * Registers an appender provider by name. - * @param name of the appender. - * @param appenderProvider factory to be used for creating appenders. - */ - void register(String name, Provider appenderProvider); + public void register(String scheme, AppenderProvider provider); /** * Creates a log appender registry. @@ -63,12 +57,7 @@ private static String validateName(String name) { final class DefaultAppenderRegistry implements LogAppenderRegistry { - /* - * 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. - */ - private final Map> providers = new ConcurrentHashMap<>(); + private final Map providers = new ConcurrentHashMap<>(); static final Property fileProperty = Property.builder().toURI().build(LogProperties.FILE_PROPERTY); @@ -77,6 +66,21 @@ final class DefaultAppenderRegistry implements LogAppenderRegistry { .orElse(List.of()) .build(LogProperties.APPENDERS_PROPERTY); + @Override + public LogAppender provide(URI uri, String name, LogConfig properties) { + throw new NoSuchElementException(uri.toString()); + } + + @Override + public void register(String scheme, AppenderProvider provider) { + providers.put(scheme, provider); + } + + /* + * 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. + */ static List defaultAppenders(LogConfig config) { List appenders = new ArrayList<>(); fileAppender(config).ifPresent(appenders::add); @@ -135,25 +139,29 @@ static LogAppender appender( // } static LogAppender appender( // - AppenderConfig builder, // + AppenderConfig appenderConfig, // LogConfig config, // Property outputProperty, Property encoderProperty) { @Nullable - LogOutput output = builder.output(); + LogOutput output = appenderConfig.output(); @Nullable - LogEncoder encoder = builder.encoder(); + LogEncoder encoder = appenderConfig.encoder(); var properties = config.properties(); if (output == null) { output = outputProperty.get(properties).value(); } + if (output instanceof LogEncoder e) { + encoder = e; + } + if (encoder == null) { var _output = output; encoder = encoderProperty.get(properties).value(() -> { - var formatterRegistry = config.formatterRegistry(); - return LogEncoder.of(formatterRegistry.formatterForOutputType(_output.type())); + var encoderRegistry = config.encoderRegistry(); + return encoderRegistry.encoderForOutputType(_output.type()); }); } @@ -165,11 +173,6 @@ static LogAppender appender( // String name, // LogConfig config, // Property outputProperty, Property encoderProperty) { - var appenderRegistry = config.appenderRegistry(); - var appender = appenderRegistry.appender(name).map(a -> a.provide(config)).orElse(null); - if (appender != null) { - return appender; - } var builder = new AppenderConfig(name, null, null); return appender(builder, config, outputProperty, encoderProperty); @@ -199,15 +202,4 @@ private static Property encoderProperty(String propertyName, String .buildWithName(propertyName, name); } - @Override - public Optional> appender(String name) { - return Optional.ofNullable(providers.get(name)); - } - - @Override - public void register(String name, Provider appenderProvider) { - providers.put(name, appenderProvider); - - } - } diff --git a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java index 81004443..e5a2835f 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogConfig.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogConfig.java @@ -38,12 +38,6 @@ public sealed interface LogConfig { */ public LevelConfig levelResolver(); - /** - * Registered formatters. - * @return formatter registry. - */ - public LogFormatterRegistry formatterRegistry(); - /** * Output provider that uses URI to find output. * @return output provider. @@ -126,6 +120,16 @@ public interface Provider { return provider.provide(config); } + /** + * Creates a provider of instance that is already configured. + * @param component + * @param instance component instance. + * @return this. + */ + public static Provider of(U instance) { + return c -> instance; + } + } /** @@ -299,8 +303,6 @@ final class DefaultLogConfig implements LogConfig { private final LogOutputRegistry outputRegistry; - private final LogFormatterRegistry formatterRegistry; - private final LogAppenderRegistry appenderRegistry; private final LogEncoderRegistry encoderRegistry; @@ -317,7 +319,6 @@ protected LogConfig _config() { } }; this.outputRegistry = LogOutputRegistry.of(); - this.formatterRegistry = LogFormatterRegistry.of(); this.appenderRegistry = LogAppenderRegistry.of(); this.encoderRegistry = LogEncoderRegistry.of(); } @@ -347,11 +348,6 @@ public LogOutputRegistry outputRegistry() { return this.outputRegistry; } - @Override - public LogFormatterRegistry formatterRegistry() { - return this.formatterRegistry; - } - @Override public LogAppenderRegistry appenderRegistry() { return this.appenderRegistry; diff --git a/core/src/main/java/io/jstach/rainbowgum/LogEncoder.java b/core/src/main/java/io/jstach/rainbowgum/LogEncoder.java index 75a754f4..be36ab8c 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogEncoder.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogEncoder.java @@ -1,13 +1,6 @@ package io.jstach.rainbowgum; import java.net.URI; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.BitSet; - -import org.eclipse.jdt.annotation.Nullable; import io.jstach.rainbowgum.LogEncoder.AbstractEncoder; import io.jstach.rainbowgum.LogEncoder.Buffer.StringBuilderBuffer; @@ -43,7 +36,7 @@ * @see LogAppender * @see StringBuilderBuffer */ -public interface LogEncoder extends LogConfig.Provider { +public interface LogEncoder { /** * Creates a new buffer. The encoder should not try to reuse buffers @@ -85,11 +78,6 @@ public interface EncoderProvider { } - @Override - default LogEncoder provide(LogConfig config) { - return this; - } - /** * Encoders buffer. */ @@ -256,125 +244,3 @@ protected StringBuilderBuffer doBuffer() { } } - -final class PercentCodec { - - static final BitSet GEN_DELIMS = new BitSet(256); - static final BitSet SUB_DELIMS = new BitSet(256); - static final BitSet UNRESERVED = new BitSet(256); - static final BitSet URIC = new BitSet(256); - - static { - GEN_DELIMS.set(':'); - GEN_DELIMS.set('/'); - GEN_DELIMS.set('?'); - GEN_DELIMS.set('#'); - GEN_DELIMS.set('['); - GEN_DELIMS.set(']'); - GEN_DELIMS.set('@'); - - SUB_DELIMS.set('!'); - SUB_DELIMS.set('$'); - SUB_DELIMS.set('&'); - SUB_DELIMS.set('\''); - SUB_DELIMS.set('('); - SUB_DELIMS.set(')'); - SUB_DELIMS.set('*'); - SUB_DELIMS.set('+'); - SUB_DELIMS.set(','); - SUB_DELIMS.set(';'); - SUB_DELIMS.set('='); - - for (int i = 'a'; i <= 'z'; i++) { - UNRESERVED.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - UNRESERVED.set(i); - } - // numeric characters - for (int i = '0'; i <= '9'; i++) { - UNRESERVED.set(i); - } - UNRESERVED.set('-'); - UNRESERVED.set('.'); - UNRESERVED.set('_'); - UNRESERVED.set('~'); - URIC.or(SUB_DELIMS); - URIC.or(UNRESERVED); - } - - private static final int RADIX = 16; - - static void encode(final StringBuilder buf, final CharSequence content, final @Nullable Charset charset, - final BitSet safechars, final boolean blankAsPlus) { - final CharBuffer cb = CharBuffer.wrap(content); - final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb); - while (bb.hasRemaining()) { - final int b = bb.get() & 0xff; - if (safechars.get(b)) { - buf.append((char) b); - } - else if (blankAsPlus && b == ' ') { - buf.append("+"); - } - else { - buf.append("%"); - final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX)); - final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX)); - buf.append(hex1); - buf.append(hex2); - } - } - } - - static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, - final boolean blankAsPlus) { - encode(buf, content, charset, UNRESERVED, blankAsPlus); - } - - public static void encode(final StringBuilder buf, final CharSequence content, final Charset charset) { - encode(buf, content, charset, UNRESERVED, false); - } - - public static String encode(final CharSequence content, final Charset charset) { - - final StringBuilder buf = new StringBuilder(); - encode(buf, content, charset, UNRESERVED, false); - return buf.toString(); - } - - static String decode(final CharSequence content, final @Nullable Charset charset, final boolean plusAsBlank) { - final ByteBuffer bb = ByteBuffer.allocate(content.length()); - final CharBuffer cb = CharBuffer.wrap(content); - while (cb.hasRemaining()) { - final char c = cb.get(); - if (c == '%' && cb.remaining() >= 2) { - final char uc = cb.get(); - final char lc = cb.get(); - final int u = Character.digit(uc, RADIX); - final int l = Character.digit(lc, RADIX); - if (u != -1 && l != -1) { - bb.put((byte) ((u << 4) + l)); - } - else { - bb.put((byte) '%'); - bb.put((byte) uc); - bb.put((byte) lc); - } - } - else if (plusAsBlank && c == '+') { - bb.put((byte) ' '); - } - else { - bb.put((byte) c); - } - } - bb.flip(); - return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString(); - } - - public static String decode(final CharSequence content, final Charset charset) { - return decode(content, charset, false); - } - -} diff --git a/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java index 0267ddb1..6a50a84f 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogEncoderRegistry.java @@ -1,12 +1,17 @@ package io.jstach.rainbowgum; import java.net.URI; +import java.util.EnumMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; import io.jstach.rainbowgum.LogEncoder.EncoderProvider; +import io.jstach.rainbowgum.LogOutput.OutputType; +import io.jstach.rainbowgum.format.StandardEventFormatter; /** * Encoder registry @@ -34,6 +39,20 @@ public sealed interface LogEncoderRegistry extends EncoderProvider { */ public void register(String name, EncoderProvider encoder); + /** + * 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 LogEncoder encoderForOutputType(OutputType outputType); + + /** + * Sets a default formatter for a specific output type. + * @param outputType output type. + * @param formatter formatter. + */ + public void setEncoderForOutputType(OutputType outputType, Supplier formatter); + /** * Creates encoder registry * @return encoder registry @@ -93,4 +112,42 @@ public void register(String name, EncoderProvider encoder) { } + 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 encoder for output type. + */ + public LogEncoder encoderForOutputType(OutputType outputType) { + lock.readLock().lock(); + try { + var formatter = formatters.get(outputType); + if (formatter == null) { + return LogEncoder.of(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 setEncoderForOutputType(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/LogFormatterRegistry.java b/core/src/main/java/io/jstach/rainbowgum/LogFormatterRegistry.java deleted file mode 100644 index fb015f0f..00000000 --- a/core/src/main/java/io/jstach/rainbowgum/LogFormatterRegistry.java +++ /dev/null @@ -1,79 +0,0 @@ -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/LogOutput.java b/core/src/main/java/io/jstach/rainbowgum/LogOutput.java index c314e46b..68614fa8 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogOutput.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogOutput.java @@ -31,7 +31,7 @@ * @see LogOutput.OutputProvider * @see Buffer */ -public interface LogOutput extends LogLifecycle, Flushable, LogConfig.Provider { +public interface LogOutput extends LogLifecycle, Flushable { /** * {@link FileDescriptor#err} URI scheme. @@ -70,10 +70,6 @@ private static URI uri(String s) { } } - default LogOutput provide(LogConfig config) { - return this; - } - /** * Finds output based on URI. */ diff --git a/core/src/main/java/io/jstach/rainbowgum/LogProperties.java b/core/src/main/java/io/jstach/rainbowgum/LogProperties.java index 5a314aa1..a6dd52b2 100644 --- a/core/src/main/java/io/jstach/rainbowgum/LogProperties.java +++ b/core/src/main/java/io/jstach/rainbowgum/LogProperties.java @@ -203,7 +203,7 @@ default int order() { */ public final static class Builder { - private String description = "custom"; + private @Nullable String description = null; private int order = 0; @@ -262,6 +262,9 @@ public Builder renameKey(Function renameKey) { */ public Builder fromProperties(String properties) { function = Format.PROPERTIES.parse(properties); + if (description == null) { + description = "Properties String"; + } return this; } @@ -276,6 +279,25 @@ public Builder fromURIQuery(String query) { return this; } + /** + * Parses a string as a URI query string. If the URI has no query portion then it + * will be equivalent to empty properties. + * @param uri percent encoded uri with separator as "&" and key + * value separator of "=". + * @return this. + */ + public Builder fromURIQuery(URI uri) { + String query = uri.getRawQuery(); + if (query == null) { + query = ""; + } + if (description == null) { + description = "URI('" + uri + "')"; + } + function = Format.URI_QUERY.parse(query); + return this; + } + /** * Builds LogProperties based on builder config. * @return this. @@ -284,6 +306,10 @@ public LogProperties build() { if (function == null) { throw new IllegalStateException("function is was not set"); } + String description = this.description; + if (description == null) { + description = "custom"; + } return new DefaultLogProperties(function, description, renameKey, order); } @@ -299,7 +325,12 @@ private record DefaultLogProperties( // @Override public String description(String key) { - return renameKey.apply(key); + String rename = renameKey.apply(key); + String desc = "'" + key + "' from " + description; + if (!rename.equals(key)) { + desc += "[" + rename + "]"; + } + return desc; } } @@ -362,7 +393,9 @@ public static LogProperties of(List logProperties, LogP if (logProperties.size() == 0) { return logProperties.get(0); } - var array = logProperties.toArray(new LogProperties[] {}); + var array = logProperties.stream() + .filter(p -> p != StandardProperties.EMPTY) + .toArray(size -> new LogProperties[size]); Arrays.sort(array, Comparator.comparingInt(LogProperties::order).reversed()); return new CompositeLogProperties(array); } @@ -490,12 +523,12 @@ else if (idx < 0) { } /** - * Common log properties. + * Common static log properties such as System properties ane environment variables. */ enum StandardProperties implements LogProperties { /** - * Empty properties. + * Empty properties. The order is -1. */ EMPTY { @Override @@ -510,7 +543,8 @@ public int order() { }, /** * Uses {@link System#getProperty(String)} for {@link #valueOrNull(String)}. The - * {@link #order()} is the same value as microprofile config ordinal. + * {@link #order()} is 400 which the same value as microprofile + * config ordinal. */ SYSTEM_PROPERTIES { @Override @@ -522,6 +556,37 @@ public int order() { public int order() { return 400; } + }, + /** + * Uses {@link System#getenv(String)} for {@link #valueOrNull(String)}.The + * {@link #order()} is 300 which is the same value as microprofile + * config ordinal. + */ + ENVIRONMENT_VARIABLES { + @Override + public @Nullable String valueOrNull(String key) { + return System.getenv(translateKey(key)); + } + + @Override + protected String translateKey(String key) { + return key.replace(".", "_"); + } + + }; + + @Override + public String description(String key) { + String k = translateKey(key); + String description = "'" + key + "' from " + name(); + if (!k.equals(key)) { + description += "[" + k + "]"; + } + return description; + } + + protected String translateKey(String key) { + return key; } } @@ -624,7 +689,7 @@ private static PropertyConvertException throwPropertyError(String key, Exception } private static RuntimeException throwMissingError(List keys) { - throw new PropertyMissingException("Property missing. key: " + keys); + throw new PropertyMissingException("Property missing. keys: " + keys); } /** @@ -1555,4 +1620,19 @@ record CompositeLogProperties(LogProperties[] properties) implements LogProperti return null; } + public String description(String key) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (var p : properties) { + if (first) { + first = false; + } + else { + sb.append(", "); + } + sb.append(p.description(key)); + } + return sb.toString(); + } + } diff --git a/core/src/main/java/io/jstach/rainbowgum/PropertiesParser.java b/core/src/main/java/io/jstach/rainbowgum/PropertiesParser.java index 6f38e9f5..a6e03864 100644 --- a/core/src/main/java/io/jstach/rainbowgum/PropertiesParser.java +++ b/core/src/main/java/io/jstach/rainbowgum/PropertiesParser.java @@ -8,7 +8,11 @@ import java.io.StringReader; import java.io.StringWriter; import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.BitSet; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -184,3 +188,125 @@ public DuplicateKeyException(String key, int lineNumber) { } } + +final class PercentCodec { + + static final BitSet GEN_DELIMS = new BitSet(256); + static final BitSet SUB_DELIMS = new BitSet(256); + static final BitSet UNRESERVED = new BitSet(256); + static final BitSet URIC = new BitSet(256); + + static { + GEN_DELIMS.set(':'); + GEN_DELIMS.set('/'); + GEN_DELIMS.set('?'); + GEN_DELIMS.set('#'); + GEN_DELIMS.set('['); + GEN_DELIMS.set(']'); + GEN_DELIMS.set('@'); + + SUB_DELIMS.set('!'); + SUB_DELIMS.set('$'); + SUB_DELIMS.set('&'); + SUB_DELIMS.set('\''); + SUB_DELIMS.set('('); + SUB_DELIMS.set(')'); + SUB_DELIMS.set('*'); + SUB_DELIMS.set('+'); + SUB_DELIMS.set(','); + SUB_DELIMS.set(';'); + SUB_DELIMS.set('='); + + for (int i = 'a'; i <= 'z'; i++) { + UNRESERVED.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + UNRESERVED.set(i); + } + // numeric characters + for (int i = '0'; i <= '9'; i++) { + UNRESERVED.set(i); + } + UNRESERVED.set('-'); + UNRESERVED.set('.'); + UNRESERVED.set('_'); + UNRESERVED.set('~'); + URIC.or(SUB_DELIMS); + URIC.or(UNRESERVED); + } + + private static final int RADIX = 16; + + static void encode(final StringBuilder buf, final CharSequence content, final @Nullable Charset charset, + final BitSet safechars, final boolean blankAsPlus) { + final CharBuffer cb = CharBuffer.wrap(content); + final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb); + while (bb.hasRemaining()) { + final int b = bb.get() & 0xff; + if (safechars.get(b)) { + buf.append((char) b); + } + else if (blankAsPlus && b == ' ') { + buf.append("+"); + } + else { + buf.append("%"); + final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX)); + final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX)); + buf.append(hex1); + buf.append(hex2); + } + } + } + + static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, + final boolean blankAsPlus) { + encode(buf, content, charset, UNRESERVED, blankAsPlus); + } + + public static void encode(final StringBuilder buf, final CharSequence content, final Charset charset) { + encode(buf, content, charset, UNRESERVED, false); + } + + public static String encode(final CharSequence content, final Charset charset) { + + final StringBuilder buf = new StringBuilder(); + encode(buf, content, charset, UNRESERVED, false); + return buf.toString(); + } + + static String decode(final CharSequence content, final @Nullable Charset charset, final boolean plusAsBlank) { + final ByteBuffer bb = ByteBuffer.allocate(content.length()); + final CharBuffer cb = CharBuffer.wrap(content); + while (cb.hasRemaining()) { + final char c = cb.get(); + if (c == '%' && cb.remaining() >= 2) { + final char uc = cb.get(); + final char lc = cb.get(); + final int u = Character.digit(uc, RADIX); + final int l = Character.digit(lc, RADIX); + if (u != -1 && l != -1) { + bb.put((byte) ((u << 4) + l)); + } + else { + bb.put((byte) '%'); + bb.put((byte) uc); + bb.put((byte) lc); + } + } + else if (plusAsBlank && c == '+') { + bb.put((byte) ' '); + } + else { + bb.put((byte) c); + } + } + bb.flip(); + return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString(); + } + + public static String decode(final CharSequence content, final Charset charset) { + return decode(content, charset, false); + } + +} diff --git a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java index c570bd3e..0d459cb9 100644 --- a/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java +++ b/core/src/main/java/io/jstach/rainbowgum/RainbowGum.java @@ -216,6 +216,7 @@ public RainbowGum build() { /** * Builds an unstarted {@link RainbowGum}. + * @param instanceId unique id for rainbow gum instance. * @return an unstarted {@link RainbowGum}. */ private RainbowGum build(UUID instanceId) { diff --git a/core/src/test/java/io/jstach/rainbowgum/LogPropertiesTest.java b/core/src/test/java/io/jstach/rainbowgum/LogPropertiesTest.java index 041e86de..3792301d 100644 --- a/core/src/test/java/io/jstach/rainbowgum/LogPropertiesTest.java +++ b/core/src/test/java/io/jstach/rainbowgum/LogPropertiesTest.java @@ -1,16 +1,23 @@ package io.jstach.rainbowgum; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import java.lang.System.Logger.Level; +import java.net.URI; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import io.jstach.rainbowgum.LogProperties.Property; +import io.jstach.rainbowgum.LogProperties.PropertyMissingException; import io.jstach.rainbowgum.LogProperties.PropertyValue; class LogPropertiesTest { @Test - void testValue() { + void testLevelResolverProperty() { Map m = Map.of("logging.level.com.stuff", "DEBUG"); LogProperties props = s -> m.get(s); var extractor = ConfigLevelResolver.levelExtractor; @@ -20,4 +27,48 @@ void testValue() { value.value(); } + @Test + void testStaticPropertiesDescription() { + var properties = LogProperties.of(List.of(LogProperties.StandardProperties.SYSTEM_PROPERTIES, + LogProperties.StandardProperties.ENVIRONMENT_VARIABLES)); + try { + Property.builder().build("logging.some.ignoreMe").get(properties).value(); + fail("expected exception"); + } + catch (PropertyMissingException e) { + assertEquals( + "Property missing. keys: ['logging.some.ignoreMe' from SYSTEM_PROPERTIES, " + + "'logging.some.ignoreMe' from ENVIRONMENT_VARIABLES[logging_some_ignoreMe]]", + e.getMessage()); + } + } + + @Test + void testLogPropertiesBuilderMissingDescription() { + var props = LogProperties.builder() + .fromURIQuery(URI.create("stuff:///?blah=hello")) + .renameKey(k -> LogProperties.removeKeyPrefix(k, LogProperties.ROOT_PREFIX)) + .build(); + try { + Property.builder().build("logging.some.ignoreMe").get(props).value(); + fail("expected exception"); + } + catch (PropertyMissingException e) { + assertEquals( + "Property missing. keys: ['logging.some.ignoreMe' from URI('stuff:///?blah=hello')[some.ignoreMe]]", + e.getMessage()); + } + } + + @Test + void testLogPropertiesRenameKey() { + var props = LogProperties.builder() + .fromURIQuery(URI.create("stuff:///?blah=hello")) + .renameKey(k -> LogProperties.removeKeyPrefix(k, LogProperties.ROOT_PREFIX)) + .build(); + String actual = Property.builder().build("logging.blah").get(props).value(); + assertEquals("hello", actual); + + } + } 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 8dff6b69..06fe2b2d 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,6 +3,7 @@ import org.fusesource.jansi.AnsiConsole; import io.jstach.rainbowgum.LogConfig; +import io.jstach.rainbowgum.LogEncoder; import io.jstach.rainbowgum.LogOutput.OutputType; import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; import io.jstach.svc.ServiceProvider; @@ -28,8 +29,9 @@ public JansiInitializer() { public boolean configure(LogConfig config) { if (installJansi(config)) { AnsiConsole.systemInstall(); - config.formatterRegistry() - .setFormatterForOutputType(OutputType.CONSOLE_OUT, () -> JansiLogFormatter.builder().build()); + config.encoderRegistry() + .setEncoderForOutputType(OutputType.CONSOLE_OUT, + () -> LogEncoder.of(JansiLogFormatter.builder().build())); } return true; }