diff --git a/instrumentation/kamon-akka-http/src/main/resources/reference.conf b/instrumentation/kamon-akka-http/src/main/resources/reference.conf index f4c6c54c9..bc1003e8f 100644 --- a/instrumentation/kamon-akka-http/src/main/resources/reference.conf +++ b/instrumentation/kamon-akka-http/src/main/resources/reference.conf @@ -50,6 +50,14 @@ kamon.instrumentation.akka.http { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # #enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + #use-context-tags = no } # diff --git a/instrumentation/kamon-akka-http/src/test/resources/application.conf b/instrumentation/kamon-akka-http/src/test/resources/application.conf index d66f4f055..67869f652 100644 --- a/instrumentation/kamon-akka-http/src/test/resources/application.conf +++ b/instrumentation/kamon-akka-http/src/test/resources/application.conf @@ -54,6 +54,14 @@ kamon.instrumentation.akka.http { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # #enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + #use-context-tags = no } diff --git a/instrumentation/kamon-instrumentation-common/src/main/resources/reference.conf b/instrumentation/kamon-instrumentation-common/src/main/resources/reference.conf index 06223ac48..f4e48f0ae 100644 --- a/instrumentation/kamon-instrumentation-common/src/main/resources/reference.conf +++ b/instrumentation/kamon-instrumentation-common/src/main/resources/reference.conf @@ -43,6 +43,14 @@ kamon { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + use-context-tags = no } # 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 8fd726016..1a0e39f76 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 @@ -27,6 +27,7 @@ import kamon.instrumentation.trace.SpanTagger.TagMode import kamon.instrumentation.tag.TagKeys import kamon.instrumentation.trace.SpanTagger import kamon.tag.Lookups.option +import kamon.tag.TagSet import kamon.trace.Span import kamon.trace.Trace.SamplingDecision import kamon.util.Filter @@ -152,6 +153,10 @@ object HttpServerInstrumentation { */ def span: Span + /** + * Tag set to propagate to metrics from context + * */ + def contextTagSet: TagSet /** * Signals that the entire request (headers and body) has been received. */ @@ -231,8 +236,11 @@ object HttpServerInstrumentation { incomingContext.withEntry(Span.Key, requestSpan) else incomingContext + val _contextTagSet: TagSet = + TagSet.from(settings.contextTagsForMetrics.collect{ case (k, TagMode.Metric) => handlerContext.getTag(option(k)).map(k -> _) }.flatten.toMap) + _metrics.foreach { httpServerMetrics => - httpServerMetrics.activeRequests.increment() + httpServerMetrics.activeRequests.withTags(_contextTagSet).increment() } new HttpServerInstrumentation.RequestHandler { @@ -242,10 +250,12 @@ object HttpServerInstrumentation { override def span: Span = requestSpan + override def contextTagSet: TagSet = _contextTagSet + override def requestReceived(receivedBytes: Long): RequestHandler = { if(receivedBytes >= 0) { _metrics.foreach { httpServerMetrics => - httpServerMetrics.requestSize.record(receivedBytes) + httpServerMetrics.requestSize.withTags(contextTagSet).record(receivedBytes) } } @@ -254,14 +264,14 @@ object HttpServerInstrumentation { override def buildResponse[HttpResponse](response: HttpMessage.ResponseBuilder[HttpResponse], context: Context): HttpResponse = { _metrics.foreach { httpServerMetrics => - httpServerMetrics.countCompletedRequest(response.statusCode) + httpServerMetrics.countCompletedRequest(response.statusCode, contextTagSet) } if(!span.isEmpty) { settings.traceIDResponseHeader.foreach(traceIDHeader => response.write(traceIDHeader, span.trace.id.string)) settings.spanIDResponseHeader.foreach(spanIDHeader => response.write(spanIDHeader, span.id.string)) settings.httpServerResponseHeaderGenerator.headers(handlerContext).foreach(header => response.write(header._1, header._2)) - + SpanTagger.tag(span, TagKeys.HttpStatusCode, response.statusCode, settings.statusCodeTagMode) val statusCode = response.statusCode @@ -275,10 +285,10 @@ object HttpServerInstrumentation { override def responseSent(sentBytes: Long): Unit = { _metrics.foreach { httpServerMetrics => - httpServerMetrics.activeRequests.decrement() + httpServerMetrics.activeRequests.withTags(contextTagSet).decrement() if(sentBytes >= 0) - httpServerMetrics.responseSize.record(sentBytes) + httpServerMetrics.responseSize.withTags(contextTagSet).record(sentBytes) } span.finish() @@ -336,7 +346,7 @@ object HttpServerInstrumentation { .foreach(tagValue => SpanTagger.tag(span, tagName, tagValue, mode)) } - + span.start() } } @@ -359,9 +369,11 @@ object HttpServerInstrumentation { unhandledOperationName: String, operationMappings: Map[Filter.Glob, String], operationNameGenerator: HttpOperationNameGenerator, - httpServerResponseHeaderGenerator:HttpServerResponseHeaderGenerator + httpServerResponseHeaderGenerator:HttpServerResponseHeaderGenerator, + tagMetricsFromContext: Boolean ) { val operationNameSettings = OperationNameSettings(defaultOperationName, operationMappings, operationNameGenerator) + def contextTagsForMetrics: Map[String, TagMode] = if (tagMetricsFromContext) contextTags else Map.empty } object Settings { @@ -375,6 +387,7 @@ object HttpServerInstrumentation { // HTTP Server metrics settings val enableServerMetrics = config.getBoolean("metrics.enabled") + val tagMetricsFromContext = config.getBoolean("metrics.use-context-tags") // Tracing settings val enableTracing = config.getBoolean("tracing.enabled") @@ -435,7 +448,8 @@ object HttpServerInstrumentation { unhandledOperationName, operationMappings, operationNameGenerator.get, - httpServerResponseHeaderGenerator.get + httpServerResponseHeaderGenerator.get, + tagMetricsFromContext ) } } diff --git a/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerMetrics.scala b/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerMetrics.scala index 2d1fc60e8..fba44e73d 100644 --- a/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerMetrics.scala +++ b/instrumentation/kamon-instrumentation-common/src/main/scala/kamon/instrumentation/http/HttpServerMetrics.scala @@ -84,17 +84,17 @@ object HttpServerMetrics { /** * Increments the appropriate response counter depending on the the status code. */ - def countCompletedRequest(statusCode: Int): Unit = { + def countCompletedRequest(statusCode: Int, contextTagSet: TagSet): Unit = { if(statusCode >= 200 && statusCode <= 299) - requestsSuccessful.increment() + requestsSuccessful.withTags(contextTagSet).increment() else if(statusCode >= 500 && statusCode <= 599) - requestsServerError.increment() + requestsServerError.withTags(contextTagSet).increment() else if(statusCode >= 400 && statusCode <= 499) - requestsClientError.increment() + requestsClientError.withTags(contextTagSet).increment() else if(statusCode >= 300 && statusCode <= 399) - requestsRedirection.increment() + requestsRedirection.withTags(contextTagSet).increment() else if(statusCode >= 100 && statusCode <= 199) - requestsInformational.increment() + requestsInformational.withTags(contextTagSet).increment() else { _logger.warn("Unknown HTTP status code {} found when recording HTTP server metrics", statusCode.toString) } diff --git a/instrumentation/kamon-instrumentation-common/src/test/resources/application.conf b/instrumentation/kamon-instrumentation-common/src/test/resources/application.conf index d92850637..b6c7c7c96 100644 --- a/instrumentation/kamon-instrumentation-common/src/test/resources/application.conf +++ b/instrumentation/kamon-instrumentation-common/src/test/resources/application.conf @@ -7,15 +7,20 @@ kamon { propagation.http.default { tags.mappings { "correlation-id" = "x-correlation-id" + "namespace" = "namespace" } } instrumentation { http-server { default { + metrics { + use-context-tags = yes + } tracing { preferred-trace-id-tag = "correlation-id" tags.from-context.peer = span + tags.from-context.namespace = metric response-headers { trace-id = "x-trace-id" span-id = "x-span-id" diff --git a/instrumentation/kamon-instrumentation-common/src/test/scala/kamon/instrumentation/http/HttpServerInstrumentationSpec.scala b/instrumentation/kamon-instrumentation-common/src/test/scala/kamon/instrumentation/http/HttpServerInstrumentationSpec.scala index 9f6ee5f32..020d7be1a 100644 --- a/instrumentation/kamon-instrumentation-common/src/test/scala/kamon/instrumentation/http/HttpServerInstrumentationSpec.scala +++ b/instrumentation/kamon-instrumentation-common/src/test/scala/kamon/instrumentation/http/HttpServerInstrumentationSpec.scala @@ -4,6 +4,7 @@ import java.time.Duration import com.typesafe.config.ConfigFactory import kamon.Kamon import kamon.context.Context +import kamon.instrumentation.trace.SpanTagger.TagMode import kamon.tag.Lookups._ import kamon.metric.{Counter, Histogram, RangeSampler, Timer} import kamon.testkit.{InitAndStopKamonAfterAll, InstrumentInspection, SpanInspection} @@ -17,7 +18,7 @@ import org.scalatest.wordspec.AnyWordSpec import scala.collection.mutable class HttpServerInstrumentationSpec extends AnyWordSpec with Matchers with InstrumentInspection.Syntax with OptionValues - with SpanInspection.Syntax with Eventually with InitAndStopKamonAfterAll { + with SpanInspection.Syntax with Eventually with InitAndStopKamonAfterAll { "the HTTP server instrumentation" when { "configured for context propagation" should { @@ -174,6 +175,21 @@ class HttpServerInstrumentationSpec extends AnyWordSpec with Matchers with Instr completedRequests(8081, 400).value() shouldBe 1L completedRequests(8081, 500).value() shouldBe 1L } + + "populate context tags" in { + activeRequests(8081).distribution() + + val handlerOne = httpServer().createHandler(fakeRequest("http://localhost:8080/", "/", "GET", Map("namespace" -> "env1"))) + val handlerTwo = httpServer().createHandler(fakeRequest("http://localhost:8080/", "/", "GET", Map("namespace" -> "env1"))) + + handlerOne.contextTagSet.get(option("namespace")) shouldBe Some("env1") + handlerTwo.contextTagSet.get(option("namespace")) shouldBe Some("env1") + handlerOne.buildResponse(fakeResponse(200, mutable.Map.empty), Context.Empty) + handlerTwo.buildResponse(fakeResponse(200, mutable.Map.empty), Context.Empty) + handlerOne.responseSent(0L) + handlerTwo.responseSent(0L) + + } } "configured for distributed tracing" should { diff --git a/instrumentation/kamon-pekko-http/src/main/resources/reference.conf b/instrumentation/kamon-pekko-http/src/main/resources/reference.conf index b497c5428..87790694f 100644 --- a/instrumentation/kamon-pekko-http/src/main/resources/reference.conf +++ b/instrumentation/kamon-pekko-http/src/main/resources/reference.conf @@ -50,6 +50,14 @@ kamon.instrumentation.pekko.http { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # #enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + #use-context-tags = no } # diff --git a/instrumentation/kamon-pekko-http/src/test/resources/application.conf b/instrumentation/kamon-pekko-http/src/test/resources/application.conf index 087660f80..349ccf841 100644 --- a/instrumentation/kamon-pekko-http/src/test/resources/application.conf +++ b/instrumentation/kamon-pekko-http/src/test/resources/application.conf @@ -54,6 +54,13 @@ kamon.instrumentation.pekko.http { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # #enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + #use-context-tags = no } diff --git a/instrumentation/kamon-play/src/main/resources/reference.conf b/instrumentation/kamon-play/src/main/resources/reference.conf index 4b81d63ef..bf5d00eca 100644 --- a/instrumentation/kamon-play/src/main/resources/reference.conf +++ b/instrumentation/kamon-play/src/main/resources/reference.conf @@ -41,6 +41,14 @@ kamon.instrumentation.play.http { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # #enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + #use-context-tags = no } diff --git a/instrumentation/kamon-spring/src/main/resources/reference.conf b/instrumentation/kamon-spring/src/main/resources/reference.conf index 0ef0b479d..f831a3f8a 100644 --- a/instrumentation/kamon-spring/src/main/resources/reference.conf +++ b/instrumentation/kamon-spring/src/main/resources/reference.conf @@ -49,6 +49,14 @@ kamon.instrumentation.spring { # metric will also have a status_code tag with the status code group (1xx, 2xx and so on). # enabled = yes + # + # Metrics can also use tags from context; this will apply to the following subset of metrics: + # - http.server.request + # - http.server.request.active + # - http.server.request.size + # - http.server.response.size + # + use-context-tags = no } #