Hello! I'm currently using the ring wrap-metrics-by-route middleware for ring server metrics (https://github.com/steffan-westcott/clj-otel/blob/0.2.7/clj-otel-api/src/steffan_westcott/clj_otel/api/metrics/http/server.clj#L157) . I'd like to be able to add additional attributes to the underlying metrics. For example, I want to include auth attributes such as the caller username and which token is being used. From looking at the current code (in particular, server-request-attrs and record-duration!, it doesn't look like there's a facility for doing this. I'm wondering if there might be a general approach to extend/update the existing implementations to achieve this. Probably in the meantime I'll use alternative, local implementations to do this, but I suspect it's something that may be useful in general.
Please note that clj-otel adheres where possible to the https://opentelemetry.io/docs/specs/semconv/http/http-metrics/. Is there a documented semantic convention for caller username and token?
I think the most straightforward solution for the custom behaviour you describe is to provide your own Ring middleware (or Pedestal interceptors) to use instead of wrap-request-duration. As an implementation note, io.opentelemetry/server-request-attrs stores data common to both traces and metrics.
My understanding is that the semantic conventions are intended to ensure that commonly defined concepts have common names, not that the attributes that can be associated with a given metric (or span) is closed. What I'm looking for is a way of adding arbitrary attributes to the metrics, not formally extend otel or have clj-otel embrace or define additional attributes.
That's what I'll end up trying to do: I won't want to hard-code anything in the implementation. There will be some mechanism for adding attributes that are specific to scenario/handler/route/what-have-you.
Thanks for the pointer to io.opentelemetry/server-request-attrs
You are on the right track. There are two particular annoyances of the Java implementation that clj-otel works around through storing data in the request map:
• Trace and metrics data are write only and cannot be read
• Metrics values once written cannot be modified e.g. add attributes values
What I ended up doing was this:
(def ^:dynamic *additional-request-attributes* {})
(defn additional-request-attributes []
*additional-request-attributes*)
(defmacro with-additional-request-attributes
[attrs & forms]
`(binding [*additional-request-attributes* (merge *additional-request-attributes* ~attrs)]
~@forms))
(defn- record-duration!
([start-time server-request-attrs status]
(record-duration! start-time server-request-attrs status (context/dyn)))
([start-time server-request-attrs status context]
(let [attributes (merge (additional-request-attributes)
(request-duration-or-size-attrs server-request-attrs status))]
(instrument/record! @request-duration
{:value (since-seconds! start-time)
:attributes attributes
:context context}))))
(defn- record-request-size!
([server-request-attrs status] (record-request-size! server-request-attrs status (context/dyn)))
([server-request-attrs status context]
(when-let [size (get server-request-attrs HttpIncubatingAttributes/HTTP_REQUEST_BODY_SIZE)]
(let [attributes (merge (additional-request-attributes)
(request-duration-or-size-attrs server-request-attrs status))]
(instrument/record! @request-size
{:value size
:attributes attributes
:context context})))))
And called like this (where we've already added JWT payload as :auth-claims
(defn wrap-auth-claims-attributes [handler]
(fn [req]
(if-let [claims (:auth-claims req)]
(let [attrs (select-keys claims ["aud" "sub"])]
(metrics.http.server/with-additional-request-attributes attrs
(handler req)))
(handler req))))
Need to do some finessing with attribute naming and such (I need to namespace them), but otherwise seems to be working for us so far. Cheers!