LogfmtFormatterBuilder.java
package io.jstach.rainbowgum.format;
import io.jstach.rainbowgum.LogProperties;
import io.jstach.rainbowgum.LogProperty;
/**
* Builder to create {@link io.jstach.rainbowgum.LogFormatter }.
* Formats events as logfmt, one line of key=value pairs per event like Go's log/slog
* text handler, selected with the logfmt encoder scheme.
* <p>
* Each event is written as one line, for example:
* <code>time=2026-10-05T15:04:05.123Z level=INFO logger=com.example.App thread=main msg="hello world" requestId=42</code>
* <ul>
* <li><code>time</code> is the UTC instant with milliseconds.</li>
* <li><code>level</code> is written by the level formatter, by default
* <code>TRACE</code>, <code>DEBUG</code>, <code>INFO</code>, <code>WARN</code>, or
* <code>ERROR</code>. logfmt does not define level names, and tools differ (Go's slog
* is upper case, logrus lower case).</li>
* <li>Every key value follows <code>msg</code>, in order. A key value named like one
* of these fields is prefixed with <code>keyValues.</code>.</li>
* <li>A throwable adds <code>error</code> (its <code>toString()</code>) and
* <code>stacktrace</code>.</li>
* </ul>
* A value is quoted when it is empty or contains a space, <code>=</code>,
* <code>"</code>, or a control character. Inside quotes <code>"</code>,
* <code>\</code>, newline, carriage return, and tab are backslash escaped and other
* control characters are written as <code>\</code><code>uXXXX</code>, so every event
* stays on one line. Characters logfmt does not allow in keys, and <code>%</code>,
* are percent-encoded (<code>a b</code> is <code>a%20b</code>), and an empty key is
* <code>%</code>, so different keys stay different.
* <p>
* Values can be colored like the TTLL formatter; keys, <code>msg</code>,
* <code>error</code>, and <code>stacktrace</code> are not. Padding from a padding
* level formatter is written after the level's color.
* <table class="table">
* <caption><strong>Properties retrieved from LogProperties</strong></caption>
* <tr>
* <th>Property Pattern</th>
* <th>Type</th>
* <th>Required</th>
* <th>Default</th>
* <th>Description</th>
* </tr>
* <tr>
* <td><code>{@value PROPERTY_levelFormatter }</code></td>
* <td><code>String (converted)</code></td>
* <td><code>true</code></td>
* <td>{@link io.jstach.rainbowgum.LogFormatter.LevelFormatter#of() level_formatter}</td>
* <td>Level formatter; as a property, a level formatter constant name.</td>
* </tr>
* <tr>
* <td><code>{@value PROPERTY_color }</code></td>
* <td><code>String (converted)</code></td>
* <td><code>false</code></td>
* <td><code>null</code></td>
* <td>Whether to color values, see {@link TTLL.ColorMode}.</td>
* </tr>
* <tr>
* <td><code>{@value PROPERTY_theme }</code></td>
* <td><code>String (converted)</code></td>
* <td><code>false</code></td>
* <td><code>null</code></td>
* <td>Which colors, see {@link TTLL.ColorTheme}.</td>
* </tr>
* </table>
*/
public final class LogfmtFormatterBuilder implements io.jstach.rainbowgum.LogBuilder<LogfmtFormatterBuilder,io.jstach.rainbowgum.LogFormatter> {
/**
* The properties to be retrieved from config will have
* this prefix.
*/
static final String PROPERTY_PREFIX = "logging.appender.{name}.encoder.logfmt.";
/**
* <code>{@value #PROPERTY_levelFormatter } = io.jstach.rainbowgum.LogFormatter.LevelFormatter</code> Level formatter; as a property, a level formatter constant name.
*/
static final String PROPERTY_levelFormatter = PROPERTY_PREFIX + "levelFormatter";
/**
* <code>{@value #PROPERTY_color } = io.jstach.rainbowgum.format.TTLL.ColorMode</code> Whether to color values, see {@link TTLL.ColorMode}.
*/
static final String PROPERTY_color = PROPERTY_PREFIX + "color";
/**
* <code>{@value #PROPERTY_theme } = io.jstach.rainbowgum.format.TTLL.ColorTheme</code> Which colors, see {@link TTLL.ColorTheme}.
*/
static final String PROPERTY_theme = PROPERTY_PREFIX + "theme";
private final String propertyPrefix;
final String property_levelFormatter;
final String property_color;
final String property_theme;
private final java.lang.String name;
private io.jstach.rainbowgum.LogFormatter.LevelFormatter levelFormatter = io.jstach.rainbowgum.format.LogfmtFormatter.DEFAULT_LEVEL_FORMATTER;
private io.jstach.rainbowgum.format.TTLL.@org.jspecify.annotations.Nullable ColorMode color = null;
private io.jstach.rainbowgum.format.TTLL.@org.jspecify.annotations.Nullable ColorTheme theme = null;
private io.jstach.rainbowgum.@org.jspecify.annotations.Nullable ServiceRegistry serviceRegistry;
/**
* Create a builder for {@code io.jstach.rainbowgum.LogFormatter }.
* @param name will fill <code>{name}</code> in <code>logging.appender.{name}.encoder.logfmt.</code>. Encoder name, used for property lookup.
*/
public LogfmtFormatterBuilder(
java.lang.String name
) {
try {
java.util.Map<String,String> prefixParameters = java.util.Map.of(
"name", name
);
this.propertyPrefix = LogProperties.interpolateKey(PROPERTY_PREFIX, prefixParameters);
this.name = name;
property_levelFormatter = LogProperties.interpolateKey(PROPERTY_levelFormatter, prefixParameters);
property_color = LogProperties.interpolateKey(PROPERTY_color, prefixParameters);
property_theme = LogProperties.interpolateKey(PROPERTY_theme, prefixParameters);
}
catch (IllegalArgumentException e) {
throw LogProperty.ValidationException.of(this.getClass(), e);
}
}
/**
* Sets serviceRegistry.
* Where themes chosen by name in properties are looked up.
* Default is <code>null</code>.
* @param serviceRegistry not configurable through properties Where themes chosen by name in properties are looked up.
* @return this builder.
*/
public LogfmtFormatterBuilder serviceRegistry(io.jstach.rainbowgum.@org.jspecify.annotations.Nullable ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
return this;
}
/**
* Sets <strong>required</strong> levelFormatter.
* Level formatter; as a property, a level formatter constant name.
* Default is {@link io.jstach.rainbowgum.LogFormatter.LevelFormatter#of() level_formatter}.
* @param levelFormatter <code>{@value #PROPERTY_levelFormatter } = io.jstach.rainbowgum.LogFormatter.LevelFormatter</code> Level formatter; as a property, a level formatter constant name.
* @return this builder.
*/
public LogfmtFormatterBuilder levelFormatter(io.jstach.rainbowgum.LogFormatter.LevelFormatter levelFormatter) {
this.levelFormatter = levelFormatter;
return this;
}
/**
* Sets color.
* Whether to color values, see {@link TTLL.ColorMode}.
* Default is <code>null</code>.
* @param color <code>{@value #PROPERTY_color } = io.jstach.rainbowgum.format.TTLL.ColorMode</code> Whether to color values, see {@link TTLL.ColorMode}.
* @return this builder.
*/
public LogfmtFormatterBuilder color(io.jstach.rainbowgum.format.TTLL.@org.jspecify.annotations.Nullable ColorMode color) {
this.color = color;
return this;
}
/**
* Sets theme.
* Which colors, see {@link TTLL.ColorTheme}.
* Default is <code>null</code>.
* @param theme <code>{@value #PROPERTY_theme } = io.jstach.rainbowgum.format.TTLL.ColorTheme</code> Which colors, see {@link TTLL.ColorTheme}.
* @return this builder.
*/
public LogfmtFormatterBuilder theme(io.jstach.rainbowgum.format.TTLL.@org.jspecify.annotations.Nullable ColorTheme theme) {
this.theme = theme;
return this;
}
/**
* Creates {@code io.jstach.rainbowgum.LogFormatter } from this builder.
* @return {@code io.jstach.rainbowgum.LogFormatter }.
*/
public io.jstach.rainbowgum.LogFormatter build() {
try {
return io.jstach.rainbowgum.format.LogfmtFormatter.of(
this.name
, io.jstach.rainbowgum.LogProperty.require(property_levelFormatter, this.levelFormatter)
, this.color
, this.theme
, this.serviceRegistry
);
}
catch (IllegalArgumentException | LogProperty.PropertyMissingException e) {
throw LogProperty.ValidationException.of(this.getClass(), e);
}
}
@Override
public LogfmtFormatterBuilder fromProperties(LogProperties properties) {
var __v = LogProperty.Validator.of(this.getClass());
var _levelFormatter = properties.forKey(property_levelFormatter).ofString().map(_v -> io.jstach.rainbowgum.format.LogfmtFormatter.convertLevelFormatter(_v)).or(this.levelFormatter).validate(__v);
var _color = properties.forKey(property_color).ofString().map(_v -> io.jstach.rainbowgum.format.LogfmtFormatter.convertColor(_v)).or(this.color).validateIfError(__v);
var _theme = properties.forKey(property_theme).ofString().map(_v -> io.jstach.rainbowgum.format.LogfmtFormatter.convertTheme(_v)).or(this.theme).validateIfError(__v);
__v.validate();
this.levelFormatter = _levelFormatter.value();
this.color = _color.valueOrNull();
this.theme = _theme.valueOrNull();
return this;
}
/**
* The interpolated property prefix: {@value #PROPERTY_PREFIX}.
* @return resolved prefix which should end with a "<code>.</code>".
*/
@Override
public String propertyPrefix() {
return this.propertyPrefix;
}
}