-
Notifications
You must be signed in to change notification settings - Fork 12
/
context.clj
152 lines (134 loc) · 5.53 KB
/
context.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
(ns steffan-westcott.clj-otel.context
"Functions for working with `io.opentelemetry.context.Context` objects."
(:require [clojure.string :as str]
[steffan-westcott.clj-otel.api.otel :as otel]
[steffan-westcott.clj-otel.util :as util])
(:import (io.opentelemetry.context Context ContextKey ImplicitContextKeyed Scope)
(io.opentelemetry.context.propagation TextMapGetter TextMapPropagator TextMapSetter)
(java.util HashMap Map)))
(defn current
"Returns the current context, a thread local `Context` object. If no such
context exists, the root context is returned instead."
[]
(Context/current))
(defn set-current!
"Sets the current context. The returned `Scope` must be closed to prevent
broken traces and memory leaks. [[with-context!]] and [[with-value!]] can
be used instead of this function to ensure the scope is closed when leaving a
lexical boundary i.e. a body of forms."
^Scope [^Context context]
(.makeCurrent context))
(defn close-scope!
"Closes the given scope."
[^Scope scope]
(.close scope))
(defmacro with-context!
"Sets the current context to be the provided `context`, then evaluates
`body`. The original current context is restored after body evaluation
completes."
[context & body]
`(let [^Context context# ~context]
(with-open [_scope# (.makeCurrent context#)]
~@body)))
(defmacro with-value!
"Make a new current context by associating an `ImplicitContextKeyed` instance
`implicit-context-keyed` then evaluate `body`. The original current context
is restored after body evaluation completes."
[implicit-context-keyed & body]
`(let [^ImplicitContextKeyed value# ~implicit-context-keyed]
(with-open [_scope# (.makeCurrent value#)]
~@body)))
(def ^:private context-key*
(memoize (fn [k]
(ContextKey/named (util/qualified-name k)))))
(defprotocol AsContextKey
(context-key [k]
"Coerces k to a `ContextKey`."))
(extend-protocol AsContextKey
ContextKey
(context-key [k]
k)
Object
(context-key [k]
(context-key* k)))
(defn get-value
"Returns the value stored in the context for the given context key."
[^Context context key]
(.get context (context-key key)))
(defn assoc-value
"Associates a key-value with a `context`, returning a new `Context` that
includes the key-value. Does not use nor affect the current context. Takes
`key` and `value`, or an `ImplicitContextKeyed` instance which is a value
that uses a predetermined key."
(^Context [^Context context ^ImplicitContextKeyed implicit-context-keyed]
(.with context implicit-context-keyed))
(^Context [^Context context key value]
(.with context (context-key key) value)))
(defn root
"Returns the root context that all other contexts are derived from."
[]
(Context/root))
(defn current-context-interceptor
"Returns a Pedestal interceptor that will on entry set the current context to
the value that has key `context-key` in the interceptor context map. The
original value of current context is restored on evaluation on interceptor
exit (either `leave` or `error`). The `Scope` of the set context is stored
in the interceptor context map with key `scope-key`."
[context-key scope-key]
{:name ::current-context
:enter (fn [ctx]
(let [scope (set-current! (get ctx context-key))]
(assoc ctx scope-key scope)))
:leave (fn [ctx]
(close-scope! (get ctx scope-key))
ctx)
:error (fn [ctx e]
(close-scope! (get ctx scope-key))
(assoc ctx :io.pedestal.interceptor.chain/error e))})
(def ^:private map-setter
(reify
TextMapSetter
(set [_ carrier key value]
(.put ^Map carrier key value))))
(def ^:private map-getter
(reify
TextMapGetter
(keys [_ carrier]
(keys carrier))
(get [_ carrier key]
(some-> (get carrier key)
(str/split #",")
first
str/trim))))
(defn ->headers
"Returns a map to merge into the headers of an HTTP request for the purpose
of context propagation i.e. transfer context to a remote server. May take an
optional map argument as follows:
| key | description |
|----------------------|-------------|
|`:context` | Context to propagate (default: current context).
|`:text-map-propagator`| Propagator used to create headers map entries (default: propagator set in global `OpenTelemetry` instance)."
([]
(->headers {}))
([{:keys [^Context context ^TextMapPropagator text-map-propagator]
:or {context (current)
text-map-propagator (otel/get-text-map-propagator)}}]
(let [carrier (HashMap.)]
(.inject text-map-propagator context carrier map-setter)
(into {} carrier))))
(defn headers->merged-context
"Returns a context formed by extracting a propagated context from a map
`headers` and merging with another context i.e. accept context transfer from
a remote server. May take an option map as follows:
| key | description |
|----------------------|-------------|
|`:context` | Context to merge with (default: current context).
|`:text-map-propagator`| Propagator used to extract data from the headers map (default: propagator set in global `OpenTelemetry` instance)."
([headers]
(headers->merged-context headers {}))
(^Context
[headers
{:keys [^Context context ^TextMapPropagator text-map-propagator]
:or {context (current)
text-map-propagator (otel/get-text-map-propagator)}}]
(.extract text-map-propagator context headers map-getter)))