otel4s icon indicating copy to clipboard operation
otel4s copied to clipboard

Unseal SpanProcessor and other traits

Open rochala opened this issue 3 months ago • 2 comments

Hey, I'm currently implementing async-profiler profiling data to be linked with the traces. This is one case in which custom SpanProcessor may actually be useful to be provided. Obviously we can work around this by writing a wrapper around TracerProvider to call required methods by ourselves. See:

https://github.com/grafana/otel-profiling-java/blob/6ddc19fe2e37ad1b60ec26baf613a049c2be30e5/src/main/java/io/otel/pyroscope/PyroscopeOtelSpanProcessor.java#L8

  class ProfilingSpanProcessor(localSpan: LocalSpan) extends SpanProcessor[IO] {

    override def name: String = "ProfilingSpanProcessor"

    override def onStart: OnStart[IO] = new OnStart[IO] {
      override def apply(parentContext: Option[SpanContext], span: SpanRef[IO]): IO[Unit] = {
        val spanIdStr = span.context.spanIdHex
        val spanId = java.lang.Long.parseUnsignedLong(spanIdStr, 16)
        for {
          _ <- span.addAttributes(Seq(Attribute("pyroscope.profile.id", spanIdStr)))
          _ <- IO(localSpan.set(Some(TraceSpan(spanId))))
        } yield ()
      }
    }

    override def onEnd: OnEnd[IO] = new OnEnd[IO] {
      override def apply(span: SpanData): IO[Unit] = IO(localSpan.set(None))
    }

    override def forceFlush: IO[Unit] = IO.unit

  }

I've also created a discussion that shows the concrete use case.

rochala avatar Sep 23 '25 17:09 rochala

Which backend do you plan to use?

  • otel4s-oteljava (backed by https://github.com/open-telemetry/opentelemetry-java)
  • otel4s-sdk (our own implementation of OpenTelemetry)?

If you plan to use otel4s-oteljava, you can use https://github.com/grafana/otel-profiling-java almost directly. However, if you plan to use otel4s-sdk, then we should unseal the interface.

iRevive avatar Sep 24 '25 07:09 iRevive

I think in cats-effect runtime we can't use otel-profiling-java because the spanId assignment to async-profiler tracing context won't guarantee us that all of those computations will be run on the same Thread, thus the context won't stay correct.

E.g

Tracer[IO].span("do-something") {
  // at this place onStart from otel-profiling-java will trigger setting the context
  IO(doSomething) >> IO.blocking(blockingOp) >> IO(doSomething)
}

I'm quite new to cats, but according to my understanding of runtime, there are no guarantees that each of those IO's will be run on the same Thread (it is preferred but not guaranteed). That means if IO(doSomething) is run on different thread it won't have the tracing context of span set correctly.

rochala avatar Sep 24 '25 09:09 rochala