pillars.Logging.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pillars-core_3 Show documentation
Show all versions of pillars-core_3 Show documentation
pillars-core is a scala 3 library providing base services for writing backend applications
// Copyright (c) 2024-2024 by Raphaël Lemaitre and Contributors
// This software is licensed under the Eclipse Public License v2.0 (EPL-2.0).
// For more information see LICENSE or https://opensource.org/license/epl-2-0
package pillars
import cats.Show
import cats.effect.*
import cats.syntax.all.*
import fs2.io.file.Path
import io.circe.*
import io.circe.Codec
import io.circe.Decoder
import io.circe.Encoder
import io.circe.derivation.Configuration
import io.circe.syntax.*
import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.all.*
import scribe.Level
import scribe.Logger
import scribe.Scribe
import scribe.file.PathBuilder
import scribe.format.Formatter
import scribe.json.ScribeCirceJsonSupport
import scribe.mdc.MDC
import scribe.writer.ConsoleWriter
import scribe.writer.Writer
def logger[F[_]](using p: Pillars[F]): Scribe[F] = p.logger
object Logging:
def init[F[_]: Sync](config: Config): F[Unit] =
Sync[F]
.delay(
scribe.Logger.root
.clearHandlers()
.clearModifiers()
.withHandler(
formatter = config.format.formatter,
minimumLevel = Some(config.level),
writer = writer(config)
)
.replace()
)
.void
private def writer(config: Config): Writer =
config.format match
case Format.Json => ScribeCirceJsonSupport.writer(config.output.writer)
case _ => config.output.writer
private type BufferSizeConstraint = Positive DescribedAs "Buffer size should be positive"
opaque type BufferSize <: Int = Int :| BufferSizeConstraint
object BufferSize extends RefinedTypeOps[Int, BufferSizeConstraint, BufferSize]
enum Format:
case Json
case Simple
case Colored
case Classic
case Compact
case Enhanced
case Advanced
case Strict
def formatter: Formatter = this match
case Format.Json => Formatter.default
case Format.Simple => Formatter.simple
case Format.Colored => Formatter.colored
case Format.Classic => Formatter.classic
case Format.Compact => Formatter.compact
case Format.Enhanced => Formatter.enhanced
case Format.Advanced => Formatter.advanced
case Format.Strict => Formatter.strict
end Format
private object Format:
given Show[Format] = Show.fromToString
given Encoder[Format] = Encoder.encodeString.contramap(_.toString.toLowerCase)
given Decoder[Format] = Decoder.decodeString.emap {
case "json" => Right(Format.Json)
case "simple" => Right(Format.Simple)
case "colored" => Right(Format.Colored)
case "classic" => Right(Format.Classic)
case "compact" => Right(Format.Compact)
case "enhanced" => Right(Format.Enhanced)
case "advanced" => Right(Format.Advanced)
case "strict" => Right(Format.Strict)
case other => Left(s"Unknown output format: $other")
}
end Format
enum Output:
case Console
case File(path: Path)
def writer: Writer = this match
case Output.Console => ConsoleWriter
case Output.File(path) => scribe.file.FileWriter(PathBuilder.static(path.toNioPath))
end Output
private object Output:
given Show[Output] = Show.show:
case Console => "console"
case File(path) => s"file($path)"
given Encoder[Output] = Encoder.instance:
case Output.File(path) => Json.obj("type" -> "file".asJson, "path" -> path.toString.asJson)
case Output.Console => Json.obj("type" -> "console".asJson)
given Decoder[Output] = Decoder.instance: cursor =>
cursor
.downField("type")
.as[String]
.flatMap:
case "console" => Right(Output.Console)
case "file" =>
for
p <- cursor.downField("path").as[String]
path <- Either.cond(
p.nonEmpty,
Path(p),
DecodingFailure("Missing path for file output", cursor.history)
)
yield Output.File(path)
case other => Left(DecodingFailure(s"Unknown output type: $other", cursor.history))
end Output
final case class Config(
level: Level = Level.Info,
format: Logging.Format = Logging.Format.Enhanced,
output: Logging.Output = Logging.Output.Console,
excludeHikari: Boolean = false
) extends pillars.Config
object Config:
given Configuration = Configuration.default.withKebabCaseMemberNames.withKebabCaseConstructorNames.withDefaults
given Decoder[Level] = Decoder.decodeString.emap(s => Level.get(s).toRight(s"Invalid log level $s"))
given Encoder[Level] = Encoder.encodeString.contramap(_.name)
given Codec[Config] = Codec.AsObject.derivedConfigured
end Config
final case class HttpConfig(
enabled: Boolean = false,
level: Level = Level.Debug,
headers: Boolean = false,
body: Boolean = true
) extends pillars.Config:
def logAction[F[_]: Sync]: Option[String => F[Unit]] = Some(scribe.cats.effect[F].log(level, MDC.instance, _))
end HttpConfig
object HttpConfig:
import Config.given
given Codec[HttpConfig] = Codec.AsObject.derivedConfigured
end HttpConfig
end Logging
© 2015 - 2025 Weber Informatics LLC | Privacy Policy