Skip to content

Commit

Permalink
Add kamon http4s (#1319)
Browse files Browse the repository at this point in the history
* Added kamon-http4s

* Compiling and almost working tests

* Final touches

* Cleanup

* Packaging

* Change approach to release 2 diffeent modules

* Cleanup

* Address flakiness

* Revert change

---------

Co-authored-by: irodotos <[email protected]>
  • Loading branch information
vaslabs and irodotos7 authored Mar 1, 2024
1 parent 1d57d57 commit 6ad4fcc
Show file tree
Hide file tree
Showing 23 changed files with 2,359 additions and 1 deletion.
48 changes: 47 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ val instrumentationProjects = Seq[ProjectReference](
`kamon-lagom`,
`kamon-finagle`,
`kamon-aws-sdk`,
`kamon-alpakka-kafka`
`kamon-alpakka-kafka`,
`kamon-http4s-1_0`,
`kamon-http4s-0_23`
)

lazy val instrumentation = (project in file("instrumentation"))
Expand Down Expand Up @@ -757,6 +759,50 @@ lazy val `kamon-alpakka-kafka` = (project in file("instrumentation/kamon-alpakka
)
).dependsOn(`kamon-core`, `kamon-akka`, `kamon-testkit` % "test")

lazy val `kamon-http4s-1_0` = (project in file("instrumentation/kamon-http4s-1.0"))
.disablePlugins(AssemblyPlugin)
.enablePlugins(JavaAgent)
.settings(instrumentationSettings)
.settings(
name := "kamon-http4s-1.0",
crossScalaVersions := Seq(`scala_2.13_version`, `scala_3_version`),
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-client" % "1.0.0-M38" % Provided,
"org.http4s" %% "http4s-server" % "1.0.0-M38" % Provided,
"org.http4s" %% "http4s-blaze-client" % "1.0.0-M38" % Test,
"org.http4s" %% "http4s-blaze-server" % "1.0.0-M38" % Test,
"org.http4s" %% "http4s-dsl" % "1.0.0-M38" % Test,
scalatest % Test,
)
)
.dependsOn(
`kamon-core`,
`kamon-instrumentation-common`,
`kamon-testkit` % Test)

lazy val `kamon-http4s-0_23` = (project in file("instrumentation/kamon-http4s-0.23"))
.disablePlugins(AssemblyPlugin)
.enablePlugins(JavaAgent)
.settings(instrumentationSettings)
.settings(
name := "kamon-http4s-0.23",
scalacOptions ++= { if(scalaBinaryVersion.value == "2.12") Seq("-Ypartial-unification") else Seq.empty },
crossScalaVersions := Seq(`scala_2.12_version`, `scala_2.13_version`, `scala_3_version`),
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-client" % "0.23.19" % Provided,
"org.http4s" %% "http4s-server" % "0.23.19" % Provided,
"org.http4s" %% "http4s-blaze-client" % "0.23.14" % Test,
"org.http4s" %% "http4s-blaze-server" % "0.23.14" % Test,
"org.http4s" %% "http4s-dsl" % "0.23.19" % Test,
scalatest % Test
)
)
.dependsOn(
`kamon-core`,
`kamon-instrumentation-common`,
`kamon-testkit` % Test
)

/**
* Reporters
*/
Expand Down
254 changes: 254 additions & 0 deletions instrumentation/kamon-http4s-0.23/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
# ======================================= #
# Kamon-Http4s Reference Configuration #
# ======================================= #

