diff --git a/effect/src/main/java/com/github/tonivade/purefun/effect/util/PureLog.java b/effect/src/main/java/com/github/tonivade/purefun/effect/util/PureLog.java new file mode 100644 index 000000000..b349fdd6e --- /dev/null +++ b/effect/src/main/java/com/github/tonivade/purefun/effect/util/PureLog.java @@ -0,0 +1,89 @@ +package com.github.tonivade.purefun.effect.util; + +import java.lang.StackWalker.Option; +import java.lang.StackWalker.StackFrame; +import java.util.Queue; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import com.github.tonivade.purefun.core.Producer; +import com.github.tonivade.purefun.core.Unit; +import com.github.tonivade.purefun.effect.RIO; + +public interface PureLog { + + PureLog.Service logger(StackFrame frame); + + static RIO debug(Producer message) { + var frame = getFrame(); + return RIO.accessM(env -> env.logger(frame).debug(message)); + } + + static RIO info(Producer message) { + var frame = getFrame(); + return RIO.accessM(env -> env.logger(frame).info(message)); + } + + static RIO warn(Producer message) { + var frame = getFrame(); + return RIO.accessM(env -> env.logger(frame).warn(message)); + } + + static RIO error(Producer message) { + var frame = getFrame(); + return RIO.accessM(env -> env.logger(frame).error(message)); + } + + interface Service { + + default RIO debug(Producer message) { + return log(Level.FINE, message); + } + + default RIO info(Producer message) { + return log(Level.INFO, message); + } + + default RIO warn(Producer message) { + return log(Level.WARNING, message); + } + + default RIO error(Producer message) { + return log(Level.SEVERE, message); + } + + RIO log(Level level, Producer message); + } + + static PureLog javaUtilLogging() { + return new PureLog() { + @Override + public Service logger(StackFrame frame) { + return (level, message) -> { + var logger = Logger.getLogger(frame.getClassName()); + return RIO.exec(() -> logger.logp(level, frame.getClassName(), frame.getMethodName(), message.get())); + }; + } + }; + } + + static PureLog test(final Queue traces) { + return new PureLog() { + @Override + public Service logger(StackFrame frame) { + return (level, message) -> RIO.exec(() -> { + var log = new LogRecord(level, message.get()); + log.setSourceClassName(frame.getClassName()); + log.setSourceMethodName(frame.getMethodName()); + traces.add(log); + }); + } + }; + } + + private static StackFrame getFrame() { + var walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); + return walker.walk(frames -> frames.skip(2).findFirst().orElseThrow()); + } +} diff --git a/effect/src/test/java/com/github/tonivade/purefun/effect/RIOTest.java b/effect/src/test/java/com/github/tonivade/purefun/effect/RIOTest.java index 91984c3d2..4f05c0681 100644 --- a/effect/src/test/java/com/github/tonivade/purefun/effect/RIOTest.java +++ b/effect/src/test/java/com/github/tonivade/purefun/effect/RIOTest.java @@ -11,6 +11,7 @@ import static com.github.tonivade.purefun.effect.RIO.sleep; import static com.github.tonivade.purefun.effect.RIO.task; import static com.github.tonivade.purefun.effect.RIOOf.toRIO; +import static com.github.tonivade.purefun.effect.util.PureLog.info; import static java.util.concurrent.ThreadLocalRandom.current; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,6 +37,7 @@ import com.github.tonivade.purefun.core.Producer; import com.github.tonivade.purefun.core.Unit; import com.github.tonivade.purefun.data.Sequence; +import com.github.tonivade.purefun.effect.util.PureLog; import com.github.tonivade.purefun.instances.RIOInstances; import com.github.tonivade.purefun.type.Either; import com.github.tonivade.purefun.type.Try; @@ -171,22 +173,22 @@ public void traverse() { @Test void raceA() { - RIO> race = race( - RIO.sleep(Duration.ofMillis(10)).map(x -> 10), - RIO.sleep(Duration.ofMillis(100)).map(x -> "b")); + RIO> race = race( + RIO.sleep(Duration.ofMillis(10)).andThen(info(() -> "done A")).map(x -> 10), + RIO.sleep(Duration.ofMillis(100)).andThen(info(() -> "done B")).map(x -> "b")); - Try> orElseThrow = race.safeRunSync(null); + Try> orElseThrow = race.safeRunSync(PureLog.javaUtilLogging()); assertEquals(Try.success(Either.left(10)), orElseThrow); } @Test void raceB() { - RIO> race = race( - RIO.sleep(Duration.ofMillis(100)).map(x -> 10), - RIO.sleep(Duration.ofMillis(10)).map(x -> "b")); + RIO> race = race( + RIO.sleep(Duration.ofMillis(100)).andThen(info(() -> "done A")).map(x -> 10), + RIO.sleep(Duration.ofMillis(10)).andThen(info(() -> "done B")).map(x -> "b")); - Try> orElseThrow = race.safeRunSync(null); + Try> orElseThrow = race.safeRunSync(PureLog.javaUtilLogging()); assertEquals(Try.success(Either.right("b")), orElseThrow); } diff --git a/effect/src/test/java/com/github/tonivade/purefun/effect/util/PureLogTest.java b/effect/src/test/java/com/github/tonivade/purefun/effect/util/PureLogTest.java new file mode 100644 index 000000000..4db8a4157 --- /dev/null +++ b/effect/src/test/java/com/github/tonivade/purefun/effect/util/PureLogTest.java @@ -0,0 +1,26 @@ +package com.github.tonivade.purefun.effect.util; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.junit.jupiter.api.Test; + +class PureLogTest { + + @Test + void test() { + Queue traces = new LinkedList<>(); + + PureLog.info(() -> "this is a test").safeRunSync(PureLog.test(traces)); + + LogRecord logRecord = traces.poll(); + assertEquals("this is a test", logRecord.getMessage()); + assertEquals(Level.INFO, logRecord.getLevel()); + assertEquals(PureLogTest.class.getName(), logRecord.getSourceClassName()); + assertEquals("test", logRecord.getSourceMethodName()); + } +}