Scala 3: Using context functions for request context with http4s and log4cats
Introduction
During studying Scala 3 Context Functions first use case I thought about it, was X-Request-ID
, X-Correlation-ID
or similar headers identifying request context. So in this post, I wanted to show Context Function
usage on an example of implementing wiring request context to logs in a simple application with http4s
and log4cats
.
Alternative approaches
But first, let’s consider alternative approaches to solving this problem:
- Java’s
TheadLocal
possible for context propagation only in case of synchronous invocations; - Monix has
Local
implementation, which can deal with context propagation forTask
andFuture
. - ZIO has
FiberRef
for context propagation in terms of fiber - Cats Effect IO’s recently got Fiber Locals similar to previous solutions
Context function
Dotty’s Context Functions on another hand, allows being independent of the underlying effect library because provides the capability to pass request context as a hidden implicit parameter.
Request context propagation
Fortunately, http4s provides RequestId
middleware which does half of the job for us: create random UUID and add it as X-Request-ID
for into response. We need retrieve it from Request[F]
from attributes and pass it through the application.
First let’s define simple model of our request context:
Since we need to use RequestContext
in logging, we need special logger to do this job:
For sake of example, let’s define simple service which contains some dummy business level login of handling abstract request:
In order to isloate logic of retrving requestIdAttrKey
and converting it to implicit parameter let's define next helper function:
And use it in HTTP routes service:
Putting all pieces together in final application:
So let’s run our application and test it:
curl -X GET http://localhost:8080/contextual/test -v
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /contextual/test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
< X-Request-ID: f6e8d59e-ddd3-438c-88f6-1136d6838234
< Date: Sun, 30 May 2021 18:21:10 GMT
< Content-Length: 2
<
* Connection #0 to host localhost left intact
OK* Closing connection 0
So you can see X-Request-ID
is our request id in response with value: f6e8d59e-ddd3-438c-88f6-1136d6838234
. And log line in application logs with same request id (rest of logs ommited):
...
21:21:09.972 [io-compute-7] INFO <empty>.ApplicationService - f6e8d59e-ddd3-438c-88f6-1136d6838234 - Received request to handle: test
....
Pros and cons
From my perspective using context function over other approaches of context propagations (e.g. fiber locals) has next :
Cons:
- It is an invasive technic: context function type should be used across the whole project codebase, which might be harder to refactor, maintain and compose.
Pros:
- Contextual function prooves on type-level that certain context was provided.
Further reading
Related blog posts which you might be interested in and inspired this post: