Skip to content

Commit 2df2e11

Browse files
authored
Merge branch 'master' into update/opentelemetry-api-1.61.0
2 parents 48e13c3 + dfa3e0a commit 2df2e11

14 files changed

Lines changed: 135 additions & 82 deletions

File tree

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = 3.10.7
1+
version = 3.11.0
22
maxColumn = 120
33
runner.dialect = scala213
44
fileOverride {

build.sbt

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ val playJsonVersion = "3.0.6"
129129
val catsEffect_3_version = "3.7.0"
130130
val fs2_3_version = "3.13.0"
131131

132-
// This version provides Scala Native 0.5.x support. Drop this when 3.7.0 is released.
133-
val catsEffect_3_RC_version = "3.7.0-RC1"
134-
135132
val catsEffect_2_version = "2.5.5"
136133

137134
val fs2_2_version = "2.5.13"
@@ -165,13 +162,13 @@ val braveOpentracingVersion = "1.0.1"
165162
val zipkinSenderOkHttpVersion = "3.5.1"
166163
val resilience4jVersion = "2.4.0"
167164
val http4s_ce2_version = "0.22.15"
168-
val http4s_ce3_version = "0.23.33"
165+
val http4s_ce3_version = "0.23.34"
169166
val osLibVersion = "0.11.4"
170167
val tethysVersion = "0.29.8"
171168
val openTelemetryVersion = "1.61.0"
172169
val openTelemetrySemconvVersion = "1.40.0"
173-
val otel4s = "0.15.2"
174-
val otel4sSdk = "0.17.0"
170+
val otel4s = "0.16.0"
171+
val otel4sSdk = "0.18.0"
175172
val slf4jVersion = "1.7.36"
176173

177174
val compileAndTest = "compile->compile;test->test"
@@ -397,13 +394,10 @@ lazy val catsCe2 = (projectMatrix in file("effects/cats-ce2"))
397394
)
398395

399396
lazy val catsEffect = Def.setting {
400-
val ceVersion =
401-
if (virtualAxes.value.contains(VirtualAxis.native)) catsEffect_3_RC_version
402-
else catsEffect_3_version
403397
Seq(
404-
"org.typelevel" %%% "cats-effect-kernel" % ceVersion,
405-
"org.typelevel" %%% "cats-effect-std" % ceVersion,
406-
"org.typelevel" %%% "cats-effect" % ceVersion % Test
398+
"org.typelevel" %%% "cats-effect-kernel" % catsEffect_3_version,
399+
"org.typelevel" %%% "cats-effect-std" % catsEffect_3_version,
400+
"org.typelevel" %%% "cats-effect" % catsEffect_3_version % Test
407401
)
408402
}
409403

@@ -424,7 +418,7 @@ lazy val cats = (projectMatrix in file("effects/cats"))
424418
settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings
425419
)
426420
.nativePlatform(
427-
scalaVersions = List(scala3),
421+
scalaVersions = scala2And3,
428422
settings = commonNativeSettings ++ testServerSettings
429423
)
430424

