如何避免调用挂起函数时Micrometer观测被终止?
核心问题:协程挂起导致观测提前终止
Kotlin suspend函数编译后会被转换为带有Continuation参数的普通方法,而Micrometer 1.10.3版本的ObservedAspect并未针对协程挂起场景做适配——当执行到await()挂起点时,切面会误以为方法已经执行完毕,直接终止观测并关闭作用域,后续协程恢复后的代码就脱离了原有观测上下文。
解决方案
1. 升级Micrometer版本(优先推荐)
Micrometer在1.11.x及以上版本中优化了对Kotlin协程suspend函数的观测支持,专门处理了挂起函数的生命周期,确保观测会持续到协程完全执行结束。只需将依赖版本升级到1.11.0或更高:
// build.gradle.kts implementation("io.micrometer:micrometer-core:1.11.0")
升级后,@Observed注解可以正常作用于suspend函数,观测会覆盖整个协程的执行周期,包括await()之后的代码。
2. 手动管理观测作用域(无法升级版本时)
如果暂时无法升级,放弃使用@Observed注解,改用Micrometer的ObservationAPI手动控制观测生命周期,确保观测上下文在协程挂起恢复后依然有效:
import io.micrometer.observation.Observation import io.micrometer.observation.ObservationRegistry import kotlinx.coroutines.withContext class YourService(private val observationRegistry: ObservationRegistry) { suspend fun yourSuspendFunction() { // 创建并启动观测 val observation = Observation.createNotStarted("your.observation.name", observationRegistry) .start() try { // 将观测上下文绑定到协程上下文 withContext(observation.asContextElement()) { // 执行包含await的异步逻辑 val result = someAsyncOperation().await() // await后的代码仍处于观测上下文内 doSomethingAfterAwait(result) } } finally { // 确保观测最终正常结束 observation.stop() } } }
通过observation.asContextElement()将观测上下文注入协程上下文,保证挂起恢复后代码依然能关联到同一个观测实例。
关于返回类型判定为Object的问题
这是Kotlin suspend函数的编译特性导致的:所有suspend函数编译后的Java字节码签名中,返回类型都是java.lang.Object(实际返回值通过Continuation回调传递),因此ObservedAspect无法识别其为CompletionStage——哪怕你声明函数返回CompletionStage,编译后的字节码依然会被转换为带Continuation参数的Object返回方法。
升级Micrometer到1.11+版本后,这个问题会被间接解决,因为新版本不再依赖返回类型判断观测生命周期,而是直接适配suspend函数的编译结构。
内容的提问来源于stack exchange,提问作者Charles Lee