kamon.instrumentation.http4s {

# Settings to control the HTTP Server instrumentation.
#
# IMPORTANT: Besides the "initial-operation-name" and "unhandled-operation-name" settings, the entire configuration of
# the HTTP Server Instrumentation is based on the constructs provided by the Kamon Instrumentation Common library
# which will always fallback to the settings found under the "kamon.instrumentation.http-server.default" path. The
# default settings have been included here to make them easy to find and understand in the context of this project and
# commented out so that any changes to the default settings will actually have effect.
#
server {

#
# Configuration for HTTP context propagation.
#
propagation {

# Enables or disables HTTP context propagation on this HTTP server instrumentation. Please note that if
# propagation is disabled then some distributed tracing features will not be work as expected (e.g. Spans can
# be created and reported but will not be linked across boundaries nor take trace identifiers from tags).
#enabled = yes

# HTTP propagation channel to b used by this instrumentation. Take a look at the kamon.propagation.http.default
# configuration for more details on how to configure the detault HTTP context propagation.
channel = "default"


}


#
# Configuration for HTTP server metrics collection.
#
metrics {

# Enables collection of HTTP server metrics. When enabled the following metrics will be collected, assuming
# that the instrumentation is fully compliant:
#
# - http.server.requets
# - http.server.request.active
# - http.server.request.size
# - http.server.response.size
# - http.server.connection.lifetime
# - http.server.connection.usage
# - http.server.connection.open
#
# All metrics have at least three tags: component, interface and port. Additionally, the http.server.requests
# metric will also have a status_code tag with the status code group (1xx, 2xx and so on).
#
#enabled = yes
}


#
# Configuration for HTTP request tracing.
#
tracing {

# Enables HTTP request tracing. When enabled the instrumentation will create Spans for incoming requests
# and finish them when the response is sent back to the clients.
#enabled = yes

# Select a context tag that provides a preferred trace identifier. The preferred trace identifier will be used
# only if all these conditions are met:
# - the context tag is present.
# - there is no parent Span on the incoming context (i.e. this is the first service on the trace).
# - the identifier is valid in accordance to the identity provider.
#preferred-trace-id-tag = "none"

# Enables collection of span metrics using the `span.processing-time` metric.
#span-metrics = on

# Select which tags should be included as span and span metric tags. The possible options are:
# - span: the tag is added as a Span tag (i.e. using span.tag(...))
# - metric: the tag is added a a Span metric tag (i.e. using span.tagMetric(...))
# - off: the tag is not used.
#
tags {

# Use the http.url tag.
#url = span

# Use the http.method tag.
#method = metric

# Use the http.status_code tag.
#status-code = metric

# Copy tags from the context into the Spans with the specified purpouse. For example, to copy a customer_type
# tag from the context into the HTTP Server Span created by the instrumentation, the following configuration
# should be added:
#
# from-context {
# customer_type = span
# }
#
from-context {

}
}

# Controls writing trace and span identifiers to HTTP response headers sent by the instrumented servers. The
# configuration can be set to either "none" to disable writing the identifiers on the response headers or to
# the header name to be used when writing the identifiers.
response-headers {

# HTTP response header name for the trace identifier, or "none" to disable it.
#trace-id = "trace-id"

# HTTP response header name for the server span identifier, or "none" to disable it.
#span-id = none
}

# Custom mappings between routes and operation names.
operations {

# The default operation name to be used when creating Spans to handle the HTTP server requests. In most
# cases it is not possible to define an operation name right at the moment of starting the HTTP server Span
# and in those cases, this operation name will be initially assigned to the Span. Instrumentation authors
# should do their best effort to provide a suitable operation name or make use of the "mappings" facilities.
default = "http.server.request"

# The operation name to be assigned when an application cannot find any route/endpoint/controller to handle
# a given request. Depending on the instrumented framework, it might be possible to apply this operation
# name automatically or not, check the frameworks' instrumentation docs for more details.
unhandled = "unhandled"

# FQCN for a HttpOperationNameGenerator implementation, or ony of the following shorthand forms:
# - default: Uses the set default operation name
# - method: Uses the request HTTP method as the operation name.
#
name-generator = "kamon.http4s.PathOperationNameGenerator"

# 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.
# For example, with the following configuration:
# mappings {
# "/organization/*/user/*/profile" = "/organization/:orgID/user/:userID/profile"
# "/events/*/rsvps" = "EventRSVPs"
# }
#
# Requests to "/organization/3651/user/39652/profile" and "/organization/22234/user/54543/profile" will have
# the same operation name "/organization/:orgID/user/:userID/profile".
#
# Similarly, requests to "/events/aaa-bb-ccc/rsvps" and "/events/1234/rsvps" will have the same operation
# name "EventRSVPs".
#
# The patterns are expressed as globs and the operation names are free form.
#
mappings {

}
}
}
}

# Settings to control the HTTP Client instrumentation
#
# IMPORTANT: The entire configuration of the HTTP Client Instrumentation is based on the constructs provided by the
# Kamon Instrumentation Common library which will always fallback to the settings found under the
# "kamon.instrumentation.http-client.default" path. The default settings have been included here to make them easy to
# find and understand in the context of this project and commented out so that any changes to the default settings
# will actually have effect.
#
client {

#
# Configuration for HTTP context propagation.
#
propagation {

# Enables or disables HTTP context propagation on this HTTP server instrumentation. Please note that if
# propagation is disabled then some distributed tracing features will not be work as expected (e.g. Spans can
# be created and reported but will not be linked across boundaries nor take trace identifiers from tags).
#enabled = yes

# HTTP propagation channel to be used by this instrumentation. Take a look at the kamon.propagation.http.default
# configuration for more details on how to configure the detault HTTP context propagation.
#channel = "default"
}

tracing {

# Enables HTTP request tracing. When enabled the instrumentation will create Spans for outgoing requests
# and finish them when the response is received from the server.
#enabled = yes

# Enables collection of span metrics using the `span.processing-time` metric.
#span-metrics = on

# Select which tags should be included as span and span metric tags. The possible options are:
# - span: the tag is added as a Span tag (i.e. using span.tag(...))
# - metric: the tag is added a a Span metric tag (i.e. using span.tagMetric(...))
# - off: the tag is not used.
#
tags {

# Use the http.url tag.
#url = span

# Use the http.method tag.
#method = metric

# Use the http.status_code tag.
#status-code = metric

# Copy tags from the context into the Spans with the specified purpouse. For example, to copy a customer_type
# tag from the context into the HTTP Server Span created by the instrumentation, the following configuration
# should be added:
#
# from-context {
# customer_type = span
# }
#
from-context {

}
}

operations {

# The default operation name to be used when creating Spans to handle the HTTP client requests. The HTTP
# Client instrumentation will always try to use the HTTP Operation Name Generator configured bellow to get
# a name, but if it fails to generate it then this name will be used.
#default = "http.client.request"

# FQCN for a HttpOperationNameGenerator implementation, or ony of the following shorthand forms:
# - hostname: Uses the request Host as the operation name.
# - method: Uses the request HTTP method as the operation name.
#
name-generator = "kamon.http4s.PathOperationNameGenerator"
}
}
}


}


