Skip to content

Commit

Permalink
Work around the collections change between 2.12 and 2.13
Browse files Browse the repository at this point in the history
  • Loading branch information
morgen-peschke committed Jun 25, 2024
1 parent 30f79f3 commit a808c2c
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,89 @@
import org.slf4j.Logger;
import org.slf4j.MDC;
import org.slf4j.Marker;
import org.typelevel.log4cats.extras.DeferredLogMessage;
import org.typelevel.log4cats.extras.DeferredLogMessage$;
import scala.collection.immutable.Map;
import org.typelevel.log4cats.extras.LogLevel;
import scala.Option;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;

public class JTestLogger implements Logger {
// Java -> Scala compat helpers

private static final scala.Option<Throwable> none = scala.Option$.MODULE$.empty();
private static scala.Option<Throwable> some(Throwable t) { return scala.Option$.MODULE$.apply(t); }
private static final LogLevel.Trace$ Trace = LogLevel.Trace$.MODULE$;
private static final LogLevel.Debug$ Debug = LogLevel.Debug$.MODULE$;
private static final LogLevel.Info$ Info = LogLevel.Info$.MODULE$;
private static final LogLevel.Warn$ Warn = LogLevel.Warn$.MODULE$;
private static final LogLevel.Error$ Error = LogLevel.Error$.MODULE$;

private Map<String, String> captureContext () {
java.util.Map<String, String> mdc = MDC.getCopyOfContextMap();
if (mdc == null) {
return new HashMap<>();
}
return MDC.getCopyOfContextMap();
}

public static class TestLogMessage {
public final LogLevel logLevel;
public final java.util.Map<String, String> context;
public final Option<Throwable> throwableOpt;
public final Supplier<String> message;

public TestLogMessage(LogLevel logLevel,
java.util.Map<String, String> context,
Option<Throwable> throwableOpt,
Supplier<String> message) {
this.logLevel = logLevel;
this.context = context;
this.throwableOpt = throwableOpt;
this.message = message;
}

@Override
public String toString() {
return new StringBuilder()
.append("TestLogMessage(")
.append("logLevel=").append(logLevel)
.append(", ")
.append("context=").append(context)
.append(", ")
.append("throwableOpt=").append(throwableOpt)
.append(", ")
.append("message=").append(message.get())
.append(')')
.toString();
}

static TestLogMessage of(LogLevel logLevel,
java.util.Map<String, String> context,
Throwable throwable,
Supplier<String> message) {
return new TestLogMessage(logLevel, context, some(throwable), message);
}

static TestLogMessage of(LogLevel logLevel,
java.util.Map<String, String> context,
Supplier<String> message) {
return new TestLogMessage(logLevel, context, none, message);
}
}

private final String loggerName;
private final boolean traceEnabled;
private final boolean debugEnabled;
private final boolean infoEnabled;
private final boolean warnEnabled;
private final boolean errorEnabled;
private final AtomicReference<List<DeferredLogMessage>> loggedMessages;
private final AtomicReference<List<TestLogMessage>> loggedMessages;


public JTestLogger(String loggerName,
boolean traceEnabled,
Expand All @@ -49,35 +115,17 @@ public JTestLogger(String loggerName,
this.infoEnabled = infoEnabled;
this.warnEnabled = warnEnabled;
this.errorEnabled = errorEnabled;
loggedMessages = new AtomicReference<>(new ArrayList<DeferredLogMessage>());
}

// Java -> Scala compat helpers

private final scala.Option<Throwable> none = scala.Option$.MODULE$.empty();
private scala.Option<Throwable> some(Throwable t) { return scala.Option$.MODULE$.apply(t); }

private Map<String, String> captureContext () {
java.util.Map<String, String> mdc = MDC.getCopyOfContextMap();
if (mdc == null) {
return scala.collection.immutable.Map$.MODULE$.empty();
}
return scala.collection.immutable.Map$.MODULE$.from(
scala.jdk.javaapi.CollectionConverters.asScala(MDC.getCopyOfContextMap())
);
loggedMessages = new AtomicReference<>(new ArrayList<TestLogMessage>());
}

// Way to long to type the full DeferredLogMessage$.MODULE$ all the time
private DeferredLogMessage$ DLM = DeferredLogMessage$.MODULE$;

private void save(Function<Map<String, String>, DeferredLogMessage> mkLogMessage) {
private void save(Function<Map<String, String>, TestLogMessage> mkLogMessage) {
loggedMessages.updateAndGet(ll -> {
ll.add(mkLogMessage.apply(captureContext()));
return ll;
});
}

public List<DeferredLogMessage> logs() { return loggedMessages.get(); }
public List<TestLogMessage> logs() { return loggedMessages.get(); }
public void reset() { loggedMessages.set(new ArrayList<>()); }

@Override public String getName() { return loggerName;}
Expand All @@ -95,20 +143,20 @@ private void save(Function<Map<String, String>, DeferredLogMessage> mkLogMessage
@Override public boolean isWarnEnabled(Marker marker) { return warnEnabled; }
@Override public boolean isErrorEnabled(Marker marker) { return errorEnabled; }

@Override public void trace(String msg) { save(ctx -> DLM.trace(ctx, none, () -> msg)); }
@Override public void trace(String msg, Throwable t) { save(ctx -> DLM.trace(ctx, some(t), () -> msg)); }
@Override public void trace(String msg) { save(ctx -> TestLogMessage.of(Trace, ctx, () -> msg)); }
@Override public void trace(String msg, Throwable t) { save(ctx -> TestLogMessage.of(Trace, ctx, t, () -> msg)); }

@Override public void debug(String msg) { save(ctx -> DLM.debug(ctx, none, () -> msg)); }
@Override public void debug(String msg, Throwable t) { save(ctx -> DLM.debug(ctx, some(t), () -> msg)); }
@Override public void debug(String msg) { save(ctx -> TestLogMessage.of(Debug, ctx, () -> msg)); }
@Override public void debug(String msg, Throwable t) { save(ctx -> TestLogMessage.of(Debug, ctx, t, () -> msg)); }

@Override public void info(String msg) { save(ctx -> DLM.info(ctx, none, () -> msg)); }
@Override public void info(String msg, Throwable t) { save(ctx -> DLM.info(ctx, some(t), () -> msg)); }
@Override public void info(String msg) { save(ctx -> TestLogMessage.of(Info, ctx, () -> msg)); }
@Override public void info(String msg, Throwable t) { save(ctx -> TestLogMessage.of(Info, ctx, t, () -> msg)); }

@Override public void warn(String msg) { save(ctx -> DLM.warn(ctx, none, () -> msg)); }
@Override public void warn(String msg, Throwable t) { save(ctx -> DLM.warn(ctx, some(t), () -> msg)); }
@Override public void warn(String msg) { save(ctx -> TestLogMessage.of(Warn, ctx, () -> msg)); }
@Override public void warn(String msg, Throwable t) { save(ctx -> TestLogMessage.of(Warn, ctx, t, () -> msg)); }

@Override public void error(String msg) { save(ctx -> DLM.error(ctx, none, () -> msg)); }
@Override public void error(String msg, Throwable t) { save(ctx -> DLM.error(ctx, some(t), () -> msg)); }
@Override public void error(String msg) { save(ctx -> TestLogMessage.of(Error, ctx, () -> msg)); }
@Override public void error(String msg, Throwable t) { save(ctx -> TestLogMessage.of(Error, ctx, t, () -> msg)); }

// We shouldn't need these for our tests, so we're treating these variants as if they were the standard method

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ import java.util.concurrent.ThreadFactory
import org.slf4j.MDC
import munit.CatsEffectSuite
import org.typelevel.log4cats.extras.DeferredLogMessage
import org.typelevel.log4cats.slf4j.internal.JTestLogger.TestLogMessage

import java.util
import java.util.function
import java.util.function.{BiConsumer, BinaryOperator, Supplier}
import java.util.stream.Collector
import scala.collection.mutable.ListBuffer
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContextExecutorService
import scala.jdk.CollectionConverters.*
import scala.util.control.NoStackTrace

class Slf4jLoggerInternalSuite extends CatsEffectSuite {
Expand Down Expand Up @@ -88,12 +93,47 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite {
.assertEquals(initial)
}

// Collections compat with Java is really annoying across the 2.12 / 2.13 boundary
//
// If you are reading this and support for 2.12 has been dropped, kindly rip this
// out and call one of the helpers from scala.jdk.javaapi instead.
private def toScalaList[A]: Collector[A, ListBuffer[A], List[A]] =
new Collector[A, ListBuffer[A], List[A]] {
override val characteristics: util.Set[Collector.Characteristics] =
new util.HashSet[Collector.Characteristics]()

override val supplier: Supplier[ListBuffer[A]] = () => new ListBuffer[A]

override val accumulator: BiConsumer[ListBuffer[A], A] = (b, e) => b.append(e)

override val combiner: BinaryOperator[ListBuffer[A]] = (a, b) => {
a.appendAll(b)
a
}

override val finisher: function.Function[ListBuffer[A], List[A]] = _.result()
}

private def toDeferredLogs(jl: java.util.List[TestLogMessage]): List[DeferredLogMessage] =
jl.stream()
.map[DeferredLogMessage] { tl =>
val context =
tl.context
.entrySet()
.stream()
.map[(String, String)](e => e.getKey -> e.getValue)
.collect(toScalaList)
.toMap
DeferredLogMessage(tl.logLevel, context, tl.throwableOpt, () => tl.message.get())
}
.collect(toScalaList[DeferredLogMessage])

testLoggerFixture().test("Slf4jLoggerInternal correctly sets the MDC") { testLogger =>
Slf4jLogger
.getLoggerFromSlf4j[IO](testLogger)
.info(Map("foo" -> "bar"))("A log went here") >>
IO(testLogger.logs())
.map(_.asScala.toList)
.map(toDeferredLogs)
.assertEquals(
List(
DeferredLogMessage.info(Map("foo" -> "bar"), none, () => "A log went here")
Expand Down Expand Up @@ -253,51 +293,59 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite {
slf4jLogger.info("info(msg)").assert >>
slf4jLogger.warn("warn(msg)").assert >>
slf4jLogger.error("error(msg)").assert >>
IO(testLogger.logs().asScala.toList).assertEquals(
List(
DeferredLogMessage.trace(Map.empty, none, () => "trace(msg)"),
DeferredLogMessage.debug(Map.empty, none, () => "debug(msg)"),
DeferredLogMessage.info(Map.empty, none, () => "info(msg)"),
DeferredLogMessage.warn(Map.empty, none, () => "warn(msg)"),
DeferredLogMessage.error(Map.empty, none, () => "error(msg)")
)
) >>
IO(testLogger.logs())
.map(toDeferredLogs)
.assertEquals(
List(
DeferredLogMessage.trace(Map.empty, none, () => "trace(msg)"),
DeferredLogMessage.debug(Map.empty, none, () => "debug(msg)"),
DeferredLogMessage.info(Map.empty, none, () => "info(msg)"),
DeferredLogMessage.warn(Map.empty, none, () => "warn(msg)"),
DeferredLogMessage.error(Map.empty, none, () => "error(msg)")
)
) >>
IO(testLogger.reset()) >>
slf4jLogger.trace(throwable("trace(t)(msg)"))("trace(t)(msg)").assert >>
slf4jLogger.debug(throwable("debug(t)(msg)"))("debug(t)(msg)").assert >>
slf4jLogger.info(throwable("info(t)(msg)"))("info(t)(msg)").assert >>
slf4jLogger.warn(throwable("warn(t)(msg)"))("warn(t)(msg)").assert >>
slf4jLogger.error(throwable("error(t)(msg)"))("error(t)(msg)").assert >>
IO(testLogger.logs().asScala.toList).assertEquals(
List(
DeferredLogMessage
.trace(Map.empty, throwable("trace(t)(msg)").some, () => "trace(t)(msg)"),
DeferredLogMessage
.debug(Map.empty, throwable("debug(t)(msg)").some, () => "debug(t)(msg)"),
DeferredLogMessage.info(Map.empty, throwable("info(t)(msg)").some, () => "info(t)(msg)"),
DeferredLogMessage.warn(Map.empty, throwable("warn(t)(msg)").some, () => "warn(t)(msg)"),
DeferredLogMessage.error(
Map.empty,
throwable("error(t)(msg)").some,
() => "error(t)(msg)"
IO(testLogger.logs())
.map(toDeferredLogs)
.assertEquals(
List(
DeferredLogMessage
.trace(Map.empty, throwable("trace(t)(msg)").some, () => "trace(t)(msg)"),
DeferredLogMessage
.debug(Map.empty, throwable("debug(t)(msg)").some, () => "debug(t)(msg)"),
DeferredLogMessage
.info(Map.empty, throwable("info(t)(msg)").some, () => "info(t)(msg)"),
DeferredLogMessage
.warn(Map.empty, throwable("warn(t)(msg)").some, () => "warn(t)(msg)"),
DeferredLogMessage.error(
Map.empty,
throwable("error(t)(msg)").some,
() => "error(t)(msg)"
)
)
)
) >>
) >>
IO(testLogger.reset()) >>
slf4jLogger.trace(ctx("trace(ctx)(msg)"))("trace(ctx)(msg)").assert >>
slf4jLogger.debug(ctx("debug(ctx)(msg)"))("debug(ctx)(msg)").assert >>
slf4jLogger.info(ctx("info(ctx)(msg)"))("info(ctx)(msg)").assert >>
slf4jLogger.warn(ctx("warn(ctx)(msg)"))("warn(ctx)(msg)").assert >>
slf4jLogger.error(ctx("error(ctx)(msg)"))("error(ctx)(msg)").assert >>
IO(testLogger.logs().asScala.toList).assertEquals(
List(
DeferredLogMessage.trace(ctx("trace(ctx)(msg)"), none, () => "trace(ctx)(msg)"),
DeferredLogMessage.debug(ctx("debug(ctx)(msg)"), none, () => "debug(ctx)(msg)"),
DeferredLogMessage.info(ctx("info(ctx)(msg)"), none, () => "info(ctx)(msg)"),
DeferredLogMessage.warn(ctx("warn(ctx)(msg)"), none, () => "warn(ctx)(msg)"),
DeferredLogMessage.error(ctx("error(ctx)(msg)"), none, () => "error(ctx)(msg)")
)
) >>
IO(testLogger.logs())
.map(toDeferredLogs)
.assertEquals(
List(
DeferredLogMessage.trace(ctx("trace(ctx)(msg)"), none, () => "trace(ctx)(msg)"),
DeferredLogMessage.debug(ctx("debug(ctx)(msg)"), none, () => "debug(ctx)(msg)"),
DeferredLogMessage.info(ctx("info(ctx)(msg)"), none, () => "info(ctx)(msg)"),
DeferredLogMessage.warn(ctx("warn(ctx)(msg)"), none, () => "warn(ctx)(msg)"),
DeferredLogMessage.error(ctx("error(ctx)(msg)"), none, () => "error(ctx)(msg)")
)
) >>
IO(testLogger.reset()) >>
slf4jLogger
.trace(ctx("trace(ctx, t)(msg)"), throwable("trace(ctx, t)(msg)"))("trace(ctx, t)(msg)")
Expand All @@ -314,34 +362,36 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite {
slf4jLogger
.error(ctx("error(ctx, t)(msg)"), throwable("error(ctx, t)(msg)"))("error(ctx, t)(msg)")
.assert >>
IO(testLogger.logs().asScala.toList).assertEquals(
List(
DeferredLogMessage.trace(
ctx("trace(ctx, t)(msg)"),
throwable("trace(ctx, t)(msg)").some,
() => "trace(ctx, t)(msg)"
),
DeferredLogMessage.debug(
ctx("debug(ctx, t)(msg)"),
throwable("debug(ctx, t)(msg)").some,
() => "debug(ctx, t)(msg)"
),
DeferredLogMessage.info(
ctx("info(ctx, t)(msg)"),
throwable("info(ctx, t)(msg)").some,
() => "info(ctx, t)(msg)"
),
DeferredLogMessage.warn(
ctx("warn(ctx, t)(msg)"),
throwable("warn(ctx, t)(msg)").some,
() => "warn(ctx, t)(msg)"
),
DeferredLogMessage.error(
ctx("error(ctx, t)(msg)"),
throwable("error(ctx, t)(msg)").some,
() => "error(ctx, t)(msg)"
IO(testLogger.logs())
.map(toDeferredLogs)
.assertEquals(
List(
DeferredLogMessage.trace(
ctx("trace(ctx, t)(msg)"),
throwable("trace(ctx, t)(msg)").some,
() => "trace(ctx, t)(msg)"
),
DeferredLogMessage.debug(
ctx("debug(ctx, t)(msg)"),
throwable("debug(ctx, t)(msg)").some,
() => "debug(ctx, t)(msg)"
),
DeferredLogMessage.info(
ctx("info(ctx, t)(msg)"),
throwable("info(ctx, t)(msg)").some,
() => "info(ctx, t)(msg)"
),
DeferredLogMessage.warn(
ctx("warn(ctx, t)(msg)"),
throwable("warn(ctx, t)(msg)").some,
() => "warn(ctx, t)(msg)"
),
DeferredLogMessage.error(
ctx("error(ctx, t)(msg)"),
throwable("error(ctx, t)(msg)").some,
() => "error(ctx, t)(msg)"
)
)
)
)
}
}

0 comments on commit a808c2c

Please sign in to comment.