Skip to content

Commit

Permalink
Add better decoding for re-logging of process logs
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalin-Rudnicki committed May 6, 2024
1 parent bee64da commit c2ad405
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ object Executable extends ExecutableBuilders.Builder1 {
LongName.unsafe("config-path"),
Defaultable.Some(ShortName.unsafe('C')),
helpHint = List(
"env:VAR_NAME",
"jar:path/to/jar/res",
"file:path/to/file",
"""json:{ "js": { "path": "some-json" } }""",
Expand Down
28 changes: 7 additions & 21 deletions modules/harness-zio/shared/src/main/scala/harness/zio/Logger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cats.syntax.option.*
import harness.core.*
import harness.zio.json.*
import java.io.PrintStream
import java.time.OffsetDateTime
import java.time.Instant
import scala.collection.mutable
import zio.*
import zio.json.*
Expand All @@ -24,7 +24,7 @@ final case class Logger(
target: Logger.Target,
logLevel: Option[Logger.LogLevel],
event: Logger.Event,
now: OffsetDateTime,
now: Instant,
): UIO[Any] =
event match {
case Logger.Event.Compound(events) =>
Expand All @@ -38,14 +38,14 @@ final case class Logger(
}
}

def execOnSource(source: Logger.Source, now: OffsetDateTime): URIO[Scope, Any] =
def execOnSource(source: Logger.Source, now: Instant): URIO[Scope, Any] =
source.target.flatMap(handle(source.minLogTolerance.getOrElse(defaultMinLogTolerance), _, None, event, now))

(hasSourceWithLogToleranceOverride, event) match {
case (false, Logger.Event.AtLogLevel(logLevel, _)) if logLevel.logPriority < defaultMinLogTolerance.tolerancePriority => ZIO.unit
case _ =>
ZIO.scoped {
Clock.currentDateTime.flatMap { now =>
Clock.instant.flatMap { now =>
ZIO.foreachParDiscard(sources)(execOnSource(_, now))
}
}
Expand Down Expand Up @@ -130,7 +130,7 @@ object Logger { self =>
logLevel: Option[LogLevel],
message: String,
context: Map[String, String],
dateTime: OffsetDateTime,
at: Instant,
) {

// TODO (KR) : Option to show `dateTime` in log message
Expand All @@ -144,23 +144,9 @@ object Logger { self =>
s"[${logLevel.fold(LogLevel.emptyDisplayName)(_.colorizedDisplayName(colorMode))}]: $contextMsg$msg"
}

def toEncoded: ExecutedEvent.Encoded = ExecutedEvent.Encoded(logLevel, message, context, dateTime)

}
object ExecutedEvent {

final case class Encoded(
logLevel: Option[LogLevel],
message: String,
context: Map[String, String],
dateTime: OffsetDateTime,
)
object Encoded {

implicit val jsonCodec: JsonCodec[Encoded] = DeriveJsonCodec.gen

}

implicit val jsonCodec: JsonCodec[ExecutedEvent] = DeriveJsonCodec.gen
}

trait Target {
Expand Down Expand Up @@ -201,7 +187,7 @@ object Logger { self =>
minLogTolerance: Option[LogLevel],
): Source =
Source.const(
Target.fromPrintStream(scala.Console.out, _.toEncoded.toJson),
Target.fromPrintStream(scala.Console.out, _.toJson),
minLogTolerance,
)

Expand Down
56 changes: 52 additions & 4 deletions modules/harness-zio/shared/src/main/scala/harness/zio/Sys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,67 @@ import cats.data.NonEmptyList
import cats.syntax.option.*
import harness.core.*
import harness.zio.error.SysError
import scala.annotation.tailrec
import scala.sys.process.*
import zio.*
import zio.json.*

object Sys {

final case class HarnessProcessLogger(runtime: Runtime[Logger], outLevel: Logger.LogLevel, errLevel: Logger.LogLevel) extends ProcessLogger {
override def out(s: => String): Unit =
Unsafe.unsafely { runtime.unsafe.run(Logger.log(outLevel, s)).getOrThrow() }

override def err(s: => String): Unit =
Unsafe.unsafely { runtime.unsafe.run(Logger.log(errLevel, s)).getOrThrow() }
private def run(defaultLevel: Logger.LogLevel, message: String): Unit =
Unsafe.unsafely { runtime.unsafe.run(Logger.execute(HarnessProcessLogger.MessageDecoder.decode(defaultLevel, message, HarnessProcessLogger.MessageDecoder.all))).getOrThrow() }

override def out(s: => String): Unit = run(outLevel, s)

override def err(s: => String): Unit = run(errLevel, s)

override def buffer[T](f: => T): T = f

}
object HarnessProcessLogger {

trait MessageDecoder {
def decode(defaultLevel: Logger.LogLevel, message: String): Option[Logger.Event]
}
object MessageDecoder {

def make(f: PartialFunction[(Logger.LogLevel, String), Logger.Event]): MessageDecoder =
(l, m) => f.lift((l, m))

private def decodeJson[A: JsonDecoder]: Unapply[String, A] = _.fromJson[A].toOption

private val harnessLoggerJson: Unapply[String, Logger.ExecutedEvent] = decodeJson

private val decodeLogLevel: Unapply[String, Logger.LogLevel] = Logger.LogLevel.stringDecoder.decode(_).toOption

private val levelPrefixRegex = "^(\\[[A-Za-z]+])[ \t]*(.*)$".r

private def makeEvent(level: Logger.LogLevel, context: Map[String, String], message: String): Logger.Event =
Logger.Event.AtLogLevel(level, () => Logger.Event.Output(context, message))

val all: List[MessageDecoder] =
List(
MessageDecoder.make { case (defaultLevel, harnessLoggerJson(event)) => makeEvent(event.logLevel.getOrElse(defaultLevel), event.context, event.message) },
MessageDecoder.make { case (defaultLevel, levelPrefixRegex(_, harnessLoggerJson(event))) => makeEvent(event.logLevel.getOrElse(defaultLevel), event.context, event.message) },
MessageDecoder.make { case (_, levelPrefixRegex(decodeLogLevel(level), message)) => makeEvent(level, Map.empty, message) },
// TODO (KR) : add decoders for SLF4J json
)

@tailrec
def decode(defaultLevel: Logger.LogLevel, message: String, decoders: List[MessageDecoder]): Logger.Event =
decoders match {
case head :: tail =>
head.decode(defaultLevel, message) match {
case Some(event) => event
case None => decode(defaultLevel, message, tail)
}
case Nil => makeEvent(defaultLevel, Map.empty, message)
}

}

}

final case class Command(cmdAndArgs: NonEmptyList[String]) {
Expand Down

0 comments on commit c2ad405

Please sign in to comment.