Skip to content

Instantly share code, notes, and snippets.

@Chuckame
Last active January 4, 2024 16:12
Show Gist options
  • Save Chuckame/014f3926c67c72f1555029550438fec6 to your computer and use it in GitHub Desktop.
Save Chuckame/014f3926c67c72f1555029550438fec6 to your computer and use it in GitHub Desktop.
Convenient way to trace a method call with opentelemetry (datadog by example) with an annotation using spring-aop
import io.opentracing.log.Fields
import io.opentracing.tag.Tags
import io.opentracing.util.GlobalTracer
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
/**
* Wraps the annotated method inside a span.
* By default, it adds a `resource.name` (conventional datadog resource tag name) where the tag's value will be `<className>.<methodName>`.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class CoTrace(
val resourceName: String = RESOURCE_NAME_FROM_SIGNATURE_PLACEHOLDER
)
private const val RESOURCE_NAME_FROM_SIGNATURE_PLACEHOLDER = "__signature__placeholder__"
private const val RESOURCE_NAME_DATADOG_TAG = "resource.name"
@Aspect
@Component
class CoroutineTracer {
@Around("@annotation(annotation) && args(.., kotlin.coroutines.Continuation)")
fun execute(joinPoint: ProceedingJoinPoint, annotation: CoTrace): Mono<Any?> = Mono.using(
{
val tracer = GlobalTracer.get()
val span = tracer.buildSpan("service.call")
.withTag(RESOURCE_NAME_DATADOG_TAG, annotation.resourceName.takeIf { it != RESOURCE_NAME_FROM_SIGNATURE_PLACEHOLDER } ?: "${joinPoint.signature.declaringType.simpleName}.${joinPoint.signature.name}")
.start()
val scope = tracer.activateSpan(span)
return@using span to scope
},
{ (span, _) ->
(joinPoint.proceed() as Mono<*>).doOnError {
span.setTag(Tags.ERROR, true)
span.log(mapOf(Fields.ERROR_OBJECT to it))
}
},
{ (span, scope) ->
scope.close()
span.finish()
}
)
}

Here is what we have in a spring application, in case of it can help someone. It uses an around aspect to intercept the suspendable method call, wrapping it inside a Mono.using to start and then stop the span accordingly. For sure it's not the best regarding performances (coroutine -> mono -> coroutine), but we accept it as the tracing is really important on our side.

⚠️ Don't forget to put @EnableAspectJAutoProxy on you main class !

Note that joinPoint.proceed() returns a Mono thanks to spring aop that wraps automatically coroutines to a Mono

Usage example:

@CoTrace
suspend fun aTracedMethod() {
  // ...
}

@CoTrace(resourceName = "here a custom resource name")
suspend fun anotherTracedMethod() {
  // ...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment