From 93b118a2b009b93b4e1e1cc6a69701b2f6dd122b Mon Sep 17 00:00:00 2001 From: SimunKaracic Date: Wed, 21 Oct 2020 17:15:20 +0200 Subject: [PATCH] WIP MORE WIP WIP WIP --- .../ArmeriaHttpServiceInstrumentation.java | 94 ++-- .../ArmeriaTopoServiceHttp.scala | 27 ++ .../src/main/resources/reference.conf | 2 +- .../ArmeriaHttpServerInstrumentation.scala | 6 +- .../ArmeriaHttpServerResponseHandler.scala | 1 + .../src/test/resources/application.conf | 20 +- .../client/ArmeriaHttpClientTracingSpec.scala | 456 +++++++++--------- .../server/ArmeriaHttpServerTracingSpec.scala | 150 +++--- .../scala/utils/ArmeriaServerSupport.scala | 1 + .../test/scala/utils/TestRoutesSupport.scala | 3 + .../http/HttpServerInstrumentation.scala | 3 + 11 files changed, 382 insertions(+), 381 deletions(-) create mode 100644 instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaTopoServiceHttp.scala diff --git a/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaHttpServiceInstrumentation.java b/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaHttpServiceInstrumentation.java index 570a7534c..7fec09a27 100644 --- a/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaHttpServiceInstrumentation.java +++ b/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaHttpServiceInstrumentation.java @@ -1,82 +1,62 @@ package kamon.armeria.instrumentation; import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.server.RequestConverter; import com.linecorp.armeria.server.ServiceRequestContext; import io.netty.util.AttributeKey; import kamon.Kamon; import kamon.context.Storage; +import kamon.instrumentation.http.HttpMessage; +import kamon.instrumentation.http.HttpServerInstrumentation; import kamon.trace.Span; import kamon.trace.SpanPropagation.B3; import kanela.agent.libs.net.bytebuddy.asm.Advice; +import scala.Int; +import scala.Option; -// why is this good? -// i mean, the serve method could still spawn some shit off -// and go to another thread? -// ok, so this is about context propagation, right? -// when I store the context, how do other threads know about that? -// how does it FORWARD THAT INFORMATION to other threads? -// the information that yeah, there's a current trace in progress -// and you're a part of it +import java.util.Map; public class ArmeriaHttpServiceInstrumentation { @Advice.OnMethodEnter() - private static void enter( - @Advice.Local("scope") Storage.Scope scope, - @Advice.Local("span") Span span, - @Advice.This Object service, + private static Storage.Scope enter( @Advice.Argument(0) ServiceRequestContext context, @Advice.Argument(1) HttpRequest request) { - // what can I do with the context? - // What can i extract out of it? + // make sure that this _is_ truly available here + long startTime = context.log().partial().requestStartTimeNanos(); - // I add a header here with the traceID right? - // I mean not here, but at the beginning of the pipeline - // is toString needed here? - // the the elastic-armeria shit without is too -// context.log().context().setAttr(AttributeKey.newInstance("Kamon.Context"), Kamon.currentContext()); -// System.out.println("Context:"); -// System.out.println("Context:"); -// System.out.println(context); -// System.out.println("Request"); -// System.out.println("Request"); -// System.out.println(request); -// System.out.println("Service??"); -// System.out.println("Service??"); -// System.out.println(service); -// System.out.println(service.getClass().getEnclosingClass().getSimpleName()); -// System.out.println("Context again"); -// System.out.println("Context again"); -// System.out.println(context.log().context()); + // figure out how to get the host and the port + // also, this handler will need to end this span at the end of the pipeline + // meaning, we need to propagate it there somehow + HttpServerInstrumentation httpServerInstrumentation = HttpServerInstrumentation.from(Kamon.config().getConfig("kamon.instrumentation.armeria.http-server"), + "armeria.http.server", "0.0.0.0", 1234); + HttpServerInstrumentation.RequestHandler handler = httpServerInstrumentation + .createHandler(RequestConverter.toRequest(request, "whatever", 1234), true); +// context.log().whenComplete().thenAccept(requestLog -> { +// // shit +// handler.responseSent(); +// // this takes a time +// // Kamon.Clock to convert it +// // but which time to use? +//// handler.span().finish(); +// }); + // add a decorator + // which has acces to this same context + // context.attr, modify response +// context.respo + context.setAttr(AttributeKey.newInstance("myHandler"), handler); - // this is wrong - // i should be starting a span here - // what tags do I add to it? - span = Kamon.serverSpanBuilder("some.smart.operation.name", "armeria???").start(); - // parentSpan? - // wrong context to store? - scope = Kamon.storeContext(Kamon.currentContext()); + // figure out a nice name + handler.span().name("CoolName").takeSamplingDecision(); + return Kamon.storeContext(handler.context()); } @Advice.OnMethodExit() - private static void exit(@Advice.Local("scope") Storage.Scope scope, - @Advice.Local("span") Span span, - @Advice.This Object service, - @Advice.Argument(0) ServiceRequestContext context, - @Advice.Argument(1) HttpRequest request) { - // how do i tell here if the thing finished correctly - // or if I should fail the span? - System.out.println("Context:"); - System.out.println("Context:"); - System.out.println(context); - System.out.println("Request"); - System.out.println("Request"); - System.out.println(request); - System.out.println("Service??"); - System.out.println("Service??"); - System.out.println(service); - System.out.println(service.getClass().getEnclosingClass().getSimpleName()); - span.finish(); + // Advice.Enter will bind the return from the enter to the exit :O + // :O :O :O :O :O + private static void exit(@Advice.Enter Storage.Scope scope) { scope.close(); } } + + diff --git a/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaTopoServiceHttp.scala b/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaTopoServiceHttp.scala new file mode 100644 index 000000000..3ff9c692a --- /dev/null +++ b/instrumentation/kamon-armeria/src/main/java/kamon/armeria/instrumentation/ArmeriaTopoServiceHttp.scala @@ -0,0 +1,27 @@ +package com.linecorp.armeria.server + +import com.linecorp.armeria.common.HttpRequest +import kamon.instrumentation.http.HttpMessage + +import scala.collection.JavaConverters.iterableAsScalaIterableConverter + +object RequestConverter { + def toRequest(request: HttpRequest, serverHost: String, serverPort: Int): HttpMessage.Request = new HttpMessage.Request { + + override def url: String = request.uri().toString + + override def path: String = request.path() + + override def method: String = request.method().name() + + override def host: String = serverHost + + override def port: Int = serverPort + + override def read(header: String): Option[String] = + Option(request.headers().get(header)) + + override def readAll(): Map[String, String] = + request.headers().asScala.map(e => e.getKey.toString() -> e.getValue).toMap + } +} diff --git a/instrumentation/kamon-armeria/src/main/resources/reference.conf b/instrumentation/kamon-armeria/src/main/resources/reference.conf index 06ea9dd1f..75b0c7286 100644 --- a/instrumentation/kamon-armeria/src/main/resources/reference.conf +++ b/instrumentation/kamon-armeria/src/main/resources/reference.conf @@ -134,7 +134,7 @@ kamon.instrumentation.armeria { # - default: Uses the set default operation name # - method: Uses the request HTTP method as the operation name. # - name-generator = "kamon.armeria.instrumentation.server.KamonArmeriaOperationNameGenerator" + # name-generator = "kamon.armeria.instrumentation.server.KamonArmeriaOperationNameGenerator" # Provides custom mappings from HTTP paths into operation names. Meant to be used in cases where the bytecode # instrumentation is not able to provide a sensible operation name that is free of high cardinality values. diff --git a/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerInstrumentation.scala b/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerInstrumentation.scala index e8789dc4e..3d8ceab9d 100644 --- a/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerInstrumentation.scala +++ b/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerInstrumentation.scala @@ -30,9 +30,9 @@ class ArmeriaHttpServerInstrumentation extends InstrumentationBuilder { // onSubTypesOf("io.netty.channel.Channel") // .mixin(classOf[HasRequestProcessingContextMixin]) // -// onType("com.linecorp.armeria.server.HttpServerPipelineConfigurator") -// .bridge(classOf[HttpPipelineConfiguratorInternalState]) -// .advise(method("configureHttp"), classOf[ConfigureMethodAdvisor]) + onType("com.linecorp.armeria.server.HttpServerPipelineConfigurator") + .bridge(classOf[HttpPipelineConfiguratorInternalState]) + .advise(method("configureHttp"), classOf[ConfigureMethodAdvisor]) // // onType("com.linecorp.armeria.server.FallbackService") // .advise(method("serve"), classOf[ServeMethodAdvisor]) diff --git a/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/handlers/ArmeriaHttpServerResponseHandler.scala b/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/handlers/ArmeriaHttpServerResponseHandler.scala index 4b5704d22..479c7b4e3 100644 --- a/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/handlers/ArmeriaHttpServerResponseHandler.scala +++ b/instrumentation/kamon-armeria/src/main/scala/kamon/armeria/instrumentation/server/handlers/ArmeriaHttpServerResponseHandler.scala @@ -28,6 +28,7 @@ final class ArmeriaHttpServerResponseHandler(serverInstrumentation: HttpServerIn else { val response = msg.asInstanceOf[HttpResponse] val processingContext = ctx.channel().asInstanceOf[HasRequestProcessingContext].getRequestProcessingContext + response. /** * processingContext.requestHandler.span.operationName() will be empty if an HttpStatusException is thrown diff --git a/instrumentation/kamon-armeria/src/test/resources/application.conf b/instrumentation/kamon-armeria/src/test/resources/application.conf index f6b889f36..71531befe 100644 --- a/instrumentation/kamon-armeria/src/test/resources/application.conf +++ b/instrumentation/kamon-armeria/src/test/resources/application.conf @@ -134,7 +134,7 @@ kamon.instrumentation.armeria { # - default: Uses the set default operation name # - method: Uses the request HTTP method as the operation name. # - name-generator = "kamon.armeria.instrumentation.server.KamonArmeriaOperationNameGenerator" + # name-generator = "kamon.armeria.instrumentation.server.KamonArmeriaOperationNameGenerator" # Provides custom mappings from HTTP paths into operation names. Meant to be used in cases where the bytecode # instrumentation is not able to provide a sensible operation name that is free of high cardinality values. @@ -153,7 +153,7 @@ kamon.instrumentation.armeria { # The patterns are expressed as globs and the operation names are free form. # mappings { - "/dummy-resources/*/other-resources/*" = "dummy-resources/{}/other-resources/{}" + # "/dummy-resources/*/other-resources/*" = "dummy-resources/{}/other-resources/{}" } } } @@ -232,24 +232,10 @@ kamon.instrumentation.armeria { # - hostname: Uses the request Host as the operation name. # - method: Uses the request HTTP method as the operation name. # - name-generator = "kamon.armeria.instrumentation.client.KamonArmeriaOperationNameGenerator" + # name-generator = "kamon.armeria.instrumentation.client.KamonArmeriaOperationNameGenerator" } } } } -kanela { - show-banner = false - modules { - armeria { - name = "Armeria instrumentation" - stoppable = true - instrumentations = [ - "kamon.armeria.instrumentation.server.ArmeriaHttpServerInstrumentation", - "kamon.armeria.instrumentation.client.ArmeriaHttpClientInstrumentation" - ] - within = [ "io.netty..*", "com.linecorp.armeria..*"] - } - } -} diff --git a/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/client/ArmeriaHttpClientTracingSpec.scala b/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/client/ArmeriaHttpClientTracingSpec.scala index 3a2f01c28..6b08550af 100644 --- a/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/client/ArmeriaHttpClientTracingSpec.scala +++ b/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/client/ArmeriaHttpClientTracingSpec.scala @@ -1,228 +1,228 @@ -package kamon.armeria.instrumentation.client - -import com.linecorp.armeria.client.{ClientFactory, Clients, WebClient} -import com.linecorp.armeria.common.{HttpMethod, HttpRequest, RequestHeaders} -import kamon.Kamon -import kamon.context.Context -import kamon.tag.Lookups.{plain, plainBoolean, plainLong} -import kamon.testkit.TestSpanReporter -import kamon.trace.Span -import kamon.trace.SpanPropagation.B3 -import org.scalatest.concurrent.Eventually -import org.scalatest.time.SpanSugar.convertIntToGrainOfTime -import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec} -import utils.ArmeriaServerSupport.startArmeriaServer -import utils.TestSupport.getResponseHeaders - -class ArmeriaHttpClientTracingSpec extends WordSpec - with Matchers - with BeforeAndAfterAll - with Eventually - with TestSpanReporter - with OptionValues { - - val customTag = "requestId" - val customHeaderName = "X-Request-Id" - - val interface = "127.0.0.1" - val httpPort = 8081 - - val httpServer = startArmeriaServer(httpPort) - - "The Armeria http client tracing instrumentation" should { - - "propagate the current context and generate a span around an async request" in { - val path = "/dummy" - val url = s"http://$interface:$httpPort" - - val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() - val client = Clients.builder(url).build(classOf[WebClient]) - val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) - - val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { - client.execute(request).aggregate().get() - } - - val span = eventually(timeout(3 seconds)) { - val span = testSpanReporter().nextSpan().value - span.operationName shouldBe s"$interface.dummy.get" - span.kind shouldBe Span.Kind.Client - span.metricTags.get(plain("component")) shouldBe "armeria-http-client" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainLong("http.status_code")) shouldBe 200 - span.metricTags.get(plainBoolean("error")) shouldBe false - span.tags.get(plain("http.url")) shouldBe s"$url$path" - okSpan.id shouldBe span.parentId - - testSpanReporter().nextSpan() shouldBe None - - span - } - - val responseHeaders = getResponseHeaders(response) - - responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) - responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) - responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) - responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") - - - } - - "propagate context tags" in { - val path = "/dummy" - val url = s"http://$interface:$httpPort" - - val okSpan = Kamon.spanBuilder("ok-span-with-extra-tags").start() - val client = Clients.builder(url).build(classOf[WebClient]) - val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) - - val response = Kamon.runWithContext(Context.of(Span.Key, okSpan).withTag(customTag, "1234")) { - client.execute(request).aggregate().get() - } - - val span: Span.Finished = eventually(timeout(3 seconds)) { - val span = testSpanReporter().nextSpan().value - - span.operationName shouldBe s"$interface.dummy.get" - span.kind shouldBe Span.Kind.Client - span.metricTags.get(plain("component")) shouldBe "armeria-http-client" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainLong("http.status_code")) shouldBe 200 - span.metricTags.get(plainBoolean("error")) shouldBe false - span.tags.get(plain("http.url")) shouldBe s"$url$path" - span.tags.get(plain(customTag)) shouldBe "1234" - - okSpan.id == span.parentId - - testSpanReporter().nextSpan() shouldBe None - - span - } - - val responseHeaders = getResponseHeaders(response) - - responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) - responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) - responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) - responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") - responseHeaders.get(customHeaderName.toLowerCase).value should be("1234") - - } - - "mark span as failed when server response with 5xx on async execution" in { - val path = "/dummy-error" - val url = s"http://$interface:$httpPort" - - val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() - val client = Clients.builder(url).build(classOf[WebClient]) - val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) - - val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { - val response = client.execute(request) - response.aggregate().get() - } - - val span: Span.Finished = eventually(timeout(3 seconds)) { - val span = testSpanReporter().nextSpan().value - - span.operationName shouldBe s"$interface.dummy-error.get" - span.kind shouldBe Span.Kind.Client - span.metricTags.get(plain("component")) shouldBe "armeria-http-client" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainBoolean("error")) shouldBe true - span.metricTags.get(plainLong("http.status_code")) shouldBe 500 - span.tags.get(plain("http.url")) shouldBe s"$url$path" - - okSpan.id == span.parentId - - testSpanReporter().nextSpan() shouldBe None - - span - } - - val responseHeaders = getResponseHeaders(response) - - responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) - responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) - responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) - responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") - } - - "add timing marks to the generated span" in { - val path = "/dummy" - val url = s"http://$interface:$httpPort" - - val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() - /** - * For testing purpose we override client idle timeout property so when - * client.timing.Timing#takeTimings(RequestLog, Span) is executed this condition - * log.connectionTimings() isn't null - */ - val clientFactory = ClientFactory.builder().idleTimeoutMillis(1).build() - val webClient = Clients.builder(url).build(classOf[WebClient]) - val client = clientFactory.newClient(webClient).asInstanceOf[WebClient] - - val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) - - val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { - client.execute(request).aggregate().get() - } - - val span = eventually(timeout(3 seconds)) { - val span = testSpanReporter().nextSpan().value - - span.operationName shouldBe s"$interface.dummy.get" - span.kind shouldBe Span.Kind.Client - span.metricTags.get(plain("component")) shouldBe "armeria-http-client" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainLong("http.status_code")) shouldBe 200 - span.metricTags.get(plainBoolean("error")) shouldBe false - span.tags.get(plain("http.url")) shouldBe s"$url$path" - okSpan.id shouldBe span.parentId - - span.marks.map(_.key) should contain allOf( - "connection-acquire.start", - "connection-acquire.end", - "socket-connect.start", - "socket-connect.end" - ) - - testSpanReporter().nextSpan() shouldBe None - - span - } - - val responseHeaders = getResponseHeaders(response) - - responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) - responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) - responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) - responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") - - } - - } - - - override protected def beforeAll(): Unit = { - applyConfig( - s""" - |kamon { - | propagation.http.default.tags.mappings { - | $customTag = $customHeaderName - | } - | instrumentation.http-client.default.tracing.tags.from-context { - | $customTag = span - | } - |} - |""".stripMargin) - enableFastSpanFlushing() - sampleAlways() - } - - - override protected def afterAll(): Unit = { - httpServer.close() - } -} +//package kamon.armeria.instrumentation.client +// +//import com.linecorp.armeria.client.{ClientFactory, Clients, WebClient} +//import com.linecorp.armeria.common.{HttpMethod, HttpRequest, RequestHeaders} +//import kamon.Kamon +//import kamon.context.Context +//import kamon.tag.Lookups.{plain, plainBoolean, plainLong} +//import kamon.testkit.TestSpanReporter +//import kamon.trace.Span +//import kamon.trace.SpanPropagation.B3 +//import org.scalatest.concurrent.Eventually +//import org.scalatest.time.SpanSugar.convertIntToGrainOfTime +//import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, WordSpec} +//import utils.ArmeriaServerSupport.startArmeriaServer +//import utils.TestSupport.getResponseHeaders +// +//class ArmeriaHttpClientTracingSpec extends WordSpec +// with Matchers +// with BeforeAndAfterAll +// with Eventually +// with TestSpanReporter +// with OptionValues { +// +// val customTag = "requestId" +// val customHeaderName = "X-Request-Id" +// +// val interface = "127.0.0.1" +// val httpPort = 8081 +// +// val httpServer = startArmeriaServer(httpPort) +// +// "The Armeria http client tracing instrumentation" should { +// +// "propagate the current context and generate a span around an async request" in { +// val path = "/dummy" +// val url = s"http://$interface:$httpPort" +// +// val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() +// val client = Clients.builder(url).build(classOf[WebClient]) +// val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) +// +// val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { +// client.execute(request).aggregate().get() +// } +// +// val span = eventually(timeout(3 seconds)) { +// val span = testSpanReporter().nextSpan().value +// span.operationName shouldBe s"$interface.dummy.get" +// span.kind shouldBe Span.Kind.Client +// span.metricTags.get(plain("component")) shouldBe "armeria-http-client" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainLong("http.status_code")) shouldBe 200 +// span.metricTags.get(plainBoolean("error")) shouldBe false +// span.tags.get(plain("http.url")) shouldBe s"$url$path" +// okSpan.id shouldBe span.parentId +// +// testSpanReporter().nextSpan() shouldBe None +// +// span +// } +// +// val responseHeaders = getResponseHeaders(response) +// +// responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) +// responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) +// responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) +// responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") +// +// +// } +// +// "propagate context tags" in { +// val path = "/dummy" +// val url = s"http://$interface:$httpPort" +// +// val okSpan = Kamon.spanBuilder("ok-span-with-extra-tags").start() +// val client = Clients.builder(url).build(classOf[WebClient]) +// val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) +// +// val response = Kamon.runWithContext(Context.of(Span.Key, okSpan).withTag(customTag, "1234")) { +// client.execute(request).aggregate().get() +// } +// +// val span: Span.Finished = eventually(timeout(3 seconds)) { +// val span = testSpanReporter().nextSpan().value +// +// span.operationName shouldBe s"$interface.dummy.get" +// span.kind shouldBe Span.Kind.Client +// span.metricTags.get(plain("component")) shouldBe "armeria-http-client" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainLong("http.status_code")) shouldBe 200 +// span.metricTags.get(plainBoolean("error")) shouldBe false +// span.tags.get(plain("http.url")) shouldBe s"$url$path" +// span.tags.get(plain(customTag)) shouldBe "1234" +// +// okSpan.id == span.parentId +// +// testSpanReporter().nextSpan() shouldBe None +// +// span +// } +// +// val responseHeaders = getResponseHeaders(response) +// +// responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) +// responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) +// responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) +// responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") +// responseHeaders.get(customHeaderName.toLowerCase).value should be("1234") +// +// } +// +// "mark span as failed when server response with 5xx on async execution" in { +// val path = "/dummy-error" +// val url = s"http://$interface:$httpPort" +// +// val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() +// val client = Clients.builder(url).build(classOf[WebClient]) +// val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) +// +// val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { +// val response = client.execute(request) +// response.aggregate().get() +// } +// +// val span: Span.Finished = eventually(timeout(3 seconds)) { +// val span = testSpanReporter().nextSpan().value +// +// span.operationName shouldBe s"$interface.dummy-error.get" +// span.kind shouldBe Span.Kind.Client +// span.metricTags.get(plain("component")) shouldBe "armeria-http-client" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainBoolean("error")) shouldBe true +// span.metricTags.get(plainLong("http.status_code")) shouldBe 500 +// span.tags.get(plain("http.url")) shouldBe s"$url$path" +// +// okSpan.id == span.parentId +// +// testSpanReporter().nextSpan() shouldBe None +// +// span +// } +// +// val responseHeaders = getResponseHeaders(response) +// +// responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) +// responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) +// responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) +// responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") +// } +// +// "add timing marks to the generated span" in { +// val path = "/dummy" +// val url = s"http://$interface:$httpPort" +// +// val okSpan = Kamon.spanBuilder("ok-async-operation-span").start() +// /** +// * For testing purpose we override client idle timeout property so when +// * client.timing.Timing#takeTimings(RequestLog, Span) is executed this condition +// * log.connectionTimings() isn't null +// */ +// val clientFactory = ClientFactory.builder().idleTimeoutMillis(1).build() +// val webClient = Clients.builder(url).build(classOf[WebClient]) +// val client = clientFactory.newClient(webClient).asInstanceOf[WebClient] +// +// val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, path)) +// +// val response = Kamon.runWithContext(Context.of(Span.Key, okSpan)) { +// client.execute(request).aggregate().get() +// } +// +// val span = eventually(timeout(3 seconds)) { +// val span = testSpanReporter().nextSpan().value +// +// span.operationName shouldBe s"$interface.dummy.get" +// span.kind shouldBe Span.Kind.Client +// span.metricTags.get(plain("component")) shouldBe "armeria-http-client" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainLong("http.status_code")) shouldBe 200 +// span.metricTags.get(plainBoolean("error")) shouldBe false +// span.tags.get(plain("http.url")) shouldBe s"$url$path" +// okSpan.id shouldBe span.parentId +// +// span.marks.map(_.key) should contain allOf( +// "connection-acquire.start", +// "connection-acquire.end", +// "socket-connect.start", +// "socket-connect.end" +// ) +// +// testSpanReporter().nextSpan() shouldBe None +// +// span +// } +// +// val responseHeaders = getResponseHeaders(response) +// +// responseHeaders.get(B3.Headers.TraceIdentifier.toLowerCase).value should be(span.trace.id.string) +// responseHeaders.get(B3.Headers.SpanIdentifier.toLowerCase).value should be(span.id.string) +// responseHeaders.get(B3.Headers.ParentSpanIdentifier.toLowerCase).value should be(span.parentId.string) +// responseHeaders.get(B3.Headers.Sampled.toLowerCase).value should be("1") +// +// } +// +// } +// +// +// override protected def beforeAll(): Unit = { +// applyConfig( +// s""" +// |kamon { +// | propagation.http.default.tags.mappings { +// | $customTag = $customHeaderName +// | } +// | instrumentation.http-client.default.tracing.tags.from-context { +// | $customTag = span +// | } +// |} +// |""".stripMargin) +// enableFastSpanFlushing() +// sampleAlways() +// } +// +// +// override protected def afterAll(): Unit = { +// httpServer.close() +// } +//} diff --git a/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerTracingSpec.scala b/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerTracingSpec.scala index 5062da347..349a980ad 100644 --- a/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerTracingSpec.scala +++ b/instrumentation/kamon-armeria/src/test/scala/kamon/armeria/instrumentation/server/ArmeriaHttpServerTracingSpec.scala @@ -60,81 +60,81 @@ class ArmeriaHttpServerTracingSpec extends WordSpec } } - "set operation name with unhandled" when { - "request path doesn't exists" in { - val target = s"$protocol://$interface:$port/$dummyNotFoundPath" - val expected = "unhandled" - okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() - - eventually(timeout(10 seconds)) { - val span = testSpanReporter().nextSpan().value - println(span.operationName) - span.operationName shouldBe expected - span.tags.get(plain("http.url")) shouldBe target - span.metricTags.get(plain("component")) shouldBe "armeria-http-server" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainBoolean("error")) shouldBe false - span.metricTags.get(plainLong("http.status_code")) shouldBe 404 - } - } - } - - "set operation name with path + http method" when { - "resource doesn't exist" in { - val target = s"$protocol://$interface:$port/$dummyResourceNotFoundPath" - val expected = "dummy-resource-not-found.get" - okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() - - eventually(timeout(10 seconds)) { - val span = testSpanReporter().nextSpan().value - span.operationName shouldBe expected - span.tags.get(plain("http.url")) shouldBe target - span.metricTags.get(plain("component")) shouldBe "armeria-http-server" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainBoolean("error")) shouldBe false - span.metricTags.get(plainLong("http.status_code")) shouldBe 404 - } - } - } - - "not include path variables names" in { - val target = s"$protocol://$interface:$port/$dummyMultipleResourcesPath" - val expected = "dummy-resources/{}/other-resources/{}" - okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() - - eventually(timeout(10 seconds)) { - val span = testSpanReporter().nextSpan().value - span.operationName shouldBe expected - } - } - - "not fail when request url contains special regexp chars" in { - val target = s"$protocol://$interface:$port/$dummyMultipleResourcesPath**" - val expected = "dummy-resources/{}/other-resources/{}" - val response = okHttp.newCall(new Request.Builder().url(target).build()).execute() - - eventually(timeout(10 seconds)) { - val span = testSpanReporter().nextSpan().value - span.operationName shouldBe expected - response.code() shouldBe 200 - } - response.close() - } - - "mark spans as failed when request fails" in { - val target = s"$protocol://$interface:$port/$dummyErrorPath" - val expected = s"$dummyErrorPath.get" - okHttp.newCall(new Request.Builder().url(target).build()).execute().close() - - eventually(timeout(10 seconds)) { - val span = testSpanReporter().nextSpan().value - span.tags.get(plain("http.url")) shouldBe target - span.metricTags.get(plain("component")) shouldBe "armeria-http-server" - span.metricTags.get(plain("http.method")) shouldBe "GET" - span.metricTags.get(plainBoolean("error")) shouldBe true - span.metricTags.get(plainLong("http.status_code")) shouldBe 500 - } - } +// "set operation name with unhandled" when { +// "request path doesn't exists" in { +// val target = s"$protocol://$interface:$port/$dummyNotFoundPath" +// val expected = "unhandled" +// okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() +// +// eventually(timeout(10 seconds)) { +// val span = testSpanReporter().nextSpan().value +// println(span.operationName) +// span.operationName shouldBe expected +// span.tags.get(plain("http.url")) shouldBe target +// span.metricTags.get(plain("component")) shouldBe "armeria-http-server" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainBoolean("error")) shouldBe false +// span.metricTags.get(plainLong("http.status_code")) shouldBe 404 +// } +// } +// } +// +// "set operation name with path + http method" when { +// "resource doesn't exist" in { +// val target = s"$protocol://$interface:$port/$dummyResourceNotFoundPath" +// val expected = "dummy-resource-not-found.get" +// okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() +// +// eventually(timeout(10 seconds)) { +// val span = testSpanReporter().nextSpan().value +// span.operationName shouldBe expected +// span.tags.get(plain("http.url")) shouldBe target +// span.metricTags.get(plain("component")) shouldBe "armeria-http-server" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainBoolean("error")) shouldBe false +// span.metricTags.get(plainLong("http.status_code")) shouldBe 404 +// } +// } +// } +// +// "not include path variables names" in { +// val target = s"$protocol://$interface:$port/$dummyMultipleResourcesPath" +// val expected = "dummy-resources/{}/other-resources/{}" +// okHttp.newCall(new Request.Builder().url(target).get().build()).execute().close() +// +// eventually(timeout(10 seconds)) { +// val span = testSpanReporter().nextSpan().value +// span.operationName shouldBe expected +// } +// } +// +// "not fail when request url contains special regexp chars" in { +// val target = s"$protocol://$interface:$port/$dummyMultipleResourcesPath**" +// val expected = "dummy-resources/{}/other-resources/{}" +// val response = okHttp.newCall(new Request.Builder().url(target).build()).execute() +// +// eventually(timeout(10 seconds)) { +// val span = testSpanReporter().nextSpan().value +// span.operationName shouldBe expected +// response.code() shouldBe 200 +// } +// response.close() +// } +// +// "mark spans as failed when request fails" in { +// val target = s"$protocol://$interface:$port/$dummyErrorPath" +// val expected = s"$dummyErrorPath.get" +// okHttp.newCall(new Request.Builder().url(target).build()).execute().close() +// +// eventually(timeout(10 seconds)) { +// val span = testSpanReporter().nextSpan().value +// span.tags.get(plain("http.url")) shouldBe target +// span.metricTags.get(plain("component")) shouldBe "armeria-http-server" +// span.metricTags.get(plain("http.method")) shouldBe "GET" +// span.metricTags.get(plainBoolean("error")) shouldBe true +// span.metricTags.get(plainLong("http.status_code")) shouldBe 500 +// } +// } } } diff --git a/instrumentation/kamon-armeria/src/test/scala/utils/ArmeriaServerSupport.scala b/instrumentation/kamon-armeria/src/test/scala/utils/ArmeriaServerSupport.scala index b536001f7..62886c249 100644 --- a/instrumentation/kamon-armeria/src/test/scala/utils/ArmeriaServerSupport.scala +++ b/instrumentation/kamon-armeria/src/test/scala/utils/ArmeriaServerSupport.scala @@ -28,6 +28,7 @@ object ArmeriaServerSupport { .service("/health-check", HealthCheckService.of()) .annotatedService().build(TestRoutesSupport()) .http(InetSocketAddress.createUnresolved("localhost", port)) + .decorator() .build() server diff --git a/instrumentation/kamon-armeria/src/test/scala/utils/TestRoutesSupport.scala b/instrumentation/kamon-armeria/src/test/scala/utils/TestRoutesSupport.scala index e0493de73..61ab4cee0 100644 --- a/instrumentation/kamon-armeria/src/test/scala/utils/TestRoutesSupport.scala +++ b/instrumentation/kamon-armeria/src/test/scala/utils/TestRoutesSupport.scala @@ -17,10 +17,13 @@ package utils import com.linecorp.armeria.common.{HttpRequest, HttpResponse, HttpStatus, ResponseHeaders} import com.linecorp.armeria.server.annotation.{Get, Param} +import kamon.Kamon final class TestRoutesSupport { @Get("/dummy") def getDummy(req: HttpRequest): HttpResponse = { + println("Current span in test:") + println(Kamon.currentSpan().operationName()) val responseHeaders = ResponseHeaders.builder(HttpStatus.OK).add(req.headers()).build() HttpResponse.of(responseHeaders) } diff --git a/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerInstrumentation.scala b/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerInstrumentation.scala index 367317c1d..389ba4069 100644 --- a/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerInstrumentation.scala +++ b/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerInstrumentation.scala @@ -169,6 +169,9 @@ object HttpServerInstrumentation { * Signals that the entire response (headers and body) has been sent to the client and records its size, if * available. */ + // add a implementaion that takes a time + // with which to END the span + // the current ones just _end_ it def responseSent(sentBytes: Long): Unit }