Skip to content

Commit

Permalink
[core] Log improvements (#783)
Browse files Browse the repository at this point in the history
Following up on
#780 (comment)
  • Loading branch information
fwbrasil authored Oct 28, 2024
1 parent 42dabc5 commit 378f79f
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package kyo.internal
import kyo.Log

trait LogPlatformSpecific:
val unsafe: Log.Unsafe = Log.Unsafe.ConsoleLogger("kyo.logs")
val live: Log = Log(Log.Unsafe.ConsoleLogger("kyo.logs", Log.Level.debug))
38 changes: 18 additions & 20 deletions kyo-core/jvm/src/main/scala/kyo/internal/LogPlatformSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package kyo.internal
import kyo.AllowUnsafe
import kyo.Frame
import kyo.Log
import kyo.Log.Level

trait LogPlatformSpecific:
val unsafe: Log.Unsafe = LogPlatformSpecific.Unsafe.SLF4J("kyo.logs")
val live: Log = Log(LogPlatformSpecific.Unsafe.SLF4J("kyo.logs"))

object LogPlatformSpecific:

Expand All @@ -16,45 +17,42 @@ object LogPlatformSpecific:
def apply(name: String) = new SLF4J(org.slf4j.LoggerFactory.getLogger(name))

class SLF4J(logger: org.slf4j.Logger) extends Log.Unsafe:
inline def traceEnabled: Boolean = logger.isTraceEnabled

inline def debugEnabled: Boolean = logger.isDebugEnabled

inline def infoEnabled: Boolean = logger.isInfoEnabled

inline def warnEnabled: Boolean = logger.isWarnEnabled

inline def errorEnabled: Boolean = logger.isErrorEnabled
def level =
if logger.isTraceEnabled() then Level.trace
else if logger.isDebugEnabled() then Level.debug
else if logger.isInfoEnabled() then Level.info
else if logger.isWarnEnabled() then Level.warn
else Level.error

inline def trace(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit =
if traceEnabled then logger.trace(s"[${frame.parse.position}] $msg")
if Level.trace.enabled(level) then logger.trace(s"[${frame.parse.position}] $msg")

inline def trace(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit =
if traceEnabled then logger.trace(s"[${frame.parse.position}] $msg", t)
if Level.trace.enabled(level) then logger.trace(s"[${frame.parse.position}] $msg", t)

inline def debug(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit =
if debugEnabled then logger.debug(s"[${frame.parse.position}] $msg")
if Level.debug.enabled(level) then logger.debug(s"[${frame.parse.position}] $msg")

inline def debug(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit =
if debugEnabled then logger.debug(s"[${frame.parse.position}] $msg", t)
if Level.debug.enabled(level) then logger.debug(s"[${frame.parse.position}] $msg", t)

inline def info(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit =
if infoEnabled then logger.info(s"[${frame.parse.position}] $msg")
if Level.info.enabled(level) then logger.info(s"[${frame.parse.position}] $msg")

inline def info(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit =
if infoEnabled then logger.info(s"[${frame.parse.position}] $msg", t)
if Level.info.enabled(level) then logger.info(s"[${frame.parse.position}] $msg", t)

inline def warn(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit =
if warnEnabled then logger.warn(s"[${frame.parse.position}] $msg")
if Level.warn.enabled(level) then logger.warn(s"[${frame.parse.position}] $msg")

inline def warn(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit =
if warnEnabled then logger.warn(s"[${frame.parse.position}] $msg", t)
if Level.warn.enabled(level) then logger.warn(s"[${frame.parse.position}] $msg", t)

inline def error(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit =
if errorEnabled then logger.error(s"[${frame.parse.position}] $msg")
if Level.error.enabled(level) then logger.error(s"[${frame.parse.position}] $msg")

inline def error(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit =
if errorEnabled then logger.error(s"[${frame.parse.position}] $msg", t)
if Level.error.enabled(level) then logger.error(s"[${frame.parse.position}] $msg", t)
end SLF4J
end Unsafe
end LogPlatformSpecific
6 changes: 3 additions & 3 deletions kyo-core/shared/src/main/scala/kyo/KyoApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import scala.collection.mutable.ListBuffer
* Note: This class and its methods are unsafe and should only be used as the entrypoint of an application.
*/
abstract class KyoApp extends KyoApp.Base[KyoApp.Effects]:
def log: Log.Unsafe = Log.unsafe
def random: Random = Random.live
def clock: Clock = Clock.live
def log: Log = Log.live
def random: Random = Random.live
def clock: Clock = Clock.live

override protected def handle[A: Flat](v: A < KyoApp.Effects)(using Frame): Unit =
import AllowUnsafe.embrace.danger
Expand Down
140 changes: 97 additions & 43 deletions kyo-core/shared/src/main/scala/kyo/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,35 @@ package kyo

import kyo.internal.LogPlatformSpecific

final case class Log(unsafe: Log.Unsafe) extends AnyVal:
def level: Log.Level = unsafe.level
inline def trace(inline msg: => String)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.trace(msg))
inline def trace(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.trace(msg, t))
inline def debug(inline msg: => String)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.debug(msg))
inline def debug(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.debug(msg, t))
inline def info(inline msg: => String)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.info(msg))
inline def info(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.info(msg, t))
inline def warn(inline msg: => String)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.warn(msg))
inline def warn(inline msg: => String, t: => Throwable)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.warn(msg, t))
inline def error(inline msg: => String)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.error(msg))
inline def error(inline msg: => String, t: => Throwable)(using inline frame: Frame): Unit < IO = IO.Unsafe(unsafe.error(msg, t))
end Log

/** Logging utility object for Kyo applications. */
object Log extends LogPlatformSpecific:

private val local = Local.init[Unsafe](unsafe)
final class Level private (private val priority: Int) extends AnyVal:
def enabled(minLevel: Level) = priority >= minLevel.priority

object Level:
val trace: Level = Level(10)
val debug: Level = Level(20)
val info: Level = Level(30)
val warn: Level = Level(40)
val error: Level = Level(50)
end Level

private val local = Local.init(live)

/** Executes a function with a custom Unsafe logger.
*
Expand All @@ -16,16 +41,52 @@ object Log extends LogPlatformSpecific:
* @return
* The result of the function execution
*/
def let[A, S](u: Unsafe)(f: => A < (IO & S))(using Frame): A < (IO & S) =
local.let(u)(f)
def let[A, S](log: Log)(f: => A < S)(using Frame): A < S =
local.let(log)(f)

/** Gets the current logger from the local context.
*
* @return
* The current Log instance wrapped in an effect
*/
def get(using Frame): Log < Any = local.get

/** Executes a function with access to the current logger.
*
* @param f
* The function to execute, which takes a Log instance as input
* @return
* The result of the function execution
*/
def use[A, S](f: Log => A < S)(using Frame): A < S = local.use(f)

/** Executes an effect with a console logger using the default name "kyo.logs" and debug level.
*
* @param v
* The effect to execute with the console logger
* @return
* The result of executing the effect with the console logger
*/
def withConsoleLogger[A, S](v: A < S)(using Frame): A < S =
withConsoleLogger()(v)

/** Executes an effect with a console logger using a custom name and log level.
*
* @param name
* The name to use for the console logger
* @param level
* The log level
* @param v
* The effect to execute with the console logger
* @return
* The result of executing the effect with the console logger
*/
def withConsoleLogger[A, S](name: String = "kyo.logs", level: Level = Level.debug)(v: A < S)(using Frame): A < S =
let(Log(Unsafe.ConsoleLogger(name, level)))(v)

/** WARNING: Low-level API meant for integrations, libraries, and performance-sensitive code. See AllowUnsafe for more details. */
abstract class Unsafe:
def traceEnabled: Boolean
def debugEnabled: Boolean
def infoEnabled: Boolean
def warnEnabled: Boolean
def errorEnabled: Boolean
def level: Level

def trace(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit
def trace(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit
Expand All @@ -37,89 +98,82 @@ object Log extends LogPlatformSpecific:
def warn(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit
def error(msg: => String)(using frame: Frame, allow: AllowUnsafe): Unit
def error(msg: => String, t: => Throwable)(using frame: Frame, allow: AllowUnsafe): Unit

def safe: Log = Log(this)
end Unsafe

/** WARNING: Low-level API meant for integrations, libraries, and performance-sensitive code. See AllowUnsafe for more details. */
object Unsafe:
class ConsoleLogger(name: String) extends Log.Unsafe:
inline def traceEnabled: Boolean = true

inline def debugEnabled: Boolean = true

inline def infoEnabled: Boolean = true

inline def warnEnabled: Boolean = true

inline def errorEnabled: Boolean = true

case class ConsoleLogger(name: String, level: Level) extends Log.Unsafe:
inline def trace(msg: => String)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if traceEnabled then println(s"TRACE $name -- [${frame.parse.position}] $msg")
): Unit = if Level.trace.enabled(level) then println(s"TRACE $name -- [${frame.parse.position}] $msg")

inline def trace(msg: => String, t: => Throwable)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if traceEnabled then println(s"TRACE $name -- [${frame.parse.position}] $msg $t")
): Unit = if Level.trace.enabled(level) then println(s"TRACE $name -- [${frame.parse.position}] $msg $t")

inline def debug(msg: => String)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if debugEnabled then println(s"DEBUG $name -- [${frame.parse.position}] $msg")
): Unit =
if Level.debug.enabled(level) then println(s"DEBUG $name -- [${frame.parse.position}] $msg")

inline def debug(msg: => String, t: => Throwable)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if debugEnabled then println(s"DEBUG $name -- [${frame.parse.position}] $msg $t")
): Unit = if Level.debug.enabled(level) then println(s"DEBUG $name -- [${frame.parse.position}] $msg $t")

inline def info(msg: => String)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if infoEnabled then println(s"INFO $name -- [${frame.parse.position}] $msg")
): Unit = if Level.info.enabled(level) then println(s"INFO $name -- [${frame.parse.position}] $msg")

inline def info(msg: => String, t: => Throwable)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if infoEnabled then println(s"INFO $name -- [${frame.parse.position}] $msg $t")
): Unit = if Level.info.enabled(level) then println(s"INFO $name -- [${frame.parse.position}] $msg $t")

inline def warn(msg: => String)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if warnEnabled then println(s"WARN $name -- [${frame.parse.position}] $msg")
): Unit = if Level.warn.enabled(level) then println(s"WARN $name -- [${frame.parse.position}] $msg")

inline def warn(msg: => String, t: => Throwable)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if warnEnabled then println(s"WARN $name -- [${frame.parse.position}] $msg $t")
): Unit = if Level.warn.enabled(level) then println(s"WARN $name -- [${frame.parse.position}] $msg $t")

