├── resources └── clj-kondo.exports │ └── opentracing-clj │ └── config.edn ├── .gitignore ├── test └── opentracing_clj │ ├── test_utils.clj │ ├── propagation_test.clj │ ├── ring_test.clj │ ├── span_builder_test.clj │ └── core_test.clj ├── deps.edn ├── .travis.yml ├── src └── clj │ └── opentracing_clj │ ├── propagation.clj │ ├── span_builder.clj │ ├── ring.clj │ └── core.clj ├── project.clj ├── CHANGELOG.md ├── README.md └── LICENSE /resources/clj-kondo.exports/opentracing-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:linters {:unresolved-symbol {:exclude [(opentracing-clj.core/with-span [s])]}}} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *.jar 4 | *.class 5 | /lib/ 6 | /classes/ 7 | /target/ 8 | /checkouts/ 9 | /codox/ 10 | .lein-deps-sum 11 | .lein-repl-history 12 | .lein-plugins/ 13 | .lein-failures 14 | .nrepl-port 15 | .cpcache/ 16 | -------------------------------------------------------------------------------- /test/opentracing_clj/test_utils.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.test-utils 2 | (:require 3 | [opentracing-clj.core :as tracing]) 4 | (:import 5 | [io.opentracing.mock MockTracer])) 6 | 7 | (defn with-mock-tracer 8 | [f] 9 | (binding [tracing/*tracer* (new MockTracer)] 10 | (f))) 11 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {io.opentracing/opentracing-api {:mvn/version "0.33.0"} 2 | io.opentracing/opentracing-noop {:mvn/version "0.33.0"} 3 | io.opentracing/opentracing-util {:mvn/version "0.33.0"} 4 | ring/ring-core {:mvn/version "1.7.1"}} 5 | :paths ["src/clj" "resources"] 6 | :aliases {:test {:extra-paths ["test"] 7 | :extra-deps {io.opentracing/opentracing-mock "0.33.0"}}}} 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | branches: 3 | except: 4 | - gh-pages 5 | after_success: 6 | - lein codox 7 | deploy: 8 | - provider: script 9 | skip_cleanup: true 10 | script: lein deploy 11 | on: 12 | branch: master 13 | - provider: pages 14 | skip_cleanup: true 15 | keep_history: true 16 | github_token: $GITHUB_TOKEN 17 | local_dir: codox 18 | on: 19 | tags: true 20 | branch: master 21 | -------------------------------------------------------------------------------- /test/opentracing_clj/propagation_test.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.propagation-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [opentracing-clj.core :as tracing] 5 | [opentracing-clj.propagation :refer :all] 6 | [opentracing-clj.test-utils :as utils]) 7 | (:import 8 | [io.opentracing.mock MockTracer])) 9 | 10 | (use-fixtures :each utils/with-mock-tracer) 11 | 12 | (deftest inject-extract-roundtrip-test 13 | (testing "inject extract roundtrip" 14 | (let [in-baggage {:baggage1 "1" 15 | :baggage2 "baggage2"}] 16 | (tracing/with-span [s {:name "test" 17 | :tags {:test.tag1 "tag1"}}] 18 | (tracing/set-baggage-items in-baggage) 19 | (let [ctx (tracing/context) 20 | spanId (.spanId ctx) 21 | traceId (.traceId ctx) 22 | baggage (.baggageItems ctx)] 23 | 24 | (let [extract-ctx (extract (inject :http) :http)] 25 | (is (= spanId (.spanId extract-ctx))) 26 | (is (= traceId (.traceId extract-ctx))) 27 | (doseq [[k v] baggage] 28 | (is (= v (.getBaggageItem extract-ctx k)))))))))) 29 | -------------------------------------------------------------------------------- /src/clj/opentracing_clj/propagation.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.propagation 2 | "Functions for cross-process propagation of span contexts." 3 | (:require [opentracing-clj.core :as tracing]) 4 | (:import (io.opentracing SpanContext) 5 | (io.opentracing.propagation Format$Builtin 6 | TextMapAdapter))) 7 | 8 | (def formats {:http Format$Builtin/HTTP_HEADERS 9 | :text Format$Builtin/TEXT_MAP}) 10 | 11 | (defn inject 12 | "Returns a map of the SpanContext in the specified carrier format for 13 | the purpose of propagation across process boundaries. 14 | 15 | Defaults to active span context." 16 | ([format] 17 | (when-let [s (tracing/active-span)] 18 | (inject (tracing/context s) format))) 19 | ([^SpanContext ctx format] 20 | (when-let [t tracing/*tracer*] 21 | (let [hm (java.util.HashMap.) 22 | tm (TextMapAdapter. hm)] 23 | (.inject t ctx (get formats format) tm) 24 | (into {} hm))))) 25 | 26 | (defn extract 27 | "Extract a SpanContext from a carrier of a given type, presumably 28 | after propagation across a process boundary." 29 | [^java.util.Map carrier format] 30 | (when-let [t tracing/*tracer*] 31 | (let [hm (java.util.HashMap. carrier) 32 | tm (TextMapAdapter. hm)] 33 | (.extract t (get formats format) tm)))) 34 | -------------------------------------------------------------------------------- /src/clj/opentracing_clj/span_builder.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc opentracing-clj.span-builder 2 | (:import (io.opentracing Span SpanContext Tracer Tracer$SpanBuilder))) 3 | 4 | (defn add-reference 5 | [^Tracer$SpanBuilder sb ^String type ^SpanContext ctx] 6 | (.addReference sb type ctx)) 7 | 8 | (defn ignore-active 9 | [^Tracer$SpanBuilder sb] 10 | (.ignoreActiveSpan sb)) 11 | 12 | (defn add-tag 13 | [^Tracer$SpanBuilder sb ^String k v] 14 | (cond 15 | (instance? Boolean v) (.withTag sb k ^Boolean v) 16 | (instance? Number v) (.withTag sb k ^Number v) 17 | :else (.withTag sb k ^String (str v)))) 18 | 19 | (defn add-tags 20 | [^Tracer$SpanBuilder sb m] 21 | (when (map? m) 22 | (doseq [[k v] m] 23 | (add-tag sb (if (keyword? k) (name k) (str k)) 24 | v))) 25 | sb) 26 | 27 | (defmulti child-of (fn [sb parent] (class parent))) 28 | 29 | (defmethod child-of Span 30 | [^Tracer$SpanBuilder sb ^Span parent] 31 | (.asChildOf sb parent)) 32 | 33 | (defmethod child-of SpanContext 34 | [^Tracer$SpanBuilder sb ^SpanContext parent] 35 | (.asChildOf sb parent)) 36 | 37 | (defn with-start-timestamp 38 | [^Tracer$SpanBuilder sb timestamp] 39 | (.withStartTimestamp sb timestamp)) 40 | 41 | (defn start 42 | [^Tracer$SpanBuilder sb] 43 | (.start sb)) 44 | 45 | (defn build-span 46 | [^Tracer tracer ^String n] 47 | (.buildSpan tracer n)) 48 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject opentracing-clj "0.2.3-SNAPSHOT" 2 | :description "Opentracing API support for Clojure built on top of opentracing-java." 3 | :url "https://github.com/alvinfrancis/opentracing-clj" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | :source-paths ["src/clj"] 8 | :plugins [[lein-codox "0.10.7"]] 9 | :dependencies [[io.opentracing/opentracing-api "0.33.0"] 10 | [io.opentracing/opentracing-noop "0.33.0"] 11 | [io.opentracing/opentracing-util "0.33.0"] 12 | [ring/ring-core "1.8.1"]] 13 | :codox {:output-path "codox" 14 | :metadata {:doc/format :markdown} 15 | :source-uri "https://github.com/alvinfrancis/opentracing-clj/blob/v{version}/{filepath}#L{line}"} 16 | :deploy-repositories [["snapshots" {:url "https://clojars.org/repo" 17 | :username [:env/clojars_username :gpg] 18 | :password [:env/clojars_password :gpg]}] 19 | ["releases" {:url "https://clojars.org/repo" 20 | :username [:env/clojars_username :gpg] 21 | :password [:env/clojars_password :gpg] 22 | :sign-releases false}]] 23 | :profiles {:provided {:dependencies [[org.clojure/clojure "1.10.1"]]} 24 | :test {:dependencies [[io.opentracing/opentracing-mock "0.33.0"] 25 | [ring/ring-mock "0.4.0"]]}}) 26 | -------------------------------------------------------------------------------- /src/clj/opentracing_clj/ring.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.ring 2 | "Ring middleware for opentracing." 3 | (:require 4 | [clojure.string :as string] 5 | [opentracing-clj.core :as tracing] 6 | [opentracing-clj.propagation :as propagation] 7 | [ring.util.request])) 8 | 9 | (defn default-request-tags 10 | [{:keys [request-method] :as request}] 11 | {:http.method (string/upper-case (name request-method)) 12 | :http.url (ring.util.request/request-url request)}) 13 | 14 | (defn default-response-tags 15 | [{:keys [status] :as response}] 16 | {:http.status_code status}) 17 | 18 | (defn default-op-name 19 | [{:keys [request-method uri] :as request}] 20 | (str (string/upper-case (name request-method)) " " uri)) 21 | 22 | (defn wrap-opentracing 23 | "Middleware for instrumenting a ring handler with tracing. Handles 24 | HTTP header context propagation. 25 | 26 | Adds a ::span field to the ring request for use downstream. 27 | 28 | op-name-fn = (f ring-request) => op-name 29 | request-tags-fn = (f ring-request) => request-tags 30 | response-tags-fn = (f ring-response) => response-tags" 31 | ([handler] 32 | (wrap-opentracing handler default-op-name)) 33 | ([handler op-name-fn] 34 | (wrap-opentracing handler op-name-fn default-request-tags)) 35 | ([handler op-name-fn request-tags-fn] 36 | (wrap-opentracing handler op-name-fn request-tags-fn default-response-tags)) 37 | ([handler op-name-fn request-tags-fn response-tags-fn] 38 | (fn [request] 39 | (if (some? (::span request)) 40 | (handler request) 41 | (let [ctx (propagation/extract (:headers request) :http)] 42 | (tracing/with-span [s {:name (op-name-fn request) 43 | :child-of ctx 44 | :tags (request-tags-fn request)}] 45 | (let [response (handler (assoc request ::span s))] 46 | (tracing/set-tags s (response-tags-fn response)) 47 | response))))))) 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.2.2] - 2021-02-22 8 | ### Added 9 | - Add clj-kondo config to classpath for export. 10 | - Add support for using io.opentracing.tag in `set-tags`. 11 | - Add support for keywords in `set-tags`. 12 | 13 | ## [0.2.1] - 2020-02-09 14 | ### Fixed 15 | - Fixed TextMap inject/extract split (opentracing v0.32.0) breaking propagation. 16 | 17 | ## [0.2.0] - 2019-10-26 18 | ### Added 19 | - Added convenience functions `scope-manager` and `activate`. 20 | 21 | ### Changed 22 | - Dependency updates 23 | - opentracing 0.32.0 to 0.33.0 24 | - Replaced deprecated `opentracing-clj.span-builder/start`. No longer accepts 25 | `finish-on-close?`. Spans started with this function now follow the 26 | opentracing directive of disallowing automatic `Span` finish upon `Scope` 27 | close. 28 | 29 | ## [0.1.5] - 2019-09-08 30 | ### Fixed 31 | - Fix `:opentracing.span-data/child-of` spec. 32 | 33 | ## [0.1.4] - 2019-06-17 34 | ### Changed 35 | - Dependency updates 36 | - opentracing 0.31.0 to 0.32.0 37 | - ring 1.6.3 to ring-core 1.7.1 38 | 39 | ### Fixed 40 | - Nesting `wrap-opentracing` middleware will no longer create multiple spans. 41 | 42 | ## [0.1.3] - 2019-03-25 43 | ### Fixed 44 | - Fix `with-span` to use the existing span behaviour when the initializing spec is ambiguous. 45 | 46 | ## [0.1.2] - 2018-12-17 47 | ### Added 48 | - Existing spans can now be passed to `with-span`. 49 | 50 | ### Fixed 51 | - Codox source-uri 52 | 53 | ## [0.1.0] - 2018-09-17 54 | ### Added 55 | - Core functions for creating and manipulating spans. 56 | - Middleware for instrumenting Ring. 57 | - Functions for handling span context propagation. 58 | 59 | [Unreleased]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.2.2...HEAD 60 | [0.2.2]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.2.1...v0.2.2 61 | [0.2.1]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.2.0...v0.2.1 62 | [0.2.0]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.1.5...v0.2.0 63 | [0.1.5]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.1.4...v0.1.5 64 | [0.1.4]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.1.3...v0.1.4 65 | [0.1.3]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.1.2...v0.1.3 66 | [0.1.2]: https://github.com/alvinfrancis/opentracing-clj/compare/v0.1.0...v0.1.2 67 | [0.1.0]: https://github.com/alvinfrancis/opentracing-clj/compare/284ca4ca0bfadf860c46403c69fd0b313128e6ed...v0.1.0 68 | -------------------------------------------------------------------------------- /test/opentracing_clj/ring_test.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.ring-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [clojure.walk :as walk] 5 | [ring.mock.request :as mock] 6 | [opentracing-clj.core :as tracing] 7 | [opentracing-clj.propagation :as propagation] 8 | [opentracing-clj.ring :refer :all] 9 | [opentracing-clj.test-utils :as utils]) 10 | (:import 11 | [io.opentracing.mock MockTracer])) 12 | 13 | (use-fixtures :each utils/with-mock-tracer) 14 | 15 | (defn mock-handler 16 | [r] 17 | {:status 200}) 18 | 19 | (deftest wrap-opentracing-test 20 | (testing "wrap-opentracing-test" 21 | 22 | (let [uri "/test" 23 | method :get 24 | base-request (mock/request method uri) 25 | client-span {:name "client"} 26 | response (volatile! nil)] 27 | 28 | (testing "server only" 29 | (let [handler (-> mock-handler (wrap-opentracing)) 30 | request base-request] 31 | (vreset! response (handler request)) 32 | (is (= 200 (:status @response)))) 33 | 34 | (let [spans (.finishedSpans tracing/*tracer*)] 35 | (testing "spans recorded" 36 | (is (= 1 (count spans)))) 37 | 38 | (testing "operation name" 39 | (is (= (.operationName (nth spans 0)) 40 | (default-op-name base-request)))) 41 | 42 | (testing "tags set" 43 | (is (= (walk/keywordize-keys (into {} (.tags (nth spans 0)))) 44 | (merge (default-request-tags base-request) 45 | (default-response-tags @response))))))) 46 | 47 | (testing "pass-through" 48 | (.reset tracing/*tracer*) 49 | (tracing/with-span [s client-span] 50 | (let [handler (-> mock-handler (wrap-opentracing)) 51 | headers (propagation/inject (tracing/context s) :http) 52 | request (reduce (fn [r header] 53 | (mock/header r (key header) (val header))) 54 | base-request 55 | headers)] 56 | (vreset! response (handler request)) 57 | (is (= 200 (:status @response))))) 58 | 59 | (let [spans (.finishedSpans tracing/*tracer*)] 60 | (testing "spans recorded" 61 | (is (= 2 (count spans)))) 62 | 63 | (testing "operation name" 64 | (is (= (.operationName (nth spans 0)) 65 | (default-op-name base-request)))) 66 | 67 | (testing "tags set" 68 | (is (= (walk/keywordize-keys (into {} (.tags (nth spans 0)))) 69 | (merge (default-request-tags base-request) 70 | (default-response-tags @response))))) 71 | 72 | (testing "client finish" 73 | (is (= (.operationName (nth spans 1)) 74 | (:name client-span)))))) 75 | 76 | (testing "nested invocation" 77 | (.reset tracing/*tracer*) 78 | (let [handler (-> mock-handler 79 | (wrap-opentracing) 80 | (wrap-opentracing))] 81 | (vreset! response (handler base-request)) 82 | (is (= {:status 200} @response) 83 | "response passed through") 84 | 85 | (let [spans (.finishedSpans tracing/*tracer*)] 86 | (is (= 1 (count spans)) 87 | "only a single span was recorded"))))))) 88 | -------------------------------------------------------------------------------- /src/clj/opentracing_clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.core 2 | "Functions for creating and manipulating spans for opentracing." 3 | (:require 4 | [clojure.spec.alpha :as s] 5 | [clojure.walk :as walk] 6 | [opentracing-clj.span-builder :as sb] 7 | [ring.util.request]) 8 | (:import (io.opentracing Span SpanContext Tracer Scope ScopeManager) 9 | (io.opentracing.tag Tag) 10 | (io.opentracing.util GlobalTracer))) 11 | 12 | (def ^:dynamic ^Tracer *tracer* 13 | "An Tracer object representing the standard tracer for trace operations. 14 | 15 | Defaults to the value returned by GlobalTracer.get()." 16 | (GlobalTracer/get)) 17 | 18 | (defn scope-manager 19 | "Returns the ScopeManager of the tracer." 20 | (^ScopeManager [] 21 | (.scopeManager *tracer*)) 22 | (^ScopeManager [^Tracer tracer] 23 | (.scopeManager tracer))) 24 | 25 | ;; Span 26 | ;; ---- 27 | 28 | (defn active-span 29 | "Returns the current active span." 30 | [] 31 | (when *tracer* 32 | (.activeSpan (scope-manager)))) 33 | 34 | (defmacro with-active-span 35 | "Convenience macro for setting sym to the current active span. Will 36 | evaluate to nil if there are no active-spans." 37 | [sym & body] 38 | `(when-let [~sym (active-span)] 39 | ~@body)) 40 | 41 | (defn activate 42 | "Activates a span." 43 | [^Span span] 44 | (.activate (scope-manager) span)) 45 | 46 | (defn context 47 | "Returns the associated SpanContext of a span." 48 | ([] 49 | (with-active-span s 50 | (context s))) 51 | ([^Span span] 52 | (.context span))) 53 | 54 | (defn finish 55 | "Sets the end timestamp to now and records the span. Can also supply an explicit timestamp in microseconds." 56 | ([] 57 | (with-active-span s 58 | (finish s))) 59 | ([^Span span] 60 | (.finish span)) 61 | ([^Span span ^long timestamp] 62 | (.finish span timestamp))) 63 | 64 | (defn baggage-item 65 | "Returns the value of the baggage item identified by the given key, or 66 | nil if no such item could be found." 67 | ([^String key] 68 | (with-active-span s 69 | (baggage-item s key))) 70 | ([^Span span ^String key] 71 | (.getBaggageItem span key))) 72 | 73 | (defn log 74 | "Logs value v on the span. 75 | 76 | Can also supply an explicit timestamp in microseconds." 77 | ([v] 78 | (with-active-span s 79 | (log s v))) 80 | ([^Span span v] 81 | (cond 82 | (map? v) (.log span ^java.util.Map (walk/stringify-keys v)) 83 | :else (.log span ^String (str v)))) 84 | ([^Span span v ^Long timestamp] 85 | (cond 86 | (map? v) (.log span timestamp ^java.util.Map (walk/stringify-keys v)) 87 | :else (.log span timestamp ^String (str v))))) 88 | 89 | (defn set-baggage-item 90 | "Sets a baggage item on the Span as a key/value pair." 91 | ([^String key ^String val] 92 | (with-active-span s 93 | (set-baggage-item s key val))) 94 | ([^Span span ^String key ^String val] 95 | (.setBaggageItem span key val))) 96 | 97 | (defn set-baggage-items 98 | "Sets baggage items on the Span using key/value pairs of a map. 99 | 100 | Note: Will automatically convert keys into strings." 101 | ([map] 102 | (with-active-span s 103 | (set-baggage-items s map))) 104 | ([^Span span map] 105 | (when (map? map) 106 | (doseq [[k v] map] 107 | (set-baggage-item span 108 | (if (keyword? k) (name k) (str k)) 109 | (str v)))) 110 | span)) 111 | 112 | (defn set-operation-name 113 | "Sets the string name for the logical operation this span represents." 114 | ([^String name] 115 | (with-active-span s 116 | (set-operation-name s name))) 117 | ([^Span span ^String name] 118 | (.setOperationName span name))) 119 | 120 | (defn- resolve-tag-key ^String [key] 121 | (cond (keyword? key) (name key) 122 | (instance? Tag key) (.getKey ^Tag key) 123 | :else ^String key)) 124 | 125 | (defn set-tag 126 | "Sets a key/value tag on the Span." 127 | ([key value] 128 | (with-active-span s 129 | (set-tag s key value))) 130 | ([^Span span key value] 131 | (cond 132 | (instance? Boolean value) (.setTag span ^String (resolve-tag-key key) ^Boolean value) 133 | (instance? Number value) (.setTag span ^String (resolve-tag-key key) ^Number value) 134 | :else (.setTag span ^String (resolve-tag-key key) ^String (str value))))) 135 | 136 | (defn set-tags 137 | "Sets/adds tags on the Span using key/value pairs of a map. 138 | 139 | Automatically converts keys into strings. Overrides any existing tags with the same keys." 140 | ([m] 141 | (with-active-span s 142 | (set-tags s m))) 143 | ([^Span s m] 144 | (when (map? m) 145 | (doseq [[k v] m] 146 | (set-tag s k v))) 147 | s)) 148 | 149 | ;; with-span 150 | ;; --------- 151 | 152 | (s/def :opentracing/microseconds-since-epoch int?) 153 | (s/def :opentracing/span #(instance? Span %)) 154 | (s/def :opentracing/span-context #(instance? SpanContext %)) 155 | (s/def :opentracing.span-data/name string?) 156 | (s/def :opentracing.span-data/tags map?) 157 | (s/def :opentracing.span-data/ignore-active? boolean?) 158 | (s/def :opentracing.span-data/timestamp :opentracing/microseconds-since-epoch) 159 | (s/def :opentracing.span-data/child-of (s/nilable 160 | (s/or :span :opentracing/span 161 | :span-context :opentracing/span-context))) 162 | (s/def :opentracing.span-data/finish? boolean?) 163 | 164 | (s/def :opentracing/span-data 165 | (s/keys :req-un [:opentracing.span-data/name] 166 | :opt-un [:opentracing.span-data/tags 167 | :opentracing.span-data/ignore-active? 168 | :opentracing.span-data/timestamp 169 | :opentracing.span-data/child-of 170 | :opentracing.span-data/finish?])) 171 | 172 | (s/def :opentracing.span-ref/from :opentracing/span) 173 | (s/def :opentracing.span-ref/finish? boolean?) 174 | (s/def :opentracing/span-ref 175 | (s/keys :req-un [:opentracing.span-ref/from] 176 | :opt-un [:opentracing.span-ref/finish?])) 177 | 178 | (s/def :opentracing/span-init 179 | (s/or :existing :opentracing/span-ref 180 | :new :opentracing/span-data)) 181 | 182 | (s/def :opentracing/span-binding 183 | (s/spec 184 | (s/cat :span-sym simple-symbol? 185 | :span-spec any?))) 186 | 187 | (defn ^:private build-new-span 188 | "Given a span-data, create and return a new span." 189 | ^Span [span-data] 190 | (let [builder (sb/build-span *tracer* (:name span-data))] 191 | (when-let [tags# (:tags span-data)] 192 | (sb/add-tags builder tags#)) 193 | (when (:ignore-active? span-data) 194 | (sb/ignore-active builder)) 195 | (when-let [start-ts# (:start-timestamp span-data)] 196 | (sb/with-start-timestamp builder start-ts#)) 197 | (when-let [parent# (:child-of span-data)] 198 | (sb/child-of builder parent#)) 199 | (sb/start builder))) 200 | 201 | (s/fdef build-new-span 202 | :args (s/cat :span-data :opentracing/span-data) 203 | :ret :opentracing/span) 204 | 205 | (defn ^:internal ^:no-doc get-span* 206 | "Given a span-init, return the existing or new span." 207 | [span-init] 208 | (let [conformed-span-init (s/conform :opentracing/span-init span-init)] 209 | (if (= :clojure.spec.alpha/invalid conformed-span-init) 210 | (throw (ex-info "with-span binding failed to conform to :opentracing/span-init" 211 | (s/explain-data :opentracing/span-init span-init))) 212 | (case (first conformed-span-init) 213 | :new (build-new-span span-init) 214 | :existing (:from span-init))))) 215 | 216 | (s/fdef get-span* 217 | :args (s/cat :span-init :opentracing/span-init) 218 | :ret :opentracing/span) 219 | 220 | (defmacro with-span 221 | "Evaluates body in the scope of a generated span. 222 | 223 | binding => [span-sym span-init-spec] 224 | 225 | span-init-spec must evaluate at runtime to a value conforming to 226 | the :opentracing/span-init spec." 227 | [binding & body] 228 | (let [s (binding 0) 229 | m (binding 1)] 230 | `(let [m# ~m 231 | ~s (get-span* m#)] 232 | (try 233 | (with-open [^Scope _# (.activate (.scopeManager *tracer*) 234 | ~s)] 235 | ~@body) 236 | (finally 237 | (when (:finish? m# true) 238 | (finish ~s))))))) 239 | 240 | (s/fdef with-span 241 | :args (s/cat :binding :opentracing/span-binding 242 | :body (s/* any?))) 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opentracing-clj [![Build Status](https://travis-ci.org/alvinfrancis/opentracing-clj.svg?branch=master)](https://travis-ci.org/alvinfrancis/opentracing-clj) [![Clojars Project](https://img.shields.io/clojars/v/opentracing-clj.svg)](https://clojars.org/opentracing-clj) 2 | Opentracing API support for Clojure built on top of 3 | [opentracing-java](https://github.com/opentracing/opentracing-java). 4 | 5 | ## Installation 6 | 7 | Add the following dependency to your project or build file: 8 | 9 | ``` 10 | [opentracing-clj "0.2.2"] 11 | ``` 12 | 13 | ## Requirements 14 | 15 | Supports Opentracing 0.33.0 and onward. 16 | 17 | ## Documentation 18 | 19 | - [API Docs](http://alvinfrancis.github.io/opentracing-clj) 20 | 21 | ## Required Reading 22 | 23 | In order to better understand the project, it will be helpful to be 24 | first familiar with the [OpenTracing project](http://opentracing.io) 25 | and [terminology](http://opentracing.io/documentation/pages/spec.html) 26 | more specifically. 27 | 28 | ## Usage 29 | 30 | ### Creating Spans 31 | 32 | The provided `with-span` macro allows for easy creation of spans. By 33 | default, spans will be set as children of unfinished spans started 34 | prior. 35 | 36 | ``` clojure 37 | (require '[opentracing-clj.core :as tracing]) 38 | 39 | (tracing/with-span [s {:name "span-name" 40 | :tags {:component "test" 41 | :span.kind :server}}] 42 | ...) 43 | ``` 44 | 45 | The `with-span` macro configures the creation of the span by 46 | specifying necessary information through the span binding map. The 47 | following fields are used to configured the behaviour of `with-span`. 48 | 49 | *Required* 50 | - `:name` - Operation name of the span (required) 51 | 52 | *Optional* 53 | - `:tags` - A map of tags (will force keys or values into strings as necessary) 54 | - `:ignore-active?` - Set whether the created span should be set as child of the current scoped span 55 | - `:timestamp` - Start timestamp of the span in microseconds 56 | - `:child-of` - Manually set which span (or span context) the new span should be child of 57 | - `:finish?` - Set whether the span should be finished at the end of the scope 58 | 59 | Alternatively, instead of creating a new span, the `with-span` macro 60 | can also accept a span in the case of activating an existing span 61 | within the scope of the `with-span`. In this instance, the following 62 | fields are used to configure the behaviour of `with-span`. 63 | 64 | *Required* 65 | - `:from` - Span to activate within the scope of the body 66 | 67 | *Optional* 68 | - `:finish?` - Set whether the span should be finished at the end of the scope 69 | 70 | ``` clojure 71 | (tracing/with-span [s {:from some-span 72 | :finish? false}] 73 | ...) 74 | ``` 75 | 76 | Note that spans created by opentracing-clj are also compatible with 77 | those created by opentracing-java (and vice versa); keeping the span 78 | nesting intact. 79 | 80 | ### Manipulating Spans 81 | 82 | Functions are provided for manipulating spans. Functions that work on 83 | spans will default to the current active span unless explicity 84 | specified. 85 | 86 | ``` clojure 87 | (tracing/with-span [s {:name "test"}] 88 | (tracing/log "test") ; log against current active span 89 | ;; above is equivalent to (tracing/log s "test") 90 | (tracing/log {:some :map}) ; can also log maps 91 | (tracing/set-tags {:a 1 :b "val"}) ; add tags to current span 92 | (tracing/set-baggage-items {:baggage1 true :baggage2 "test"}) ; adds baggage to span for propagation across contexts 93 | ) 94 | 95 | (tracing/log "no-op") ; span functions are no-op (and evaluate to nil) if there is no active span 96 | ``` 97 | 98 | ### Async 99 | 100 | `with-span` will only set the active span for the thread and scope 101 | where it was invoked. Code that is run on a separate thread will not 102 | pick up the active span. 103 | 104 | ``` clojure 105 | (tracing/with-span [s {:name "test"}] 106 | (let [async (future (tracing/active-span))] 107 | (= @async s) ; => false 108 | )) 109 | ``` 110 | 111 | To keep a span active in a separate thread, the active span can be 112 | passed directly to `with-span`. 113 | 114 | ``` clojure 115 | (tracing/with-span [s0 {:name "test"}] 116 | (let [async (future 117 | (tracing/with-span [s1 {:from s0 118 | :finish? false}] ; NOTE: finish? is set to false to prevent early finishing of the span 119 | (= s1 s0 (tracing/active-span)) ; => true 120 | (tracing/active-span)))] 121 | (= s0 @async) ; => true 122 | )) 123 | ``` 124 | 125 | It is important to remember that `with-span` defaults to finishing a 126 | span at the end of its scope. This can cause exceptions if multiple 127 | paths can cause a span to be finished. 128 | 129 | ``` clojure 130 | ;; This would complain about finishing an already finished span since 131 | ;; the async with-span would finish the span before the main thread 132 | ;; with-span. 133 | 134 | (tracing/with-span [s0 {:name "test"}] 135 | (let [async-1 (future (tracing/with-span [s1 {:from s0}] ...))] 136 | @async-1 137 | ...)) 138 | ``` 139 | 140 | ### Exceptions 141 | 142 | Since `with-span` will finish a span unless configured otherwise, any 143 | additional data one wishes to add to the span relating to the exception 144 | should be done within the macro. The span will still be finished at 145 | the end of the scope of `with-span`. 146 | 147 | ``` clojure 148 | (tracing/with-span [s {:name "test"}] 149 | (try 150 | (throw (ex-info "test" nil)) 151 | (catch Exception e 152 | (tracing/log "exception") 153 | (tracing/set-tags {:event "error"})))) 154 | ``` 155 | 156 | ### Propagation 157 | 158 | Support is currently available for span context propagation using text 159 | map and HTTP header carrier formats. 160 | 161 | ``` clojure 162 | (require '[opentracing-clj.propagation :as propagation]) 163 | 164 | (tracing/with-span [s {:name "test"}] 165 | (let [headers (propagation/inject :http)] ; is equivalent to (propagation/inject (tracing/context s) :http) 166 | ... ; headers will be a map of the span context for use when making an HTTP call 167 | )) 168 | 169 | (defn ring-handler 170 | [request] 171 | (let [ctx (propagation/extract (:headers request) :http)] ; extract span context from request headers 172 | (tracing/with-span [s {:name "child-of-propagation" 173 | :child-of ctx}] 174 | ... ; this span will be recorded as a child of the span context propagated through the HTTP call to this handler 175 | ))) 176 | ``` 177 | 178 | ### Ring Middleware 179 | 180 | Middleware for instrumenting Ring request/responses is provided. 181 | 182 | ``` clojure 183 | (require '[opentracing-clj.ring :as tracing.ring]) 184 | 185 | (def app (-> handler (tracing.ring/wrap-opentracing))) 186 | ``` 187 | 188 | The middleware provides sane defaults for span naming and tagging. 189 | The span name defaults to `http-method url` (e.g. `GET /test`). 190 | The following semantic tags are also set: `http.method`, `http.url`, `http.status_code`. 191 | 192 | The naming and tagging behaviour can be overriden by providing your 193 | own functions for providing both. 194 | 195 | ``` clojure 196 | (defn operation-name 197 | [ring-request] 198 | (str (:server-name ring-request) ":" (:server-port ring-request) (:uri ring-request))) 199 | 200 | (defn request-tags 201 | [ring-request] 202 | {:http.protocol (:protocol ring-request)}) 203 | 204 | (defn response-tags 205 | [ring-response] 206 | {:http.date (-> ring-response :headers (get "Date"))}) 207 | 208 | (def app (-> handler (tracing.ring/wrap-opentracing operation-name request-tags response-tags))) 209 | ``` 210 | 211 | ### Tracer 212 | 213 | The tracer used by opentracing-clj defaults to the value returned by 214 | `io.opentracing.util.GlobalTracer.get()`. Alternatively, 215 | opentracing-clj exposes the underlying tracer via a dynamic var. 216 | 217 | ``` clojure 218 | 219 | ;; Set the root binding of the tracer to change the default value. 220 | ;; The following sets the tracer to the value return by TracerResolver. 221 | 222 | (import '[io.opentracing.contrib.tracerresolver TracerResolver]) 223 | (alter-var-root #'tracing/*tracer* (constantly (TracerResolver/resolveTracer))) 224 | 225 | ;; The tracer instance can also be set for a particular scope by using binding 226 | 227 | (binding [tracing/*tracer* (other-tracer)] 228 | (tracing/with-span [s {:name "test"}] 229 | ; traces will use the tracer returned by other-tracer 230 | ...)) 231 | 232 | ``` 233 | 234 | ## License 235 | 236 | Distributed under the Eclipse Public License either version 1.0 or (at 237 | your option) any later version. 238 | -------------------------------------------------------------------------------- /test/opentracing_clj/span_builder_test.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.span-builder-test 2 | (:require 3 | [clojure.test :refer :all] 4 | [opentracing-clj.core :as tracing :refer [*tracer*]] 5 | [opentracing-clj.span-builder :refer :all] 6 | [opentracing-clj.test-utils :as utils]) 7 | (:import 8 | [io.opentracing References Tracer$SpanBuilder] 9 | [io.opentracing.mock MockTracer])) 10 | 11 | (use-fixtures :each utils/with-mock-tracer) 12 | 13 | (deftest add-reference-test 14 | (testing "add-reference" 15 | (let [outer-span (.. *tracer* (buildSpan "outer") (start))] 16 | (try 17 | (with-open [outer-scope (.. *tracer* 18 | (scopeManager) 19 | (activate outer-span))] 20 | (let [[ref-type ctx] ["test-type" (.context outer-span)]] 21 | (is (= [ref-type ctx] 22 | (let [sb (.buildSpan *tracer* "test") 23 | _ (add-reference sb ref-type ctx) 24 | span (.start sb)] 25 | (try 26 | (with-open [scope (.. *tracer* 27 | (scopeManager) 28 | (activate span))] 29 | (let [ref (first (.references span))] 30 | [(.getReferenceType ref) (.getContext ref)])) 31 | (finally 32 | (.finish span)))))))) 33 | (finally 34 | (.finish outer-span)))))) 35 | 36 | (deftest ignore-active-test 37 | (testing "ignore-active" 38 | (let [outer-span (.. *tracer* (buildSpan "outer") (start))] 39 | (try 40 | (with-open [outer-scope (.. *tracer* 41 | (scopeManager) 42 | (activate outer-span))] 43 | (is (= 0 (let [sb (.. *tracer* (buildSpan "test")) 44 | _ (ignore-active sb) 45 | span (.start sb)] 46 | (try 47 | (with-open [scope (.. *tracer* 48 | (scopeManager) 49 | (activate span))] 50 | (count (.references span))) 51 | (finally 52 | (.finish span))))))) 53 | (finally 54 | (.finish outer-span)))))) 55 | 56 | 57 | (deftest add-tag-test 58 | (testing "add-tag" 59 | (let [sb (.. *tracer* (buildSpan "test"))] 60 | (is (thrown? ClassCastException (add-tag sb :non-string-key "value"))) 61 | 62 | (add-tag sb "string-key" "string-val") 63 | (add-tag sb "boolean" true) 64 | (add-tag sb "number" 1) 65 | (add-tag sb "object" {:some :map}) 66 | (let [span (.start sb)] 67 | (try 68 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 69 | (is (= {"string-key" "string-val" 70 | "boolean" true 71 | "number" 1 72 | "object" "{:some :map}"} 73 | (.tags span)))) 74 | (finally 75 | (.finish span))))))) 76 | 77 | (deftest add-tags-test 78 | (testing "add-tags" 79 | (let [in-tags {"string-key" "string-val" 80 | :keyword-key :key-val 81 | "boolean" true 82 | "number" 1 83 | "object" {:some :map}} 84 | out-tags {"string-key" "string-val" 85 | "keyword-key" ":key-val" 86 | "boolean" true 87 | "number" 1 88 | "object" "{:some :map}"}] 89 | 90 | (is (= out-tags (let [sb (.. *tracer* (buildSpan "test")) 91 | _ (add-tags sb in-tags) 92 | span (.start sb)] 93 | (try 94 | (with-open [scope (.. *tracer* 95 | (scopeManager) 96 | (activate span))] 97 | (.tags span)) 98 | (finally 99 | (.finish span)))))) 100 | 101 | (let [to-merge-tags {"string-key2" "string-val2" 102 | "boolean" false}] 103 | (is (= (merge out-tags to-merge-tags) 104 | (let [sb (.. *tracer* (buildSpan "test")) 105 | _ (do 106 | (add-tags sb in-tags) 107 | (add-tags sb to-merge-tags)) 108 | span (.start sb)] 109 | (try 110 | (with-open [scope (.. *tracer* 111 | (scopeManager) 112 | (activate span))] 113 | (.tags span)) 114 | (finally 115 | (.finish span))))))) 116 | 117 | (is (= {} (let [sb (.. *tracer* (buildSpan "test")) 118 | _ (add-tags sb nil) 119 | span (.start sb)] 120 | (try 121 | (with-open [scope (.. *tracer* 122 | (scopeManager) 123 | (activate span))] 124 | (.tags span)) 125 | (finally 126 | (.finish span))))))))) 127 | 128 | (deftest child-of-test 129 | (testing "child-of" 130 | (let [outer-span (.. *tracer* (buildSpan "outer") (start))] 131 | (try 132 | (with-open [outer (.. *tracer* 133 | (scopeManager) 134 | (activate outer-span))] 135 | (let [ctx (.context outer-span)] 136 | (is (= [References/CHILD_OF ctx] 137 | (let [sb (.. *tracer* (buildSpan "test")) 138 | _ (child-of sb outer-span) 139 | span (.start sb)] 140 | (try 141 | (with-open [scope (.. *tracer* 142 | (scopeManager) 143 | (activate span))] 144 | (let [ref (first (.references span))] 145 | [(.getReferenceType ref) (.getContext ref)])) 146 | (finally 147 | (.finish span)))))) 148 | 149 | (is (= [References/CHILD_OF ctx] 150 | (let [sb (.. *tracer* (buildSpan "test")) 151 | _ (child-of sb ctx) 152 | span (.start sb)] 153 | (try 154 | (with-open [scope (.. *tracer* 155 | (scopeManager) 156 | (activate span))] 157 | (let [ref (first (.references span))] 158 | [(.getReferenceType ref) (.getContext ref)])) 159 | (finally 160 | (.finish span)))))))) 161 | (finally 162 | (.finish outer-span)))))) 163 | 164 | (deftest with-start-timestamp-test 165 | (testing "with-start-timestamp" 166 | (let [ms 10 167 | sb (.. *tracer* (buildSpan "test")) 168 | _ (with-start-timestamp sb ms) 169 | span (.start sb)] 170 | (try 171 | (with-open [scope (.. *tracer* 172 | (scopeManager) 173 | (activate span))] 174 | (is (= ms (.startMicros span)))) 175 | (finally 176 | (.finish span)))))) 177 | 178 | (deftest start-test 179 | (testing "start" 180 | (is (= 1 (let [span (-> *tracer* 181 | (.buildSpan "test") 182 | (start))] 183 | (try 184 | (with-open [scope (.. *tracer* (scopeManager) (activate span))]) 185 | (finally 186 | (.finish span))) 187 | (count (.finishedSpans *tracer*))))))) 188 | 189 | (deftest build-span-test 190 | (testing "build-span" 191 | (let [op-name "test"] 192 | (is (instance? Tracer$SpanBuilder (build-span *tracer* op-name))) 193 | (is (= op-name (let [span (.start (build-span *tracer* op-name))] 194 | (try 195 | (with-open [scope (.. *tracer* 196 | (scopeManager) 197 | (activate span))] 198 | (.operationName span)) 199 | (finally 200 | (.finish span))))))))) 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /test/opentracing_clj/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns opentracing-clj.core-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.walk :as walk] 4 | [opentracing-clj.core :refer :all] 5 | [opentracing-clj.test-utils :as utils]) 6 | (:import [io.opentracing References] 7 | [io.opentracing.tag Tags] 8 | [io.opentracing.mock MockSpan MockTracer])) 9 | 10 | (use-fixtures :each utils/with-mock-tracer) 11 | 12 | (deftest scope-manager-test 13 | (testing "scope manager" 14 | (is (= (.scopeManager *tracer*) (scope-manager *tracer*))) 15 | (is (= (.scopeManager *tracer*) (scope-manager))))) 16 | 17 | (deftest active-span-test 18 | (testing "active span" 19 | (let [span (.. *tracer* (buildSpan "test") (start))] 20 | (try 21 | (with-open [scope (.. *tracer* 22 | (scopeManager) 23 | (activate span))] 24 | (is (= (active-span) span))) 25 | (finally 26 | (.finish span)))) 27 | (is (nil? (active-span))))) 28 | 29 | (deftest activate-test 30 | (testing "activate" 31 | (let [span (.. *tracer* (buildSpan "test") (start))] 32 | (activate span) 33 | (is (= span (.. *tracer* (scopeManager) (activeSpan))))))) 34 | 35 | (deftest context-test 36 | (testing "context" 37 | (let [span (.. *tracer* (buildSpan "test") (start))] 38 | (try 39 | (with-open [scope (.. *tracer* 40 | (scopeManager) 41 | (activate span))] 42 | (testing "active span" 43 | (is (= (context) (.context span))) 44 | (is (= (context span) (.context span))))) 45 | (finally 46 | (.finish span)))) 47 | (testing "no active span" 48 | (is (nil? (context)))))) 49 | 50 | (deftest finish-test 51 | (testing "finish" 52 | (let [span-1 (.. *tracer* (buildSpan "span-1") (start)) 53 | scope-1 (.. *tracer* (scopeManager) (activate span-1)) 54 | span-2 (.. *tracer* (buildSpan "span-2") (start)) 55 | scope-2 (.. *tracer* (scopeManager) (activate span-2)) 56 | span-3 (.. *tracer* (buildSpan "span-3") (start)) 57 | scope-3 (.. *tracer* (scopeManager) (activate span-3))] 58 | (testing "active span" 59 | (do 60 | (finish) 61 | (let [spans (.finishedSpans *tracer*)] 62 | (is (= 1 (count spans))) 63 | (is (= "span-3" (.operationName (nth spans 0)))))) 64 | 65 | (do 66 | (finish span-2) 67 | (let [spans (.finishedSpans *tracer*)] 68 | (is (= 2 (count spans))) 69 | (is (= "span-2" (.operationName (nth spans 1))))))) 70 | 71 | (testing "timestamp" 72 | (.reset *tracer*) 73 | (finish span-1 10) 74 | (let [span (first (.finishedSpans *tracer*))] 75 | (is (= "span-1" (.operationName span))) 76 | (is (= 10 (.finishMicros span)))))) 77 | 78 | (testing "no active span" 79 | (is (thrown? IllegalStateException (finish)))))) 80 | 81 | (deftest log-test 82 | (testing "log" 83 | (testing "active span" 84 | (let [span (.. *tracer* (buildSpan "test") (start))] 85 | (try 86 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 87 | (log "string-value") 88 | (log :non-string-value) 89 | (log {:keyword-key :non-string-value 90 | "string-key" "string-value"}) 91 | (log span "explicit set span")) 92 | (finally 93 | (.finish span)))) 94 | 95 | (let [span (first (.finishedSpans *tracer*)) 96 | logs (.logEntries span)] 97 | (is (= 4 (count logs))) 98 | (is (= "string-value" (-> logs (nth 0) (.fields) (get "event")))) 99 | (is (= ":non-string-value" (-> logs (nth 1) (.fields) (get "event")))) 100 | (is (= :non-string-value (-> logs (nth 2) (.fields) (get "keyword-key")))) 101 | (is (= "string-value" (-> logs (nth 2) (.fields) (get "string-key")))) 102 | (is (= "explicit set span" (-> logs (nth 3) (.fields) (get "event")))))) 103 | 104 | (testing "with timestamp" 105 | (.reset *tracer*) 106 | (let [span (.. *tracer* (buildSpan "test") (start))] 107 | (try 108 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 109 | (log span "string-value" 10) 110 | (log span :non-string-value 10) 111 | (log span {:keyword-key :non-string-value 112 | "string-key" "string-value"} 10)) 113 | (finally 114 | (.finish span)))) 115 | 116 | (let [span (first (.finishedSpans *tracer*)) 117 | logs (.logEntries span)] 118 | (is (< 0 (count logs))) 119 | (is (= "string-value" (-> logs (nth 0) (.fields) (get "event")))) 120 | (is (= ":non-string-value" (-> logs (nth 1) (.fields) (get "event")))) 121 | (is (= :non-string-value (-> logs (nth 2) (.fields) (get "keyword-key")))) 122 | (is (= "string-value" (-> logs (nth 2) (.fields) (get "string-key")))))) 123 | 124 | (testing "no active span" 125 | (is (nil? (log "test")))))) 126 | 127 | (deftest baggage-item-test 128 | (testing "baggage-item" 129 | (let [span (.. *tracer* (buildSpan "test") (start))] 130 | (try 131 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 132 | (.setBaggageItem span "key" "value") 133 | (testing "active span" 134 | (is (= "value" (baggage-item "key"))) 135 | (is (= "value" (baggage-item span "key"))) 136 | (is (nil? (baggage-item "unknown"))) 137 | (is (nil? (baggage-item span "unknown"))))) 138 | (finally 139 | (.finish span)))) 140 | 141 | (testing "no active span" 142 | (is (nil? (baggage-item "key")))))) 143 | 144 | (deftest set-baggage-item-test 145 | (testing "baggage-item" 146 | (testing "active span" 147 | (let [span (.. *tracer* (buildSpan "test") (start))] 148 | (try 149 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 150 | (is (= "active" (do (set-baggage-item "active" "active") 151 | (.getBaggageItem span "active")))) 152 | (is (= "override" (do (set-baggage-item "active" "override") 153 | (.getBaggageItem span "active")))) 154 | (is (= "passed" (do (set-baggage-item span "passed" "passed") 155 | (.getBaggageItem span "passed"))))) 156 | (finally 157 | (.finish span))))) 158 | 159 | (testing "no active span" 160 | (is (nil? (set-baggage-item "key" "value")))))) 161 | 162 | (deftest set-baggage-items-test 163 | (testing "baggage-item" 164 | (testing "active span" 165 | (let [in-baggage {"string-key" "string-val" 166 | :keyword-key :key-val 167 | "number" 1 168 | "object" {:some :map}}] 169 | (let [span (.. *tracer* 170 | (buildSpan "test") 171 | (start))] 172 | (try 173 | (with-open [scope (.. *tracer* 174 | (scopeManager) 175 | (activate span))] 176 | (set-baggage-items in-baggage) 177 | (is (= "string-val" (.getBaggageItem span "string-key"))) 178 | (is (= ":key-val" (.getBaggageItem span "keyword-key"))) 179 | (is (= "1" (.getBaggageItem span "number"))) 180 | (is (= "{:some :map}" (.getBaggageItem span "object")))) 181 | (finally 182 | (.finish span)))) 183 | (let [span (.. *tracer* 184 | (buildSpan "test") 185 | (start))] 186 | (try 187 | (with-open [scope (.. *tracer* 188 | (scopeManager) 189 | (activate span))] 190 | (set-baggage-items span in-baggage) 191 | (is (= "string-val" (.getBaggageItem span "string-key"))) 192 | (is (= ":key-val" (.getBaggageItem span "keyword-key"))) 193 | (is (= "1" (.getBaggageItem span "number"))) 194 | (is (= "{:some :map}" (.getBaggageItem span "object")))) 195 | (finally 196 | (.finish span)))))) 197 | 198 | (testing "no active span" 199 | (is (nil? (set-baggage-items {:key "value"})))))) 200 | 201 | (deftest set-operation-name-test 202 | (testing "set-operation-name" 203 | (testing "active span" 204 | (let [span (.. *tracer* 205 | (buildSpan "test") 206 | (start))] 207 | (try 208 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 209 | (is (= "active" (do (set-operation-name "active") 210 | (.operationName span)))) 211 | (is (= "passed" (do (set-operation-name span "passed") 212 | (.operationName span))))) 213 | (finally 214 | (.finish span))))) 215 | 216 | (testing "no active span" 217 | (is (nil? (set-operation-name "unknown")))))) 218 | 219 | (deftest set-tag-test 220 | (testing "set-tag" 221 | (testing "active span" 222 | (let [span (.. *tracer* (buildSpan "test") (start))] 223 | (try 224 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 225 | (is (= "active" (do (set-tag "key" "active") 226 | (get (.tags span) "key")))) 227 | (is (= "override" (do (set-tag "key" "active") 228 | (set-tag "key" "override") 229 | (get (.tags span) "key")))) 230 | (is (= "passed" (do (set-tag "key" "passed") 231 | (get (.tags span) "key")))) 232 | (is (= "passed" (do (set-tag :key "passed") 233 | (get (.tags span) "key")))) 234 | (is (= "passed" (do (set-tag :namespaced.key "passed") 235 | (get (.tags span) "namespaced.key")))) 236 | (is (= true (do (set-tag "boolean" true) 237 | (get (.tags span) "boolean")))) 238 | (is (= 1 (do (set-tag "number" 1) 239 | (get (.tags span) "number")))) 240 | (is (= true (do (set-tag Tags/ERROR true) 241 | (get (.tags span) "error")))) 242 | (is (= "client" (do (set-tag Tags/SPAN_KIND Tags/SPAN_KIND_CLIENT) 243 | (get (.tags span) "span.kind")))) 244 | (is (= "client" (do (set-tag "span.kind" Tags/SPAN_KIND_CLIENT) 245 | (get (.tags span) "span.kind")))) 246 | (is (= "{:some :map}" (do (set-tag "map" {:some :map}) 247 | (get (.tags span) "map")))) 248 | (is (thrown? ClassCastException (set-tag 123 "key-val")))) 249 | (finally 250 | (.finish span))))) 251 | 252 | (testing "no active span" 253 | (is (nil? (set-tag "unknown" "unknown")))))) 254 | 255 | (deftest set-tags-test 256 | (testing "set-tags" 257 | (testing "active span" 258 | (let [in-tags {"string-key" "string-val" 259 | :keyword-key :key-val 260 | Tags/ERROR true 261 | Tags/DB_TYPE "postgres" 262 | Tags/SPAN_KIND Tags/SPAN_KIND_CLIENT 263 | "boolean" true 264 | "number" 1 265 | "object" {:some :map}} 266 | out-tags {"string-key" "string-val" 267 | "keyword-key" ":key-val" 268 | "error" true 269 | "db.type" "postgres" 270 | "span.kind" "client" 271 | "boolean" true 272 | "number" 1 273 | "object" "{:some :map}"}] 274 | (let [span (.. *tracer* (buildSpan "test") (start))] 275 | (try 276 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 277 | (is (= out-tags (do (set-tags in-tags) 278 | (.tags span))))) 279 | (finally 280 | (.finish span)))) 281 | 282 | (let [span (.. *tracer* (buildSpan "test") (start))] 283 | (try 284 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 285 | (is (= out-tags (do (set-tags span in-tags) 286 | (.tags span))))) 287 | (finally 288 | (.finish span)))) 289 | 290 | (let [span (.. *tracer* (buildSpan "test") (start))] 291 | (try 292 | (with-open [scope (.. *tracer* (scopeManager) (activate span))] 293 | (is (= {} (do (set-tags nil) 294 | (.tags span)))) 295 | (is (= {"a" "base" "b" "override" "c" "new"} 296 | (do (set-tags {:a "base" :b "base"}) 297 | (set-tags {:a "base" :b "override"}) 298 | (set-tags {:c "new"}) 299 | (.tags span))))) 300 | (finally 301 | (.finish span)))))) 302 | 303 | (testing "no active span" 304 | (is (nil? (set-tags {:key "value"})))))) 305 | 306 | (deftest with-span-test 307 | (testing "with-span" 308 | (testing "set span name" 309 | (let [span-name "test-1"] 310 | (with-span [s {:name span-name}] 311 | (+ 1 1)) 312 | (is (= span-name (.operationName (first (.finishedSpans *tracer*))))) 313 | (is (= 1 (count (.finishedSpans *tracer*)))))) 314 | 315 | (testing "set span tags" 316 | (.reset *tracer*) 317 | (let [span-name "test-2" 318 | span-tags {:component "test-component"}] 319 | (with-span [s {:name span-name 320 | :tags span-tags}] 321 | (+ 1 1)) 322 | (is (= (walk/stringify-keys span-tags) 323 | (.tags (first (.finishedSpans *tracer*))))))) 324 | 325 | (testing "set timestamp" 326 | (.reset *tracer*) 327 | (let [span-name "test-1" 328 | start-micros 10000000] 329 | (with-span [s {:name span-name 330 | :start-timestamp start-micros}] 331 | (is (= start-micros (.startMicros s)))))) 332 | 333 | (testing "existing span" 334 | (.reset *tracer*) 335 | (let [s1 (-> *tracer* (.buildSpan "test1") (.start)) 336 | process-1 (future 337 | (with-span [t {:from s1}] 338 | (is (= s1 (.activeSpan *tracer*))))) 339 | s2 (-> *tracer* (.buildSpan "test2") (.start)) 340 | process-2 (future 341 | (with-span [t {:from s2 342 | :finish? false}] 343 | (is (= s2 (.activeSpan *tracer*)))))] 344 | @process-1 345 | @process-2 346 | (is (= 1 (count (.finishedSpans *tracer*)))))) 347 | 348 | (testing "failed spec" 349 | (.reset *tracer*) 350 | (is (thrown? Exception (with-span [s {:unrecognized-keyword "test"}]))) 351 | (try 352 | (with-span [s {:unrecognized-keyword "test"}]) 353 | (catch Exception e 354 | (let [error (Throwable->map e)] 355 | (is (= "with-span binding failed to conform to :opentracing/span-init" 356 | (:cause error))))))) 357 | 358 | (testing "ambiguous spec" 359 | (.reset *tracer*) 360 | (let [existing (-> *tracer* (.buildSpan "test") (.start)) 361 | process (future 362 | ;; an init spec conforming to both the 363 | ;; existing span spec and the new span spec 364 | ;; should choose to use the existing span spec 365 | (with-span [t {:name "new" 366 | :from existing}] 367 | (is (= existing (.activeSpan *tracer*)))))] 368 | @process 369 | (is (= 1 (count (.finishedSpans *tracer*)))))) 370 | 371 | (testing "ignores active" 372 | (.reset *tracer*) 373 | (with-span [outer {:name "outer"}] 374 | (is (= 0 (with-span [inner {:name "inner" 375 | :ignore-active? true}] 376 | (count (.references inner))))))) 377 | 378 | (testing "exception in span" 379 | (.reset *tracer*) 380 | (let [s1 (-> *tracer* (.buildSpan "test1") (.start)) 381 | process-1 (future 382 | (try 383 | (with-span [t {:from s1}] 384 | (throw (Exception. "BOOM!"))) 385 | (catch Exception e))) 386 | s2 (-> *tracer* (.buildSpan "test2") (.start)) 387 | process-2 (future 388 | (try 389 | (with-span [t {:from s2 390 | :finish? false}] 391 | (throw (Exception. "BOOM!"))) 392 | (catch Exception e)))] 393 | @process-1 394 | @process-2 395 | (is (= 1 (count (.finishedSpans *tracer*)))))) 396 | 397 | (testing "set CHILD_OF reference" 398 | (.reset *tracer*) 399 | (testing "implicitly" 400 | (with-span [outer {:name "outer"}] 401 | (is (= [References/CHILD_OF (.context outer)] 402 | (with-span [inner {:name "inner"}] 403 | (let [ref (first (.references inner))] 404 | [(.getReferenceType ref) (.getContext ref)])))))) 405 | 406 | (let [outer-span-1 (.. *tracer* (buildSpan "outer-scope-1") (start))] 407 | (try 408 | (with-open [outer-scope-1 (.. *tracer* (scopeManager) (activate outer-span-1))] 409 | (let [outer-span-2 (.. *tracer* (buildSpan "outer-scope-2") (start))] 410 | (try 411 | (with-open [outer-scope-2 (.. *tracer* (scopeManager) (activate outer-span-2))] 412 | (let [ctx (.context outer-span-1)] 413 | (testing "using span" 414 | (is (= [References/CHILD_OF ctx] 415 | (with-span [t {:name "test" 416 | :child-of outer-span-1}] 417 | (let [ref (first (.references t))] 418 | [(.getReferenceType ref) (.getContext ref)]))))) 419 | (testing "using span context" 420 | (is (= [References/CHILD_OF ctx] 421 | (with-span [t {:name "test" 422 | :child-of (.context outer-span-1)}] 423 | (let [ref (first (.references t))] 424 | [(.getReferenceType ref) (.getContext ref)]))))))) 425 | (finally 426 | (.finish outer-span-2))))) 427 | (finally 428 | (.finish outer-span-1))))))) 429 | --------------------------------------------------------------------------------