From 6859213d1fa7ea90a530c56ca3852f0ca384ba92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCder=2C=20Lena?= Date: Wed, 11 Jul 2018 16:18:47 +0200 Subject: [PATCH] added opentracing headers support, fixes #287 --- src/main/java/org/restheart/Bootstrapper.java | 34 ++++---- .../TracingInstrumentationHandler.java | 84 +++++++++++++++++++ src/main/resources/logback.xml | 2 +- .../TracingInstrumentationHandlerTest.java | 18 ++++ 4 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/restheart/handlers/TracingInstrumentationHandler.java create mode 100644 src/test/java/org/restheart/handlers/TracingInstrumentationHandlerTest.java diff --git a/src/main/java/org/restheart/Bootstrapper.java b/src/main/java/org/restheart/Bootstrapper.java index 67ff5f23e..5e16bec19 100644 --- a/src/main/java/org/restheart/Bootstrapper.java +++ b/src/main/java/org/restheart/Bootstrapper.java @@ -17,6 +17,7 @@ */ package org.restheart; +import org.restheart.handlers.TracingInstrumentationHandler; import org.restheart.init.Initializer; import com.mongodb.MongoClient; import static com.sun.akuma.CLibrary.LIBC; @@ -811,6 +812,7 @@ private static GracefulShutdownHandler getHandlersPipe(final AuthenticationMecha .allMatch(url -> !isPathTemplate(url)); final PipedHttpHandler baseChain = new MetricsInstrumentationHandler( + new TracingInstrumentationHandler( new RequestLoggerHandler( new CORSHandler( new OptionsHandler( @@ -819,7 +821,7 @@ private static GracefulShutdownHandler getHandlersPipe(final AuthenticationMecha coreHandlerChain, authenticationMechanism, identityManager, - accessManager)))))); + accessManager))))))); if (!allPathTemplates && !allPaths) { LOGGER.error("No mongo resource mounted! Check your mongo-mounts. where url must be either all absolute paths or all path templates"); @@ -1066,22 +1068,24 @@ private static void pipeApplicationLogicHandlers( new BodyInjectorHandler(alHandler)); if (alSecured) { - paths.addPrefixPath("/_logic" + alWhere, new RequestLoggerHandler( - new CORSHandler( - new SecurityHandlerDispacher( - handler, - authenticationMechanism, - identityManager, - accessManager)))); + paths.addPrefixPath("/_logic" + alWhere, new TracingInstrumentationHandler( + new RequestLoggerHandler( + new CORSHandler( + new SecurityHandlerDispacher( + handler, + authenticationMechanism, + identityManager, + accessManager))))); } else { paths.addPrefixPath("/_logic" + alWhere, - new RequestLoggerHandler( - new CORSHandler( - new SecurityHandlerDispacher( - handler, - authenticationMechanism, - identityManager, - new FullAccessManager())))); + new TracingInstrumentationHandler( + new RequestLoggerHandler( + new CORSHandler( + new SecurityHandlerDispacher( + handler, + authenticationMechanism, + identityManager, + new FullAccessManager()))))); } LOGGER.info("URL {} bound to application logic handler {}." diff --git a/src/main/java/org/restheart/handlers/TracingInstrumentationHandler.java b/src/main/java/org/restheart/handlers/TracingInstrumentationHandler.java new file mode 100644 index 000000000..cfa9430e0 --- /dev/null +++ b/src/main/java/org/restheart/handlers/TracingInstrumentationHandler.java @@ -0,0 +1,84 @@ +package org.restheart.handlers; + +import com.google.common.annotations.VisibleForTesting; + +import com.codahale.metrics.MetricRegistry; + +import org.restheart.Bootstrapper; +import org.restheart.Configuration; +import org.restheart.db.DbsDAO; +import org.restheart.utils.SharedMetricRegistryProxy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderValues; +import io.undertow.util.HexConverter; +import io.undertow.util.HttpString; + +import static org.restheart.Configuration.METRICS_GATHERING_LEVEL.COLLECTION; +import static org.restheart.Configuration.METRICS_GATHERING_LEVEL.DATABASE; +import static org.restheart.Configuration.METRICS_GATHERING_LEVEL.ROOT; + +/** + * Handler to write tracing headers to the logging MDC. Pick it up via the default way with "%X{name}", e.g. "%X{x-b3-traceid}". + */ +public class TracingInstrumentationHandler extends PipedHttpHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(TracingInstrumentationHandler.class); + public static final String X_B3_TRACEID = "x-b3-traceid"; + public static final String UBER_TRACEID = "uber-trace-id"; + public static final String X_B3_SPANID = "x-b3-spanid"; + public static final String X_B3_PARENTSPANID = "x-b3-parentspanid"; + public static final String X_B3_SAMPLED = "x-b3-sampled"; + + public TracingInstrumentationHandler(PipedHttpHandler next) { + super(next); + } + + @VisibleForTesting + static String generateRandomTraceId() { + byte[] data = new byte[8]; + new Random().nextBytes(data); + return HexConverter.convertToHexString(data); + } + + @Override + public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception { + //header definitions can be found at https://github.com/openzipkin/b3-propagation + //additionally, there is support for incoming trace headers from jaeger + String traceID = Optional.ofNullable(exchange.getRequestHeaders().get(X_B3_TRACEID)) + .map(HeaderValues::peekFirst) + //support for jaeger trace header + .orElse(Optional.ofNullable(exchange.getRequestHeaders().get(UBER_TRACEID)) + .map(HeaderValues::peekFirst) + .orElse(generateRandomTraceId())); + MDC.put(X_B3_TRACEID, traceID); + String spanID = Optional.ofNullable(exchange.getRequestHeaders().get(X_B3_SPANID)) + .map(HeaderValues::peekFirst) + .orElse(generateRandomTraceId()); + MDC.put(X_B3_SPANID, spanID); + String parentSpanID = Optional.ofNullable(exchange.getRequestHeaders().get(X_B3_PARENTSPANID)) + .map(HeaderValues::peekFirst) + .orElse(""); + MDC.put(X_B3_PARENTSPANID, parentSpanID); + String sampled = Optional.ofNullable(exchange.getRequestHeaders().get(X_B3_SAMPLED)) + .map(HeaderValues::peekFirst) + .orElse("0"); + MDC.put(X_B3_SAMPLED, sampled); + + exchange.getResponseHeaders().put(HttpString.tryFromString(X_B3_TRACEID), traceID) + .put(HttpString.tryFromString(UBER_TRACEID), traceID) + .put(HttpString.tryFromString(X_B3_SPANID), spanID) + .put(HttpString.tryFromString(X_B3_PARENTSPANID), parentSpanID) + .put(HttpString.tryFromString(X_B3_SAMPLED), sampled); + + if (!exchange.isResponseComplete() && getNext() != null) { + next(exchange, context); + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 1f42ad6dd..e1ff5e7c7 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -21,7 +21,7 @@ true - %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n + %d{HH:mm:ss.SSS} [%thread / %X{x-b3-traceid}] %highlight(%-5level) %logger{36} - %msg%n diff --git a/src/test/java/org/restheart/handlers/TracingInstrumentationHandlerTest.java b/src/test/java/org/restheart/handlers/TracingInstrumentationHandlerTest.java new file mode 100644 index 000000000..85d5a5e69 --- /dev/null +++ b/src/test/java/org/restheart/handlers/TracingInstrumentationHandlerTest.java @@ -0,0 +1,18 @@ +package org.restheart.handlers; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TracingInstrumentationHandlerTest { + + @Test + public void generateRandomTraceId() { + for (int i=0; i<100; i++) { + String traceId = TracingInstrumentationHandler.generateRandomTraceId(); + String regex = "[a-f0-9]+"; + assertTrue(traceId + "did not match " + regex, traceId.matches(regex)); + assertEquals(traceId + " did not have length 16!", 16, traceId.length()); + } + } +} \ No newline at end of file