I use AWS X-Ray SDK for Java to enable X-Ray tracing for my Spring Boot micro services.
With following snippet I am able to attach a custom SegmentListener
:
final AWSXRayRecorder recorder = AWSXRayRecorderBuilder
.standard()
.withPlugin(new EcsPlugin())
.withSegmentListener(new SLF4JSegmentListener())
.withSegmentListener(new MyHttpHeaderSegementListener())
.build();
AWSXRay.setGlobalRecorder(recorder);
In MyHttpHeaderSegementListener
I try to inject a X-Ray annotation based on an incoming HTTP request header (from the frontend):
public class MyHttpHeaderSegementListener implements SegmentListener {
// snippet source: https://stackoverflow.com/a/54349178/6489012
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public MyHttpHeaderSegementListener() {}
@Override
public void onBeginSegment(final Segment segment) {
final var httpContext = MyHttpHeaderSegementListener.getCurrentHttpRequest();
httpContext.ifPresent(context -> segment.putAnnotation("Origin", context.getHeader("Origin")));
}
}
The segment listener is triggered as expected onBeginSegment
segment but MyHttpHeaderSegementListener.getCurrentHttpRequest()
always returns an Optional.empty
.
Controller
) within a SegmentListener
?aws-xray-sdk-java
maybe even support a native way to do so?RequestContextHolder
always empty?(A bit off-topic but: 4. Is it even a good practice to set an annotation based on a HTTP header)
I have no answer for the 2. and 3. question but I found an answer for 1. question.
For incoming requests you need to add a Spring Filter to configure AWS X-Ray. As filters have access to the HTTP request I just wrapped my own filter around the com.amazonaws.xray.javax.servlet.AWSXRayServletFilter
of AWS:
public class XRayServletFilter extends AWSXRayServletFilter {
public XRayServletFilter(String fixedSegmentName) {
super(fixedSegmentName);
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
this.addHttpRequestToContext(request);
super.doFilter(request, response, chain);
}
private void addHttpRequestToContext(final ServletRequest request){
final Optional<HttpServletRequest> httpServletRequest = HttpRequestUtils.castToHttpRequest(request);
if (httpServletRequest.isPresent()) {
final ServletRequestAttributes requestAttributes = new ServletRequestAttributes(httpServletRequest.get());
RequestContextHolder.setRequestAttributes(requestAttributes);
}
}
}
Which uses a static class that I wrote:
public final class HttpRequestUtils {
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public static Optional<HttpServletRequest> castToHttpRequest(ServletRequest request) {
try {
return Optional.of((HttpServletRequest) request);
} catch (ClassCastException classCastException) {
return Optional.empty();
}
}
}
This custom filter basically sets the HTTP requests in the RequestContextHolder
. After that you can use it in your segment listeners:
public class MyHttpHeaderSegementListener implements SegmentListener {
public MyHttpHeaderSegementListener() {}
@Override
public void onBeginSegment(final Segment segment) {
final Optional<HttpServletRequest> request = HttpRequestUtils.getCurrentHttpRequest();
request.map(req -> req.getHeader("Origin")).ifPresent(origin -> segment.putAnnotation("client_origin", origin));;
}
}