Lifecycle Hooks
The CommandTracingListener interface lets you hook into command lifecycle events without modifying the tracing library itself.
Use cases
-
Setting
cmdUuidandcmdSourceRefin MDC for log correlation -
Starting and stopping OpenTelemetry spans
-
Collecting custom metrics per command
Interface
public interface CommandTracingListener {
default void onCommandStarted(Command command) {}
default void onCommandCompleted(Command command) {}
default void onCommandFailed(Command command, Throwable error) {}
default void onCommandFailed(Command command, Problem problem) {}
}
All methods have default no-op implementations — implement only what you need.
The two onCommandFailed overloads
The Throwable overload is called in the interceptor’s direct tracing path (non-HTTP and spawned commands), where the original exception is available.
The Problem overload is called in the HTTP filter path, where the exception has already been serialized to a problem+json response.
By default, the Problem overload delegates to the Throwable overload via problem.toThrowable(), so listeners that only implement the Throwable variant still receive failure notifications from both paths.
Example: MDC integration
import jakarta.inject.Singleton;
import org.slf4j.MDC;
import no.conta.command.Command;
import no.conta.command.tracing.CommandTracingListener;
import no.conta.problem.Problem;
@Singleton
public class MdcCommandTracingListener implements CommandTracingListener {
@Override
public void onCommandStarted(Command command) {
MDC.put("cmdUuid", String.valueOf(command.getCmdUuid()));
MDC.put("cmdSourceRef", command.getCmdSourceRef());
}
@Override
public void onCommandCompleted(Command command) {
MDC.remove("cmdUuid");
MDC.remove("cmdSourceRef");
}
@Override
public void onCommandFailed(Command command, Throwable error) {
MDC.remove("cmdUuid");
MDC.remove("cmdSourceRef");
}
}
Register as a Micronaut bean (@Singleton) and the interceptor and filter will discover it automatically.
How it works
The listener callbacks fire at different points depending on the execution context:
- Non-HTTP path (direct tracing)
-
The interceptor calls all three callbacks directly:
onCommandStartedbefore execution,onCommandCompletedafter success, andonCommandFailed(Command, Throwable)on exception. - HTTP-delegated path
-
onCommandStartedfires in the interceptor beforeproceed().onCommandCompletedoronCommandFailed(Command, Problem)fires inCommandTracingFilterwhen the response is returned. Both depend onCommandTracingContextpropagation — the same constraint that applies to event publishing.