/* * Copyright 2025 Magnus Madsen * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// An effect used to log messages.///pubeffLogger {////// Logs the given message `m` at the given severity `s`.///deflog(s: Logger.Severity, m: RichString): Unit}pubmod Logger {use Sys.Consoleuse RichString.{gray, cyan, green, yellow, red, bold, text}use Severity.{Trace, Debug, Info, Warn, Fatal}////// Represents levels of severity.////// `Trace` is the lowest level of severity./// `Fatal` is the highest level of severity.///pubenumSeveritywithEq, Order, ToString {case Tracecase Debugcase Infocase Warncase Fatal }instanceFormattable[Severity] {pubdefformat(s: Severity): RichString = matchs {case Severity.Trace => RichString.gray("Trace")case Severity.Debug => RichString.blue("Debug")case Severity.Info => RichString.green("Info")case Severity.Warn => RichString.yellow("Warn")case Severity.Fatal => RichString.red("Fatal") } }////// Logs the message `m` at the `Trace` level.///pubdeftrace(m: a): Unit \ (Logger + Formattable.Aef[a]) withFormattable[a] = Logger.log(Trace, Formattable.format(m))////// Logs the message `m` at the `Debug` level.///pubdefdebug(m: a): Unit \ (Logger + Formattable.Aef[a]) withFormattable[a] = Logger.log(Debug, Formattable.format(m))////// Logs the message `m` at the `Info` level.///pubdefinfo(m: a): Unit \ (Logger + Formattable.Aef[a]) withFormattable[a] = Logger.log(Info, Formattable.format(m))////// Logs the message `m` at the `Warn` level.///pubdefwarn(m: a): Unit \ (Logger + Formattable.Aef[a]) withFormattable[a] = Logger.log(Warn, Formattable.format(m))////// Logs the message `m` at the `Fatal` level.///pubdeffatal(m: a): Unit \ (Logger + Formattable.Aef[a]) withFormattable[a] = Logger.log(Fatal, Formattable.format(m))////// Handles the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `IO` effect.///pubdefhandle(f: a -> b \ ef): a -> b \ (ef - Logger) + IO = x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {letprefix = matchs {case Trace => gray("[Trace]")case Debug => cyan("[Debug]")case Info => green("[Info]")case Warn => yellow("[Warn]")case Fatal => bold(red("[Fatal]")) };letoutput = prefix+text(" ") +m;println(RichString.toString(output));k() } }////// Runs the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `IO` effect.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - Logger) + IO = handle(f)()////// Handles the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `Console` effect.////// Prints to standard out.///pubdefhandleWithStdOut(f: a -> b \ ef): (a -> b \ ef - Logger + Console) =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {letprefix = matchs {case Severity.Debug => cyan("[Debug]")case Severity.Fatal => bold(red("[Fatal]"))case Severity.Info => green("[Info]")case Severity.Trace => gray("[Trace]")case Severity.Warn => yellow("[Warn]") };letoutput = prefix+text(" ") +m;Console.println(RichString.toString(output));k() } }////// Runs the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `Console` effect.////// Prints to standard out.///pubdefrunWithStdOut(f: Unit -> a \ ef): a \ ef - Logger + Console = handleWithStdOut(f)()////// Handles the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `Console` effect.////// Prints to standard error.///pubdefhandleWithStdErr(f: a -> b \ ef): (a -> b \ ef - Logger + Console) =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {letprefix = matchs {case Severity.Debug => cyan("[Debug]")case Severity.Fatal => bold(red("[Fatal]"))case Severity.Info => green("[Info]")case Severity.Trace => gray("[Trace]")case Severity.Warn => yellow("[Warn]") };letoutput = prefix+text(" ") +m;Console.eprintln(RichString.toString(output));k() } }////// Runs the `Logger` effect of the given function `f`.////// In other words, re-interprets the `Logger` effect using the `Console` effect.////// Prints to standard error.///pubdefrunWithStdErr(f: Unit -> a \ ef): a \ ef - Logger + Console = handleWithStdErr(f)()////// Handles the `Logger` effect of the given function `f`. Appends the logged to `file`.////// Discards style from the logged messages./// Aborts if an error is encountered while writing to file.///pubdefhandleWithFileWriteAbort(file: String, f: a -> b \ ef): a -> b \ (ef - Logger) + Fs.FileWrite + Abort =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {letoutput = "[${s}] ${RichString.toPlainString(m)}\n";letres = Fs.FileWrite.append({str = output}, file);matchres {case Err(e) => Abort.abort(RichString.text(e))case Ok() => () };k() } }////// Runs the `Logger` effect of the given function `f`. Appends the logged to `file`.////// Discards style from the logged messages./// Aborts if an error is encountered while writing to file.///pubdefrunWithFileWriteAbort(file: String, f: Unit -> a \ ef): a \ (ef - Logger) + Fs.FileWrite + Abort =handleWithFileWriteAbort(file, f)()////// Handles the `Logger` effect of the given function `f`. Appends the logged to `file`.////// Discards style from the logged messages./// Ignores errors encountered while writing.///pubdefhandleWithFileWrite(file: String, f: a -> b \ ef): a -> b \ (ef - Logger) + Fs.FileWrite =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {letoutput = "[${s}] ${RichString.toPlainString(m)}\n";let_ = Fs.FileWrite.append({str = output}, file);k() } }////// Runs the `Logger` effect of the given function `f`. Appends the logged to `file`.////// Discards style from the logged messages./// Ignores errors encountered while writing.///pubdefrunWithFileWrite(file: String, f: Unit -> a \ ef): a \ (ef - Logger) + Fs.FileWrite =handleWithFileWrite(file, f)()////// Aborts with the message `m` from `Logger.log` if given function `g` returns true./// Otherwise, lets `Logger` effect propagate.///pubdefhandleWithAbort(g: (Severity, RichString) -> Bool \ ef1, f: a -> b \ ef2): a -> b \ ef1 + ef2 + Abort + Logger =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) =if (g(s, m)) {Abort.abort(m) } else {Logger.log(s, m); k() } }////// Aborts with the message `m` from `Logger.log` if given function `g` returns true./// Otherwise, lets `Logger` effect propagate.///pubdefrunWithAbort(g: (Severity, RichString) -> Bool \ ef1, f: Unit -> a \ ef2): a \ ef1 + ef2 + Abort + Logger =handleWithAbort(g, f)()////// Handles the `Logger` effect of the given function `f`.////// In other words, returns a list of `(Severity, RichString)` that represents and is ordered by the calls to log.///pubdefhandleWithList(f: a -> b \ ef): a -> (b, List[(Severity, RichString)]) \ (ef - Logger) =x ->run { (f(x), List.empty()) } withhandlerLogger {deflog(s, m, k) = {let (r, l) = k(); (r, (s, m) :: l) } }////// Runs the `Logger` effect of the given function `f`.////// In other words, returns a list of `(Severity, RichString)` that represents and is ordered by the calls to log.///pubdefrunWithList(f: Unit -> b \ ef): (b, List[(Severity, RichString)]) \ (ef - Logger) = handleWithList(f)()////// Handles the `Logger` effect of the given function `f`.////// Filters the logging, by the `filter` function.///pubdefhandleWithFilter(filter: (Severity, RichString) -> Bool \ ef1, f: a -> b \ ef2): a -> b \ (ef2 - Logger) + Logger + ef1 =x ->run {f(x) } withhandlerLogger {deflog(s, m, k) = {if (filter(s, m)) {Logger.log(s, m);k() } else {k() } } }////// Runs the `Logger` effect of the given function `f`.////// Filters the logging, by the `filter` function.///pubdefrunWithFilter(filter: (Severity, RichString) -> Bool \ ef1, f: Unit -> b \ ef2): b \ (ef2 - Logger) + Logger + ef1 = handleWithFilter(filter, f)()////// Filters logs with greater or equal severity level///pubdefrunWithSeverityFilter(s: Severity, f: Unit -> b \ ef): b \ (ef - Logger) + Logger =run {f() } withrunWithFilter((sev, _) -> Order.greaterEqual(sev, s))}