@@ -665,7 +659,7 @@ lazy val http4sBackend = (projectMatrix in file("http4s-backend"))
665659
name := "http4s-backend",
666660
libraryDependencies ++= Seq(
667661
"org.http4s" %% "http4s-client" % http4s_ce3_version,
668-
"org.http4s" %% "http4s-ember-client" % "0.23.33" % Optional,
662+
"org.http4s" %% "http4s-ember-client" % "0.23.34" % Optional,
669663
"org.http4s" %% "http4s-blaze-client" % "0.23.17" % Optional
670664
),
671665
evictionErrorLevel := Level.Info
@@ -975,13 +969,14 @@ lazy val otel4sMetricsBackend = (projectMatrix in file("observability/otel4s-met
975969
libraryDependencies ++= Seq(
976970
"org.typelevel" %%% "otel4s-core-metrics" % otel4s,
977971
"org.typelevel" %%% "otel4s-semconv" % otel4s,
978-
"org.typelevel" %%% "otel4s-semconv-experimental" % otel4s,
972+
"org.typelevel" %%% "otel4s-semconv-experimental" % otel4s % Test,
979973
"org.typelevel" %%% "otel4s-semconv-metrics-experimental" % otel4s % Test,
980974
"org.typelevel" %%% "otel4s-sdk-metrics-testkit" % otel4sSdk % Test
981975
)
982976
)
983977
.jvmPlatform(scalaVersions = scala2_13And3, settings = commonJvmSettings)
984978
.jsPlatform(scalaVersions = scala2_13And3, settings = commonJsSettings)
979+
.nativePlatform(scalaVersions = scala2_13And3, settings = commonNativeSettings)
985980
.dependsOn(cats % Test)
986981
.dependsOn(core % compileAndTest)
987982

@@ -997,6 +992,7 @@ lazy val otel4sTracingBackend = (projectMatrix in file("observability/otel4s-tra
997992
)
998993
.jvmPlatform(scalaVersions = scala2_13And3, settings = commonJvmSettings)
999994
.jsPlatform(scalaVersions = scala2_13And3, settings = commonJsSettings)
995+
.nativePlatform(scalaVersions = scala2_13And3, settings = commonNativeSettings)
1000996
.dependsOn(cats % Test)
1001997
.dependsOn(core % compileAndTest)
1002998

effects/cats/src/main/scalanative/sttp/client4/curl/cats/CurlCatsBackend.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
package sttp.client4.curl.cats
22

33
import cats.effect.kernel.Sync
4-
import sttp.capabilities.Effect
54
import sttp.client4.curl.AbstractSyncCurlBackend
65
import sttp.client4.impl.cats.CatsMonadError
76
import sttp.client4.wrappers.FollowRedirectsBackend
87
import sttp.client4.Backend
98

109
class CurlCatsBackend[F[_]: Sync] private (verbose: Boolean)
1110
extends AbstractSyncCurlBackend[F](new CatsMonadError[F], verbose)
12-
with Backend[F] {
13-
override type R = Effect[F]
14-
}
11+
with Backend[F]
1512

1613
object CurlCatsBackend {
1714

observability/otel4s-metrics-backend/src/main/scala/sttp/client4/opentelemetry/otel4s/Otel4sMetricsBackend.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import org.typelevel.otel4s.semconv.attributes.{
1818
ServerAttributes,
1919
UrlAttributes
2020
}
21-
import org.typelevel.otel4s.semconv.experimental.attributes.UrlExperimentalAttributes
2221
import sttp.client4.listener.{ListenerBackend, RequestListener}
2322
import sttp.client4._
2423
import sttp.model.{HttpVersion, ResponseMetadata, StatusCode}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package sttp.client4.opentelemetry.otel4s
2+
3+
import org.typelevel.otel4s.AttributeKey
4+
5+
private object UrlExperimentalAttributes {
6+
// url.template is still experimental, so we need to wait for it to be stable before pulling it from the otel4s semconv
7+
val UrlTemplate: AttributeKey[String] =
8+
AttributeKey("url.template")
9+
}
Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
package sttp.client4.opentelemetry.otel4s
22

33
import cats.effect.IO
4-
import cats.effect.unsafe.IORuntime
54
import cats.effect.unsafe.implicits.global
6-
import org.scalatest.flatspec.AnyFlatSpec
75
import org.scalatest.freespec.AsyncFreeSpec
86
import org.scalatest.matchers.should.Matchers
97
import org.typelevel.otel4s.metrics.MeterProvider
108
import org.typelevel.otel4s.sdk.metrics.data.MetricData
9+
import org.typelevel.otel4s.sdk.testkit.metrics.{MetricExpectation, MetricExpectations}
1110
import org.typelevel.otel4s.sdk.testkit.metrics.MetricsTestkit
11+
import org.typelevel.otel4s.semconv.metrics.HttpMetrics
1212
import org.typelevel.otel4s.semconv.experimental.metrics.HttpExperimentalMetrics
1313
import org.typelevel.otel4s.semconv.{MetricSpec, Requirement}
1414
import sttp.model.{Header, StatusCode}
1515
import sttp.client4._
1616
import sttp.client4.impl.cats.CatsMonadAsyncError
1717
import sttp.client4.testing.{BackendStub, ResponseStub, StubBody}
1818

19-
import scala.concurrent.ExecutionContext
2019
import scala.concurrent.duration._
2120

22-
class Otel4sMetricsBackendTest extends AsyncFreeSpec with Matchers {
23-
24-
override def executionContext: ExecutionContext = ExecutionContext.global
21+
class Otel4sMetricsBackendTest extends AsyncFreeSpec with Matchers with AsyncExecutionContext {
2522

2623
"Otel4sMetricsBackend" - {
2724
"should pass the client semantic test" in {
2825
val specs = List(
29-
HttpExperimentalMetrics.ClientRequestDuration,
26+
HttpMetrics.ClientRequestDuration,
3027
HttpExperimentalMetrics.ClientRequestBodySize,
3128
HttpExperimentalMetrics.ClientResponseBodySize,
3229
HttpExperimentalMetrics.ClientActiveRequests
@@ -50,41 +47,41 @@ class Otel4sMetricsBackendTest extends AsyncFreeSpec with Matchers {
5047

5148
makeBackend.use { backend =>
5249
for {
53-
r <- backend.send(basicRequest.post(uri"http://localhost:8080/success").body("payload"))
50+
_ <- backend.send(basicRequest.post(uri"http://localhost:8080/success").body("payload"))
5451
// we use `.unsafeRunAndForget()` in the backend and JS could be slow
5552
_ <- IO.sleep(1.second)
5653
metrics <- testkit.collectMetrics
57-
_ = specs.foreach(spec => specTest(metrics, spec))
58-
} yield succeed
54+
} yield assertMetricsMatch(metrics, specs.map(metricExpectation))
5955
}
6056
}
6157
.unsafeToFuture()
6258
}
6359
}
6460

65-
private def specTest(metrics: List[MetricData], spec: MetricSpec): Unit = {
66-
val metric = metrics.find(_.name == spec.name)
67-
assert(
68-
metric.isDefined,
69-
s"${spec.name} metric is missing. Available [${metrics.map(_.name).mkString(", ")}]"
70-
)
71-
72-
metric.foreach { md =>
73-
md.name shouldBe spec.name
74-
md.description shouldBe Some(spec.description)
75-
md.unit shouldBe Some(spec.unit)
76-
77-
val required = spec.attributeSpecs
78-
.filter(_.requirement.level == Requirement.Level.Required)
79-
.map(_.key)
80-
.toSet
61+
private def metricExpectation(spec: MetricSpec): MetricExpectation = {
62+
val required = spec.attributeSpecs
63+
.filter(_.requirement.level == Requirement.Level.Required)
64+
.map(_.key)
65+
.toSet
8166

82-
val current = md.data.points.toVector
83-
.flatMap(_.attributes.map(_.key))
84-
.filter(key => required.contains(key))
85-
.toSet
67+
MetricExpectation
68+
.name(spec.name)
69+
.description(spec.description)
70+
.unit(spec.unit)
71+
.clue(spec.name)
72+
.where("required semantic-convention attributes are present") { metric =>
73+
metric.data.points.iterator
74+
.flatMap(_.attributes.iterator.map(_.key))
75+
.filter(required.contains)
76+
.toSet == required
77+
}
78+
}
8679

87-
current shouldBe required
80+
private def assertMetricsMatch(metrics: List[MetricData], expectations: List[MetricExpectation]) =
81+
MetricExpectations.checkAllDistinct(metrics, expectations) match {
82+
case Right(_) =>
83+
succeed
84+
case Left(mismatches) =>
85+
fail(MetricExpectations.format(mismatches))
8886
}
89-
}
9087
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package sttp.client4.opentelemetry.otel4s
2+
3+
import org.scalatest.AsyncTestSuite
4+
5+
import scala.concurrent.ExecutionContext
6+
7+
trait AsyncExecutionContext { self: AsyncTestSuite =>
8+
override def executionContext: ExecutionContext = ExecutionContext.global
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package sttp.client4.opentelemetry.otel4s
2+
3+
trait AsyncExecutionContext {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package sttp.client4.opentelemetry.otel4s
2+
3+
trait AsyncExecutionContext {}

observability/otel4s-tracing-backend/src/test/scala/sttp/client4/opentelemetry/otel4s/Otel4sTracingBackendTest.scala

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,20 @@ import cats.effect.unsafe.implicits.global
2222
import org.scalatest.freespec.AsyncFreeSpec
2323
import org.scalatest.matchers.should.Matchers
2424
import org.typelevel.otel4s.sdk.data.LimitedData
25-
import org.typelevel.otel4s.sdk.testkit.trace.TracesTestkit
25+
import org.typelevel.otel4s.sdk.testkit.trace._
2626
import org.typelevel.otel4s.sdk.trace.context.propagation.W3CTraceContextPropagator
27-
import org.typelevel.otel4s.sdk.trace.data.{EventData, StatusData}
28-
import org.typelevel.otel4s.trace.{StatusCode, TracerProvider}
27+
import org.typelevel.otel4s.sdk.trace.data.{EventData, SpanData}
28+
import org.typelevel.otel4s.trace.TracerProvider
2929
import org.typelevel.otel4s.{Attribute, Attributes}
3030
import sttp.client4._
3131
import sttp.client4.impl.cats.CatsMonadAsyncError
3232
import sttp.client4.testing.{BackendStub, ResponseStub, StubBody}
3333
import sttp.model.{StatusCode => HttpStatusCode}
3434

35-
import scala.concurrent.ExecutionContext
3635
import scala.concurrent.duration.Duration
3736
import scala.util.control.NoStackTrace
3837

39-
class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
40-
41-
override def executionContext: ExecutionContext = ExecutionContext.global
38+
class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers with AsyncExecutionContext {
4239

4340
"Otel4sTracingBackend" - {
4441
"should add tracing headers to the request" in {
@@ -86,8 +83,6 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
8683
response <- backend.send(basicRequest.get(uri"http://user:pwd@localhost:8080/success?q=v"))
8784
spans <- testkit.finishedSpans
8885
} yield {
89-
val status = StatusData(StatusCode.Unset)
90-
9186
val attributes = Attributes(
9287
Attribute("http.request.method", "GET"),
9388
Attribute("http.response.status_code", 200L),
@@ -99,11 +94,19 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
9994

10095
response.code shouldBe HttpStatusCode.Ok
10196

102-
spans.map(_.attributes.elements) shouldBe List(attributes)
103-
spans.map(_.events.elements) shouldBe List(Vector.empty)
104-
spans.map(_.status) shouldBe List(status)
105-
106-
succeed
97+
assertSpansMatch(
98+
spans,
99+
TraceForestExpectation.ordered(
100+
TraceExpectation.leaf(
101+
SpanExpectation
102+
.client("GET")
103+
.noParentSpanContext
104+
.attributesExact(attributes)
105+
.status(StatusExpectation.unset)
106+
.eventCount(0)
107+
)
108+
)
109+
)
107110
}
108111
}
109112
.unsafeToFuture()
@@ -130,8 +133,6 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
130133
response <- backend.send(basicRequest.get(uri"http://user@localhost:8080/bad-request?q=v"))
131134
spans <- testkit.finishedSpans
132135
} yield {
133-
val status = StatusData(StatusCode.Error)
134-
135136
val attributes = Attributes(
136137
Attribute("http.request.method", "GET"),
137138
Attribute("http.response.status_code", 400L),
@@ -144,11 +145,19 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
144145

145146
response.code shouldBe HttpStatusCode.BadRequest
146147

147-
spans.map(_.attributes.elements) shouldBe List(attributes)
148-
spans.map(_.events.elements) shouldBe List(Vector.empty)
149-
spans.map(_.status) shouldBe List(status)
150-
151-
succeed
148+
assertSpansMatch(
149+
spans,
150+
TraceForestExpectation.ordered(
151+
TraceExpectation.leaf(
152+
SpanExpectation
153+
.client("GET")
154+
.noParentSpanContext
155+
.attributesExact(attributes)
156+
.status(StatusExpectation.error)
157+
.eventCount(0)
158+
)
159+
)
160+
)
152161
}
153162
}
154163
.unsafeToFuture()
@@ -179,8 +188,6 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
179188
response <- backend.send(basicRequest.get(uri"http://localhost/error")).attempt
180189
spans <- testkit.finishedSpans
181190
} yield {
182-
val status = StatusData(StatusCode.Error)
183-
184191
val attributes = Attributes(
185192
Attribute("http.request.method", "GET"),
186193
Attribute("server.address", "localhost"),
@@ -197,16 +204,34 @@ class Otel4sTracingBackendTest extends AsyncFreeSpec with Matchers {
197204

198205
response shouldBe Left(Err)
199206

200-
spans.map(_.attributes.elements) shouldBe List(attributes)
201-
spans.map(_.events.elements) shouldBe List(Vector(event))
202-
spans.map(_.status) shouldBe List(status)
203-
204-
succeed
207+
assertSpansMatch(
208+
spans,
209+
TraceForestExpectation.ordered(
210+
TraceExpectation.leaf(
211+
SpanExpectation
212+
.client("GET")
213+
.noParentSpanContext
214+
.attributesExact(attributes)
215+
.status(StatusExpectation.error)
216+
.exactlyEvents(
217+
EventExpectation.any.where("matches the recorded exception event")(_ == event)
218+
)
219+
)
220+
)
221+
)
205222
}
206223
}
207224
}
208225
.unsafeToFuture()
209226
}
210227
}
211228

229+
private def assertSpansMatch(spans: List[SpanData], expectation: TraceForestExpectation) =
230+
TraceExpectations.check(spans, expectation) match {
231+
case Right(_) =>
232+
succeed
233+
case Left(mismatches) =>
234+
fail(TraceExpectations.format(mismatches))
235+
}
236+
212237
}

0 commit comments

Comments
 (0)