Skip to content

Commit

Permalink
Support OTel Resource via config within SpanExporter adapter (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
HaloFour committed Aug 8, 2023
1 parent 37e1225 commit b9e1862
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 7 deletions.
Expand Up @@ -24,14 +24,16 @@ import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.trace.ReadableSpan
import io.opentelemetry.sdk.trace.data.{ EventData, LinkData, SpanData, StatusData }
import io.opentelemetry.api.trace.{ SpanContext, SpanKind, TraceState, Span => OtelSpan }
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes

import scala.collection.JavaConverters._

private[otel] class MoneyReadableSpanData(info: SpanInfo) extends ReadableSpan with SpanData {
private[otel] class MoneyReadableSpanData(info: SpanInfo, resourceAttributes: Attributes) extends ReadableSpan with SpanData {
private val id = info.id
private lazy val spanContext = id.toSpanContext
private lazy val parentSpanContext = convertParentSpanContext(info.id)
private lazy val libraryInfo = convertLibraryInfo(info.library)
private lazy val resource = convertResource(info, resourceAttributes)
private lazy val attributes = convertAttributes(info.notes)
private lazy val events = convertEvents(info.events)
private lazy val links = convertLinks(info.links)
Expand All @@ -45,7 +47,7 @@ private[otel] class MoneyReadableSpanData(info: SpanInfo) extends ReadableSpan w
override def getLatencyNanos: Long = info.durationNanos
override def getTraceId: String = id.traceIdAsHex
override def getSpanId: String = id.selfIdAsHex
override def getResource: Resource = Resource.getDefault
override def getResource: Resource = resource
override def getKind: SpanKind = info.kind
override def getStartEpochNanos: Long = info.startTimeNanos
override def getLinks: util.List[LinkData] = links
Expand All @@ -72,6 +74,18 @@ private[otel] class MoneyReadableSpanData(info: SpanInfo) extends ReadableSpan w
InstrumentationLibraryInfo.empty
}

private def convertResource(info: SpanInfo, resourceAttributes: Attributes): Resource = {
val library = info.library()
Resource.create(Attributes.builder()
.put(ResourceAttributes.SERVICE_NAME, info.appName())
.put(ResourceAttributes.TELEMETRY_SDK_NAME, library.name())
.put(ResourceAttributes.TELEMETRY_SDK_VERSION, library.version())
.put(ResourceAttributes.TELEMETRY_SDK_LANGUAGE, "scala")
.put(ResourceAttributes.HOST_NAME, info.host())
.putAll(resourceAttributes)
.build())
}

private def appendNoteToBuilder[T](builder: AttributesBuilder, note: Note[T]): AttributesBuilder =
builder.put(note.key, note.value)

Expand Down
Expand Up @@ -18,9 +18,11 @@ package com.comcast.money.otel.handlers

import com.comcast.money.api.{ SpanHandler, SpanInfo }
import com.typesafe.config.{ Config, ConfigFactory }
import io.opentelemetry.api.common.{ AttributeKey, Attributes }
import io.opentelemetry.sdk.trace.SpanProcessor
import io.opentelemetry.sdk.trace.`export`.{ BatchSpanProcessor, SimpleSpanProcessor, SpanExporter }

import scala.collection.JavaConverters._
import java.time.Duration

/**
Expand All @@ -29,6 +31,7 @@ import java.time.Duration
*/
abstract class OtelSpanHandler(config: Config) extends SpanHandler {

private[otel] val resourceAttributes: Attributes = createResourceAttributes(config)
private[otel] val exporter: SpanExporter = createSpanExporter(getExporterConfig(config))
private[otel] val processor: SpanProcessor = createSpanProcessor(exporter, config)

Expand All @@ -38,7 +41,24 @@ abstract class OtelSpanHandler(config: Config) extends SpanHandler {
* @param span `SpanInfo` that contains the information for the completed span
*/
override def handle(span: SpanInfo): Unit = {
processor.onEnd(new MoneyReadableSpanData(span))
processor.onEnd(new MoneyReadableSpanData(span, resourceAttributes))
}

protected def createResourceAttributes(config: Config): Attributes = {

val resourceKey = "resource"
if (config.hasPath(resourceKey)) {
val attributes = Attributes.builder()
val resourceConfig = config.getConfig(resourceKey)
for (entry <- resourceConfig.entrySet().asScala) {
val key = entry.getKey
val value = resourceConfig.getString(key)
attributes.put(AttributeKey.stringKey(key), value)
}
attributes.build()
} else {
Attributes.empty()
}
}

protected def createSpanProcessor(spanExporter: SpanExporter, config: Config): SpanProcessor = {
Expand Down
Expand Up @@ -20,12 +20,15 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -137,6 +140,23 @@ public void configuresBatchSpanProcessor() {
assertThat(spanContext.getSpanId()).isEqualTo(spanId.selfIdAsHex());
}

@Test
public void configuresResourceAttributes() {

Config config = ConfigFactory.parseString(
"resource = {\n" +
" foo = \"bar\"\n" +
" container.name = \"money-core-autoconf\"\n" +
"}"
);

OtelSpanHandler underTest = new TestOtelSpanHandler(config);
assertThat(underTest.resourceAttributes()).isEqualTo(Attributes.of(
AttributeKey.stringKey("foo"), "bar",
ResourceAttributes.CONTAINER_NAME, "money-core-autoconf"
));
}

class TestOtelSpanHandler extends OtelSpanHandler {
public TestOtelSpanHandler(Config config) {
super(config);
Expand Down
Expand Up @@ -23,6 +23,7 @@ import io.opentelemetry.api.common.{ AttributeKey, Attributes }
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.api.trace.{ Span, SpanContext, SpanKind, StatusCode, TraceFlags, TraceState }
import io.opentelemetry.sdk.trace.data.StatusData
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

Expand All @@ -31,10 +32,11 @@ import scala.collection.JavaConverters._
class MoneyReadableSpanDataSpec extends AnyWordSpec with Matchers {
val spanId = SpanId.createFrom(UUID.fromString("01234567-890A-BCDE-F012-34567890ABCD"), 81985529216486895L, 81985529216486895L)
val childSpanId = SpanId.createFrom(UUID.fromString("01234567-890A-BCDE-F012-34567890ABCD"), 1147797409030816545L, 81985529216486895L)
val resourceAttributes = Attributes.of(AttributeKey.stringKey("foo"), "bar")

"MoneyReadableSpanDataSpec" should {
"wrap Money SpanInfo" in {
val underTest = new MoneyReadableSpanData(TestSpanInfo(spanId))
val underTest = new MoneyReadableSpanData(TestSpanInfo(spanId), resourceAttributes)

underTest.getInstrumentationLibraryInfo.getName shouldBe "test"
underTest.getTraceId shouldBe "01234567890abcdef01234567890abcd"
Expand All @@ -47,7 +49,13 @@ class MoneyReadableSpanDataSpec extends AnyWordSpec with Matchers {
underTest.hasEnded shouldBe true
underTest.getLinks.asScala should contain(MoneyLink(link))
underTest.getTotalRecordedLinks shouldBe 0
underTest.getResource shouldBe Resource.getDefault
underTest.getResource.getAttributes shouldBe Attributes.of(
ResourceAttributes.TELEMETRY_SDK_NAME, "test",
ResourceAttributes.TELEMETRY_SDK_VERSION, "0.0.1",
ResourceAttributes.TELEMETRY_SDK_LANGUAGE, "scala",
ResourceAttributes.SERVICE_NAME, "app",
ResourceAttributes.HOST_NAME, "host",
AttributeKey.stringKey("foo"), "bar")
underTest.getLatencyNanos shouldBe 2000000L
underTest.getStatus shouldBe StatusData.create(StatusCode.OK, "description")
underTest.getTotalAttributeCount shouldBe 1
Expand All @@ -59,7 +67,7 @@ class MoneyReadableSpanDataSpec extends AnyWordSpec with Matchers {
}

"wrap child Money SpanInfo" in {
val underTest = new MoneyReadableSpanData(TestSpanInfo(childSpanId))
val underTest = new MoneyReadableSpanData(TestSpanInfo(childSpanId), resourceAttributes)

underTest.getInstrumentationLibraryInfo.getName shouldBe "test"
underTest.getTraceId shouldBe "01234567890abcdef01234567890abcd"
Expand All @@ -72,7 +80,13 @@ class MoneyReadableSpanDataSpec extends AnyWordSpec with Matchers {
underTest.hasEnded shouldBe true
underTest.getLinks.asScala should contain(MoneyLink(link))
underTest.getTotalRecordedLinks shouldBe 0
underTest.getResource shouldBe Resource.getDefault
underTest.getResource.getAttributes shouldBe Attributes.of(
ResourceAttributes.TELEMETRY_SDK_NAME, "test",
ResourceAttributes.TELEMETRY_SDK_VERSION, "0.0.1",
ResourceAttributes.TELEMETRY_SDK_LANGUAGE, "scala",
ResourceAttributes.SERVICE_NAME, "app",
ResourceAttributes.HOST_NAME, "host",
AttributeKey.stringKey("foo"), "bar")
underTest.getLatencyNanos shouldBe 2000000L
underTest.getStatus shouldBe StatusData.create(StatusCode.OK, "description")
underTest.getTotalAttributeCount shouldBe 1
Expand Down

0 comments on commit b9e1862

Please sign in to comment.