mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 10:28:27 +00:00
Adopt to new loggin api
This commit is contained in:
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.syntax.all._
|
||||
import cats.{Applicative, Id}
|
||||
|
||||
final private[logging] class AndThenLogger[F[_]: Applicative](
|
||||
val loggers: NonEmptyList[Logger[F]]
|
||||
) extends Logger[F] {
|
||||
def log(ev: LogEvent): F[Unit] =
|
||||
loggers.traverse(_.log(ev)).as(())
|
||||
|
||||
def asUnsafe: Logger[Id] =
|
||||
new Logger[Id] { self =>
|
||||
def log(ev: LogEvent): Unit =
|
||||
loggers.toList.foreach(_.asUnsafe.log(ev))
|
||||
def asUnsafe = self
|
||||
}
|
||||
}
|
||||
|
||||
private[logging] object AndThenLogger {
|
||||
def combine[F[_]: Applicative](a: Logger[F], b: Logger[F]): Logger[F] =
|
||||
(a, b) match {
|
||||
case (aa: AndThenLogger[F], bb: AndThenLogger[F]) =>
|
||||
new AndThenLogger[F](aa.loggers ++ bb.loggers.toList)
|
||||
case (aa: AndThenLogger[F], _) =>
|
||||
new AndThenLogger[F](aa.loggers.prepend(b))
|
||||
case (_, bb: AndThenLogger[F]) =>
|
||||
new AndThenLogger[F](bb.loggers.prepend(a))
|
||||
case _ =>
|
||||
new AndThenLogger[F](NonEmptyList.of(a, b))
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import cats.Order
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
sealed trait Level { self: Product =>
|
||||
val name: String =
|
||||
productPrefix.toUpperCase
|
||||
|
||||
val value: Double
|
||||
}
|
||||
|
||||
object Level {
|
||||
case object Fatal extends Level {
|
||||
val value = 600.0
|
||||
}
|
||||
case object Error extends Level {
|
||||
val value = 500.0
|
||||
}
|
||||
case object Warn extends Level {
|
||||
val value = 400.0
|
||||
}
|
||||
case object Info extends Level {
|
||||
val value = 300.0
|
||||
}
|
||||
case object Debug extends Level {
|
||||
val value = 200.0
|
||||
}
|
||||
case object Trace extends Level {
|
||||
val value = 100.0
|
||||
}
|
||||
|
||||
val all: NonEmptyList[Level] =
|
||||
NonEmptyList.of(Fatal, Error, Warn, Info, Debug, Trace)
|
||||
|
||||
def fromString(str: String): Either[String, Level] = {
|
||||
val s = str.toUpperCase
|
||||
all.find(_.name == s).toRight(s"Invalid level name: $str")
|
||||
}
|
||||
|
||||
implicit val order: Order[Level] =
|
||||
Order.by(_.value)
|
||||
|
||||
implicit val jsonEncoder: Encoder[Level] =
|
||||
Encoder.encodeString.contramap(_.name)
|
||||
|
||||
implicit val jsonDecoder: Decoder[Level] =
|
||||
Decoder.decodeString.emap(fromString)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
final case class LogConfig(minimumLevel: Level, format: LogConfig.Format) {}
|
||||
|
||||
object LogConfig {
|
||||
|
||||
sealed trait Format { self: Product =>
|
||||
def name: String =
|
||||
productPrefix.toLowerCase
|
||||
}
|
||||
object Format {
|
||||
case object Plain extends Format
|
||||
case object Fancy extends Format
|
||||
case object Json extends Format
|
||||
case object Logfmt extends Format
|
||||
|
||||
val all: NonEmptyList[Format] =
|
||||
NonEmptyList.of(Plain, Fancy, Json, Logfmt)
|
||||
|
||||
def fromString(str: String): Either[String, Format] =
|
||||
all.find(_.name.equalsIgnoreCase(str)).toRight(s"Invalid format name: $str")
|
||||
|
||||
implicit val jsonDecoder: Decoder[Format] =
|
||||
Decoder.decodeString.emap(fromString)
|
||||
|
||||
implicit val jsonEncoder: Encoder[Format] =
|
||||
Encoder.encodeString.contramap(_.name)
|
||||
}
|
||||
|
||||
implicit val jsonDecoder: Decoder[LogConfig] =
|
||||
Decoder.forProduct2("minimumLevel", "format")(LogConfig.apply)
|
||||
|
||||
implicit val jsonEncoder: Encoder[LogConfig] =
|
||||
Encoder.forProduct2("minimumLevel", "format")(r => (r.minimumLevel, r.format))
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import io.circe.{Encoder, Json}
|
||||
import sourcecode._
|
||||
|
||||
final case class LogEvent(
|
||||
level: Level,
|
||||
msg: () => String,
|
||||
additional: List[() => LogEvent.AdditionalMsg],
|
||||
data: Map[String, () => Json],
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
) {
|
||||
|
||||
def asString =
|
||||
s"${level.name} ${name.value}/${fileName}:${line.value} - ${msg()}"
|
||||
|
||||
def data[A: Encoder](key: String, value: => A): LogEvent =
|
||||
copy(data = data.updated(key, () => Encoder[A].apply(value)))
|
||||
|
||||
def addMessage(msg: => String): LogEvent =
|
||||
copy(additional = (() => Left(msg)) :: additional)
|
||||
|
||||
def addError(ex: Throwable): LogEvent =
|
||||
copy(additional = (() => Right(ex)) :: additional)
|
||||
|
||||
def findErrors: List[Throwable] =
|
||||
additional.map(a => a()).collect { case Right(ex) =>
|
||||
ex
|
||||
}
|
||||
}
|
||||
|
||||
object LogEvent {
|
||||
|
||||
type AdditionalMsg = Either[String, Throwable]
|
||||
|
||||
def of(l: Level, m: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): LogEvent = LogEvent(l, () => m, Nil, Map.empty, pkg, fileName, name, line)
|
||||
}
|
166
modules/logging/api/src/main/scala/docspell/logging/Logger.scala
Normal file
166
modules/logging/api/src/main/scala/docspell/logging/Logger.scala
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import java.io.PrintStream
|
||||
import java.time.Instant
|
||||
|
||||
import cats.effect.{Ref, Sync}
|
||||
import cats.syntax.applicative._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.order._
|
||||
import cats.{Applicative, Id}
|
||||
|
||||
import sourcecode._
|
||||
|
||||
trait Logger[F[_]] extends LoggerExtension[F] {
|
||||
|
||||
def log(ev: LogEvent): F[Unit]
|
||||
|
||||
def asUnsafe: Logger[Id]
|
||||
|
||||
def trace(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Trace, msg))
|
||||
|
||||
def traceWith(msg: => String)(modify: LogEvent => LogEvent)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(modify(LogEvent.of(Level.Trace, msg)))
|
||||
|
||||
def debug(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Debug, msg))
|
||||
|
||||
def debugWith(msg: => String)(modify: LogEvent => LogEvent)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(modify(LogEvent.of(Level.Debug, msg)))
|
||||
|
||||
def info(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Info, msg))
|
||||
|
||||
def infoWith(msg: => String)(modify: LogEvent => LogEvent)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(modify(LogEvent.of(Level.Info, msg)))
|
||||
|
||||
def warn(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Warn, msg))
|
||||
|
||||
def warnWith(msg: => String)(modify: LogEvent => LogEvent)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(modify(LogEvent.of(Level.Warn, msg)))
|
||||
|
||||
def warn(ex: Throwable)(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Warn, msg).addError(ex))
|
||||
|
||||
def error(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Error, msg))
|
||||
|
||||
def errorWith(msg: => String)(modify: LogEvent => LogEvent)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(modify(LogEvent.of(Level.Error, msg)))
|
||||
|
||||
def error(ex: Throwable)(msg: => String)(implicit
|
||||
pkg: Pkg,
|
||||
fileName: FileName,
|
||||
name: Name,
|
||||
line: Line
|
||||
): F[Unit] =
|
||||
log(LogEvent.of(Level.Error, msg).addError(ex))
|
||||
}
|
||||
|
||||
object Logger {
|
||||
def off: Logger[Id] =
|
||||
new Logger[Id] {
|
||||
def log(ev: LogEvent): Unit = ()
|
||||
def asUnsafe = this
|
||||
}
|
||||
|
||||
def offF[F[_]: Applicative]: Logger[F] =
|
||||
new Logger[F] {
|
||||
def log(ev: LogEvent) = ().pure[F]
|
||||
def asUnsafe = off
|
||||
}
|
||||
|
||||
def buffer[F[_]: Sync](): F[(Ref[F, Vector[LogEvent]], Logger[F])] =
|
||||
for {
|
||||
buffer <- Ref.of[F, Vector[LogEvent]](Vector.empty[LogEvent])
|
||||
logger =
|
||||
new Logger[F] {
|
||||
def log(ev: LogEvent) =
|
||||
buffer.update(_.appended(ev))
|
||||
def asUnsafe = off
|
||||
}
|
||||
} yield (buffer, logger)
|
||||
|
||||
/** Just prints to the given print stream. Useful for testing. */
|
||||
def simple(ps: PrintStream, minimumLevel: Level): Logger[Id] =
|
||||
new Logger[Id] {
|
||||
def log(ev: LogEvent): Unit =
|
||||
if (ev.level >= minimumLevel)
|
||||
ps.println(s"${Instant.now()} [${Thread.currentThread()}] ${ev.asString}")
|
||||
else
|
||||
()
|
||||
|
||||
def asUnsafe = this
|
||||
}
|
||||
|
||||
def simpleF[F[_]: Sync](ps: PrintStream, minimumLevel: Level): Logger[F] =
|
||||
new Logger[F] {
|
||||
def log(ev: LogEvent) =
|
||||
Sync[F].delay(asUnsafe.log(ev))
|
||||
|
||||
val asUnsafe = simple(ps, minimumLevel)
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging
|
||||
|
||||
import cats.Applicative
|
||||
import fs2.Stream
|
||||
|
||||
trait LoggerExtension[F[_]] { self: Logger[F] =>
|
||||
|
||||
def stream: Logger[Stream[F, *]] =
|
||||
new Logger[Stream[F, *]] {
|
||||
def log(ev: LogEvent) =
|
||||
Stream.eval(self.log(ev))
|
||||
|
||||
def asUnsafe = self.asUnsafe
|
||||
}
|
||||
|
||||
def andThen(other: Logger[F])(implicit F: Applicative[F]): Logger[F] =
|
||||
AndThenLogger.combine(self, other)
|
||||
|
||||
def >>(other: Logger[F])(implicit F: Applicative[F]): Logger[F] =
|
||||
AndThenLogger.combine(self, other)
|
||||
}
|
Reference in New Issue
Block a user