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 for Task and Future.
  • 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:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store