clj-otel 2026-02-02

I'm hoping someone can point me in the right direction 👋 We're using clj-otel with jetty/reitit with manual instrumentation, something along the lines of

(defn otel-middleware
  [handler]
  (-> handler
      trace-http/wrap-reitit-route
      (trace-http/wrap-server-span {:create-span? true}))
and it works great for 90% of the app. But there is one route that calls this
(reify ring-protocols/StreamableResponseBody
which makes stuff happen async
(ns ring.core.protocols
  ...

(defprotocol ^{:added "1.6"} StreamableResponseBody
  "A protocol for writing data to the response body via an output stream."
  (write-body-to-stream [body response output-stream]
    "Write a value representing a response body to an output stream. The stream
    will be closed after the value had been written. The stream may be written
    asynchronously from asynchronous handlers. In synchronous handlers, the
    response is considered completed once this method ends."))
And the result is that the span only contains everything right before and after of that StreamableResponseBody. Anyone know how I can go from here?

got something working! in the handler I use

[steffan-westcott.clj-otel.context :as context]
 telemetry-context (context/dyn)
and then I pass that telemetry-context down into my async code block:
(let [child-context (span/new-span! {:name   "generic-streaming-operation"
                                     :parent telemetry-context})]
  (do-more-stuff async with some other args)
  (span/end-span! {:context child-context}))
I'll probably use try & finally in there. Any feedback will be very welcome!

ok instead of try/finally, i'm using this

(trace-span/with-span! {:name   "some-async-streaming"
                         :parent telemetry-context}
and sub-spans associate nicely again too 🙂 One last missing piece of the puzzle I'm hoping someone can help me out with: The end time of the main (route-level) span, still stops when the synchronous code finishes I've included a picture, the main route-level span at the top in purple, and the async span at the bottom (with it's child spans). Is this a normal looking graph for these kind of operations, or can they all be under the same main span ? Thanks in advance!

Child spans may start after the parent finishes in asynchronous code. This is common in distributed traces but may also occur locally.

1