Skip to content

Commit

Permalink
Put log string creation in the printing thread (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnynek authored Jan 3, 2023
1 parent 58c5ab6 commit 6cace6f
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 49 deletions.
23 changes: 4 additions & 19 deletions src/main/scala/afenton/bazel/bsp/BazelBspApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,6 @@ object BazelBspApp
case u: Unit => IO.unit
}

private def processErrStream(
errPipe: Pipe[IO, String, Unit],
errQ: Queue[IO, String]
): Stream[IO, Unit] =
Stream
.fromQueueUnterminated(errQ, 100)
.through(errPipe)

private def processOutStream(
outPipe: Pipe[IO, String, Unit],
outQ: Queue[IO, Message],
Expand All @@ -165,22 +157,15 @@ object BazelBspApp
errPipe: Pipe[IO, String, Unit]
): IO[ExitCode] =
val program = for
errQ <- Queue.bounded[IO, String](100)
loggerStream <- Logger.queue(100, errPipe, verbose)
(logger, logStream) = loggerStream
outQ <- Queue.bounded[IO, Message](100)
logger = Logger.toQueue(errQ, verbose)
client = BspClient.toQueue(outQ, logger)
stateRef <- Ref.of[IO, BazelBspServer.ServerState](
BazelBspServer.defaultState
)
server = new BazelBspServer(
client,
logger,
stateRef
)
server <- BazelBspServer.create(client, logger)
all = Stream(
processInStream(inStream, server, outQ, logger),
processOutStream(outPipe, outQ, logger),
processErrStream(errPipe, errQ)
logStream
).parJoin(3)
_ <- all.compile.drain
yield ()
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/afenton/bazel/bsp/BazelBspServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class BazelBspServer(

end BazelBspServer

object BazelBspServer:
object BazelBspServer:

case class ServerState(
targetSourceMap: BazelBspServer.TargetSourceMap,
Expand All @@ -317,9 +317,15 @@ object BazelBspServer:
targets: List[BuildTarget]
)

def defaultState: ServerState =
def defaultState: ServerState =
ServerState(BazelBspServer.TargetSourceMap.empty, Nil, None, None, Nil)

def create(client: BspClient, logger: Logger): IO[BazelBspServer] =
Ref.of[IO, BazelBspServer.ServerState](defaultState)
.map { stateRef =>
BazelBspServer(client, logger, stateRef)
}

protected case class TargetSourceMap(
val _targetSources: Map[BuildTargetIdentifier, List[
TextDocumentIdentifier
Expand Down
73 changes: 45 additions & 28 deletions src/main/scala/afenton/bazel/bsp/Logger.scala
Original file line number Diff line number Diff line change
@@ -1,48 +1,65 @@
package afenton.bazel.bsp

import fs2.{Pipe, Stream}
import cats.effect.IO
import cats.effect.std.Queue
import cats.effect.std.{Queue, QueueSink}

import scala.io.AnsiColor

trait Logger:
def trace(msg: String*): IO[Unit]
def info(msg: String*): IO[Unit]
def error(msg: String*): IO[Unit]
def trace(msg: => String): IO[Unit]
def info(msg: => String): IO[Unit]
def error(msg: => String): IO[Unit]

object Logger:
enum Level:
case Trace, Info, Error

def noOp = new Logger {
def trace(msgs: String*): IO[Unit] = IO { () }
def info(msgs: String*): IO[Unit] = trace(msgs*)
def error(msgs: String*): IO[Unit] = trace(msgs*)
val noOp: Logger = new Logger {
def trace(msgs: => String): IO[Unit] = IO.unit
def info(msgs: => String): IO[Unit] = IO.unit
def error(msgs: => String): IO[Unit] = IO.unit
}

def toQueue(errQ: Queue[IO, String], verbose: Boolean): Logger =
QueueLogger(errQ, verbose)
def queue(size: Int, out: Pipe[IO, String, Unit], verbose: Boolean): IO[(Logger, Stream[IO, Unit])] =
Queue.bounded[IO, () => String](size)
.map { errQ =>
val logger = if verbose then QueueVerboseLogger(errQ) else QueueQuietLogger(errQ)

private class QueueLogger(errQ: Queue[IO, String], verbose: Boolean) extends Logger:
private def format(level: Logger.Level, msgs: Seq[String]) =
def fmt(level: String, color: String, msgs: Seq[String]) =
val withVisibleLineEndings =
msgs.map(_.replace("\r\n", "[CRLF]\n")).mkString("\n")
val resStream = Stream
.fromQueueUnterminated(errQ, size)
.through { s => out(s.map(_.apply())) }

s"[${color}${level}${AnsiColor.RESET}] ${color}${withVisibleLineEndings}${AnsiColor.RESET}"

level match {
case Logger.Level.Trace => fmt("trace", AnsiColor.CYAN, msgs)
case Logger.Level.Info => fmt("info", AnsiColor.GREEN, msgs)
case Logger.Level.Error => fmt("error", AnsiColor.RED, msgs)
(logger, resStream)
}

def trace(msgs: String*): IO[Unit] =
if verbose then errQ.offer(format(Logger.Level.Trace, msgs))
else IO.unit
private inline def fmt(inline level: String, inline color: String, msgs: String) =
val withVisibleLineEndings = msgs.replace("\r\n", "[CRLF]\n")

s"[${color}${level}${AnsiColor.RESET}] ${color}${withVisibleLineEndings}${AnsiColor.RESET}"

private inline def format(inline level: Logger.Level, msgs: String) =
inline level match {
case Logger.Level.Trace => fmt("trace", AnsiColor.CYAN, msgs)
case Logger.Level.Info => fmt("info", AnsiColor.GREEN, msgs)
case Logger.Level.Error => fmt("error", AnsiColor.RED, msgs)
}

private class QueueVerboseLogger(errQ: QueueSink[IO, () => String]) extends Logger:
def trace(msgs: => String): IO[Unit] =
errQ.offer(() => format(Logger.Level.Trace, msgs))

def info(msgs: => String): IO[Unit] =
errQ.offer(() => format(Logger.Level.Info, msgs))

def error(msgs: => String): IO[Unit] =
errQ.offer(() => format(Logger.Level.Error, msgs))

private class QueueQuietLogger(errQ: QueueSink[IO, () => String]) extends Logger:
def trace(msgs: => String): IO[Unit] = IO.unit

def info(msgs: String*): IO[Unit] =
errQ.offer(format(Logger.Level.Info, msgs))
def info(msgs: => String): IO[Unit] =
errQ.offer(() => format(Logger.Level.Info, msgs))

def error(msgs: String*): IO[Unit] =
errQ.offer(format(Logger.Level.Error, msgs))
def error(msgs: => String): IO[Unit] =
errQ.offer(() => format(Logger.Level.Error, msgs))

0 comments on commit 6cace6f

Please sign in to comment.