1package dev.rafex.ether.http.jetty12;
29import java.time.Duration;
30import java.time.Instant;
31import java.util.logging.Level;
32import java.util.logging.Logger;
34import org.eclipse.jetty.server.Handler;
35import org.eclipse.jetty.server.Request;
36import org.eclipse.jetty.server.Response;
37import org.eclipse.jetty.util.Callback;
39import dev.rafex.ether.observability.core.request.RequestIdGenerator;
40import dev.rafex.ether.observability.core.timing.TimingRecorder;
41import dev.rafex.ether.observability.core.timing.TimingSample;
43final class JettyObservabilityHandler
extends Handler.Wrapper {
45 private static final Logger LOG = Logger.getLogger(JettyObservabilityHandler.class.getName());
47 static final String REQUEST_ID_ATTRIBUTE =
"ether.request.id";
48 private static final String REQUEST_ID_HEADER =
"X-Request-Id";
50 private final RequestIdGenerator requestIdGenerator;
51 private final TimingRecorder timingRecorder;
53 JettyObservabilityHandler(
final Handler next,
final RequestIdGenerator requestIdGenerator,
54 final TimingRecorder timingRecorder) {
56 this.requestIdGenerator = requestIdGenerator;
57 this.timingRecorder = timingRecorder;
61 public boolean handle(
final Request request,
final Response response,
final Callback callback)
throws Exception {
62 final var requestId = resolveRequestId(request);
63 request.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
64 response.getHeaders().put(REQUEST_ID_HEADER, requestId);
66 final var method = request.getMethod();
67 final var path = request.getHttpURI().getPath();
68 LOG.info(() ->
"[%s] → %s %s".formatted(requestId, method, path));
70 final var startedAt = Instant.now();
71 final var startNanos = System.nanoTime();
72 final var wrappedCallback =
new Callback() {
74 public void succeeded() {
75 final var elapsed = Duration.ofNanos(System.nanoTime() - startNanos);
76 LOG.info(() ->
"[%s] ← %s %s %d (%dms)".formatted(
77 requestId, method, path, response.getStatus(), elapsed.toMillis()));
82 public void failed(
final Throwable x) {
83 final var elapsed = Duration.ofNanos(System.nanoTime() - startNanos);
84 LOG.log(Level.WARNING, x, () ->
"[%s] ← %s %s FAILED (%dms)".formatted(
85 requestId, method, path, elapsed.toMillis()));
90 Request.addCompletionListener(request, failure -> timingRecorder.record(
new TimingSample(
91 method +
" " + path, startedAt, Instant.now())));
92 return super.handle(request, response, wrappedCallback);
95 private String resolveRequestId(
final Request request) {
96 final var incoming = request.getHeaders().get(REQUEST_ID_HEADER);
97 if (incoming !=
null && !incoming.isBlank()) {
100 return requestIdGenerator.nextId();
record TimingSample(String name, Instant startedAt, Instant finishedAt)
Muestra de tiempo para medir la duración de una operación.