LogEventHandler.java
package io.jstach.rainbowgum.slf4j;
import java.lang.StackWalker.Option;
import java.lang.System.Logger.Level;
import org.jspecify.annotations.Nullable;
import org.slf4j.spi.LoggingEventBuilder;
import io.jstach.rainbowgum.KeyValues;
import io.jstach.rainbowgum.KeyValues.MutableKeyValues;
import io.jstach.rainbowgum.LogEvent;
import io.jstach.rainbowgum.LogEvent.Caller;
import io.jstach.rainbowgum.LogEventFactory;
import io.jstach.rainbowgum.LogEventFactory.KeyValuesContributor;
import io.jstach.rainbowgum.LogEventLogger;
interface LogEventHandler extends LogEventFactory, LogEventLogger {
/**
* Implemented by loggers whose dispatch target can be swapped after creation
* (currently only {@link ReplaceableLogger}), so a route change (e.g. the global
* router being replaced once a real RainbowGum loads) can rebind an already-created
* logger's handler instead of leaving it pointed at whatever it resolved to at
* creation time - the queued placeholder router, in the common pre-boot case.
*/
interface EventHandlerChangeable {
void setEventHandler(LogEventHandler eventHandler);
}
@Override
default void log(LogEvent event) {
handle(event);
}
void handle(LogEvent event);
default void handle(LogEvent event, @Nullable Caller caller) {
handle(event);
}
default void handle(Level level, String msg) {
handle(eventNoArg(level, msg, (Throwable) null));
}
default void handle(Level level, String format, Throwable throwable) {
handle(eventNoArg(level, format, throwable));
}
default void handle(Level level, String format, Object arg) {
handle(eventOneArg(level, format, arg));
}
default void handle(Level level, String format, Object arg1, Object arg2) {
handle(eventTwoArg(level, format, arg1, arg2));
}
default void handleArray(Level level, String format, Object[] args) {
handle(eventArgs(level, format, args));
}
public boolean isCallerAware();
/**
* The other registered key values contributors, merged with {@link #mdc()}'s current
* key values by {@link #defaultKeyValues()} and {@link #copyDefaultKeyValues()}: MDC
* wins over all of them except the application's own USER contributor. Defaults to a
* no-op contributor so no call site ever needs to null check. A real one is only ever
* supplied at handler-construction time in {@link RainbowGumLoggerFactory}.
* @return contributor, never {@code null}.
*/
default KeyValuesContributor contributor() {
return NoopKeyValuesContributor.INSTANCE;
}
@Override
default KeyValues defaultKeyValues() {
/*
* The contributor hands back mdc().keyValues() unchanged when nothing else
* contributes (the overwhelming majority of log calls) - no composite, no copy,
* for that common case. Router.log() (LogRouter.java) freezes the event - which
* defensively copies the key values - only when the route is actually
* asynchronous, i.e. only when a copy is ever needed at all.
*/
return contributor().keyValues(mdc().keyValues());
}
/**
* Like {@link #defaultKeyValues()} but a genuinely independent copy, safe for the
* caller to mutate further (e.g. to seed a fluent builder that will add more key
* values on top) - unlike {@link #defaultKeyValues()}, which is allowed to return
* MDC's live, not-yet-exposed instance for the common case where nothing further
* mutates it.
* @return a new mutable key values, safe to mutate.
*/
@SuppressWarnings("ReferenceEquality") // unchanged MDC means nothing else contributed
default MutableKeyValues copyDefaultKeyValues() {
var own = mdc().keyValues();
var merged = contributor().keyValues(own);
if (merged == own) {
return mdc().copyMutableKeyValues();
}
var buf = MutableKeyValues.of();
merged.forEach(buf);
return buf;
}
public RainbowGumMDCAdapter mdc();
default LoggingEventBuilder eventBuilder(org.slf4j.event.Level level) {
return new RainbowGumEventBuilder(this, Levels.toSystemLevel(level));
}
public LogEventHandler withDepth(int depth);
static LogEventHandler of(String loggerName, LogEventLogger logger, RainbowGumMDCAdapter mdc,
KeyValuesContributor contributor) {
record DefaultLogEventHandler(String loggerName, LogEventLogger logger, RainbowGumMDCAdapter mdc,
KeyValuesContributor contributor) implements LogEventHandler {
@Override
public void handle(LogEvent event) {
logger.log(event);
}
@Override
public LogEventHandler withDepth(int depth) {
return this;
}
@Override
public boolean isCallerAware() {
return false;
}
}
return new DefaultLogEventHandler(loggerName, logger, mdc, contributor);
}
static LogEventHandler ofCallerInfo(String loggerName, LogEventLogger logger, RainbowGumMDCAdapter mdc, int depth,
KeyValuesContributor contributor) {
return new CallerInfoEventDecorator(loggerName, mdc, logger, contributor, depth + CALLER_DEPTH_DELTA);
}
static final int CALLER_DEPTH_DELTA = 2;
static final StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
}