RainbowGumEventBuilder.java

package io.jstach.rainbowgum.slf4j;

import java.lang.System.Logger.Level;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import org.jspecify.annotations.Nullable;
import org.slf4j.Marker;
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.LogEventLogger;
import io.jstach.rainbowgum.LogEvent.Caller;
import io.jstach.rainbowgum.LogMessageFormatter;
import io.jstach.rainbowgum.LogMessageFormatter.StandardMessageFormatter;
import io.jstach.rainbowgum.slf4j.spi.LoggerDecoratorService.DepthAwareEventBuilder;

/*
 * log(String message, Object arg0, Object arg1)'s param names are off by one from
 * errorprone's synthesized arg0/arg1/arg2 positional guess (slf4j-api isn't compiled
 * with -parameters, so it can't see the real names) - not an actual mismatch.
 */
@SuppressWarnings("OverridingMethodInconsistentArgumentNamesChecker")
class RainbowGumEventBuilder implements LoggingEventBuilder, DepthAwareEventBuilder {

	private final LogEventHandler handler;

	private @Nullable List<@Nullable Object> args;

	private @Nullable MutableKeyValues mutableKeyValues;

	private final String loggerName;

	private final Level level;

	private @Nullable String message;

	private @Nullable Throwable throwable;

	private static final LogMessageFormatter messageFormatter = StandardMessageFormatter.SLF4J;

	private static final int DEPTH_DELTA = 2;

	private int depth = 0;

	private LogEventLogger logger;

	public RainbowGumEventBuilder(LogEventHandler handler, Level level) {
		this.handler = handler;
		this.level = level;
		this.loggerName = handler.loggerName();
		this.logger = handler;
	}

	@Override
	public RainbowGumEventBuilder setDepth(int depth) {
		this.depth = depth;
		return this;

	}

	MutableKeyValues kvs() {
		var kvs = this.mutableKeyValues;
		if (kvs == null) {
			kvs = this.mutableKeyValues = handler.copyDefaultKeyValues();
		}
		return kvs;
	}

	List<@Nullable Object> args() {
		var args = this.args;
		if (args == null) {
			args = this.args = new ArrayList<>(2);
		}
		return args;
	}

	@Override
	public LoggingEventBuilder setCause(Throwable cause) {
		this.throwable = cause;
		return this;
	}

	@Override
	public LoggingEventBuilder addMarker(Marker marker) {
		return this;
	}

	@Override
	public LoggingEventBuilder addArgument(@Nullable Object p) {
		args().add(p);
		return this;
	}

	@Override
	public LoggingEventBuilder addArgument(Supplier<?> objectSupplier) {
		return addArgument(objectSupplier.get());
	}

	@Override
	public LoggingEventBuilder addKeyValue(String key, @Nullable Object value) {
		kvs().putKeyValue(key, value == null ? null : value.toString());
		return this;
	}

	@Override
	public LoggingEventBuilder addKeyValue(String key, Supplier<Object> valueSupplier) {
		return addKeyValue(key, valueSupplier.get());
	}

	@Override
	public LoggingEventBuilder setMessage(String message) {
		this.message = message;
		return this;
	}

	@Override
	public LoggingEventBuilder setMessage(Supplier<String> messageSupplier) {
		return setMessage(messageSupplier.get());
	}

	@Override
	public LoggingEventBuilder setLogger(LogEventLogger logger) {
		this.logger = logger;
		return this;
	}

	@Override
	public @Nullable String message() {
		return this.message;
	}

	@Override
	public @Nullable Throwable throwable() {
		return this.throwable;
	}

	@Override
	public List<@Nullable Object> arguments() {
		var args = this.args;
		if (args == null) {
			return List.of();
		}
		return Collections.unmodifiableList(new ArrayList<>(args));
	}

	@Override
	public KeyValues keyValues() {
		var keyValues = this.mutableKeyValues;
		if (keyValues == null) {
			return handler.defaultKeyValues();
		}
		return keyValues;
	}

	private void _log() {
		Instant timestamp = Instant.now();
		var thread = Thread.currentThread();
		String threadName = thread.getName();
		long threadId = thread.threadId();
		KeyValues keyValues = this.mutableKeyValues;
		if (keyValues == null) {
			keyValues = handler.defaultKeyValues();
		}
		var event = LogEvent.ofAll(timestamp, threadName, threadId, level, loggerName, message, keyValues, throwable,
				messageFormatter, this.args);
		if (handler.isCallerAware()) {
			@Nullable Caller caller = LogEventHandler.stackWalker
				.walk(s -> s.skip(depth + DEPTH_DELTA).limit(1).map(f -> Caller.of(f)).findFirst().orElse(null));
			if (caller != null) {
				event = LogEvent.withCaller(event, caller);
			}
		}
		logger.log(event);
	}

	@Override
	public void log() {
		_log();
	}

	@Override
	public void log(String message) {
		this.message = Objects.requireNonNullElse(message, "");
		_log();
	}

	@Override
	public void log(String message, Object arg) {
		setMessage(message);
		addArgument(arg);
		_log();
	}

	@Override
	public void log(String message, Object arg0, Object arg1) {
		setMessage(message);
		var args = args();
		args.add(arg0);
		args.add(arg1);
		_log();
	}

	@Override
	public void log(String message, Object... args) {
		setMessage(message);
		var args_ = args();
		if (args != null) {
			for (var a : args) {
				args_.add(a);
			}
		}
		_log();
	}

	@Override
	public void log(Supplier<String> messageSupplier) {
		setMessage(messageSupplier);
		_log();
	}

}