kanela.modules {
http4s {
name = "Http4s Instrumentation"
description = "Provides context propagation, distributed tracing and HTTP client and server metrics for Http4s"

instrumentations = []

within = []
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* =========================================================================================
* Copyright © 2013-2018 the kamon project <http://kamon.io/>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
* =========================================================================================
*/

package kamon.http4s

import com.typesafe.config.Config
import kamon.util.DynamicAccess
import kamon.Kamon
import kamon.instrumentation.http.{HttpMessage, HttpOperationNameGenerator}

object Http4s {
@volatile var nameGenerator: HttpOperationNameGenerator =
nameGeneratorFromConfig(Kamon.config())

private def nameGeneratorFromConfig(
config: Config
): HttpOperationNameGenerator = {
val dynamic = new DynamicAccess(getClass.getClassLoader)
val nameGeneratorFQCN = config.getString(
"kamon.instrumentation.http4s.client.tracing.operations.name-generator"
)
dynamic
.createInstanceFor[HttpOperationNameGenerator](nameGeneratorFQCN, Nil)
}

Kamon.onReconfigure { newConfig =>
nameGenerator = nameGeneratorFromConfig(newConfig)
}
}

class DefaultNameGenerator extends HttpOperationNameGenerator {

import java.util.Locale

import scala.collection.concurrent.TrieMap

private val localCache = TrieMap.empty[String, String]
private val normalizePattern = """\$([^<]+)<[^>]+>""".r

override def name(request: HttpMessage.Request): Option[String] = {
Some(
localCache.getOrElseUpdate(
s"${request.method}${request.path}", {
// Convert paths of form GET /foo/bar/$paramname<regexp>/blah to foo.bar.paramname.blah.get
val p = normalizePattern
.replaceAllIn(request.path, "$1")
.replace('/', '.')
.dropWhile(_ == '.')
val normalisedPath = {
if (p.lastOption.exists(_ != '.')) s"$p."
else p
}
s"$normalisedPath${request.method.toLowerCase(Locale.ENGLISH)}"
}
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kamon.http4s

import kamon.instrumentation.http.{HttpMessage, HttpOperationNameGenerator}

class PathOperationNameGenerator extends HttpOperationNameGenerator {
override def name(request: HttpMessage.Request): Option[String] = Some(
request.path
)
}
Loading

0 comments on commit 6ad4fcc

Please sign in to comment.