Skip to content

Commit 6ad4fcc

Browse files
vaslabsirodotos7
andauthored
Add kamon http4s (#1319)
* 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]>
1 parent 1d57d57 commit 6ad4fcc

File tree

23 files changed

+2359
-1
lines changed

23 files changed

+2359
-1
lines changed

build.sbt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ val instrumentationProjects = Seq[ProjectReference](
144144
`kamon-lagom`,
145145
`kamon-finagle`,
146146
`kamon-aws-sdk`,
147-
`kamon-alpakka-kafka`
147+
`kamon-alpakka-kafka`,
148+
`kamon-http4s-1_0`,
149+
`kamon-http4s-0_23`
148150
)
149151

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

762+
lazy val `kamon-http4s-1_0` = (project in file("instrumentation/kamon-http4s-1.0"))
763+
.disablePlugins(AssemblyPlugin)
764+
.enablePlugins(JavaAgent)
765+
.settings(instrumentationSettings)
766+
.settings(
767+
name := "kamon-http4s-1.0",
768+
crossScalaVersions := Seq(`scala_2.13_version`, `scala_3_version`),
769+
libraryDependencies ++= Seq(
770+
"org.http4s" %% "http4s-client" % "1.0.0-M38" % Provided,
771+
"org.http4s" %% "http4s-server" % "1.0.0-M38" % Provided,
772+
"org.http4s" %% "http4s-blaze-client" % "1.0.0-M38" % Test,
773+
"org.http4s" %% "http4s-blaze-server" % "1.0.0-M38" % Test,
774+
"org.http4s" %% "http4s-dsl" % "1.0.0-M38" % Test,
775+
scalatest % Test,
776+
)
777+
)
778+
.dependsOn(
779+
`kamon-core`,
780+
`kamon-instrumentation-common`,
781+
`kamon-testkit` % Test)
782+
783+
lazy val `kamon-http4s-0_23` = (project in file("instrumentation/kamon-http4s-0.23"))
784+
.disablePlugins(AssemblyPlugin)
785+
.enablePlugins(JavaAgent)
786+
.settings(instrumentationSettings)
787+
.settings(
788+
name := "kamon-http4s-0.23",
789+
scalacOptions ++= { if(scalaBinaryVersion.value == "2.12") Seq("-Ypartial-unification") else Seq.empty },
790+
crossScalaVersions := Seq(`scala_2.12_version`, `scala_2.13_version`, `scala_3_version`),
791+
libraryDependencies ++= Seq(
792+
"org.http4s" %% "http4s-client" % "0.23.19" % Provided,
793+
"org.http4s" %% "http4s-server" % "0.23.19" % Provided,
794+
"org.http4s" %% "http4s-blaze-client" % "0.23.14" % Test,
795+
"org.http4s" %% "http4s-blaze-server" % "0.23.14" % Test,
796+
"org.http4s" %% "http4s-dsl" % "0.23.19" % Test,
797+
scalatest % Test
798+
)
799+
)
800+
.dependsOn(
801+
`kamon-core`,
802+
`kamon-instrumentation-common`,
803+
`kamon-testkit` % Test
804+
)
805+
760806
/**
761807
* Reporters
762808
*/
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# ======================================= #
2+
# Kamon-Http4s Reference Configuration #
3+
# ======================================= #
4+
5+
kamon.instrumentation.http4s {
6+
7+
# Settings to control the HTTP Server instrumentation.
8+
#
9+
# IMPORTANT: Besides the "initial-operation-name" and "unhandled-operation-name" settings, the entire configuration of
10+
# the HTTP Server Instrumentation is based on the constructs provided by the Kamon Instrumentation Common library
11+
# which will always fallback to the settings found under the "kamon.instrumentation.http-server.default" path. The
12+
# default settings have been included here to make them easy to find and understand in the context of this project and
13+
# commented out so that any changes to the default settings will actually have effect.
14+
#
15+
server {
16+
17+
#
18+
# Configuration for HTTP context propagation.
19+
#
20+
propagation {
21+
22+
# Enables or disables HTTP context propagation on this HTTP server instrumentation. Please note that if
23+
# propagation is disabled then some distributed tracing features will not be work as expected (e.g. Spans can
24+
# be created and reported but will not be linked across boundaries nor take trace identifiers from tags).
25+
#enabled = yes
26+
27+
# HTTP propagation channel to b used by this instrumentation. Take a look at the kamon.propagation.http.default
28+
# configuration for more details on how to configure the detault HTTP context propagation.
29+
channel = "default"
30+
31+
32+
}
33+
34+
35+
#
36+
# Configuration for HTTP server metrics collection.
37+
#
38+
metrics {
39+
40+
# Enables collection of HTTP server metrics. When enabled the following metrics will be collected, assuming
41+
# that the instrumentation is fully compliant:
42+
#
43+
# - http.server.requets
44+
# - http.server.request.active
45+
# - http.server.request.size
46+
# - http.server.response.size
47+
# - http.server.connection.lifetime
48+
# - http.server.connection.usage
49+
# - http.server.connection.open
50+
#
51+
# All metrics have at least three tags: component, interface and port. Additionally, the http.server.requests
52+
# metric will also have a status_code tag with the status code group (1xx, 2xx and so on).
53+
#
54+
#enabled = yes
55+
}
56+
57+
58+
#
59+
# Configuration for HTTP request tracing.
60+
#
61+
tracing {
62+
63+
# Enables HTTP request tracing. When enabled the instrumentation will create Spans for incoming requests
64+
# and finish them when the response is sent back to the clients.
65+
#enabled = yes
66+
67+
# Select a context tag that provides a preferred trace identifier. The preferred trace identifier will be used
68+
# only if all these conditions are met:
69+
# - the context tag is present.
70+
# - there is no parent Span on the incoming context (i.e. this is the first service on the trace).
71+
# - the identifier is valid in accordance to the identity provider.
72+
#preferred-trace-id-tag = "none"
73+
74+
# Enables collection of span metrics using the `span.processing-time` metric.
75+
#span-metrics = on
76+
77+
# Select which tags should be included as span and span metric tags. The possible options are:
78+
# - span: the tag is added as a Span tag (i.e. using span.tag(...))
79+
# - metric: the tag is added a a Span metric tag (i.e. using span.tagMetric(...))
80+
# - off: the tag is not used.
81+
#
82+
tags {
83+
84+
# Use the http.url tag.
85+
#url = span
86+
87+
# Use the http.method tag.
88+
#method = metric
89+
90+
# Use the http.status_code tag.
91+
#status-code = metric
92+
93+
# Copy tags from the context into the Spans with the specified purpouse. For example, to copy a customer_type
94+
# tag from the context into the HTTP Server Span created by the instrumentation, the following configuration
95+
# should be added:
96+
#
97+
# from-context {
98+
# customer_type = span
99+
# }
100+
#
101+
from-context {
102+
103+
}
104+
}
105+
106+
# Controls writing trace and span identifiers to HTTP response headers sent by the instrumented servers. The
107+
# configuration can be set to either "none" to disable writing the identifiers on the response headers or to
108+
# the header name to be used when writing the identifiers.
109+
response-headers {
110+
111+
# HTTP response header name for the trace identifier, or "none" to disable it.
112+
#trace-id = "trace-id"
113+
114+
# HTTP response header name for the server span identifier, or "none" to disable it.
115+
#span-id = none
116+
}
117+
118+
# Custom mappings between routes and operation names.
119+
operations {
120+
121+
# The default operation name to be used when creating Spans to handle the HTTP server requests. In most
122+
# cases it is not possible to define an operation name right at the moment of starting the HTTP server Span
123+
# and in those cases, this operation name will be initially assigned to the Span. Instrumentation authors
124+
# should do their best effort to provide a suitable operation name or make use of the "mappings" facilities.
125+
default = "http.server.request"
126+
127+
# The operation name to be assigned when an application cannot find any route/endpoint/controller to handle
128+
# a given request. Depending on the instrumented framework, it might be possible to apply this operation
129+
# name automatically or not, check the frameworks' instrumentation docs for more details.
130+
unhandled = "unhandled"
131+
132+
# FQCN for a HttpOperationNameGenerator implementation, or ony of the following shorthand forms:
133+
# - default: Uses the set default operation name
134+
# - method: Uses the request HTTP method as the operation name.
135+
#
136+
name-generator = "kamon.http4s.PathOperationNameGenerator"
137+
138+
# Provides custom mappings from HTTP paths into operation names. Meant to be used in cases where the bytecode
139+
# instrumentation is not able to provide a sensible operation name that is free of high cardinality values.
140+
# For example, with the following configuration:
141+
# mappings {
142+
# "/organization/*/user/*/profile" = "/organization/:orgID/user/:userID/profile"
143+
# "/events/*/rsvps" = "EventRSVPs"
144+
# }
145+
#
146+
# Requests to "/organization/3651/user/39652/profile" and "/organization/22234/user/54543/profile" will have
147+
# the same operation name "/organization/:orgID/user/:userID/profile".
148+
#
149+
# Similarly, requests to "/events/aaa-bb-ccc/rsvps" and "/events/1234/rsvps" will have the same operation
150+
# name "EventRSVPs".
151+
#
152+
# The patterns are expressed as globs and the operation names are free form.
153+
#
154+
mappings {
155+
156+
}
157+
}
158+
}
159+
}
160+
161+
# Settings to control the HTTP Client instrumentation
162+
#
163+
# IMPORTANT: The entire configuration of the HTTP Client Instrumentation is based on the constructs provided by the
164+
# Kamon Instrumentation Common library which will always fallback to the settings found under the
165+
# "kamon.instrumentation.http-client.default" path. The default settings have been included here to make them easy to
166+
# find and understand in the context of this project and commented out so that any changes to the default settings
167+
# will actually have effect.
168+
#
169+
client {
170+
171+
#
172+
# Configuration for HTTP context propagation.
173+
#
174+
propagation {
175+
176+
# Enables or disables HTTP context propagation on this HTTP server instrumentation. Please note that if
177+
# propagation is disabled then some distributed tracing features will not be work as expected (e.g. Spans can
178+
# be created and reported but will not be linked across boundaries nor take trace identifiers from tags).
179+
#enabled = yes
180+
181+
# HTTP propagation channel to be used by this instrumentation. Take a look at the kamon.propagation.http.default
182+
# configuration for more details on how to configure the detault HTTP context propagation.
183+
#channel = "default"
184+
}
185+
186+
tracing {
187+
188+
# Enables HTTP request tracing. When enabled the instrumentation will create Spans for outgoing requests
189+
# and finish them when the response is received from the server.
190+
#enabled = yes
191+
192+
# Enables collection of span metrics using the `span.processing-time` metric.
193+
#span-metrics = on
194+
195+
# Select which tags should be included as span and span metric tags. The possible options are:
196+
# - span: the tag is added as a Span tag (i.e. using span.tag(...))
197+
# - metric: the tag is added a a Span metric tag (i.e. using span.tagMetric(...))
198+
# - off: the tag is not used.
199+
#
200+
tags {
201+
202+
# Use the http.url tag.
203+
#url = span
204+
205+
# Use the http.method tag.
206+
#method = metric
207+
208+
# Use the http.status_code tag.
209+
#status-code = metric
210+
211+
# Copy tags from the context into the Spans with the specified purpouse. For example, to copy a customer_type
212+
# tag from the context into the HTTP Server Span created by the instrumentation, the following configuration
213+
# should be added:
214+
#
215+
# from-context {
216+
# customer_type = span
217+
# }
218+
#
219+
from-context {
220+
221+
}
222+
}
223+
224+
operations {
225+
226+
# The default operation name to be used when creating Spans to handle the HTTP client requests. The HTTP
227+
# Client instrumentation will always try to use the HTTP Operation Name Generator configured bellow to get
228+
# a name, but if it fails to generate it then this name will be used.
229+
#default = "http.client.request"
230+
231+
# FQCN for a HttpOperationNameGenerator implementation, or ony of the following shorthand forms:
232+
# - hostname: Uses the request Host as the operation name.
233+
# - method: Uses the request HTTP method as the operation name.
234+
#
235+
name-generator = "kamon.http4s.PathOperationNameGenerator"
236+
}
237+
}
238+
}
239+
240+
241+
}
242+
243+
244+
kanela.modules {
245+
http4s {
246+
name = "Http4s Instrumentation"
247+
description = "Provides context propagation, distributed tracing and HTTP client and server metrics for Http4s"
248+
249+
instrumentations = []
250+
251+
within = []
252+
}
253+
}
254+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* =========================================================================================
3+
* Copyright © 2013-2018 the kamon project <http://kamon.io/>
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
6+
* except in compliance with the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software distributed under the
11+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12+
* either express or implied. See the License for the specific language governing permissions
13+
* and limitations under the License.
14+
* =========================================================================================
15+
*/
16+
17+
package kamon.http4s
18+
19+
import com.typesafe.config.Config
20+
import kamon.util.DynamicAccess
21+
import kamon.Kamon
22+
import kamon.instrumentation.http.{HttpMessage, HttpOperationNameGenerator}
23+
24+
object Http4s {
25+
@volatile var nameGenerator: HttpOperationNameGenerator =
26+
nameGeneratorFromConfig(Kamon.config())
27+
28+
private def nameGeneratorFromConfig(
29+
config: Config
30+
): HttpOperationNameGenerator = {
31+
val dynamic = new DynamicAccess(getClass.getClassLoader)
32+
val nameGeneratorFQCN = config.getString(
33+
"kamon.instrumentation.http4s.client.tracing.operations.name-generator"
34+
)
35+
dynamic
36+
.createInstanceFor[HttpOperationNameGenerator](nameGeneratorFQCN, Nil)
37+
}
38+
39+
Kamon.onReconfigure { newConfig =>
40+
nameGenerator = nameGeneratorFromConfig(newConfig)
41+
}
42+
}
43+
44+
class DefaultNameGenerator extends HttpOperationNameGenerator {
45+
46+
import java.util.Locale
47+
48+
import scala.collection.concurrent.TrieMap
49+
50+
private val localCache = TrieMap.empty[String, String]
51+
private val normalizePattern = """\$([^<]+)<[^>]+>""".r
52+
53+
override def name(request: HttpMessage.Request): Option[String] = {
54+
Some(
55+
localCache.getOrElseUpdate(
56+
s"${request.method}${request.path}", {
57+
// Convert paths of form GET /foo/bar/$paramname<regexp>/blah to foo.bar.paramname.blah.get
58+
val p = normalizePattern
59+
.replaceAllIn(request.path, "$1")
60+
.replace('/', '.')
61+
.dropWhile(_ == '.')
62+
val normalisedPath = {
63+
if (p.lastOption.exists(_ != '.')) s"$p."
64+
else p
65+
}
66+
s"$normalisedPath${request.method.toLowerCase(Locale.ENGLISH)}"
67+
}
68+
)
69+
)
70+
}
71+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package kamon.http4s
2+
3+
import kamon.instrumentation.http.{HttpMessage, HttpOperationNameGenerator}
4+
5+
class PathOperationNameGenerator extends HttpOperationNameGenerator {
6+
override def name(request: HttpMessage.Request): Option[String] = Some(
7+
request.path
8+
)
9+
}

0 commit comments

Comments
 (0)