我们正在使用带有 Play 框架的 New Relic Java 代理,当执行传递到另一个线程时,在跟踪事务段/详细信息时遇到一些麻烦。
考虑这段代码:
public class SomeController {
private final com.google.common.util.concurrent.TimeLimiter timeLimiter = SimpleTimeLimiter.create(
Executors.newCachedThreadPool(
new ThreadFactoryBuilder()
.setNameFormat("SomePrefix-%d")
.setDaemon(true)
.build()
)
);
@Trace(async = true)
private Result someMethodInternal(Http.Request request, com.newrelic.api.agent.Token token) {
token.link();
... business logic here ...
}
public Result someMethod(Http.Request request) {
var token = NewRelic.getAgent().getTransaction().getToken();
try {
return timeLimiter.callWithTimeout(
() -> someMethodInternal(token),
1L, TimeUnit.MINUTES
);
} finally {
token.expire();
}
}
}
在 New Relic 中查看时,
someMethod
交易可见如下(注意缺少段/详细信息)。
如果不使用
timeLimiter
,这将显示执行内部调用(例如数据库等)所花费时间的不同部分:
NewRelic 的
Token::link
的文档表明 @Trace(async=true)
是上述代码中存在的内部方法所必需的。
TimeLimiter
有自己的线程来控制执行,但对我来说,它应该没问题,因为我们正确地传递了令牌。
这里还可能缺少什么?
尝试删除
token.expire()
的finally块中的someMethod
,并将token.link()
中的someMethodInternal
更改为token.linkAndExpire()
。
当令牌过期时,它就不能再用于链接线程代码。最后块很可能在
someMethodInternal
之前执行。
您是对的,您需要
@Trace(async = true)
。
当您不使用
TimeLimiter
时,代码将在单个线程中执行,并且代理能够跟踪所有活动。现在,不支持TimeLimiter
,它将执行移动到不同的线程。发生这种情况时,代理不会收到通知,因此不会跟踪活动。
完成这项工作的一种可重用方法是为您的可运行程序创建一个包装器。
public class TokenRunnable implements Runnable {
private Token token;
private Runnable runnable;
public TokenRunnable(Runnable runnable) {
this.runnable = runnable;
this.token = NewRelic.getAgent().getTransaction().getToken();
}
@Trace(async = true)
public void run() {
token.linkAndExpire();
// this sets the name of the segment to your runnable class
NewRelic.getAgent().getTracedMethod().setMetricName("Java", runnable.getClass().getName(), "run");
// might want to add null check here
this.runnable.run();
}
}
然后不需要
@Trace
也不需要令牌链接,只需调用 new TokenRunnable(...)
timeLimiter.callWithTimeout(
new TokenRunnable(() -> someMethodInternal(token)),
1L, TimeUnit.MINUTES
);