@@ -9,26 +9,29 @@ instructions on what to put into http spans, and sampling policy.
9
9
10
10
## Span data policy
11
11
By default, the following are added to both http client and server spans:
12
- * Span.name as the http method in lowercase: ex "get"
12
+ * Span.name is the http method in lowercase: ex "get" or a route described below
13
13
* Tags/binary annotations:
14
+ * "http.method", eg "GET"
14
15
* "http.path", which does not include query parameters.
15
16
* "http.status_code" when the status us not success.
16
17
* "error", when there is an exception or status is >=400
17
18
* Remote IP and port information
18
19
20
+ A route based span name looks like "/users/{userId}", "not_found" or
21
+ "redirected". There's a longer section on Http Route later in this doc.
22
+
19
23
Naming and tags are configurable in a library-agnostic way. For example,
20
24
the same ` HttpTracing ` component configures OkHttp or Apache HttpClient
21
25
identically.
22
26
23
- For example, to change the span and tag naming policy for clients, you
24
- can do something like this:
27
+ For example, to change the tagging policy for clients, you can do
28
+ something like this:
25
29
26
30
``` java
27
31
httpTracing = httpTracing. toBuilder()
28
32
.clientParser(new HttpClientParser () {
29
33
@Override
30
34
public <Req > void request (HttpAdapter<Req , ?> adapter , Req req , SpanCustomizer customizer ) {
31
- customizer. name(adapter. method(req). toLowerCase() + " " + adapter. path(req));
32
35
customizer. tag(TraceKeys . HTTP_URL , adapter. url(req)); // the whole url, not just the path
33
36
}
34
37
})
@@ -85,6 +88,29 @@ httpTracingBuilder.serverSampler(HttpRuleSampler.newBuilder()
85
88
.build());
86
89
```
87
90
91
+ ## Http Route
92
+ The http route is an expression such as ` /items/:itemId ` representing an
93
+ application endpoint. ` HttpAdapter.route() ` parses this from a response,
94
+ returning the route that matched the request, empty if no route matched,
95
+ or null if routes aren't supported. This value is either used to create
96
+ a tag "http.route" or as an input to a span naming function.
97
+
98
+ ### Http route cardinality
99
+ The http route groups similar requests together, so results in limited
100
+ cardinality, often better choice for a span name vs the http method.
101
+
102
+ For example, the route ` /users/{userId} ` , matches ` /users/25f4c31d ` and
103
+ ` /users/e3c553be ` . If a span name function used the http path instead,
104
+ it could DOS-style attack vector on your span name index, as it would
105
+ grow unbounded vs ` /users/{userId} ` . Even if different frameworks use
106
+ different formats, such as ` /users/[0-9a-f]+ ` or ` /users/:userId ` , the
107
+ cardinality is still fixed with regards to request count.
108
+
109
+ The http route can can be empty on redirect or not-found. If you are
110
+ using the route for metrics, coerce empty to constants like "redirected"
111
+ or "not_found" using the http status. For example, the default span name
112
+ policy does this.
113
+
88
114
# Developing new instrumentation
89
115
90
116
Check for [ instrumentation written here] ( ../instrumentation/ ) and [ Zipkin's list] ( http://zipkin.io/pages/existing_instrumentations.html )
@@ -177,4 +203,76 @@ try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // 2.
177
203
} finally {
178
204
handler. handleSend(response, error, span); // 5.
179
205
}
180
- ```
206
+ ```
207
+
208
+ ### Supporting HttpAdapter.route(response)
209
+
210
+ Eventhough the route is associated with the request, not the response,
211
+ it is parsed from the response object. The reason is that many server
212
+ implementations process the request before they can identify the route.
213
+
214
+ Instrumentation authors implement support via extending HttpAdapter
215
+ accordingly. There are a few patterns which might help.
216
+
217
+ #### Callback with non-final type
218
+ When a framework uses an callback model, you are in control of the type
219
+ being parsed. If the response type isn't final, simply subclass it with
220
+ the route data.
221
+
222
+ For example, if spring MVC, it would be this:
223
+ ``` java
224
+ // check for a wrapper type which holds the template
225
+ handler = HttpServerHandler . create(httpTracing, new HttpServletAdapter () {
226
+ @Override public String template (HttpServletResponse response ) {
227
+ return response instanceof HttpServletResponseWithTemplate
228
+ ? ((HttpServletResponseWithTemplate ) response). route : null ;
229
+ }
230
+
231
+ @Override public String toString () {
232
+ return " WebMVCAdapter{}" ;
233
+ }
234
+ });
235
+
236
+ -- snip--
237
+
238
+ // when parsing the response, scope the route from the request
239
+
240
+ Object template = request. getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE );
241
+ if (route != null ) {
242
+ response = new HttpServletResponseWithTemplate (response, route. toString());
243
+ }
244
+ handler. handleSend(response, ex, span);
245
+ ```
246
+
247
+ #### Callback with final type
248
+ If the response type is final, you may be able to make a copy and stash
249
+ the route as a synthetic header. Since this is a copy of the response,
250
+ there's no chance a user will receive this header in a real response.
251
+
252
+ Here's an example for Play, where the header "brave-http-template" holds
253
+ the route temporarily until the parser can read it.
254
+ ``` scala
255
+ result.onComplete {
256
+ case Failure (t) => handler.handleSend(null , t, span)
257
+ case Success (r) => {
258
+ // add a synthetic header if there was a routing path set
259
+ var resp = template.map(t => r.withHeaders(" brave-http-template" -> t)).getOrElse(r)
260
+ handler.handleSend(resp, null , span)
261
+ }
262
+ }
263
+ -- snip--
264
+ override def template (response : Result ): String =
265
+ response.header.headers.apply(" brave-http-template" )
266
+ ```
267
+
268
+ #### Common mistakes
269
+
270
+ For grouping to work, we want routes that are effectively the same, to
271
+ in fact be the same. Here are a couple things on that.
272
+
273
+ * Always start with a leading slash
274
+ * This allows you to differentiate the root path from empty (no route)
275
+ * This prevents accidental partitioning like ` users/:userId ` from ` /users/:userId `
276
+ * Take care not to duplicate slashes
277
+ * When joining nested paths, avoid writing templates like ` /nested//users/:userId `
278
+ * The ` ITHttpServer ` test will catch some of this
0 commit comments