mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Adopt to new loggin api
This commit is contained in:
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging.impl
|
||||
|
||||
import io.circe.syntax._
|
||||
import scribe._
|
||||
import scribe.output._
|
||||
import scribe.output.format.OutputFormat
|
||||
import scribe.writer._
|
||||
|
||||
final case class JsonWriter(writer: Writer, compact: Boolean = true) extends Writer {
|
||||
override def write[M](
|
||||
record: LogRecord[M],
|
||||
output: LogOutput,
|
||||
outputFormat: OutputFormat
|
||||
): Unit = {
|
||||
val r = Record.fromLogRecord(record)
|
||||
val json = r.asJson
|
||||
val jsonString = if (compact) json.noSpaces else json.spaces2
|
||||
writer.write(record, new TextOutput(jsonString), outputFormat)
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging.impl
|
||||
|
||||
import io.circe.syntax._
|
||||
import scribe._
|
||||
import scribe.output._
|
||||
import scribe.output.format.OutputFormat
|
||||
import scribe.writer._
|
||||
|
||||
// https://brandur.org/logfmt
|
||||
final case class LogfmtWriter(writer: Writer) extends Writer {
|
||||
override def write[M](
|
||||
record: LogRecord[M],
|
||||
output: LogOutput,
|
||||
outputFormat: OutputFormat
|
||||
): Unit = {
|
||||
val r = Record.fromLogRecord(record)
|
||||
val data = r.data
|
||||
.map { case (k, v) =>
|
||||
s"$k=${v.noSpaces}"
|
||||
}
|
||||
.mkString(" ")
|
||||
val logfmtStr =
|
||||
s"""level=${r.level.asJson.noSpaces} levelValue=${r.levelValue} message=${r.message.asJson.noSpaces} fileName=${r.fileName.asJson.noSpaces} className=${r.className.asJson.noSpaces} methodName=${r.methodName.asJson.noSpaces} line=${r.line.asJson.noSpaces} column=${r.column.asJson.noSpaces} $data timestamp=${r.timeStamp} date=${r.date} time=${r.time}"""
|
||||
writer.write(record, new TextOutput(logfmtStr), outputFormat)
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging.impl
|
||||
|
||||
import docspell.logging.impl.Record._
|
||||
|
||||
import io.circe.syntax._
|
||||
import io.circe.{Encoder, Json}
|
||||
import perfolation._
|
||||
import scribe.LogRecord
|
||||
import scribe.data.MDC
|
||||
import scribe.message.Message
|
||||
|
||||
// From: https://github.com/outr/scribe/blob/8e99521e1ee1f0c421629764dd96e4eb193d84bd/json/shared/src/main/scala/scribe/json/JsonWriter.scala
|
||||
// which would introduce jackson and other dependencies. Modified to work with circe.
|
||||
// Original licensed under MIT.
|
||||
|
||||
private[impl] case class Record(
|
||||
level: String,
|
||||
levelValue: Double,
|
||||
message: String,
|
||||
additionalMessages: List[String],
|
||||
fileName: String,
|
||||
className: String,
|
||||
methodName: Option[String],
|
||||
line: Option[Int],
|
||||
column: Option[Int],
|
||||
data: Map[String, Json],
|
||||
traces: List[Trace],
|
||||
timeStamp: Long,
|
||||
date: String,
|
||||
time: String
|
||||
)
|
||||
|
||||
private[impl] object Record {
|
||||
|
||||
def fromLogRecord[M](record: LogRecord[M]): Record = {
|
||||
val l = record.timeStamp
|
||||
val traces = record.additionalMessages.collect {
|
||||
case message: Message[_] if message.value.isInstanceOf[Throwable] =>
|
||||
throwable2Trace(message.value.asInstanceOf[Throwable])
|
||||
}
|
||||
val additionalMessages = record.additionalMessages.map(_.logOutput.plainText)
|
||||
|
||||
Record(
|
||||
level = record.level.name,
|
||||
levelValue = record.levelValue,
|
||||
message = record.logOutput.plainText,
|
||||
additionalMessages = additionalMessages,
|
||||
fileName = record.fileName,
|
||||
className = record.className,
|
||||
methodName = record.methodName,
|
||||
line = record.line,
|
||||
column = record.column,
|
||||
data = (record.data ++ MDC.map).map { case (key, value) =>
|
||||
value() match {
|
||||
case value: Json => key -> value
|
||||
case value: Int => key -> value.asJson
|
||||
case value: Long => key -> value.asJson
|
||||
case value: Double => key -> value.asJson
|
||||
case any => key -> Json.fromString(any.toString)
|
||||
}
|
||||
},
|
||||
traces = traces,
|
||||
timeStamp = l,
|
||||
date = l.t.F,
|
||||
time = s"${l.t.T}.${l.t.L}${l.t.z}"
|
||||
)
|
||||
}
|
||||
|
||||
private def throwable2Trace(throwable: Throwable): Trace = {
|
||||
val elements = throwable.getStackTrace.toList.map { e =>
|
||||
TraceElement(e.getClassName, e.getMethodName, e.getLineNumber)
|
||||
}
|
||||
Trace(
|
||||
throwable.getLocalizedMessage,
|
||||
elements,
|
||||
Option(throwable.getCause).map(throwable2Trace)
|
||||
)
|
||||
}
|
||||
|
||||
implicit val jsonEncoder: Encoder[Record] =
|
||||
Encoder.forProduct14(
|
||||
"level",
|
||||
"levelValue",
|
||||
"message",
|
||||
"additionalMessages",
|
||||
"fileName",
|
||||
"className",
|
||||
"methodName",
|
||||
"line",
|
||||
"column",
|
||||
"data",
|
||||
"traces",
|
||||
"timestamp",
|
||||
"date",
|
||||
"time"
|
||||
)(r => Record.unapply(r).get)
|
||||
|
||||
case class Trace(message: String, elements: List[TraceElement], cause: Option[Trace])
|
||||
|
||||
object Trace {
|
||||
implicit def jsonEncoder: Encoder[Trace] =
|
||||
Encoder.forProduct3("message", "elements", "cause")(r => Trace.unapply(r).get)
|
||||
|
||||
implicit def openEncoder: Encoder[Option[Trace]] =
|
||||
Encoder.instance(opt => opt.map(jsonEncoder.apply).getOrElse(Json.Null))
|
||||
}
|
||||
|
||||
case class TraceElement(`class`: String, method: String, line: Int)
|
||||
|
||||
object TraceElement {
|
||||
implicit val jsonEncoder: Encoder[TraceElement] =
|
||||
Encoder.forProduct3("class", "method", "line")(r => TraceElement.unapply(r).get)
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging.impl
|
||||
|
||||
import cats.effect.Sync
|
||||
|
||||
import docspell.logging.LogConfig
|
||||
import docspell.logging.LogConfig.Format
|
||||
|
||||
import scribe.format.Formatter
|
||||
import scribe.jul.JULHandler
|
||||
import scribe.writer.ConsoleWriter
|
||||
|
||||
object ScribeConfigure {
|
||||
|
||||
def configure[F[_]: Sync](cfg: LogConfig): F[Unit] =
|
||||
Sync[F].delay {
|
||||
replaceJUL()
|
||||
unsafeConfigure(scribe.Logger.root, cfg)
|
||||
}
|
||||
|
||||
def unsafeConfigure(logger: scribe.Logger, cfg: LogConfig): Unit = {
|
||||
val mods = List[scribe.Logger => scribe.Logger](
|
||||
_.clearHandlers(),
|
||||
_.withMinimumLevel(ScribeWrapper.convertLevel(cfg.minimumLevel)),
|
||||
l =>
|
||||
cfg.format match {
|
||||
case Format.Fancy =>
|
||||
l.withHandler(formatter = Formatter.enhanced)
|
||||
case Format.Plain =>
|
||||
l.withHandler(formatter = Formatter.classic)
|
||||
case Format.Json =>
|
||||
l.withHandler(writer = JsonWriter(ConsoleWriter))
|
||||
case Format.Logfmt =>
|
||||
l.withHandler(writer = LogfmtWriter(ConsoleWriter))
|
||||
},
|
||||
_.replace()
|
||||
)
|
||||
|
||||
mods.foldLeft(logger)((l, mod) => mod(l))
|
||||
()
|
||||
}
|
||||
|
||||
def replaceJUL(): Unit = {
|
||||
scribe.Logger.system // just to load effects in Logger singleton
|
||||
val julRoot = java.util.logging.LogManager.getLogManager.getLogger("")
|
||||
julRoot.getHandlers.foreach(julRoot.removeHandler)
|
||||
julRoot.addHandler(JULHandler)
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell.logging.impl
|
||||
|
||||
import cats.Id
|
||||
import cats.effect.Sync
|
||||
|
||||
import docspell.logging.{Level, LogEvent, Logger}
|
||||
|
||||
import scribe.LoggerSupport
|
||||
import scribe.message.{LoggableMessage, Message}
|
||||
|
||||
private[logging] object ScribeWrapper {
|
||||
final class ImplUnsafe(log: scribe.Logger) extends Logger[Id] {
|
||||
override def asUnsafe = this
|
||||
|
||||
override def log(ev: LogEvent): Unit =
|
||||
log.log(convert(ev))
|
||||
}
|
||||
final class Impl[F[_]: Sync](log: scribe.Logger) extends Logger[F] {
|
||||
override def asUnsafe = new ImplUnsafe(log)
|
||||
|
||||
override def log(ev: LogEvent) =
|
||||
Sync[F].delay(log.log(convert(ev)))
|
||||
}
|
||||
|
||||
private[impl] def convertLevel(l: Level): scribe.Level =
|
||||
l match {
|
||||
case Level.Fatal => scribe.Level.Fatal
|
||||
case Level.Error => scribe.Level.Error
|
||||
case Level.Warn => scribe.Level.Warn
|
||||
case Level.Info => scribe.Level.Info
|
||||
case Level.Debug => scribe.Level.Debug
|
||||
case Level.Trace => scribe.Level.Trace
|
||||
}
|
||||
|
||||
private[this] def convert(ev: LogEvent) = {
|
||||
val level = convertLevel(ev.level)
|
||||
val additional: List[LoggableMessage] = ev.additional.map { x =>
|
||||
x() match {
|
||||
case Right(ex) => Message.static(ex)
|
||||
case Left(msg) => Message.static(msg)
|
||||
}
|
||||
}
|
||||
LoggerSupport(level, ev.msg(), additional, ev.pkg, ev.fileName, ev.name, ev.line)
|
||||
.copy(data = ev.data)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 Eike K. & Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package docspell
|
||||
|
||||
import cats.Id
|
||||
import cats.effect._
|
||||
|
||||
import docspell.logging.impl.ScribeWrapper
|
||||
|
||||
import sourcecode.Enclosing
|
||||
|
||||
package object logging {
|
||||
|
||||
def unsafeLogger(name: String): Logger[Id] =
|
||||
new ScribeWrapper.ImplUnsafe(scribe.Logger(name))
|
||||
|
||||
def unsafeLogger(implicit e: Enclosing): Logger[Id] =
|
||||
unsafeLogger(e.value)
|
||||
|
||||
def getLogger[F[_]: Sync](implicit e: Enclosing): Logger[F] =
|
||||
getLogger(e.value)
|
||||
|
||||
def getLogger[F[_]: Sync](name: String): Logger[F] =
|
||||
new ScribeWrapper.Impl[F](scribe.Logger(name))
|
||||
|
||||
def getLogger[F[_]: Sync](clazz: Class[_]): Logger[F] =
|
||||
new ScribeWrapper.Impl[F](scribe.Logger(clazz.getName))
|
||||
|
||||
}
|
Reference in New Issue
Block a user