inline def error(msg: => String)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if errorEnabled then println(s"ERROR $name -- [${frame.parse.position}] $msg")
): Unit = if Level.error.enabled(level) then println(s"ERROR $name -- [${frame.parse.position}] $msg")

inline def error(msg: => String, t: => Throwable)(
using
frame: Frame,
allow: AllowUnsafe
): Unit = if errorEnabled then println(s"ERROR $name -- [${frame.parse.position}] $msg $t")
): Unit = if Level.error.enabled(level) then println(s"ERROR $name -- [${frame.parse.position}] $msg $t")
end ConsoleLogger
end Unsafe

private inline def logWhen(inline enabled: Unsafe => Boolean)(inline log: AllowUnsafe ?=> Unsafe => Unit)(using
private inline def logWhen(inline level: Level)(inline doLog: Log => Unit < IO)(using
inline frame: Frame
): Unit < IO =
local.use { unsafe =>
if enabled(unsafe) then
IO.Unsafe(log(unsafe))
use { log =>
if level.enabled(log.level) then
IO.Unsafe(doLog(log))
else
(
)
Expand All @@ -133,7 +187,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message
*/
inline def trace(inline msg: => String)(using inline frame: Frame): Unit < IO =
logWhen(_.traceEnabled)(_.trace(msg))
logWhen(Level.trace)(_.trace(msg))

/** Logs a trace message with an exception.
*
Expand All @@ -145,7 +199,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message and exception
*/
inline def trace(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO =
logWhen(_.traceEnabled)(_.trace(msg, t))
logWhen(Level.trace)(_.trace(msg, t))

/** Logs a debug message.
*
Expand All @@ -155,7 +209,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message
*/
inline def debug(inline msg: => String)(using inline frame: Frame): Unit < IO =
logWhen(_.debugEnabled)(_.debug(msg))
logWhen(Level.debug)(_.debug(msg))

/** Logs a debug message with an exception.
*
Expand All @@ -167,7 +221,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message and exception
*/
inline def debug(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO =
logWhen(_.debugEnabled)(_.debug(msg, t))
logWhen(Level.debug)(_.debug(msg, t))

/** Logs an info message.
*
Expand All @@ -177,7 +231,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message
*/
inline def info(inline msg: => String)(using inline frame: Frame): Unit < IO =
logWhen(_.infoEnabled)(_.info(msg))
logWhen(Level.info)(_.info(msg))

/** Logs an info message with an exception.
*
Expand All @@ -189,7 +243,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message and exception
*/
inline def info(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO =
logWhen(_.infoEnabled)(_.info(msg, t))
logWhen(Level.info)(_.info(msg, t))

/** Logs a warning message.
*
Expand All @@ -199,7 +253,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message
*/
inline def warn(inline msg: => String)(using inline frame: Frame): Unit < IO =
logWhen(_.warnEnabled)(_.warn(msg))
logWhen(Level.warn)(_.warn(msg))

/** Logs a warning message with an exception.
*
Expand All @@ -211,7 +265,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message and exception
*/
inline def warn(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO =
logWhen(_.warnEnabled)(_.warn(msg, t))
logWhen(Level.warn)(_.warn(msg, t))

/** Logs an error message.
*
Expand All @@ -221,7 +275,7 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message
*/
inline def error(inline msg: => String)(using inline frame: Frame): Unit < IO =
logWhen(_.errorEnabled)(_.error(msg))
logWhen(Level.error)(_.error(msg))

/** Logs an error message with an exception.
*
Expand All @@ -233,6 +287,6 @@ object Log extends LogPlatformSpecific:
* An IO effect that logs the message and exception
*/
inline def error(inline msg: => String, inline t: => Throwable)(using inline frame: Frame): Unit < IO =
logWhen(_.errorEnabled)(_.error(msg, t))
logWhen(Level.error)(_.error(msg, t))

end Log
Loading

0 comments on commit 378f79f

Please sign in to comment.