Skip to content

Commit

Permalink
[hotrod] Return trace ID via traceresponse header (#4584)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?
Since the HotROD UI does not start traces, it can only search for a
trace corresponding to a particular request by some span tags. This
makes the experience of opening a trace a big clunky as we first see the
search page with one trace, and then have to click on the trace again.

Related to #3380

## Short description of the changes
Change the HTTP instrumentation to always generate a `traceresponse`
header per the [current W3C
draft](https://github.com/w3c/trace-context/blob/main/spec/21-http_response_header_format.md).
Then parse the trace ID from that header in the UI and generate a
hyperlink to open the trace view directly, bypassing the search.

---------

Signed-off-by: Yuri Shkuro <github@ysh.us>
  • Loading branch information
yurishkuro authored Jul 16, 2023
1 parent 7b6cd83 commit 9184b90
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 19 deletions.
18 changes: 17 additions & 1 deletion examples/hotrod/pkg/tracing/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"net/http"

"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"

Expand Down Expand Up @@ -48,7 +49,7 @@ func (tm *TracedServeMux) Handle(pattern string, handler http.Handler) {
tm.logger.Bg().Debug("registering traced handler", zap.String("endpoint", pattern))

middleware := otelhttp.NewHandler(
otelhttp.WithRouteTag(pattern, handler),
otelhttp.WithRouteTag(pattern, traceResponseHandler(handler)),
pattern,
otelhttp.WithTracerProvider(tm.tracer))

Expand All @@ -59,3 +60,18 @@ func (tm *TracedServeMux) Handle(pattern string, handler http.Handler) {
func (tm *TracedServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm.mux.ServeHTTP(w, r)
}

// Returns a handler that generates a traceresponse header.
// https://github.com/w3c/trace-context/blob/main/spec/21-http_response_header_format.md
func traceResponseHandler(handler http.Handler) http.Handler {
// We use the standard TraceContext propagator, since the formats are identical.
// But the propagator uses "traceparent" header name, so we inject it into a map
// `carrier` and then use the result to set the "tracereponse" header.
var prop propagation.TraceContext
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
carrier := make(map[string]string)
prop.Inject(r.Context(), propagation.MapCarrier(carrier))
w.Header().Add("traceresponse", carrier["traceparent"])
handler.ServeHTTP(w, r)
})
}
56 changes: 38 additions & 18 deletions examples/hotrod/services/frontend/web_assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,53 +53,73 @@ <h4>🚗 <em>Rides On Demand</em> 🚗</h4>
<script>

function formatDuration(duration) {
var d = duration / (1000000 * 1000 * 60);
var units = 'min';
const d = duration / (1000000 * 1000 * 60);
const units = 'min';
return Math.round(d) + units;
}

var clientUUID = Math.round(Math.random() * 10000);
function parseTraceResponse(value) {
const VERSION = '00';
const VERSION_PART = '(?!ff)[\\da-f]{2}';
const TRACE_ID_PART = '(?![0]{32})[\\da-f]{32}';
const PARENT_ID_PART = '(?![0]{16})[\\da-f]{16}';
const FLAGS_PART = '[\\da-f]{2}';
const TRACE_PARENT_REGEX = new RegExp(
`^\\s?(${VERSION_PART})-(${TRACE_ID_PART})-(${PARENT_ID_PART})-(${FLAGS_PART})(-.*)?\\s?$`
);
const match = TRACE_PARENT_REGEX.exec(value);
return (match) ? match[2] : null;
}

const clientUUID = Math.round(Math.random() * 10000);
var lastRequestID = 0;

$(".uuid").html(`Your web client's id: <strong>${clientUUID}</strong>`);

$(".hotrod-button").click(function(evt) {
lastRequestID++;
var requestID = clientUUID + "-" + lastRequestID;
var freshCar = $($("#hotrod-log").prepend('<div class="fresh-car"><em>Dispatching a car...[req: '+requestID+']</em></div>').children()[0]);
var customer = evt.target.dataset.customer;
var headers = {
const requestID = clientUUID + "-" + lastRequestID;
const freshCar = $($("#hotrod-log").prepend('<div class="fresh-car"><em>Dispatching a car...[req: '+requestID+']</em></div>').children()[0]);
const customer = evt.target.dataset.customer;
const headers = {
'baggage': 'session=' + clientUUID + ', request=' + requestID
};
console.log(headers);
var before = Date.now();
console.log('sending headers', headers);

// Use current URI as basepath for ajax requests
var pathPrefix = window.location.pathname;
pathPrefix = pathPrefix != "/" ? pathPrefix : '';

// TODO this should be done on page load, not on every button click
var config = {};
$.ajax(pathPrefix + '/config?nonse=' + Math.random(), {
method: 'GET',
success: function(data, textStatus) {
var after = Date.now();
console.log(data);
console.log('config', data);
config = data;
},
});

const before = Date.now();
$.ajax(pathPrefix + '/dispatch?customer=' + customer + '&nonse=' + Math.random(), {
headers: headers,
method: 'GET',
success: function(data, textStatus) {
var after = Date.now();
console.log(data);
var duration = formatDuration(data.ETA);
success: function(data, textStatus, xhr) {
const after = Date.now();
const traceResponse = xhr.getResponseHeader('traceresponse');
const traceID = parseTraceResponse(traceResponse);
console.log('response', data);
console.log('traceResponse', traceResponse, 'traceID', traceID);

const duration = formatDuration(data.ETA);
var traceLink = '';
if (config && config['jaeger']) {
var jaeger = config['jaeger'];
var trace = `${jaeger}/search?limit=20&lookback=1h&service=frontend&tags=%7B%22driver%22%3A%22${data.Driver}%22%7D`;
traceLink = ` [<a href="${trace}" target="_blank">find trace</a>]`;
const jaeger = config['jaeger'];
const findURL = `/search?limit=20&lookback=1h&service=frontend&tags=%7B%22driver%22%3A%22${data.Driver}%22%7D`;
traceLink = ` [<a href="${jaeger}${findURL}" target="_blank">find trace</a>]`;
if (traceID) {
traceLink += ` [<a href="${jaeger}/trace/${traceID}" target="_blank">open trace</a>]`;
}
}
freshCar.html(`HotROD <b>${data.Driver}</b> arriving in ${duration} [req: ${requestID}, latency: ${after-before}ms] ${traceLink}`);
},
Expand Down

0 comments on commit 9184b90

Please sign in to comment.