scoped-log4cats
We can create a wrapper for any log4cats logger that adds the fields we need to the logging context like the following:
import is.ashley.scoped.logging.ScopedLogger
import is.ashley.scoped.{ScopeToString, given}
import org.typelevel.log4cats.*
class TransactionalLogger[F[_]: Monad: Transactional](underlying: SelfAwareStructuredLogger[F])
extends ScopedLogger[F](underlying):
val context = Map("transaction_id" -> ScopeToString[F, Option[TransactionId]])
Assuming you are using the logback backend you can emit these fields in the logs with something like
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>logtype="application" transaction_id="%X{transaction_id}" req_time="%date{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}" loglevel="%level" threadname="%thread" classname="%logger{10}" linenumber="%file:%line" message="%msg"%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Then wherever we create our logger we can wrap it
given logger: SelfAwareStructuredLogger[IO] = TransactionalLogger(LoggerFactory[IO].getLogger)