Skip to content

Commit

Permalink
Merge pull request #116 from GuiaBolso/backport-otel-support
Browse files Browse the repository at this point in the history
Backport open telemetry support to v6.*.*
  • Loading branch information
cleidiano authored Aug 15, 2023
2 parents 24117b7 + a612c79 commit d29ccf4
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 0 deletions.
3 changes: 3 additions & 0 deletions impl/java/tracing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ dependencies {
// Open Tracing
api("io.opentracing:opentracing-api:0.33.0")
api("io.opentracing:opentracing-util:0.33.0")

// Open Telemetry
api("io.opentelemetry:opentelemetry-api:1.27.0")

// AspectJ
implementation("org.aspectj:aspectjweaver:1.9.7")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import br.com.guiabolso.tracing.context.ThreadContextManager
import br.com.guiabolso.tracing.engine.TracerEngine
import br.com.guiabolso.tracing.engine.datadog.DatadogStatsDTracer
import br.com.guiabolso.tracing.engine.datadog.DatadogTracer
import br.com.guiabolso.tracing.engine.opentelemetry.OpenTelemetryTracer
import br.com.guiabolso.tracing.engine.slf4j.Slf4JTracer

class TracerBuilder {
Expand All @@ -28,6 +29,11 @@ class TracerBuilder {
return this
}

fun withOpenTelemetryAPM(): TracerBuilder {
withEngine(OpenTelemetryTracer())
return this
}

fun withEngine(engine: TracerEngine): TracerBuilder {
tracerEngines.add(engine)
if (engine is ThreadContextManager<*>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package br.com.guiabolso.tracing.engine.opentelemetry

import br.com.guiabolso.tracing.context.ThreadContextManager
import br.com.guiabolso.tracing.engine.TracerEngine
import br.com.guiabolso.tracing.utils.opentelemetry.OpenTelemetryUtils
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.metrics.LongHistogram
import io.opentelemetry.api.metrics.Meter
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.context.Context
import java.io.Closeable
import java.util.concurrent.ConcurrentHashMap

@Suppress("TooManyFunctions")
class OpenTelemetryTracer : TracerEngine, ThreadContextManager<Span> {

override val type = Span::class.java

private val tracer: Tracer by lazy {
GlobalOpenTelemetry.getTracer(TRACER_NAME)
}

private val meter: Meter by lazy {
GlobalOpenTelemetry.getMeter(TRACER_NAME)
}

override fun setOperationName(name: String) {
val span = currentSpan()
span?.updateName(name)
}

override fun addProperty(key: String, value: String?) {
Span.current()?.addProperty(key, value)
}

override fun addRootProperty(key: String, value: String?) {
currentSpan()?.addProperty(key, value)
}

override fun addProperty(key: String, value: Number?) {
Span.current()?.addProperty(key, value)
}

override fun addRootProperty(key: String, value: Number?) {
currentSpan()?.addProperty(key, value)
}

override fun addProperty(key: String, value: Boolean?) {
Span.current()?.addProperty(key, value)
}

override fun addRootProperty(key: String, value: Boolean?) {
currentSpan()?.addProperty(key, value)
}

override fun addProperty(key: String, value: List<*>) {
val finalValue: String = value.joinToString(",")
addProperty(key, finalValue)
}

override fun <T> recordExecutionTime(name: String, block: (MutableMap<String, String>) -> T): T {
val start = System.currentTimeMillis()
val context = mutableMapOf<String, String>()
try {
return block(context)
} finally {
val elapsedTime = System.currentTimeMillis() - start
recordExecutionTime(name, elapsedTime, context)
}
}

override fun recordExecutionTime(name: String, elapsedTime: Long, context: Map<String, String>) {
val attributes = Attributes.builder()
for ((k, v) in context) {
attributes.put(k, v)
}
val lh = histogramCache.computeIfAbsent(name) {
meter.histogramBuilder(name).setUnit("ms").ofLongs().build()
}
lh.record(elapsedTime, attributes.build())
}

override fun notifyError(exception: Throwable, expected: Boolean) {
Span.current()?.let { span ->
OpenTelemetryUtils.notifyError(span, exception, expected)
}
}

override fun notifyRootError(exception: Throwable, expected: Boolean) {
currentSpan()?.let { span ->
OpenTelemetryUtils.notifyError(span, exception, expected)
}
}

override fun notifyError(message: String, params: Map<String, String?>, expected: Boolean) {
Span.current()?.let { span ->
OpenTelemetryUtils.notifyError(span, message, params, expected)
}
}

override fun notifyRootError(message: String, params: Map<String, String?>, expected: Boolean) {
currentSpan()?.let { span ->
OpenTelemetryUtils.notifyError(span, message, params, expected)
}
}

override fun clear() {}

override fun extract(): Span {
return Span.current()
}

override fun withContext(context: Span): Closeable {
val span = tracer.spanBuilder("asyncTask").setParent(Context.current().with(context)).startSpan()
val scope = span.makeCurrent()
return Closeable {
span.end()
scope.close()
}
}

private inline fun <reified T> Span.addProperty(key: String, value: T?) {
val attrKey = getAttributeKey<T>(key)
if (value != null) {
this.setAttribute(attrKey, value)
}
}

@Suppress("UNCHECKED_CAST")
private inline fun <reified T> getAttributeKey(key: String): AttributeKey<T> {
return keysMap.computeIfAbsent(key) { k: String ->
val tClass = T::class.java
when {
String::class.java.isAssignableFrom(tClass) -> AttributeKey.stringKey(k)
Double::class.java.isAssignableFrom(tClass) -> AttributeKey.doubleKey(k)
Float::class.java.isAssignableFrom(tClass) -> AttributeKey.doubleKey(k)
Int::class.java.isAssignableFrom(tClass) -> AttributeKey.longKey(k)
Long::class.java.isAssignableFrom(tClass) -> AttributeKey.longKey(k)
Number::class.java.isAssignableFrom(tClass) -> AttributeKey.doubleKey(k)
Boolean::class.java.isAssignableFrom(tClass) -> AttributeKey.booleanKey(k)
else -> error("Unsupported attribute type ${tClass.canonicalName}")
}
} as AttributeKey<T>
}

private fun currentSpan(): Span? = Span.current()

companion object {
const val TRACER_NAME = "events-tracing"
private val keysMap = ConcurrentHashMap<String, AttributeKey<*>>()
private val histogramCache = ConcurrentHashMap<String, LongHistogram>()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package br.com.guiabolso.tracing.utils.opentelemetry

internal class DefaultUnspecifiedException(message: String) : RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package br.com.guiabolso.tracing.utils.opentelemetry

import br.com.guiabolso.tracing.engine.opentelemetry.OpenTelemetryTracer.Companion.TRACER_NAME
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.api.trace.Tracer
import kotlinx.coroutines.runBlocking

object OpenTelemetryUtils {

private val tracer: Tracer by lazy {
GlobalOpenTelemetry.getTracer(TRACER_NAME)
}

@JvmStatic
@JvmOverloads
fun traceAsNewOperation(
name: String,
kind: SpanKind = SpanKind.SERVER,
func: () -> Unit
) = runBlocking {
coTraceAsNewOperation(name, kind, func)
}

suspend fun coTraceAsNewOperation(
name: String,
kind: SpanKind = SpanKind.SERVER,
func: suspend () -> Unit
) {
val span = tracer.spanBuilder(name)
.setSpanKind(kind)
.setNoParent()
.startSpan()!!
span.makeCurrent().use {
try {
func()
} catch (e: Exception) {
notifyError(span, e, false)
throw e
} finally {
span.end()
}
}
}

@JvmStatic
fun <T> traceBlock(name: String, func: () -> T): T = runBlocking {
suspendingTraceBlock(name) { func() }
}

suspend fun <T> suspendingTraceBlock(name: String, func: suspend () -> T): T {
val span = tracer.spanBuilder(name).startSpan()!!
val scope = span.makeCurrent()!!
return scope.use {
try {
func()
} catch (e: Exception) {
notifyError(span, e, false)
throw e
} finally {
span.end()
}
}
}

@JvmStatic
fun notifyError(span: Span, exception: Throwable, expected: Boolean) {
val status = if (expected) StatusCode.OK else StatusCode.ERROR
span.setStatus(status)
span.recordException(exception)
}

@JvmStatic
fun notifyError(span: Span, message: String, params: Map<String, String?>, expected: Boolean) {
val status = if (expected) StatusCode.OK else StatusCode.ERROR
span.setStatus(status)
val builder = Attributes.builder()
params.forEach { (key, value) ->
if (value != null) {
builder.put(key, value)
}
}
span.recordException(DefaultUnspecifiedException(message), builder.build())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import br.com.guiabolso.tracing.TracerImpl
import br.com.guiabolso.tracing.context.SimpleThreadContextManager
import br.com.guiabolso.tracing.engine.datadog.DatadogStatsDTracer
import br.com.guiabolso.tracing.engine.datadog.DatadogTracer
import br.com.guiabolso.tracing.engine.opentelemetry.OpenTelemetryTracer
import br.com.guiabolso.tracing.engine.slf4j.Slf4JTracer
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
Expand Down Expand Up @@ -37,6 +38,19 @@ class TracerBuilderTest {
assertTrue(tracer.contextManagers.first() is DatadogTracer)
}

@Test
fun `should create a tracer with opentelemtry APM`() {
val tracer = TracerBuilder().withOpenTelemetryAPM().build()

tracer as TracerImpl

assertEquals(1, tracer.engines.size)
assertTrue(tracer.engines.first() is OpenTelemetryTracer)

assertEquals(1, tracer.contextManagers.size)
assertTrue(tracer.contextManagers.first() is OpenTelemetryTracer)
}

@Test
fun `should create a tracer with datadog APM with statsD`() {
val tracer = TracerBuilder().withDatadogAPMAndStatsD("prefix", "localhost", 8080).build()
Expand Down

0 comments on commit d29ccf4

Please sign in to comment.