diff --git a/build.sbt b/build.sbt index 6ceff2b10..8417eb7ce 100644 --- a/build.sbt +++ b/build.sbt @@ -520,7 +520,7 @@ lazy val benchmarks = project lazy val examples = project .enablePlugins(NoPublishPlugin, JavaAgent) .in(file("examples")) - .dependsOn(core.jvm, oteljava) + .dependsOn(core.jvm, oteljava, sdk.jvm, `sdk-exporter`.jvm) .settings( name := "otel4s-examples", libraryDependencies ++= Seq( diff --git a/examples/src/main/scala/TraceSdkExample.scala b/examples/src/main/scala/TraceSdkExample.scala new file mode 100644 index 000000000..7f1819b25 --- /dev/null +++ b/examples/src/main/scala/TraceSdkExample.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Typelevel + * + * 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. + */ + +import cats.effect._ +import org.typelevel.otel4s.Attribute +import org.typelevel.otel4s.sdk.OpenTelemetrySdk +import org.typelevel.otel4s.sdk.exporter.otlp.trace.autoconfigure.OtlpSpanExporterAutoConfigure + +object TraceSdkExample extends IOApp.Simple { + + def run: IO[Unit] = + OpenTelemetrySdk + .autoConfigured[IO]( + _.addExporterConfigurer(OtlpSpanExporterAutoConfigure[IO]) + ) + .use { autoConfigured => + val sdk = autoConfigured.sdk + + for { + tracer <- sdk.tracerProvider.get("my-tracer") + _ <- tracer + .span("test", Attribute("test", "test123")) + .use(sd => IO.println(sd.context)) + } yield () + } + +} diff --git a/sdk/all/src/test/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdkSuite.scala b/sdk/all/src/test/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdkSuite.scala index 3c201f59e..418534312 100644 --- a/sdk/all/src/test/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdkSuite.scala +++ b/sdk/all/src/test/scala/org/typelevel/otel4s/sdk/OpenTelemetrySdkSuite.scala @@ -30,6 +30,7 @@ import org.typelevel.otel4s.sdk.autoconfigure.AutoConfigure import org.typelevel.otel4s.sdk.autoconfigure.Config import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.trace.NoopConsole +import org.typelevel.otel4s.sdk.trace.SpanLimits import org.typelevel.otel4s.sdk.trace.context.propagation.W3CBaggagePropagator import org.typelevel.otel4s.sdk.trace.context.propagation.W3CTraceContextPropagator import org.typelevel.otel4s.sdk.trace.data.LinkData @@ -272,7 +273,7 @@ class OpenTelemetrySdkSuite extends CatsEffectSuite { "OpenTelemetrySdk.AutoConfigured{sdk=" + "OpenTelemetrySdk{meterProvider=MeterProvider.Noop, " + "tracerProvider=" + - s"SdkTracerProvider{resource=$resource, sampler=$sampler, " + + s"SdkTracerProvider{resource=$resource, spanLimits=${SpanLimits.Default}, sampler=$sampler, " + "spanProcessor=SpanProcessor.Multi(" + s"BatchSpanProcessor{exporter=$exporter, scheduleDelay=5 seconds, exporterTimeout=30 seconds, maxQueueSize=2048, maxExportBatchSize=512}, " + "SpanStorage)}, " + diff --git a/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/internal/ComponentRegistry.scala b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/internal/ComponentRegistry.scala new file mode 100644 index 000000000..85ab00a56 --- /dev/null +++ b/sdk/common/src/main/scala/org/typelevel/otel4s/sdk/internal/ComponentRegistry.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Typelevel + * + * 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 org.typelevel.otel4s +package sdk +package internal + +import org.typelevel.otel4s.sdk.common.InstrumentationScope + +// todo: implement caching +final class ComponentRegistry[F[_], A]( + factory: InstrumentationScope => F[A] +) { + + def get( + name: String, + version: Option[String], + schemaUrl: Option[String], + attributes: Attributes + ): F[A] = { + buildComponent( + InstrumentationScope(name, version, schemaUrl, attributes) + ) + } + + private def buildComponent(info: InstrumentationScope): F[A] = { + val component = factory(info) + component + } + +} diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackend.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackend.scala index 88668bd3b..32b4c615a 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackend.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackend.scala @@ -68,6 +68,7 @@ import scala.concurrent.duration.FiniteDuration * the higher-kinded type of a polymorphic effect */ private final class SdkSpanBackend[F[_]: Monad: Clock: Console] private ( + spanLimits: SpanLimits, spanProcessor: SpanProcessor[F], immutableState: SdkSpanBackend.ImmutableState, mutableState: Ref[F, SdkSpanBackend.MutableState] @@ -146,7 +147,16 @@ private final class SdkSpanBackend[F[_]: Monad: Clock: Console] private ( } private def addTimedEvent(event: EventData): F[Unit] = - updateState("addEvent")(s => s.copy(events = s.events :+ event)).void + updateState("addEvent") { s => + if (s.events.sizeIs <= spanLimits.maxNumberOfEvents) { + s.copy( + events = s.events :+ event, + totalRecordedEvents = s.totalRecordedEvents + 1 + ) + } else { + s.copy(totalRecordedEvents = s.totalRecordedEvents + 1) + } + }.void // applies modifications while the span is still active // modifications are ignored when the span is ended @@ -193,6 +203,10 @@ private final class SdkSpanBackend[F[_]: Monad: Clock: Console] private ( attributes = state.attributes, events = state.events, links = immutableState.links, + /*totalRecordedEvents = state.totalRecordedEvents, + totalRecordedLinks = immutableState.totalRecordedLinks, + totalAttributeCount = + state.attributes.size, // todo: incorrect when limits are applied,*/ instrumentationScope = immutableState.scopeInfo, resource = immutableState.resource ) @@ -255,9 +269,11 @@ private object SdkSpanBackend { resource: TelemetryResource, kind: SpanKind, parentContext: Option[SpanContext], + spanLimits: SpanLimits, processor: SpanProcessor[F], attributes: Attributes, links: Vector[LinkData], + totalRecordedLinks: Int, userStartTimestamp: Option[FiniteDuration] ): F[SdkSpanBackend[F]] = { def immutableState(startTimestamp: FiniteDuration) = @@ -268,6 +284,7 @@ private object SdkSpanBackend { parentContext = parentContext, resource = resource, links = links, + totalRecordedLinks = totalRecordedLinks, startTimestamp = startTimestamp ) @@ -276,13 +293,19 @@ private object SdkSpanBackend { status = StatusData.Unset, attributes = attributes, events = Vector.empty, + totalRecordedEvents = 0, endTimestamp = None ) for { start <- userStartTimestamp.fold(Clock[F].realTime)(_.pure) state <- Ref[F].of(mutableState) - backend = new SdkSpanBackend[F](processor, immutableState(start), state) + backend = new SdkSpanBackend[F]( + spanLimits, + processor, + immutableState(start), + state + ) _ <- processor.onStart(parentContext, backend) } yield backend } @@ -294,6 +317,7 @@ private object SdkSpanBackend { parentContext: Option[SpanContext], resource: TelemetryResource, links: Vector[LinkData], + totalRecordedLinks: Int, startTimestamp: FiniteDuration ) @@ -304,6 +328,7 @@ private object SdkSpanBackend { status: StatusData, attributes: Attributes, events: Vector[EventData], + totalRecordedEvents: Int, endTimestamp: Option[FiniteDuration] ) diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilder.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilder.scala index 06e4034fb..86e723859 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilder.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilder.scala @@ -193,9 +193,11 @@ private final case class SdkSpanBuilder[F[_]: Temporal: Console]( resource = tracerSharedState.resource, kind = spanKind, parentContext = parentSpanContext, + spanLimits = tracerSharedState.spanLimits, processor = tracerSharedState.spanProcessor, attributes = attrs |+| samplingResult.attributes, links = links, + totalRecordedLinks = links.size, userStartTimestamp = startTimestamp ) .widen diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerBuilder.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerBuilder.scala index 84256963e..7d47eef4e 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerBuilder.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerBuilder.scala @@ -14,25 +14,18 @@ * limitations under the License. */ -package org.typelevel.otel4s.sdk +package org.typelevel.otel4s +package sdk package trace -import cats.effect.Temporal -import cats.effect.std.Console -import org.typelevel.otel4s.Attributes -import org.typelevel.otel4s.context.propagation.ContextPropagators -import org.typelevel.otel4s.sdk.common.InstrumentationScope -import org.typelevel.otel4s.sdk.context.Context -import org.typelevel.otel4s.sdk.trace.processor.SpanStorage -import org.typelevel.otel4s.trace.TraceScope +import cats.Functor +import cats.syntax.functor._ +import org.typelevel.otel4s.sdk.internal.ComponentRegistry import org.typelevel.otel4s.trace.Tracer import org.typelevel.otel4s.trace.TracerBuilder -private final case class SdkTracerBuilder[F[_]: Temporal: Console]( - propagators: ContextPropagators[Context], - traceScope: TraceScope[F, Context], - sharedState: TracerSharedState[F], - storage: SpanStorage[F], +private final case class SdkTracerBuilder[F[_]: Functor]( + registry: ComponentRegistry[F, SdkTracer[F]], name: String, version: Option[String] = None, schemaUrl: Option[String] = None @@ -45,13 +38,5 @@ private final case class SdkTracerBuilder[F[_]: Temporal: Console]( copy(schemaUrl = Option(schemaUrl)) def get: F[Tracer[F]] = - Temporal[F].pure( - new SdkTracer[F]( - InstrumentationScope(name, version, schemaUrl, Attributes.empty), - propagators, - sharedState, - traceScope, - storage - ) - ) + registry.get(name, version, schemaUrl, Attributes.empty).widen } diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerProvider.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerProvider.scala index b13819200..9d3a7b2b8 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerProvider.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SdkTracerProvider.scala @@ -26,6 +26,7 @@ import org.typelevel.otel4s.context.propagation.ContextPropagators import org.typelevel.otel4s.context.propagation.TextMapPropagator import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.context.LocalContext +import org.typelevel.otel4s.sdk.internal.ComponentRegistry import org.typelevel.otel4s.sdk.trace.processor.SpanProcessor import org.typelevel.otel4s.sdk.trace.processor.SpanStorage import org.typelevel.otel4s.sdk.trace.samplers.Sampler @@ -36,6 +37,7 @@ import org.typelevel.otel4s.trace.TracerProvider private class SdkTracerProvider[F[_]: Temporal: Parallel: Console]( idGenerator: IdGenerator[F], resource: TelemetryResource, + spanLimits: SpanLimits, sampler: Sampler, propagators: ContextPropagators[Context], spanProcessors: List[SpanProcessor[F]], @@ -47,16 +49,33 @@ private class SdkTracerProvider[F[_]: Temporal: Parallel: Console]( TracerSharedState( idGenerator, resource, + spanLimits, sampler, SpanProcessor.of(spanProcessors: _*) ) + private val registry: ComponentRegistry[F, SdkTracer[F]] = + new ComponentRegistry[F, SdkTracer[F]](scopeInfo => + Temporal[F].pure( + new SdkTracer[F]( + scopeInfo, + propagators, + sharedState, + traceScope, + storage + ) + ) + ) + def tracer(name: String): TracerBuilder[F] = - new SdkTracerBuilder[F](propagators, traceScope, sharedState, storage, name) + new SdkTracerBuilder[F](registry, name) override def toString: String = - s"SdkTracerProvider{resource=$resource, sampler=$sampler, spanProcessor=${sharedState.spanProcessor}}" - + "SdkTracerProvider{" + + s"resource=$resource, " + + s"spanLimits=$spanLimits, " + + s"sampler=$sampler, " + + s"spanProcessor=${sharedState.spanProcessor}}" } object SdkTracerProvider { @@ -105,6 +124,16 @@ object SdkTracerProvider { */ def addResource(resource: TelemetryResource): Builder[F] + /** Sets an initial [[SpanLimits]] that should be used with this SDK. + * + * The limits will be used for every + * [[org.typelevel.otel4s.trace.Span Span]]. + * + * @param limits + * the [[SpanLimits]] to use + */ + def withSpanLimits(limits: SpanLimits): Builder[F] + /** Sets a [[org.typelevel.otel4s.sdk.trace.samplers.Sampler Sampler]]. * * The sampler will be called each time a @@ -161,6 +190,7 @@ object SdkTracerProvider { BuilderImpl[F]( idGenerator = IdGenerator.random, resource = TelemetryResource.default, + spanLimits = SpanLimits.Default, sampler = Sampler.parentBased(Sampler.AlwaysOn), propagators = Nil, spanProcessors = Nil @@ -171,6 +201,7 @@ object SdkTracerProvider { ]( idGenerator: IdGenerator[F], resource: TelemetryResource, + spanLimits: SpanLimits, sampler: Sampler, propagators: List[TextMapPropagator[Context]], spanProcessors: List[SpanProcessor[F]] @@ -185,6 +216,9 @@ object SdkTracerProvider { def addResource(resource: TelemetryResource): Builder[F] = copy(resource = this.resource.mergeUnsafe(resource)) + def withSpanLimits(limits: SpanLimits): Builder[F] = + copy(spanLimits = limits) + def withSampler(sampler: Sampler): Builder[F] = copy(sampler = sampler) @@ -201,6 +235,7 @@ object SdkTracerProvider { new SdkTracerProvider[F]( idGenerator, resource, + spanLimits, sampler, ContextPropagators.of(propagators: _*), spanProcessors :+ storage, diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SpanLimits.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SpanLimits.scala new file mode 100644 index 000000000..2e8691375 --- /dev/null +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/SpanLimits.scala @@ -0,0 +1,197 @@ +/* + * Copyright 2023 Typelevel + * + * 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 org.typelevel.otel4s.sdk.trace + +import cats.Hash +import cats.Show + +/** Holds the limits enforced during recording of a span. */ +sealed trait SpanLimits { + + /** The max number of attributes per span. */ + def maxNumberOfAttributes: Int + + /** The max number of events per span. */ + def maxNumberOfEvents: Int + + /** The max number of links per span. */ + def maxNumberOfLinks: Int + + /** The max number of attributes per event. */ + def maxNumberOfAttributesPerEvent: Int + + /** The max number of attributes per link. */ + def maxNumberOfAttributesPerLink: Int + + /** The max number of characters for string attribute values. For string array + * attribute values, applies to each entry individually. + */ + def maxAttributeValueLength: Int + + override final def hashCode(): Int = + Hash[SpanLimits].hash(this) + + override final def equals(obj: Any): Boolean = + obj match { + case other: SpanLimits => Hash[SpanLimits].eqv(this, other) + case _ => false + } + + override final def toString: String = + Show[SpanLimits].show(this) +} + +object SpanLimits { + + private object Defaults { + val MaxNumberOfAttributes = 128 + val MaxNumberOfEvents = 128 + val MaxNumberOfLinks = 128 + val MaxNumberOfAttributesPerEvent = 127 + val MaxNumberOfAttributesPerLink = 128 + val MaxAttributeValueLength = Int.MaxValue + } + + /** Builder for [[SpanLimits]] */ + sealed trait Builder { + + /** Sets the max number of attributes per span. */ + def setMaxNumberOfAttributes(value: Int): Builder + + /** Sets the max number of events per span. */ + def setMaxNumberOfEvents(value: Int): Builder + + /** Sets the max number of links per span. */ + def setMaxNumberOfLinks(value: Int): Builder + + /** Sets the max number of attributes per event. */ + def setMaxNumberOfAttributesPerEvent(value: Int): Builder + + /** Sets the max number of attributes per link. */ + def setMaxNumberOfAttributesPerLink(value: Int): Builder + + /** Sets the max number of characters for string attribute values. For + * string array attribute values, applies to each entry individually. + */ + def setMaxAttributeValueLength(value: Int): Builder + + /** Creates a [[SpanLimits]] with the configuration of this builder. */ + def build: SpanLimits + } + + val Default: SpanLimits = + builder.build + + /** Creates a [[Builder]] for [[SpanLimits]] using the default limits. */ + def builder: Builder = + BuilderImpl( + maxNumberOfAttributes = Defaults.MaxNumberOfAttributes, + maxNumberOfEvents = Defaults.MaxNumberOfEvents, + maxNumberOfLinks = Defaults.MaxNumberOfLinks, + maxNumberOfAttributesPerEvent = Defaults.MaxNumberOfAttributesPerEvent, + maxNumberOfAttributesPerLink = Defaults.MaxNumberOfAttributesPerLink, + maxAttributeValueLength = Defaults.MaxAttributeValueLength + ) + + /** Creates a [[SpanLimits]] using the given limits. */ + def create( + maxNumberOfAttributes: Int, + maxNumberOfEvents: Int, + maxNumberOfLinks: Int, + maxNumberOfAttributesPerEvent: Int, + maxNumberOfAttributesPerLink: Int, + maxAttributeValueLength: Int + ): SpanLimits = + SpanLimitsImpl( + maxNumberOfAttributes = maxNumberOfAttributes, + maxNumberOfEvents = maxNumberOfEvents, + maxNumberOfLinks = maxNumberOfLinks, + maxNumberOfAttributesPerEvent = maxNumberOfAttributesPerEvent, + maxNumberOfAttributesPerLink = maxNumberOfAttributesPerLink, + maxAttributeValueLength = maxAttributeValueLength + ) + + implicit val spanLimitsHash: Hash[SpanLimits] = + Hash.by { s => + ( + s.maxNumberOfAttributes, + s.maxNumberOfEvents, + s.maxNumberOfLinks, + s.maxNumberOfAttributesPerEvent, + s.maxNumberOfAttributesPerLink, + s.maxAttributeValueLength + ) + } + + implicit val spanLimitsShow: Show[SpanLimits] = + Show.show { s => + "SpanLimits{" + + s"maxNumberOfAttributes=${s.maxNumberOfAttributes}, " + + s"maxNumberOfEvents=${s.maxNumberOfEvents}, " + + s"maxNumberOfLinks=${s.maxNumberOfLinks}, " + + s"maxNumberOfAttributesPerEvent=${s.maxNumberOfAttributesPerEvent}, " + + s"maxNumberOfAttributesPerLink=${s.maxNumberOfAttributesPerLink}, " + + s"maxAttributeValueLength=${s.maxAttributeValueLength}}" + } + + private final case class SpanLimitsImpl( + maxNumberOfAttributes: Int, + maxNumberOfEvents: Int, + maxNumberOfLinks: Int, + maxNumberOfAttributesPerEvent: Int, + maxNumberOfAttributesPerLink: Int, + maxAttributeValueLength: Int, + ) extends SpanLimits + + private final case class BuilderImpl( + maxNumberOfAttributes: Int, + maxNumberOfEvents: Int, + maxNumberOfLinks: Int, + maxNumberOfAttributesPerEvent: Int, + maxNumberOfAttributesPerLink: Int, + maxAttributeValueLength: Int, + ) extends Builder { + def setMaxNumberOfAttributes(value: Int): Builder = + copy(maxNumberOfAttributes = value) + + def setMaxNumberOfEvents(value: Int): Builder = + copy(maxNumberOfEvents = value) + + def setMaxNumberOfLinks(value: Int): Builder = + copy(maxNumberOfLinks = value) + + def setMaxNumberOfAttributesPerEvent(value: Int): Builder = + copy(maxNumberOfAttributesPerEvent = value) + + def setMaxNumberOfAttributesPerLink(value: Int): Builder = + copy(maxNumberOfAttributesPerLink = value) + + def setMaxAttributeValueLength(value: Int): Builder = + copy(maxAttributeValueLength = value) + + def build: SpanLimits = + SpanLimitsImpl( + maxNumberOfAttributes, + maxNumberOfEvents, + maxNumberOfLinks, + maxNumberOfAttributesPerEvent, + maxNumberOfAttributesPerLink, + maxAttributeValueLength + ) + } + +} diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/TracerSharedState.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/TracerSharedState.scala index fcb2dd102..67bfb4fd0 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/TracerSharedState.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/TracerSharedState.scala @@ -23,6 +23,7 @@ import org.typelevel.otel4s.sdk.trace.samplers.Sampler private final case class TracerSharedState[F[_]]( idGenerator: IdGenerator[F], resource: TelemetryResource, + spanLimits: SpanLimits, sampler: Sampler, spanProcessor: SpanProcessor[F] ) diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigure.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigure.scala index 20e87cbd6..50f81d7b2 100644 --- a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigure.scala +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigure.scala @@ -26,6 +26,7 @@ import cats.syntax.functor._ import org.typelevel.otel4s.sdk.autoconfigure.AutoConfigure import org.typelevel.otel4s.sdk.autoconfigure.Config import org.typelevel.otel4s.sdk.autoconfigure.ConfigurationError +import org.typelevel.otel4s.sdk.trace.exporter.LoggingSpanExporter import org.typelevel.otel4s.sdk.trace.exporter.SpanExporter /** Autoconfigures [[SpanExporter]]s. @@ -52,7 +53,8 @@ private final class SpanExportersAutoConfigure[F[_]: MonadThrow: Console]( private val configurers = { val default: Set[AutoConfigure.Named[F, SpanExporter[F]]] = Set( - AutoConfigure.Named.const(Const.NoneExporter, SpanExporter.noop[F]) + AutoConfigure.Named.const(Const.NoneExporter, SpanExporter.noop[F]), + AutoConfigure.Named.const(Const.LoggingExporter, LoggingSpanExporter[F]) ) default ++ extra diff --git a/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/exporter/LoggingSpanExporter.scala b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/exporter/LoggingSpanExporter.scala new file mode 100644 index 000000000..78d88065a --- /dev/null +++ b/sdk/trace/src/main/scala/org/typelevel/otel4s/sdk/trace/exporter/LoggingSpanExporter.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Typelevel + * + * 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 org.typelevel.otel4s.sdk.trace.exporter + +import cats.Applicative +import cats.Foldable +import cats.effect.std.Console +import cats.syntax.foldable._ +import org.typelevel.otel4s.sdk.trace.data.SpanData + +private final class LoggingSpanExporter[F[_]: Applicative: Console] + extends SpanExporter[F] { + + val name: String = "LoggingSpanExporter" + + def exportSpans[G[_]: Foldable](spans: G[SpanData]): F[Unit] = { + def log(span: SpanData): F[Unit] = { + val scope = span.instrumentationScope + + val content = s"'${span.name}' : " + + s"${span.spanContext.traceIdHex} ${span.spanContext.spanIdHex} ${span.kind} " + + s"[tracer: ${scope.name}:${scope.version.getOrElse("")}] " + + s"${span.attributes}" + + Console[F].println(s"LoggingSpanExporter: $content") + } + + spans.traverse_(span => log(span)) + } + + def flush: F[Unit] = Applicative[F].unit + +} + +object LoggingSpanExporter { + + def apply[F[_]: Applicative: Console]: SpanExporter[F] = + new LoggingSpanExporter[F] + +} diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackendSuite.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackendSuite.scala index a812ac34f..61a2a19dd 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackendSuite.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBackendSuite.scala @@ -320,9 +320,11 @@ class SdkSpanBackendSuite extends CatsEffectSuite with ScalaCheckEffectSuite { Defaults.resource, kind, parentCtx, + Defaults.spanLimits, Defaults.spanProcessor, attributes, links, + 0, userStartTimestamp ) _ <- assertIO(span.toSpanData, expected(None)) @@ -433,6 +435,7 @@ class SdkSpanBackendSuite extends CatsEffectSuite with ScalaCheckEffectSuite { kind: SpanKind = Defaults.kind, parentSpanContext: Option[SpanContext] = None, attributes: Attributes = Defaults.attributes, + spanLimits: SpanLimits = Defaults.spanLimits, spanProcessor: SpanProcessor[IO] = Defaults.spanProcessor, links: Vector[LinkData] = Vector.empty, userStartTimestamp: Option[FiniteDuration] = None @@ -444,9 +447,11 @@ class SdkSpanBackendSuite extends CatsEffectSuite with ScalaCheckEffectSuite { resource = resource, kind = kind, parentContext = parentSpanContext, + spanLimits = spanLimits, processor = spanProcessor, attributes = attributes, links = links, + totalRecordedLinks = 0, userStartTimestamp = userStartTimestamp ) } @@ -458,6 +463,7 @@ class SdkSpanBackendSuite extends CatsEffectSuite with ScalaCheckEffectSuite { val resource = TelemetryResource.default val kind = SpanKind.Client val attributes = Attributes.empty + val spanLimits = SpanLimits.Default val spanProcessor = SpanProcessor.noop[IO] } diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilderSuite.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilderSuite.scala index 1d8532180..09f568dde 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilderSuite.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkSpanBuilderSuite.scala @@ -146,6 +146,7 @@ class SdkSpanBuilderSuite extends CatsEffectSuite with ScalaCheckEffectSuite { TracerSharedState( IdGenerator.random[IO], TelemetryResource.default, + SpanLimits.Default, sampler, SimpleSpanProcessor(exporter) ) diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkTracesSuite.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkTracesSuite.scala index fb7393fbf..c6c986bc8 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkTracesSuite.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/SdkTracesSuite.scala @@ -267,7 +267,7 @@ class SdkTracesSuite extends CatsEffectSuite { exporter: String = "SpanExporter.Noop" ) = "SdkTraces{tracerProvider=" + - s"SdkTracerProvider{resource=$resource, sampler=$sampler, " + + s"SdkTracerProvider{resource=$resource, spanLimits=${SpanLimits.Default}, sampler=$sampler, " + "spanProcessor=SpanProcessor.Multi(" + s"BatchSpanProcessor{exporter=$exporter, scheduleDelay=5 seconds, exporterTimeout=30 seconds, maxQueueSize=2048, maxExportBatchSize=512}, " + "SpanStorage)}, " + diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigureSuite.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigureSuite.scala index 370de01fe..177c7acfe 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigureSuite.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/SpanExportersAutoConfigureSuite.scala @@ -117,7 +117,7 @@ class SpanExportersAutoConfigureSuite extends CatsEffectSuite { .map(_.leftMap(_.getMessage)) .assertEquals( Left("""Cannot autoconfigure [SpanExporters]. - |Cause: Unrecognized value for [otel.traces.exporter]: aws-xray. Supported options [none, otlp]. + |Cause: Unrecognized value for [otel.traces.exporter]: aws-xray. Supported options [none, logging, otlp]. |Config: |1) `otel.traces.exporter` - aws-xray""".stripMargin) ) diff --git a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/TracerProviderAutoConfigureSuite.scala b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/TracerProviderAutoConfigureSuite.scala index 165e54f60..4a590a67e 100644 --- a/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/TracerProviderAutoConfigureSuite.scala +++ b/sdk/trace/src/test/scala/org/typelevel/otel4s/sdk/trace/autoconfigure/TracerProviderAutoConfigureSuite.scala @@ -32,6 +32,7 @@ import org.typelevel.otel4s.sdk.autoconfigure.AutoConfigure import org.typelevel.otel4s.sdk.autoconfigure.Config import org.typelevel.otel4s.sdk.context.Context import org.typelevel.otel4s.sdk.trace.SdkTracerProvider +import org.typelevel.otel4s.sdk.trace.SpanLimits import org.typelevel.otel4s.sdk.trace.context.propagation.W3CBaggagePropagator import org.typelevel.otel4s.sdk.trace.context.propagation.W3CTraceContextPropagator import org.typelevel.otel4s.sdk.trace.data.LinkData @@ -169,20 +170,19 @@ class TracerProviderAutoConfigureSuite extends CatsEffectSuite { ) ) - val logging: SpanExporter[IO] = customExporter("LoggingExporter") val custom: SpanExporter[IO] = customExporter("CustomExporter") val configurers: Set[AutoConfigure.Named[IO, SpanExporter[IO]]] = Set( - AutoConfigure.Named.const("logging", logging), AutoConfigure.Named.const("custom", custom) ) val expected = "SdkTracerProvider{" + s"resource=${TelemetryResource.empty}, " + + s"spanLimits=${SpanLimits.Default}, " + s"sampler=${Sampler.AlwaysOff}, " + "spanProcessor=SpanProcessor.Multi(" + - "SimpleSpanProcessor{exporter=LoggingExporter, exportOnlySampled=true}, " + + "SimpleSpanProcessor{exporter=LoggingSpanExporter, exportOnlySampled=true}, " + "BatchSpanProcessor{exporter=CustomExporter, scheduleDelay=5 seconds, exporterTimeout=30 seconds, maxQueueSize=2048, maxExportBatchSize=512}, " + "SpanStorage)}" @@ -228,6 +228,7 @@ class TracerProviderAutoConfigureSuite extends CatsEffectSuite { ) = "SdkTracerProvider{" + s"resource=$resource, " + + s"spanLimits=${SpanLimits.Default}, " + s"sampler=$sampler, " + "spanProcessor=SpanProcessor.Multi(" + s"BatchSpanProcessor{exporter=$exporter, scheduleDelay=5 seconds, exporterTimeout=30 seconds, maxQueueSize=2048, maxExportBatchSize=512}, " +