nexora-tracing
Tracer and Span interfaces. The default implementation is a no-op; swap in an OpenTelemetry-backed implementation without touching any call sites.
Interfaces
Tracer
public interface Tracer {
Span startSpan(String operationName, TraceContext parent);
}
Span
public interface Span {
void setAttribute(String key, String value);
void setStatus(SpanStatus status);
void end();
}
SpanStatus values: OK, ERROR.
Default: NoopTracer
NoopTracer.INSTANCE is used when no tracer is configured. All calls are no-ops. Zero overhead.
Using OpenTelemetry
Add the OpenTelemetry SDK and implement the two interfaces:
public class OtelTracer implements com.nexora.tracing.Tracer {
private final io.opentelemetry.api.trace.Tracer otelTracer;
public OtelTracer(OpenTelemetry otel) {
this.otelTracer = otel.getTracer("nexora");
}
@Override
public Span startSpan(String operationName, TraceContext parent) {
Context ctx = extractContext(parent); // propagate W3C traceparent
io.opentelemetry.api.trace.Span span = otelTracer
.spanBuilder(operationName)
.setParent(ctx)
.startSpan();
return new OtelSpan(span);
}
}
public class OtelSpan implements com.nexora.tracing.Span {
private final io.opentelemetry.api.trace.Span inner;
@Override
public void setAttribute(String key, String value) {
inner.setAttribute(key, value);
}
@Override
public void setStatus(SpanStatus status) {
inner.setStatus(status == SpanStatus.OK
? StatusCode.OK : StatusCode.ERROR);
}
@Override
public void end() { inner.end(); }
}
Register it on the engine:
NexoraEngine engine = NexoraEngine.builder()
.withTracer(new OtelTracer(GlobalOpenTelemetry.get()))
.build();
TraceContext
Carries traceId and spanId strings. Created at the start of each execution and propagated through every ExecutionContext. Accessible inside capabilities:
public CapabilityResult execute(CapabilityRequest request) {
String traceId = request.context().getTraceContext().traceId();
MDC.put("trace_id", traceId); // attach to log context
...
}