PatternConfig.java
package io.jstach.rainbowgum.pattern.format;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import io.jstach.rainbowgum.LogConfig;
import io.jstach.rainbowgum.LogProperty;
import io.jstach.rainbowgum.LogProperties;
import io.jstach.rainbowgum.ServiceRegistry;
import io.jstach.rainbowgum.annotation.EnumAlias;
import io.jstach.rainbowgum.spi.RainbowGumServiceProvider.Configurator;
/**
* Pattern Config needed for pattern keywords that is generally platform specific. Through
* property configuration there can only be one pattern config but through programmatic
* configuration there is no limit by creating custom {@link PatternCompiler}.
* <p>
* This class is also a configurator for easy programmatic registration with
* {@link LogConfig.Builder#configurator(Configurator)}.
*
* @see PatternCompiler
*
*/
public sealed interface PatternConfig extends Configurator {
/**
* Formatter properties prefix
*/
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.
*/
public ZoneId zoneId();
/**
* Line separator for %n by default uses {@link System#lineSeparator()}.
* @return line separator.
*/
public String lineSeparator();
/**
* Whether or not print escape outputs. If true than the color patterns will not
* decorate.
* @return false if ANSI escape sequences can be outputted.
*/
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<String, @Nullable String> propertyFunction();
/**
* The baseline instant used by <code>%r</code>/<code>%relative</code> to compute
* elapsed milliseconds. Captured once (e.g. when this config is built) rather than
* recomputed, the same way logback's <code>RelativeTimeConverter</code> captures its
* start time once when constructed.
* @return start time.
*/
public Instant startTime();
/**
* The first value returned by <code>%lsn</code>. Defaults to <code>0</code>.
* @return sequence number start.
*/
public long sequenceNumberStart();
/**
* Whether keywords that can cache their output (currently just
* <code>%logger{N}</code> via its abbreviator) actually do so. Defaults to
* {@link CacheType#BASIC}.
* @return cache type.
*/
public CacheType abbreviatorCache();
/**
* Whether a keyword's own internal caching (currently just <code>%logger{N}</code>'s
* abbreviator) is used. {@link #BASIC} is the only real strategy implemented today -
* kept as an enum rather than a boolean so a possible future global cache toggle (the
* same idea already used for logger name caching and level resolvers) can reuse it
* without a breaking property-format change.
*/
public enum CacheType {
/**
* No caching - every call recomputes.
*/
@EnumAlias("false")
OFF,
/**
* The default cache.
*/
@EnumAlias({ "true", "default" })
BASIC;
static CacheType parse(String value) {
String v = value.toUpperCase(Locale.ROOT);
return switch (v) {
case "TRUE", "DEFAULT" -> BASIC;
case "FALSE" -> OFF;
default -> LogProperty.enumValue(CacheType.class, value, "true", "default", "false");
};
}
}
/**
* Creates a builder to create formatter config.
* @return builder.
* @apiNote {@link PatternConfig} implements {@link Configurator} so it can be
* registered at config time easily.
*/
public static PatternConfigBuilder builder() {
return builder(LogProperties.DEFAULT_NAME);
}
/**
* Creates a builder to create formatter config.
* @param name for property resolution: {@value #PATTERN_CONFIG_PREFIX}.
* @return builder.
* @apiNote {@link PatternConfig} implements {@link Configurator} so it can be
* registered at config time easily.
*/
public static PatternConfigBuilder builder(String name) {
return new PatternConfigBuilder(name);
}
/**
* Copies the config to builder.
* @param builder to receive properties.
* @param config to copy from.
* @return builder passed in.
*/
public static PatternConfigBuilder copy(PatternConfigBuilder builder, PatternConfig config) {
builder.ansiDisabled(config.ansiDisabled());
builder.lineSeparator(config.lineSeparator());
builder.zoneId(config.zoneId());
builder.propertyFunction(config.propertyFunction());
builder.startTime(config.startTime());
builder.sequenceNumberStart(config.sequenceNumberStart());
builder.abbreviatorCache(config.abbreviatorCache());
return builder;
}
/**
* Default config.
* @return default config.
*/
public static PatternConfig of() {
return StandardFormatterConfig.DEFAULT_FORMATTER_CONFIG;
}
/**
* Platform independent formatter config that will not change across timezones or
* platforms. <strong>The zoneId is UTC, line separator is LF, and ansi is
* disabled.</strong>
* @return config.
*/
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<String, @Nullable String> propertyFunction(LogProperties properties, String prefix) {
record LogPropertiesPatternKeyValues(String prefix,
LogProperties properties) implements Function<String, @Nullable String> {
@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);
return true;
}
}
non-sealed interface DefaultFormatterConfig extends PatternConfig {
/**
* Default zoneId if not specified. If not overridden the system default will be used.
* @return zone id.
*/
@Override
default ZoneId zoneId() {
return ZoneId.systemDefault();
}
/**
* Line separator for %n by default uses {@link System#lineSeparator()}.
* @return line separator.
*/
@Override
default String lineSeparator() {
return System.lineSeparator();
}
/**
* Whether or not print escape outputs. If true than the color patterns will not
* decorate.
* @return false if ANSI escape sequences can be outputted.
*/
@Override
default boolean ansiDisabled() {
return false;
}
@Override
default Function<String, @Nullable String> propertyFunction() {
return StandardPropertyFunction.INSTANCE;
}
/**
* Captured once when this interface is first loaded so all default configs share a
* single start time approximating application/logging startup.
*/
@SuppressWarnings("TimeInStaticInitializer")
Instant DEFAULT_START_TIME = Instant.now();
@Override
default Instant startTime() {
return DEFAULT_START_TIME;
}
@Override
default long sequenceNumberStart() {
return 0L;
}
@Override
default CacheType abbreviatorCache() {
return CacheType.BASIC;
}
enum StandardPropertyFunction implements Function<String, @Nullable String> {
INSTANCE;
@Override
public @Nullable String apply(String t) {
return null;
}
}
}
enum StandardFormatterConfig implements DefaultFormatterConfig {
DEFAULT_FORMATTER_CONFIG(), //
UNIVERSAL_FORMATTER_CONFIG {
@Override
public String lineSeparator() {
return "\n";
}
@Override
public ZoneId zoneId() {
return ZoneId.from(ZoneOffset.UTC);
}
@Override
public boolean ansiDisabled() {
return true;
}
@Override
public Instant startTime() {
return Instant.EPOCH;
}
};
}
record SimpleFormatterConfig(ZoneId zoneId, String lineSeparator, boolean ansiDisabled,
Function<String, @Nullable String> propertyFunction, Instant startTime, long sequenceNumberStart,
PatternConfig.CacheType abbreviatorCache) implements PatternConfig {
}