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 extends LogAppender> 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 extends LogEncoder> 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 extends LogEncoder> 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 extends LogFormatter> 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 extends LogFormatter> 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 extends LogProperties> 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;
}