Tracing Spring asynchronous code with New Relic – a better way
14 Apr 2020 | spring-boot tracing new-relic javaIn my earlier post about tracing Spring asynchronous code with New Relic I showed a simple solution using a
subclass of ApplicationEvent
to carry a New Relic token. It has some disadvantages:
-
Code that uses it must explicitly declare New Relic tracing using the
@Trace
annotation, must create subclasses ofTracedEvent
and must call theTracedEvent#linkToken
method on the event object. -
Each token can only be expired once, even if an event is listened to by multiple listeners.
A better way
This method uses an implementation of java.util.concurrent.Executor
that wraps a
delegate instance.
-
The
NewRelicTraceExecutor#execute
method is called in the parent thread. It constructs aTracedRunnable
that wraps theRunnable
instance it is given. -
The
TracedRunnable#run
method is called in the child thread. It callsToken#linkAndExpire
method before callingrun
on its delegateRunnable
.
All the New Relic-specific code is in this one class, which can be wired into a Spring Boot
application to be used with ApplicationEventMulticaster
. Each event listener has its own
Runnable
instance with its own New Relic token.
package com.example.tracing;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.Trace;
import java.util.concurrent.Executor;
public class NewRelicTraceExecutor implements Executor {
private final Executor delegate;
public NewRelicTraceExecutor(Executor delegate) {
this.delegate = delegate;
}
@Override
public void execute(Runnable command) {
Token token = NewRelic.getAgent().getTransaction().getToken();
delegate.execute(new TracedRunnable(command, token));
}
static class TracedRunnable implements Runnable {
private final Runnable delegate;
private final Token token;
TracedRunnable(Runnable delegate, Token token) {
this.delegate = delegate;
this.token = token;
}
@Trace(async = true)
@Override
public void run() {
token.linkAndExpire();
delegate.run();
}
}
}
As before, there is a dependency on the New Relic API. In Gradle:
implementation 'com.newrelic.agent.java:newrelic-api:5.11.0'