
[![Maven Central][maven_central_img]][maven_central] [![Build][gh_action_build_img]][gh_action_build] [![Code Coverage][codecov_img]][codecov] [![License][license img]][license]
Wingtips is a distributed tracing solution for Java 7 and greater based on the Google Dapper paper.
There are a few modules associated with this project:
HttpClient.RequestTracingFilter from wingtips-servlet-api. If you prefer hands-on exploration rather than readmes, the sample applications provide concrete examples of using Wingtips that are simple, compact, and straightforward.
try-with-resources Statements Distributed tracing is a mechanism to track requests through a network of distributed systems in order to create transparency and shed light on the sometimes complex interactions and behavior of those systems. For example in a cloud-based microservice architecture a single request can touch dozens or hundreds of servers as it spreads out in a tree-like fashion. Without distributed tracing it would be very difficult to identify which part(s) of such a complex system were causing performance issues - either in general or for that request in particular.
Distributed tracing provides the capability for near-realtime monitoring or historical analysis of interactions between servers given the necessary tools to collect and interpret the traces. It allows you to easily see where applications are spending their time - e.g. is it mostly in-application work, or is it mostly waiting for downstream network calls?
Distributed tracing can also be used in some cases for error debugging and problem investigations with some caveats. See the relevant section for more information on this topic.
GET_/some/endpoint for the overall span for a REST request, or downstream-POST_https://otherservice.com/other/endpoint for the span performing a downstream call to another service.See the Output and Logging section for an example of what a span looks like when it is logged.
NOTE: The following pseudo-code only applies to thread-per-request frameworks and scenarios. For asynchronous non-blocking scenarios see this section.
import com.nike.wingtips.Span;
import com.nike.wingtips.Tracer;
// ======As early in the request cycle as possible======
try {
// Determine if a parent span exists by inspecting the request (e.g. request headers)
Span parentSpan = extractParentSpanFromRequest(request);
// Start the overall request span (which becomes the "current span" for this thread unless/until a sub-span is created)
if (parentSpan == null)
Tracer.getInstance().startRequestWithRootSpan("newRequestSpanName");
else
Tracer.getInstance().startRequestWithChildSpan(parentSpan, "newRequestSpanName");
// It's recommended that you include the trace ID of the overall request span in the response headers
addTraceIdToResponseHeaders(response, Tracer.getInstance().getCurrentSpan());
// Execute the normal request logic
doRequestLogic();
}
finally {
// ======As late in the request/response cycle as possible======
Tracer.getInstance().completeRequestSpan(); // Completes the overall request span and logs it to SLF4J
}
In a typical usage scenario you'll want to call one of the Tracer.getInstance().startRequest...() methods as soon as possible when a request enters your application, and you'll want to call Tracer.getInstance().completeRequestSpan() as late as possible in the request/response cycle. In between these two calls the span that was started (the "overall-request-span") is considered the "current span" for this thread and can be retrieved if necessary by calling Tracer.getInstance().getCurrentSpan().
The extractParentSpanFromRequest() method is potentially different for different applications, however for HTTP-based frameworks the pattern is usually the same - look for and extract distributed-tracing-related information from the HTTP headers and use that information to create a parent span. There is a utility method that performs this work for you: HttpRequestTracingUtils.fromRequestWithHeaders(RequestWithHeaders, List<String>). You simply need to provide the HTTP request wrapped by an implementation of RequestWithHeaders and the list of user ID header keys for your application (if any) and it will do the rest using the standard distributed tracing header key constants found in TraceHeaders. See the javadocs for those classes and methods for more information and usage instructions.
NOTE: Given the thread-local nature of this library you'll want to make sure the span completion call is in a finally block or otherwise guaranteed to be called no matter what (even if the request fails with an error) to prevent problems when subsequent requests are processed on the same thread. The Tracer class does its best to recover from incorrect thread usage scenarios and log information about what happened but the best solution is to prevent the problems from occurring in the first place. See the section below on try-with-resources for some tips on foolproof ways to safely complete your spans.
If your application is running in a Servlet environment (e.g. Spring Boot w/ Spring MVC, raw Spring MVC, Jersey,
raw Servlets, etc) then
this entire lifecycle can be handled by a Servlet Filter. We've created one for you that's ready to drop in and go -
see the wingtips-servlet-api Wingtips plugin module library for details. That plugin
module is also a good resource to see how the code for a production-ready implementation of this library might look.
try-with-resources StatementsSpans support Java try-with-resources statements to help guarantee proper usage in blocking/non-asynchronous scenarios
(for asynchronous scenarios please refer to the asynchronous usage section of this readme). As
previously mentioned, Spans that are not properly completed can lead to incorrect distributed tracing information
showing up, and the try-with-resources statements guarantee that spans are completed appropriately. Here are some
examples (note: there are some important tradeoffs you should consider before using this
feature):
try-with-resourcestry(Span requestSpan = Tracer.getInstance().startRequestWith*(...)) {
// Traced blocking code for overall request (not asynchronous) goes here ...
}
// No finally block needed to properly complete the overall request span
try-with-resourcestry (Span subspan = Tracer.getInstance().startSubSpan(...)) {
// Traced blocking code for subspan (not asynchronous) goes here ...
}
// No finally block needed to properly complete the subspan
try-with-resources to autoclose spansThe try-with-resources feature t
$ claude mcp add wingtips \
-- python -m otcore.mcp_server <graph>