/dd-java-agent/instrumentation/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/AkkaHttpServerInstrumentation.java

https://github.com/DataDog/dd-trace-java · Java · 204 lines · 177 code · 21 blank · 6 comment · 4 complexity · 8572840cb58735e306c62da81af4ef99 MD5 · raw file

  1. package datadog.trace.instrumentation.akkahttp;
  2. import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
  3. import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeScope;
  4. import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.propagate;
  5. import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
  6. import static datadog.trace.instrumentation.akkahttp.AkkaHttpServerDecorator.AKKA_REQUEST;
  7. import static datadog.trace.instrumentation.akkahttp.AkkaHttpServerDecorator.DECORATE;
  8. import static datadog.trace.instrumentation.akkahttp.AkkaHttpServerHeaders.GETTER;
  9. import static net.bytebuddy.matcher.ElementMatchers.named;
  10. import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
  11. import akka.http.scaladsl.model.HttpRequest;
  12. import akka.http.scaladsl.model.HttpResponse;
  13. import akka.stream.Materializer;
  14. import com.google.auto.service.AutoService;
  15. import datadog.trace.agent.tooling.Instrumenter;
  16. import datadog.trace.bootstrap.instrumentation.api.AgentScope;
  17. import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
  18. import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags;
  19. import datadog.trace.bootstrap.instrumentation.api.Tags;
  20. import datadog.trace.context.TraceScope;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. import lombok.extern.slf4j.Slf4j;
  24. import net.bytebuddy.asm.Advice;
  25. import net.bytebuddy.description.method.MethodDescription;
  26. import net.bytebuddy.description.type.TypeDescription;
  27. import net.bytebuddy.matcher.ElementMatcher;
  28. import scala.Function1;
  29. import scala.concurrent.ExecutionContext;
  30. import scala.concurrent.Future;
  31. import scala.runtime.AbstractFunction1;
  32. @Slf4j
  33. @AutoService(Instrumenter.class)
  34. public final class AkkaHttpServerInstrumentation extends Instrumenter.Default {
  35. public AkkaHttpServerInstrumentation() {
  36. super("akka-http", "akka-http-server");
  37. }
  38. @Override
  39. public ElementMatcher<TypeDescription> typeMatcher() {
  40. return named("akka.http.scaladsl.HttpExt");
  41. }
  42. @Override
  43. public String[] helperClassNames() {
  44. return new String[] {
  45. AkkaHttpServerInstrumentation.class.getName() + "$DatadogWrapperHelper",
  46. AkkaHttpServerInstrumentation.class.getName() + "$DatadogSyncWrapper",
  47. AkkaHttpServerInstrumentation.class.getName() + "$DatadogAsyncWrapper",
  48. AkkaHttpServerInstrumentation.class.getName() + "$DatadogAsyncWrapper$1",
  49. AkkaHttpServerInstrumentation.class.getName() + "$DatadogAsyncWrapper$2",
  50. packageName + ".AkkaHttpServerHeaders",
  51. packageName + ".AkkaHttpServerDecorator",
  52. packageName + ".UriAdapter",
  53. };
  54. }
  55. @Override
  56. public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
  57. // Instrumenting akka-streams bindAndHandle api was previously attempted.
  58. // This proved difficult as there was no clean way to close the async scope
  59. // in the graph logic after the user's request handler completes.
  60. //
  61. // Instead, we're instrumenting the bindAndHandle function helpers by
  62. // wrapping the scala functions with our own handlers.
  63. final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
  64. transformers.put(
  65. named("bindAndHandleSync").and(takesArgument(0, named("scala.Function1"))),
  66. AkkaHttpServerInstrumentation.class.getName() + "$AkkaHttpSyncAdvice");
  67. transformers.put(
  68. named("bindAndHandleAsync").and(takesArgument(0, named("scala.Function1"))),
  69. AkkaHttpServerInstrumentation.class.getName() + "$AkkaHttpAsyncAdvice");
  70. return transformers;
  71. }
  72. public static class AkkaHttpSyncAdvice {
  73. @Advice.OnMethodEnter(suppress = Throwable.class)
  74. public static void wrapHandler(
  75. @Advice.Argument(value = 0, readOnly = false)
  76. Function1<HttpRequest, HttpResponse> handler) {
  77. handler = new DatadogSyncWrapper(handler);
  78. }
  79. }
  80. public static class AkkaHttpAsyncAdvice {
  81. @Advice.OnMethodEnter(suppress = Throwable.class)
  82. public static void wrapHandler(
  83. @Advice.Argument(value = 0, readOnly = false)
  84. Function1<HttpRequest, Future<HttpResponse>> handler,
  85. @Advice.Argument(value = 7) final Materializer materializer) {
  86. handler = new DatadogAsyncWrapper(handler, materializer.executionContext());
  87. }
  88. }
  89. public static class DatadogWrapperHelper {
  90. public static AgentScope createSpan(final HttpRequest request) {
  91. final AgentSpan.Context extractedContext = propagate().extract(request, GETTER);
  92. final AgentSpan span = startSpan(AKKA_REQUEST, extractedContext);
  93. span.setTag(InstrumentationTags.DD_MEASURED, true);
  94. DECORATE.afterStart(span);
  95. DECORATE.onConnection(span, request);
  96. DECORATE.onRequest(span, request);
  97. final AgentScope scope = activateSpan(span);
  98. scope.setAsyncPropagation(true);
  99. return scope;
  100. }
  101. public static void finishSpan(final AgentSpan span, final HttpResponse response) {
  102. DECORATE.onResponse(span, response);
  103. DECORATE.beforeFinish(span);
  104. final TraceScope scope = activeScope();
  105. if (scope != null) {
  106. scope.setAsyncPropagation(false);
  107. }
  108. span.finish();
  109. }
  110. public static void finishSpan(final AgentSpan span, final Throwable t) {
  111. DECORATE.onError(span, t);
  112. span.setTag(Tags.HTTP_STATUS, 500);
  113. DECORATE.beforeFinish(span);
  114. final TraceScope scope = activeScope();
  115. if (scope != null) {
  116. scope.setAsyncPropagation(false);
  117. }
  118. span.finish();
  119. }
  120. }
  121. public static class DatadogSyncWrapper extends AbstractFunction1<HttpRequest, HttpResponse> {
  122. private final Function1<HttpRequest, HttpResponse> userHandler;
  123. public DatadogSyncWrapper(final Function1<HttpRequest, HttpResponse> userHandler) {
  124. this.userHandler = userHandler;
  125. }
  126. @Override
  127. public HttpResponse apply(final HttpRequest request) {
  128. final AgentScope scope = DatadogWrapperHelper.createSpan(request);
  129. try {
  130. final HttpResponse response = userHandler.apply(request);
  131. scope.close();
  132. DatadogWrapperHelper.finishSpan(scope.span(), response);
  133. return response;
  134. } catch (final Throwable t) {
  135. scope.close();
  136. DatadogWrapperHelper.finishSpan(scope.span(), t);
  137. throw t;
  138. }
  139. }
  140. }
  141. public static class DatadogAsyncWrapper
  142. extends AbstractFunction1<HttpRequest, Future<HttpResponse>> {
  143. private final Function1<HttpRequest, Future<HttpResponse>> userHandler;
  144. private final ExecutionContext executionContext;
  145. public DatadogAsyncWrapper(
  146. final Function1<HttpRequest, Future<HttpResponse>> userHandler,
  147. final ExecutionContext executionContext) {
  148. this.userHandler = userHandler;
  149. this.executionContext = executionContext;
  150. }
  151. @Override
  152. public Future<HttpResponse> apply(final HttpRequest request) {
  153. final AgentScope scope = DatadogWrapperHelper.createSpan(request);
  154. Future<HttpResponse> futureResponse = null;
  155. try {
  156. futureResponse = userHandler.apply(request);
  157. } catch (final Throwable t) {
  158. scope.close();
  159. DatadogWrapperHelper.finishSpan(scope.span(), t);
  160. throw t;
  161. }
  162. final Future<HttpResponse> wrapped =
  163. futureResponse.transform(
  164. new AbstractFunction1<HttpResponse, HttpResponse>() {
  165. @Override
  166. public HttpResponse apply(final HttpResponse response) {
  167. DatadogWrapperHelper.finishSpan(scope.span(), response);
  168. return response;
  169. }
  170. },
  171. new AbstractFunction1<Throwable, Throwable>() {
  172. @Override
  173. public Throwable apply(final Throwable t) {
  174. DatadogWrapperHelper.finishSpan(scope.span(), t);
  175. return t;
  176. }
  177. },
  178. executionContext);
  179. scope.close();
  180. return wrapped;
  181. }
  182. }
  183. }