Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Log improvements #783

Merged
merged 5 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the pattern of other effects with a Log and Log.Unsafe pair

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)
Copy link
Collaborator Author

@fwbrasil fwbrasil Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convenience methods to redirect logs to the console. Note how ConsoleLogger also supports specifying a log level.


/** 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I see the value in this to be honest. It's unlike other designs where the APIs are hidden behind an opaque type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason for adding it was so it's possible to have Log.get and Local.use methods. It'd be strange to add them with a Log.Unsafe type. That's something useful because computations can get the Log instance once and avoid further Local lookups. Another use case is passing the instance to some unsafe execution context like library integrations. While not essential, adding it keeps the consistency with similar APIs and provides these benefits

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the local store the Safe variant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've changed it to use safe variant

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
Loading