Opentracing API support for Clojure built on top of opentracing-java.
Add the following dependency to your project or build file:
[opentracing-clj "0.1.3"]
In order to better understand the project, it will be helpful to be first familiar with the OpenTracing project and terminology more specifically.
The provided with-span
macro allows for easy creation of spans. By
default, spans will be set as children of unfinished spans started
prior.
(require '[opentracing-clj.core :as tracing])
(tracing/with-span [s {:name "span-name"
:tags {:component "test"
:span.kind :server}}]
...)
The with-span
macro configures the creation of the span by
specifying necessary information through the span binding map. The
following fields are used to configured the behaviour of with-span
.
Required
:name
- Operation name of the span (required)
Optional
:tags
- A map of tags (will force keys or values into strings as necessary):ignore-active?
- Set whether the created span should be set as child of the current scoped span:timestamp
- Start timestamp of the span in microseconds:child-of
- Manually set which span (or span context) the new span should be child of:finish?
- Set whether the span should be finished at the end of the scope
Alternatively, instead of creating a new span, the with-span
macro
can also accept a span in the case of activating an existing span
within the scope of the with-span
. In this instance, the following
fields are used to configure the behaviour of with-span
.
Required
:from
- Span to activate within the scope of the body
Optional
:finish?
- Set whether the span should be finished at the end of the scope
(tracing/with-span [s {:from some-span
:finish? false}]
...)
Note that spans created by opentracing-clj are also compatible with those created by opentracing-java (and vice versa); keeping the span nesting intact.
Functions are provided for manipulating spans. Functions that work on spans will default to the current active span unless explicity specified.
(tracing/with-span [s {:name "test"}]
(tracing/log "test") ; log against current active span
;; above is equivalent to (tracing/log s "test")
(tracing/log {:some :map}) ; can also log maps
(tracing/set-tags {:a 1 :b "val"}) ; add tags to current span
(tracing/set-baggage-items {:baggage1 true :baggage2 "test"}) ; adds baggage to span for propagation across contexts
)
(tracing/log "no-op") ; span functions are no-op (and evaluate to nil) if there is no active span
with-span
will only set the active span for the thread and scope
where it was invoked. Code that is run on a separate thread will not
pick up the active span.
(tracing/with-span [s {:name "test"}]
(let [async (future (tracing/active-span))]
(= @async s) ; => false
))
To keep a span active in a separate thread, the active span can be
passed directly to with-span
.
(tracing/with-span [s0 {:name "test"}]
(let [async (future
(tracing/with-span [s1 {:from s0
:finish? false}] ; NOTE: finish? is set to false to prevent early finishing of the span
(= s1 s0 (tracing/active-span)) ; => true
(tracing/active-span)))]
(= s0 @async) ; => true
))
It is important to remember that with-span
defaults to finishing a
span at the end of its scope. This can cause exceptions if multiple
paths can cause a span to be finished.
;; This would complain about finishing an already finished span since
;; the async with-span would finish the span before the main thread
;; with-span.
(tracing/with-span [s0 {:name "test"}]
(let [async-1 (future (tracing/with-span [s1 {:from s0}] ...))]
@async-1
...))
Middleware for instrumenting Ring request/responses is provided.
(require '[opentracing-clj.ring :as tracing.ring])
(def app (-> handler (tracing.ring/wrap-opentracing)))
The middleware provides sane defaults for span naming and tagging.
The span name defaults to http-method url
(e.g. GET /test
).
The following semantic tags are also set: http.method
, http.url
, http.status_code
.
The naming and tagging behaviour can be overriden by providing your own functions for providing both.
(defn operation-name
[ring-request]
(str (:server-name ring-request) ":" (:server-port ring-request) (:uri ring-request)))
(defn request-tags
[ring-request]
{:http.protocol (:protocol ring-request)})
(defn response-tags
[ring-response]
{:http.date (-> ring-response :headers (get "Date"))})
(def app (-> handler (tracing.ring/wrap-opentracing operation-name request-tags response-tags)))
Support is currently available for span context propagation using text map and HTTP header carrier formats.
(require '[opentracing-clj.propagation :as propagation])
(tracing/with-span [s {:name "test"}]
(let [headers (propagation/inject :http)] ; is equivalent to (propagation/inject s :http)
... ; headers will be a map of the span context for use when making an HTTP call
))
(let [response (http-ring-response) ; assuming this is an HTTP call returning a ring spec response
ctx (propagation/extract (:headers request) :http)]
(tracing/with-span [s {:name "child-of-propagation"
:child-of ctx}]
... ; this span will be recorded as a child of the span context propagated through the HTTP call
))
The tracer used by opentracing-clj defaults to the value returned by
io.opentracing.util.GlobalTracer.get()
. Alternatively,
opentracing-clj exposes the underlying tracer via a dynamic var.
;; Set the root binding of the tracer to change the default value.
;; The following sets the tracer to the value return by TracerResolver.
(import '[io.opentracing.contrib.tracerresolver TracerResolver])
(alter-var-root #'tracing/*tracer* (TracerResolver/resolveTracer))
;; The tracer instance can also be set for a particular scope by using binding
(binding [tracing/*tracer* (other-tracer)]
(tracing/with-span [s {:name "test"}]
; traces will use the tracer returned by other-tracer
...))
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.