So I’m building an api using http4s
, log4cats
and logback-classic
as the logging backend. I built some logging middleware (correlationIdMiddleware
) for my api which inserts a CorrelationID
into the MDC logging context while the requests are being processed.
However I noticed that sometimes the CorrelationID
is missing from the MDC context in one my endpoint handlers namely pseudonymizeColumnHandler
and I’m not sure why this happens, why it only happens intermittently and why only in that specific handler.
I can’t observe this happening at all for my helloHandler
. I thought it might have something to do with MDC context not being preserved across fibers, but afaict from the implementation the context is preserved. Here is my code:
def correlationIdMiddleware(routes: HttpRoutes[IO]): HttpRoutes[IO] =
Kleisli { (req: Request[IO]) =>
OptionT(
for
logger <- Slf4jLogger.create[IO]
correlationId <-
req.headers
.get(ci"X-Correlation-ID")
.map(_.head)
.fold(FULID[IO].generate)(x => IO.pure(x.value))
_ <- IO(MDC.put("CorrelationID", correlationId))
response <- routes(req).value
_ <- IO(MDC.remove("CorrelationID"))
yield response
)
}
// CorrelationID always present in MDC here
private val helloHandler: IO[Response[IO]] =
for
logger <- Slf4jLogger.create[IO]
_ <- logger.info("I'm inside a request handler")
res <- Ok("...and hello to you.")
yield res
// CorrelationID only sometimes present in MDC here
def pseudonymizeColumnHandler(request: Request[IO]): IO[Response[IO]] =
for
logger <- Slf4jLogger.create[IO]
eitherRes <- request.attemptAs[PseudonymizeRequest].value
// For debugging, sometimes I get None here
correlationId <- IO(MDC.get("CorrelationID"))
_ <- logger.info(s"The correlationId is $correlationId")
res <- eitherRes match
case Left(err) =>
val errMsg = s"Failed to decode JSON body. Cause ${err.cause}"
logger.error(errMsg) *> BadRequest(errMsg)
case Right(pseudReq) => logger.info(pseudReq.toString) *> Ok(
PseudonymizeResponse("Received :)").asJson
)
yield res
val routes: HttpRoutes[IO] = HttpRoutes.of {
case GET -> Root / "hello" => helloHandler
case req @ POST -> Root / "pseudonymize" / "column" =>
pseudonymizeColumnHandler(req)
}
val httpApp = correlationIdMiddleware(routes).orNotFound
My goal is to be able to annotate logging messaged with the CorrelationID
when it’s present through logback:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<appender name="STDOUT" class="ConsoleAppender">
<encoder class="PatternLayoutEncoder">
<pattern>%cyan(%d{HH:mm:ss.SSS}) %X{CorrelationID} %magenta([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>