From bc277d2e92e898e7df7374a975aab18480e5dd68 Mon Sep 17 00:00:00 2001 From: Adam Gent Date: Thu, 22 Aug 2024 16:58:39 -0400 Subject: [PATCH] Add clr and property patterns for boot support --- .../pattern/format/PatternCompiler.java | 6 +- .../pattern/format/PatternConfig.java | 60 ++++++++++- .../pattern/format/PatternConfigurator.java | 13 ++- .../format/PatternFormatterFactory.java | 101 +++++++++++++++++- .../pattern/format/PatternRegistry.java | 57 +++++++++- .../pattern/format/CompilerTest.java | 12 +++ 6 files changed, 239 insertions(+), 10 deletions(-) diff --git a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternCompiler.java b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternCompiler.java index add17ac3..d5fb8816 100644 --- a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternCompiler.java +++ b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternCompiler.java @@ -58,10 +58,14 @@ public static LogProvider of(Consumer consumer) { .build(LogProperties.GLOBAL_ANSI_DISABLE_PROPERTY) // .get(config.properties()) // .value(false); - var b = PatternConfig.builder().fromProperties(config.properties()); + var b = PatternConfig.builder() + .propertyFunction(PatternConfig.propertyFunction(config.properties(), + PatternConfig.PATTERN_PROPERY_PREFIX)) + .fromProperties(config.properties()); if (ansiDisable) { b.ansiDisabled(true); } + return b.build(); }); Builder b = builder(); diff --git a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfig.java b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfig.java index 800cec57..e17c6e6d 100644 --- a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfig.java +++ b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfig.java @@ -2,6 +2,9 @@ import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.Nullable; import io.jstach.rainbowgum.LogConfig; import io.jstach.rainbowgum.LogProperties; @@ -26,6 +29,11 @@ public sealed interface PatternConfig extends Configurator { */ public static final String PATTERN_CONFIG_PREFIX = LogProperties.ROOT_PREFIX + "pattern.config.{name}."; + /** + * Key Values that can be retrieved by various patterns. + */ + public static final String PATTERN_PROPERY_PREFIX = LogProperties.ROOT_PREFIX + "pattern.property."; + /** * Default zoneId if not specified. * @return zone id. @@ -45,6 +53,14 @@ public sealed interface PatternConfig extends Configurator { */ public boolean ansiDisabled(); + /** + * Key Value function for ad-hoc property replacements and lookup in various patterns. + * @return function. + * @see #PATTERN_PROPERY_PREFIX + */ + @SuppressWarnings("exports") + public Function propertyFunction(); + /** * Creates a builder to create formatter config. * @return builder. @@ -76,6 +92,7 @@ public static PatternConfigBuilder copy(PatternConfigBuilder builder, PatternCon builder.ansiDisabled(config.ansiDisabled()); builder.lineSeparator(config.lineSeparator()); builder.zoneId(config.zoneId()); + builder.propertyFunction(config.propertyFunction()); return builder; } @@ -97,6 +114,27 @@ public static PatternConfig ofUniversal() { return StandardFormatterConfig.UNIVERSAL_FORMATTER_CONFIG; } + /** + * Creates a property function. + * @param properties log properties. + * @param prefix prefix keys before asking LogProperties. + * @return function. + */ + @SuppressWarnings("exports") + public static Function propertyFunction(LogProperties properties, String prefix) { + record LogPropertiesPatternKeyValues(String prefix, + LogProperties properties) implements Function { + + @Override + public @Nullable String apply(String t) { + String key = LogProperties.concatKey(prefix, t); + return properties.valueOrNull(key); + } + + } + return new LogPropertiesPatternKeyValues(prefix, properties); + } + @Override default boolean configure(LogConfig config, Pass pass) { config.serviceRegistry().put(PatternConfig.class, ServiceRegistry.DEFAULT_SERVICE_NAME, this); @@ -132,11 +170,28 @@ default boolean ansiDisabled() { return false; } + @Override + default Function propertyFunction() { + return StandardPropertyFunction.INSTANCE; + } + + enum StandardPropertyFunction implements Function { + + INSTANCE; + + @Override + public @Nullable String apply(String t) { + return null; + } + + } + } enum StandardFormatterConfig implements DefaultFormatterConfig { - DEFAULT_FORMATTER_CONFIG(), UNIVERSAL_FORMATTER_CONFIG { + DEFAULT_FORMATTER_CONFIG(), // + UNIVERSAL_FORMATTER_CONFIG { @Override public String lineSeparator() { return "\n"; @@ -155,6 +210,7 @@ public boolean ansiDisabled() { } -record SimpleFormatterConfig(ZoneId zoneId, String lineSeparator, boolean ansiDisabled) implements PatternConfig { +record SimpleFormatterConfig(ZoneId zoneId, String lineSeparator, boolean ansiDisabled, + Function propertyFunction) implements PatternConfig { } \ No newline at end of file diff --git a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfigurator.java b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfigurator.java index d53638b7..3a2f92e4 100644 --- a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfigurator.java +++ b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternConfigurator.java @@ -1,6 +1,7 @@ package io.jstach.rainbowgum.pattern.format; import java.time.ZoneId; +import java.util.function.Function; import org.eclipse.jdt.annotation.Nullable; @@ -14,6 +15,7 @@ import io.jstach.rainbowgum.annotation.LogConfigurable.ConvertParameter; import io.jstach.rainbowgum.annotation.LogConfigurable.KeyParameter; import io.jstach.rainbowgum.annotation.LogConfigurable.PassThroughParameter; +import io.jstach.rainbowgum.pattern.format.DefaultFormatterConfig.StandardPropertyFunction; import io.jstach.rainbowgum.spi.RainbowGumServiceProvider; import io.jstach.rainbowgum.spi.RainbowGumServiceProvider.Configurator; import io.jstach.svc.ServiceProvider; @@ -57,14 +59,17 @@ static LogProvider provideEncoder(@KeyParameter String name, String } @LogConfigurable(name = "PatternConfigBuilder", prefix = PatternConfig.PATTERN_CONFIG_PREFIX) - static PatternConfig provideFormatterConfig(@KeyParameter String name, - @ConvertParameter("convertZoneId") @Nullable ZoneId zoneId, @Nullable String lineSeparator, - @Nullable Boolean ansiDisabled) { + static PatternConfig provideFormatterConfig(@KeyParameter String name, // + @ConvertParameter("convertZoneId") @Nullable ZoneId zoneId, // + @Nullable String lineSeparator, // + @Nullable Boolean ansiDisabled, // + @PassThroughParameter @Nullable Function propertyFunction) { PatternConfig dc = PatternConfig.of(); ansiDisabled = ansiDisabled == null ? dc.ansiDisabled() : ansiDisabled; lineSeparator = lineSeparator == null ? dc.lineSeparator() : lineSeparator; zoneId = zoneId == null ? dc.zoneId() : zoneId; - return new SimpleFormatterConfig(zoneId, lineSeparator, ansiDisabled); + propertyFunction = propertyFunction == null ? StandardPropertyFunction.INSTANCE : propertyFunction; + return new SimpleFormatterConfig(zoneId, lineSeparator, ansiDisabled, propertyFunction); } diff --git a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternFormatterFactory.java b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternFormatterFactory.java index b153bf5e..cfae7327 100644 --- a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternFormatterFactory.java +++ b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternFormatterFactory.java @@ -10,6 +10,7 @@ import static io.jstach.rainbowgum.pattern.format.ANSIConstants.RED_FG; import static io.jstach.rainbowgum.pattern.format.ANSIConstants.WHITE_FG; import static io.jstach.rainbowgum.pattern.format.ANSIConstants.YELLOW_FG; +import static io.jstach.rainbowgum.pattern.format.ANSIConstants.FAINT; import java.lang.System.Logger.Level; import java.time.ZoneId; @@ -176,6 +177,23 @@ protected LogFormatter _create(PatternConfig config, PatternKeyword node) { } + }, + PROPERTY() { + + @Override + protected LogFormatter _create(PatternConfig config, PatternKeyword node) { + String key = node.optOrNull(0); + if (key == null) { + return LogFormatter.builder().text("Property_HAS_NO_KEY").build(); + } + var f = config.propertyFunction(); + var v = f.apply(key); + if (v == null) { + return LogFormatter.noop(); + } + return LogFormatter.builder().text(v).build(); + } + }; static final String ISO8601_STR = "ISO8601"; @@ -235,6 +253,8 @@ final class ANSIConstants { final static String BOLD = "1;"; + final static String FAINT = "2;"; + final static String RESET = "0;"; final static String BLACK_FG = "30"; @@ -288,7 +308,53 @@ private static String levelToANSI(Level level) { } -enum HightlightCompositeFactory implements CompositeFactory { +record ClrLevelFormatter(@Nullable LogFormatter child) implements LogFormatter.EventFormatter { + + @Override + public void format(StringBuilder output, LogEvent event) { + /* + * TODO should a null child be a noop? Need to see what logback does for compat. + */ + var level = event.level(); + String code = levelToANSI(level); + output.append(ANSIConstants.ESC_START); + output.append(code); + output.append(ANSIConstants.ESC_END); + if (child != null) { + child.format(output, event); + } + output.append(ANSIConstants.SET_DEFAULT_COLOR); + } + + private static String levelToANSI(Level level) { + return switch (level) { + case ERROR -> RED_FG; + case WARNING -> YELLOW_FG; + case INFO -> GREEN_FG; + case DEBUG -> GREEN_FG; + case TRACE -> GREEN_FG; + default -> DEFAULT_FG; + }; + } + +} + +record ClrStaticFormatter(@Nullable LogFormatter child, String code) implements LogFormatter.EventFormatter { + + @Override + public void format(StringBuilder output, LogEvent event) { + output.append(ANSIConstants.ESC_START); + output.append(code); + output.append(ANSIConstants.ESC_END); + if (child != null) { + child.format(output, event); + } + output.append(ANSIConstants.SET_DEFAULT_COLOR); + } + +} + +enum HighlightCompositeFactory implements CompositeFactory { HIGHTLIGHT() { @@ -303,6 +369,39 @@ public LogFormatter create(PatternConfig config, PatternKeyword node, @Nullable return new HighlightFormatter(child); } + }, + + CLR() { + + @Override + public LogFormatter create(PatternConfig config, PatternKeyword node, @Nullable LogFormatter child) { + if (config.ansiDisabled()) { + if (child == null) { + return LogFormatter.noop(); + } + return child; + } + String color = node.optOrNull(0, HighlightCompositeFactory::parseColor); + if (color == null) { + return new ClrLevelFormatter(child); + } + return new ClrStaticFormatter(child, color); + } + + }; + + static String parseColor(String color) { + color = color.toLowerCase(Locale.ROOT); + return switch (color) { + case "blue" -> BLACK_FG; + case "cyan" -> CYAN_FG; + case "faint" -> FAINT + DEFAULT_FG; + case "green" -> GREEN_FG; + case "magenta" -> MAGENTA_FG; + case "red" -> RED_FG; + case "yellow" -> YELLOW_FG; + default -> throw new IllegalArgumentException("Bad color:" + color); + }; } } diff --git a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternRegistry.java b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternRegistry.java index c7f1184f..b581b54a 100644 --- a/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternRegistry.java +++ b/rainbowgum-pattern/src/main/java/io/jstach/rainbowgum/pattern/format/PatternRegistry.java @@ -145,6 +145,11 @@ enum KeywordKey implements PatternKey { * MDC keywords */ MDC("X", "mdc"), // + /** + * Property keywords + * @see PatternConfig#propertyFunction() + */ + PROPERTY("property"), // /** * Throwable keywords */ @@ -263,6 +268,47 @@ public List aliases() { } + // config.conversionRule("clr", ColorConverter.class); + // config.conversionRule("correlationId", CorrelationIdConverter.class); + // config.conversionRule("esb", EnclosedInSquareBracketsConverter.class); + // config.conversionRule("wex", WhitespaceThrowableProxyConverter.class); + // config.conversionRule("wEx", ExtendedWhitespaceThrowableProxyConverter.class) + + /** + * Spring Boot patterns. + */ + @CaseChanging + enum SpringBootKey implements PatternKey { + + /* + * TODO maybe this should be in the spring boot module only? + */ + + /** + * + * Spring Boot color pattern key + * + */ + CLR("clr"); + + private final List aliases; + + private SpringBootKey(List aliases) { + this.aliases = aliases; + } + + private SpringBootKey(String... others) { + this(List.of(others)); + } + + @Override + public List aliases() { + return this.aliases; + } + + } + } final class DefaultPatternRegistry implements PatternRegistry { @@ -325,7 +371,7 @@ static PatternRegistry registerDefaults(PatternRegistry registry) { case FILE -> KeywordFactory.of(CallerInfoFormatter.FILE); case LINE -> KeywordFactory.of(CallerInfoFormatter.LINE); case METHOD -> KeywordFactory.of(CallerInfoFormatter.METHOD); - default -> throw new IllegalArgumentException("Unexpected value: " + c); + case PROPERTY -> StandardKeywordFactory.PROPERTY; }; registry.register(c, keyword); @@ -349,11 +395,18 @@ static PatternRegistry registerDefaults(PatternRegistry registry) { case BOLD_RED -> ColorCompositeFactory.BOLD_RED; case BOLD_WHITE -> ColorCompositeFactory.BOLD_WHITE; case BOLD_YELLOW -> ColorCompositeFactory.BOLD_YELLOW; - case HIGHLIGHT -> HightlightCompositeFactory.HIGHTLIGHT; + case HIGHLIGHT -> HighlightCompositeFactory.HIGHTLIGHT; }; registry.register(c, factory); } + for (SpringBootKey sk : SpringBootKey.values()) { + var factory = switch (sk) { + case CLR -> HighlightCompositeFactory.CLR; + }; + registry.register(sk, factory); + } + registry.register(BareKey.BARE, BareCompositeFactory.BARE); return registry; } diff --git a/rainbowgum-pattern/src/test/java/io/jstach/rainbowgum/pattern/format/CompilerTest.java b/rainbowgum-pattern/src/test/java/io/jstach/rainbowgum/pattern/format/CompilerTest.java index d9361967..27435e8f 100644 --- a/rainbowgum-pattern/src/test/java/io/jstach/rainbowgum/pattern/format/CompilerTest.java +++ b/rainbowgum-pattern/src/test/java/io/jstach/rainbowgum/pattern/format/CompilerTest.java @@ -10,6 +10,7 @@ import java.lang.System.Logger.Level; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -130,6 +131,17 @@ LogEvent event() { return LogEvent.withCaller(super.event(), caller); } }, + PROPERTY(List.of("%property"), "Property_HAS_NO_KEY") { + }, + PROPERTY_KEY(List.of("%property{mykey}"), "hello") { + @Override + protected PatternConfig patternConfig() { + Map props = Map.of("mykey", "hello"); + return PatternConfig.copy(PatternConfig.builder(), PatternConfig.ofUniversal()) + .propertyFunction(props::get) + .build(); + } + }, THREAD(List.of("%t", "%thread"), "main") { }, LEVEL(List.of("%level", "%le", "%p"), "INFO") {