From bf5c6569a5b364c750b524833fdc37e258c2b02b Mon Sep 17 00:00:00 2001 From: patsta32 Date: Wed, 6 Nov 2024 09:41:48 +0100 Subject: [PATCH] feat: update tracing / telemetry --- build.sbt | 2 +- ...on.java => MiddlewareInstrumentation.java} | 51 +++++------ .../Smithy4PlayInstrumentationModule.scala | 8 +- .../SmithyPlayRouterInstrumentation.java | 89 +++++++++++++++++++ .../routing/internal/Smithy4PlayRouter.scala | 31 +++---- .../internal/Smithy4PlayRouterHandler.scala | 32 +++++++ .../middleware/Smithy4PlayMiddleware.scala | 4 + 7 files changed, 163 insertions(+), 54 deletions(-) rename smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/{TestInstrumentation.java => MiddlewareInstrumentation.java} (60%) create mode 100644 smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/SmithyPlayRouterInstrumentation.java create mode 100644 smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouterHandler.scala diff --git a/build.sbt b/build.sbt index 811b9fe..4ea2a38 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbt.Keys.cleanFiles ThisBuild / scalaVersion := Dependencies.scalaVersion scalaVersion := Dependencies.scalaVersion -val releaseVersion = sys.env.getOrElse("TAG", "2.0.0-alpha.rc.4") +val releaseVersion = sys.env.getOrElse("TAG", "2.1.0-rc-1") addCommandAlias("packageSmithy4Play", "smithy4play/package") addCommandAlias("publishSmithy4Play", "smithy4play/publish;smithy4playInstrumentation/publish") addCommandAlias("publishLocalWithInstrumentation", "publishLocalSmithy4PlayInstrumentation;publishLocalSmithy4Play") diff --git a/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/TestInstrumentation.java b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/MiddlewareInstrumentation.java similarity index 60% rename from smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/TestInstrumentation.java rename to smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/MiddlewareInstrumentation.java index 09e480e..8972e8e 100644 --- a/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/TestInstrumentation.java +++ b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/MiddlewareInstrumentation.java @@ -19,28 +19,31 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -public class TestInstrumentation implements TypeInstrumentation { + +public class MiddlewareInstrumentation implements TypeInstrumentation { @Override public ElementMatcher classLoaderOptimization() { - return hasClassesNamed("de.innfactory.smithy4play.routing.TestClass"); + return hasClassesNamed("de.innfactory.smithy4play.routing.middleware.Smithy4PlayMiddleware"); } @Override public ElementMatcher typeMatcher() { - System.out.println("- - - TestInstrumentation typeMatcher - - -"); - return named("de.innfactory.smithy4play.routing.TestClass"); + System.out.println("- - - MiddlewareInstrumentation typeMatcher - - -"); + return extendsClass( + named("de.innfactory.smithy4play.routing.middleware.Smithy4PlayMiddleware") + ).or( + named("de.innfactory.smithy4play.routing.middleware.Smithy4PlayMiddleware") + ); } @Override public void transform(TypeTransformer transformer) { - System.out.println("- - - TestInstrumentation transform - - -"); - transformer.applyAdviceToMethod( - named("test") - .and(takesArgument(0, named("java.lang.String"))) - .and(returns(named("java.lang.String"))), + named("skipMiddleware") + .and(takesArgument(0, named("de.innfactory.smithy4play.routing.context.RoutingContext"))) + .and(returns(named("java.lang.Boolean"))), this.getClass().getName() + "$ApplyAdvice"); } @@ -49,48 +52,38 @@ public static class ApplyAdvice { @Advice.OnMethodEnter() public static void onEnter( - @Advice.Argument(0) String test, + @Advice.Argument(0) Boolean b, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - - //System.out.println("TestInstrumentation ApplyAdvice onEnter " + test); - // span.addEvent("ADVICE smithy4play " + test); + Context parentContext = currentContext(); - //System.out.println("TestInstrumentation ApplyAdvice shouldStart " + shouldStart); - Span mySpan = GlobalOpenTelemetry.get().getTracer("smithy4play").spanBuilder("test span").startSpan(); - //System.out.println("TestInstrumentation ApplyAdvice mySpan " + mySpan.getSpanContext().getSpanId()); + Span mySpan = GlobalOpenTelemetry.get().getTracer("smithy4play").spanBuilder("smithy4play.Middleware").startSpan(); context = mySpan.storeInContext(parentContext); Scope myScope = mySpan.makeCurrent(); - //System.out.println("TestInstrumentation ApplyAdvice should start"); scope = myScope; } @Advice.OnMethodExit(onThrowable = Throwable.class) public static void stopTraceOnResponse( - @Advice.This Object testClass, + @Advice.This Object currentClass, @Advice.Thrown Throwable throwable, - @Advice.Argument(0) String test, - @Advice.Return(readOnly = false) String testout, + @Advice.Argument(0) Boolean b, + @Advice.Return(readOnly = false) Boolean returnValue, @Advice.Local("otelContext") Context context, @Advice.Local("otelScope") Scope scope) { - System.out.println("TestInstrumentation ApplyAdvice onExit"); Span mySpan = currentSpan(); if (scope != null) { scope.close(); } - if(mySpan != null) { - mySpan.addEvent(testout); - mySpan.setAttribute("test", testout); - mySpan.updateName("TEST / " + testout); + if (mySpan != null) { + mySpan.addEvent("applyMiddleware: " + returnValue); + mySpan.setAttribute("class", currentClass.getClass().getName()); + mySpan.updateName("smithy4play.Middleware " + currentClass.getClass().getName()); mySpan.end(); } if (throwable != null) { throwable.printStackTrace(); } - if (context != null) { - // System.out.println("TestInstrumentation ApplyAdvice onExit update update Span name"); - // Span.fromContext(context).updateName(testout); - } } } } \ No newline at end of file diff --git a/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/Smithy4PlayInstrumentationModule.scala b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/Smithy4PlayInstrumentationModule.scala index bf5d3e7..3ed1a6d 100644 --- a/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/Smithy4PlayInstrumentationModule.scala +++ b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/Smithy4PlayInstrumentationModule.scala @@ -15,15 +15,17 @@ class Smithy4PlayInstrumentationModule extends InstrumentationModule("smithy4pla override def isIndyModule: Boolean = false override def isHelperClass(className: String): Boolean = - className.startsWith("io.opentelemetry.javaagent") || className.startsWith("de.innfactory.smithy4play.instrumentation") + className.startsWith("io.opentelemetry.javaagent") || className.startsWith("de.innfactory.smithy4play") override def getAdditionalHelperClassNames: util.List[String] = List( Smithy4PlaySingleton.getClass.getName, - classOf[TestInstrumentation].getName, + classOf[SmithyPlayRouterInstrumentation].getName, + classOf[MiddlewareInstrumentation].getName, ).asJava override def typeInstrumentations(): util.List[TypeInstrumentation] = asList( - new TestInstrumentation() + new MiddlewareInstrumentation(), + new SmithyPlayRouterInstrumentation(), ) } diff --git a/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/SmithyPlayRouterInstrumentation.java b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/SmithyPlayRouterInstrumentation.java new file mode 100644 index 0000000..05b3fbb --- /dev/null +++ b/smithy4play-instrumentation/src/main/scala/de/innfactory/smithy4play/instrumentation/SmithyPlayRouterInstrumentation.java @@ -0,0 +1,89 @@ +package de.innfactory.smithy4play.instrumentation; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentSpan; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.*; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SmithyPlayRouterInstrumentation implements TypeInstrumentation { + + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("de.innfactory.smithy4play.routing.internal.Smithy4PlayRouterHandler"); + } + + @Override + public ElementMatcher typeMatcher() { + // System.out.println("- - - SmithyPlayRouterInstrumentation typeMatcher - - -"); + return named("de.innfactory.smithy4play.routing.internal.Smithy4PlayRouterHandler"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("handleForInstrument"), + this.getClass().getName() + "$ApplyAdvice"); + //System.out.println("- - - SmithyPlayRouterInstrumentation transform - - -"); + } + + @SuppressWarnings("unused") + public static class ApplyAdvice { + + @Advice.OnMethodEnter() + public static void onEnter( + @Advice.Argument(0) java.lang.String path, + @Advice.Argument(1) java.lang.String method, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + //System.out.println("ApplyAdvice Start applyHandler"); + Context parentContext = currentContext(); + Span mySpan = GlobalOpenTelemetry.get().getTracer("smithy4play").spanBuilder("smithy4play.Smithy4PlayRouter").startSpan(); + context = mySpan.storeInContext(parentContext); + Scope myScope = mySpan.makeCurrent(); + scope = myScope; + } + + @Advice.OnMethodExit(onThrowable = Throwable.class) + public static void stopTraceOnResponse( + @Advice.This Object currentClass, + @Advice.Thrown Throwable throwable, + @Advice.Argument(0) java.lang.String path, + @Advice.Argument(1) java.lang.String method, + @Advice.Return(readOnly = false) java.lang.String returnValue, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + //System.out.println("ApplyAdvice End applyHandler"); + Span mySpan = currentSpan(); + if (scope != null) { + scope.close(); + } + if (mySpan != null) { + String routeName = path; + String methodName = method; + mySpan.setAttribute("class", currentClass.getClass().getName()); + mySpan.updateName(methodName + " " + routeName); + mySpan.setAttribute("http.request.method", methodName); + mySpan.setAttribute("http.request.url", routeName); + mySpan.end(); + } + if (throwable != null) { + throwable.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouter.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouter.scala index 1c65ec4..dc57265 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouter.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouter.scala @@ -1,16 +1,11 @@ package de.innfactory.smithy4play.routing.internal -import cats.data.{ EitherT, Kleisli } -import de.innfactory.smithy4play.{ logger, ContextRoute, RoutingResult } +import cats.data.{EitherT, Kleisli} +import de.innfactory.smithy4play.{ContextRoute, RoutingResult, logger} import de.innfactory.smithy4play.codecs.Codec import de.innfactory.smithy4play.routing.context.RoutingContextBase import de.innfactory.smithy4play.routing.middleware.Middleware -import de.innfactory.smithy4play.routing.internal.{ - deconstructPath, - getSmithy4sHttpMethod, - toSmithy4sHttpRequest, - toSmithy4sHttpUri -} +import de.innfactory.smithy4play.routing.internal.{deconstructPath, getSmithy4sHttpMethod, toSmithy4sHttpRequest, toSmithy4sHttpUri} import de.innfactory.smithy4play.telemetry.Telemetry import io.opentelemetry.api.trace.Span import io.opentelemetry.context.Scope @@ -21,7 +16,8 @@ import smithy4s.http.* import smithy4s.interopcats.monadThrowShim import smithy4s.kinds.FunctorAlgebra -import scala.concurrent.{ ExecutionContext, Future } +import scala.concurrent.{ExecutionContext, Future} + class Smithy4PlayRouter[Alg[_[_, _, _, _, _]]]( impl: FunctorAlgebra[Alg, ContextRoute], @@ -32,7 +28,7 @@ class Smithy4PlayRouter[Alg[_[_, _, _, _, _]]]( private val baseResponse = HttpResponse(200, Map.empty, Blob.empty) private val errorHeaders = List(smithy4s.http.errorTypeHeader) - + private val baseServerCodec: HttpUnaryServerCodecs.Builder[ContextRoute, RequestWrapped, Result] = HttpUnaryServerCodecs .builder[ContextRoute] @@ -75,19 +71,12 @@ class Smithy4PlayRouter[Alg[_[_, _, _, _, _]]]( ), addDecodedPathParams = (r, v) => r.copy(r.req, v) ) + + private val routerHandler = new Smithy4PlayRouterHandler(router) private val handler = new PartialFunction[RequestHeader, Request[RawBuffer] => RoutingResult[Result]] { - - override def isDefinedAt(x: RequestHeader): Boolean = { - val isdefined = router.isDefinedAt(x) - if (!isdefined) logger.debug(s"[${this.getClass.getName}] router is not defined at ${isdefined} ${x}") - isdefined - } - - override def apply(v1: RequestHeader): Request[RawBuffer] => RoutingResult[Result] = { request => - val ctx: RoutingContextBase = RoutingContextBase.fromRequest(request, service.hints, v1) - router.apply(v1)(RequestWrapped(request, Map.empty)).run(ctx) - } + override def isDefinedAt(x: RequestHeader): Boolean = routerHandler.isDefinedAtHandler(x) + override def apply(v1: RequestHeader): Request[RawBuffer] => RoutingResult[Result] = routerHandler.applyHandler(v1, service.hints) } def routes(): PartialFunction[RequestHeader, Request[RawBuffer] => RoutingResult[Result]] = handler diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouterHandler.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouterHandler.scala new file mode 100644 index 0000000..a85ce62 --- /dev/null +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/internal/Smithy4PlayRouterHandler.scala @@ -0,0 +1,32 @@ +package de.innfactory.smithy4play.routing.internal + +import cats.data.{EitherT, Kleisli} +import de.innfactory.smithy4play.{ContextRoute, RoutingResult, logger} +import de.innfactory.smithy4play.codecs.Codec +import de.innfactory.smithy4play.routing.context.RoutingContextBase +import de.innfactory.smithy4play.routing.middleware.Middleware +import de.innfactory.smithy4play.routing.internal.{deconstructPath, getSmithy4sHttpMethod, toSmithy4sHttpRequest, toSmithy4sHttpUri} +import de.innfactory.smithy4play.telemetry.Telemetry +import play.api.mvc.* +import play.api.routing.Router.Routes +import smithy4s.* + +class Smithy4PlayRouterHandler(router: PartialFunction[RequestHeader, RequestWrapped => ContextRoute[Result]]) { + + def applyHandler(v1: RequestHeader, serviceHints: Hints): Request[RawBuffer] => RoutingResult[Result] = { request => + val ctx: RoutingContextBase = RoutingContextBase.fromRequest(request,serviceHints, v1) + handleForInstrument(v1.path, v1.method) + router.apply(v1)(RequestWrapped(request, Map.empty)).run(ctx) + } + + def handleForInstrument(path: String, method: String): String = { + logger.debug(s"[${this.getClass.getName}] handle route for ${method} ${path}") + "" + } + + def isDefinedAtHandler(v1: RequestHeader): Boolean = { + val isdefined = router.isDefinedAt(v1) + if (!isdefined) logger.debug(s"[${this.getClass.getName}] router is not defined at ${isdefined} ${v1}") + isdefined + } +} diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/middleware/Smithy4PlayMiddleware.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/middleware/Smithy4PlayMiddleware.scala index 5d1898a..e0eba4a 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/middleware/Smithy4PlayMiddleware.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/routing/middleware/Smithy4PlayMiddleware.scala @@ -13,6 +13,8 @@ abstract class Smithy4PlayMiddleware { def skipMiddleware(r: RoutingContext): Boolean = false + def middlewareSkippingTelemetry(b: Boolean): Boolean = b + def logic( r: RoutingContext, next: RoutingContext => RoutingResult[Result] @@ -25,9 +27,11 @@ abstract class Smithy4PlayMiddleware { f: RoutingContext => RoutingResult[Result] )(implicit ec: ExecutionContext): RoutingResult[Result] = if (skipMiddleware(r)) { + middlewareSkippingTelemetry(true) logger.debug(s"[${className}] skipping middleware") f(r) } else { + middlewareSkippingTelemetry(false) logger.debug(s"[${className}] applying middleware") logic(r, f) }