Initial Contribution
Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/incubator/open-tracing/pom.xml b/incubator/open-tracing/pom.xml
new file mode 100644
index 0000000..f96771e
--- /dev/null
+++ b/incubator/open-tracing/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>project</artifactId>
+ <version>2.28-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.glassfish.jersey.incubator</groupId>
+ <artifactId>jersey-open-tracing</artifactId>
+ <packaging>jar</packaging>
+
+ <name>jersey-open-tracing</name>
+
+ <description>
+ Jersey support for OpenTracing.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.opentracing</groupId>
+ <artifactId>opentracing-util</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.sun.istack</groupId>
+ <artifactId>maven-istack-commons-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
new file mode 100644
index 0000000..d155760
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.monitoring.ApplicationEvent;
+import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
+import org.glassfish.jersey.server.monitoring.RequestEvent;
+import org.glassfish.jersey.server.monitoring.RequestEventListener;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Application event listener responsible for creating and propagating server-side request {@link io.opentracing.Span}.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingApplicationEventListener implements ApplicationEventListener {
+ private final Tracer globalTracer = GlobalTracer.get();
+ private final OpenTracingFeature.Verbosity verbosity;
+
+ /**
+ * Creates event listener instance with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}.
+ *
+ * @param verbosity desired verbosity level
+ */
+ public OpenTracingApplicationEventListener(OpenTracingFeature.Verbosity verbosity) {
+ this.verbosity = verbosity;
+ }
+
+ @Override
+ public void onEvent(ApplicationEvent event) {
+ // we don't care about the server lifecycle
+ }
+
+ @Override
+ public RequestEventListener onRequest(RequestEvent requestEvent) {
+ if (requestEvent.getType() == RequestEvent.Type.START) {
+ Span requestSpan = handleRequestStart(requestEvent.getContainerRequest());
+ return new OpenTracingRequestEventListener(requestSpan);
+ }
+ return null;
+ }
+
+ private Span handleRequestStart(ContainerRequest request) {
+
+ final Map<String, String> mappedHeaders = request
+ .getHeaders()
+ .entrySet()
+ .stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ (entry) -> OpenTracingUtils.formatList(entry.getValue())));
+
+ final SpanContext extractedContext =
+ globalTracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapExtractAdapter(mappedHeaders));
+
+ Tracer.SpanBuilder spanBuilder = globalTracer
+ .buildSpan(OpenTracingFeature.DEFAULT_REQUEST_SPAN_NAME)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+ .withTag(Tags.HTTP_METHOD.getKey(), request.getMethod())
+ .withTag(Tags.HTTP_URL.getKey(), request.getRequestUri().toASCIIString())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+ OpenTracingUtils.headersAsString(request.getHeaders()))
+ .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), request.hasEntity());
+
+ if (extractedContext != null) {
+ spanBuilder = spanBuilder.asChildOf(extractedContext);
+ }
+
+ final Span span = spanBuilder.startManual();
+ request.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+ span.log(LocalizationMessages.OPENTRACING_LOG_REQUEST_STARTED());
+ return span;
+ }
+
+ class OpenTracingRequestEventListener implements RequestEventListener {
+ private Span requestSpan;
+ private Span resourceSpan = null;
+
+ OpenTracingRequestEventListener(final Span requestSpan) {
+ this.requestSpan = requestSpan;
+ }
+
+ @Override
+ public void onEvent(RequestEvent event) {
+
+ switch (event.getType()) {
+ case MATCHING_START:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_MATCHING_STARTED());
+ break;
+
+ case LOCATOR_MATCHED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_LOCATOR_MATCHED(
+ OpenTracingUtils.formatList(event.getUriInfo().getMatchedResourceLocators())));
+ break;
+
+ case SUBRESOURCE_LOCATED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_SUBRESOURCE_LOCATED(
+ OpenTracingUtils.formatList(event.getUriInfo().getLocatorSubResources())));
+ break;
+
+
+ case REQUEST_MATCHED:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_MATCHED(event.getUriInfo()
+ .getMatchedResourceMethod()
+ .getInvocable()
+ .getDefinitionMethod()));
+ log(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_STARTED());
+ break;
+
+ case REQUEST_FILTERED:
+ List<ContainerRequestFilter> requestFilters = new ArrayList<>();
+ event.getContainerRequestFilters().forEach(requestFilters::add);
+
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_FINISHED(requestFilters.size()));
+ if (requestFilters.size() > 0) {
+ log(LocalizationMessages.OPENTRACING_LOG_APPLIED_REQUEST_FILTERS(
+ OpenTracingUtils.formatProviders(requestFilters)));
+ }
+ break;
+
+ case RESOURCE_METHOD_START:
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_STARTED(
+ event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod()));
+
+ resourceSpan = globalTracer.buildSpan(OpenTracingFeature.DEFAULT_RESOURCE_SPAN_NAME)
+ .asChildOf(requestSpan)
+ .startManual();
+
+ event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, resourceSpan);
+ break;
+
+ case RESOURCE_METHOD_FINISHED:
+ log(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_FINISHED());
+ break;
+
+ case RESP_FILTERS_START:
+ // this is the first event after resource method is guaranteed to have finished, even for asynchronous
+ // processing; resourceSpan will be finished and the span in the context will be switched back to the
+ // resource span before any further tracing can occur.
+ event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, requestSpan);
+ resourceSpan.finish();
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_STARTED());
+ break;
+
+ case RESP_FILTERS_FINISHED:
+ List<ContainerResponseFilter> responseFilters = new ArrayList<>();
+ event.getContainerResponseFilters().forEach(responseFilters::add);
+ logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_FINISHED(responseFilters.size()));
+ if (responseFilters.size() > 0) {
+ log(LocalizationMessages.OPENTRACING_LOG_APPLIED_RESPONSE_FILTERS(
+ OpenTracingUtils.formatProviders(responseFilters)));
+ }
+ break;
+
+ case ON_EXCEPTION:
+ if (resourceSpan != null) {
+ resourceSpan.setTag(Tags.ERROR.getKey(), true);
+ resourceSpan.finish();
+ }
+ requestSpan.setTag(Tags.ERROR.getKey(), true);
+ logError(event.getException());
+ break;
+
+ case EXCEPTION_MAPPER_FOUND:
+ log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPER_FOUND(
+ event.getExceptionMapper().getClass().getName()));
+ break;
+
+ case EXCEPTION_MAPPING_FINISHED:
+ log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_FINISHED()
+ + (event.isResponseSuccessfullyMapped()
+ ? LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_SUCCESS()
+ : LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_NOEXCEPTION_OR_FAILED()));
+
+ break;
+
+
+ case FINISHED:
+ if (requestSpan != null) {
+ ContainerResponse response = event.getContainerResponse();
+ if (response != null) {
+ int status = response.getStatus();
+ requestSpan
+ .setTag(Tags.HTTP_STATUS.getKey(), status)
+ .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), response.hasEntity())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), response.getLength());
+
+ if (400 <= status) {
+ requestSpan.setTag(Tags.ERROR.getKey(), true);
+ }
+ }
+ requestSpan.finish();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Adds a {@link OpenTracingFeature.Verbosity#TRACE}-level log entry into the request span.
+ * @param s log message
+ */
+ private void logVerbose(String s) {
+ log(OpenTracingFeature.Verbosity.TRACE, s);
+ }
+
+ /**
+ * Adds a {@link OpenTracingFeature.Verbosity#INFO}-level log entry into the request span.
+ * @param s log message
+ */
+ private void log(String s) {
+ log(OpenTracingFeature.Verbosity.INFO, s);
+ }
+
+ /**
+ * Adds a log entry with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}-level into the
+ * request span.
+ *
+ * @param level desired verbosity level
+ * @param s log message
+ */
+ private void log(OpenTracingFeature.Verbosity level, String s) {
+ if (level.ordinal() <= verbosity.ordinal()) {
+ requestSpan.log(s);
+ }
+ }
+
+ /**
+ * Adds an error log into the request span.
+ * @param t exception to be logged.
+ */
+ private void logError(final Throwable t) {
+ Map<String, Object> errorMap = new HashMap<>(2);
+ errorMap.put("event", "error");
+ errorMap.put("error.object", t);
+ requestSpan.log(errorMap);
+ }
+ }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
new file mode 100644
index 0000000..7934488
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MediaType;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapInjectAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Client-side request filter, that creates the client request {@code Span}.
+ * <p>
+ * Stores request-related metadata into the {@code Span} as {@code Tags}
+ * and {@link GlobalTracer#inject(SpanContext, Format, Object) injects} it into http headers.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingClientRequestFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ Tracer.SpanBuilder spanBuilder = GlobalTracer.get()
+ .buildSpan(LocalizationMessages.OPENTRACING_SPAN_PREFIX_CLIENT() + requestContext.getMethod())
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+ .withTag(Tags.HTTP_URL.getKey(), requestContext.getUri().toASCIIString())
+ .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), requestContext.hasEntity())
+ .withTag(LocalizationMessages.OPENTRACING_TAG_ACCEPTABLE_MEDIA_TYPES(), requestContext.getAcceptableMediaTypes()
+ .stream()
+ .map(MediaType::toString)
+ .collect(Collectors.joining(", ")))
+ .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+ OpenTracingUtils.headersAsString(requestContext.getHeaders()));
+
+ // if pre-stored "span" property is found, propagate the stored context
+ final Object property = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (property != null && property instanceof SpanContext) {
+ spanBuilder = spanBuilder.asChildOf((SpanContext) property);
+ }
+ Span span = spanBuilder.startManual();
+
+ requestContext.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+ Map<String, String> addedHeaders = new HashMap<>();
+ GlobalTracer.get().inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMapInjectAdapter(addedHeaders));
+ addedHeaders.forEach((key, value) -> requestContext.getHeaders().add(key, value));
+ }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
new file mode 100644
index 0000000..0dae48f
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+
+/**
+ * Retrieves stored span from the {@link ClientRequestContext} and adds response details to the tracing info.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingClientResponseFilter implements ClientResponseFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+ final Object spanProperty = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ ((Span) spanProperty)
+ .setTag(Tags.HTTP_STATUS.getKey(), responseContext.getStatus())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), responseContext.hasEntity())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), responseContext.getLength())
+ .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_HEADERS(),
+ OpenTracingUtils.headersAsString(responseContext.getHeaders()))
+ .finish();
+ }
+ }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
new file mode 100644
index 0000000..6afb1f9
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * A feature that enables OpenTracing support on server and client.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+@Beta
+public class OpenTracingFeature implements Feature {
+ private static final Logger LOGGER = Logger.getLogger(OpenTracingFeature.class.getName());
+ private final Verbosity verbosity;
+
+ /**
+ * Creates feature instance with default ({@link Verbosity#INFO} verbosity level.
+ */
+ public OpenTracingFeature() {
+ verbosity = Verbosity.INFO;
+ }
+
+ /**
+ * Creates feature instance with given ({@link Verbosity} level.
+ * @param verbosity desired level of logging verbosity
+ */
+ public OpenTracingFeature(Verbosity verbosity) {
+ this.verbosity = verbosity;
+ }
+
+ /**
+ * Stored span's {@link ContainerRequestContext} property key.
+ */
+ public static final String SPAN_CONTEXT_PROPERTY = "span";
+
+ /**
+ * Default resource span name.
+ */
+ public static final String DEFAULT_RESOURCE_SPAN_NAME = "jersey-resource";
+
+ /**
+ * Default child span name.
+ */
+ public static final String DEFAULT_CHILD_SPAN_NAME = "jersey-resource-app";
+
+ /**
+ * Default request "root" span name.
+ */
+ public static final String DEFAULT_REQUEST_SPAN_NAME = "jersey-server";
+
+ @Override
+ public boolean configure(FeatureContext context) {
+ if (!GlobalTracer.isRegistered()) {
+ LOGGER.warning(LocalizationMessages.OPENTRACING_TRACER_NOT_REGISTERED());
+ }
+
+ switch (context.getConfiguration().getRuntimeType()) {
+ case CLIENT:
+ context.register(OpenTracingClientRequestFilter.class).register(OpenTracingClientResponseFilter.class);
+ break;
+ case SERVER:
+ context.register(new OpenTracingApplicationEventListener(verbosity));
+ }
+ return true;
+ }
+
+ /**
+ * OpenTracing Jersey event logging verbosity.
+ */
+ public enum Verbosity {
+ /**
+ * Only logs basic Jersey processing related events.
+ */
+ INFO,
+
+ /**
+ * Logs more fine grained events related to Jersey processing.
+ */
+ TRACE
+ }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
new file mode 100644
index 0000000..4bc3ad0
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedMap;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Utility methods for Jersey OpenTracing integration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingUtils {
+
+ private OpenTracingUtils() {
+ }
+
+ /**
+ * Resolve resource-level span.
+ * <p>
+ * If open tracing is enabled and {@link GlobalTracer} is registered, resource-level span should be stored in the
+ * {@link OpenTracingFeature#SPAN_CONTEXT_PROPERTY}. This span is resolved and returned as an {@link Optional}.
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @return {@link Optional} of the resolved span, if found; empty optional if not
+ */
+ public static Optional<Span> getRequestSpan(final ContainerRequestContext context) {
+ if (context != null) {
+ final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ return Optional.of((Span) spanProperty);
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Create and start ad-hoc custom span with the default name as a child span of the request span (if available).
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+ * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+ * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+ * after the traced code finishes.
+ */
+ public static Span getRequestChildSpan(final ContainerRequestContext context) {
+ return getRequestChildSpan(context, OpenTracingFeature.DEFAULT_CHILD_SPAN_NAME);
+ }
+
+ /**
+ * Create and start ad-hoc custom span with a custom name as a child span of the request span (if available).
+ *
+ * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+ * @param spanName name to be used for the created span
+ * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+ * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+ * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+ * after the traced code finishes.
+ */
+ public static Span getRequestChildSpan(final ContainerRequestContext context, final String spanName) {
+ Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(spanName);
+ if (context != null) {
+ final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+ if (spanProperty != null && spanProperty instanceof Span) {
+ spanBuilder = spanBuilder.asChildOf((Span) spanProperty);
+ }
+ }
+ return spanBuilder.startManual();
+ }
+
+ /**
+ * Convert request/response headers from {@link MultivaluedMap} into printable form.
+ *
+ * @param headers multi-valued map of request or response headers
+ * @return {@code String} representation, e.g. "[header1=foo]; [header2=bar, baz]"
+ */
+ static String headersAsString(final MultivaluedMap<String, ?> headers) {
+ return headers.entrySet()
+ .stream()
+ .map((entry) -> "["
+ + entry.getKey() + "="
+ + entry.getValue()
+ .stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(", "))
+ + "]")
+ .collect(Collectors.joining("; "));
+ }
+
+ static String formatList(List<?> list) {
+ return list.stream().map(Object::toString).collect(Collectors.joining(", "));
+ }
+
+ static String formatProviders(Iterable<?> providers) {
+ return StreamSupport.stream(providers.spliterator(), false)
+ .map((provider) -> provider.getClass().getName())
+ .collect(Collectors.joining(", "));
+ }
+}
diff --git a/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
new file mode 100644
index 0000000..67f8508
--- /dev/null
+++ b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+opentracing.tracer.not.registered=OpenTracing feature is enabled, but no Tracer has been registered. Default no-op tracer will \
+ be used.
+opentracing.log.request.started=Request started.
+opentracing.log.matching.started=Resource matching started.
+opentracing.log.request.matched=Request matched, method: {0}
+opentracing.log.locator.matched=Locator matched. Matched locators: {0}
+opentracing.log.subresource.located=Subresource located: {0}
+opentracing.log.request.filtering.started=Request filtering started.
+opentracing.log.request.filtering.finished=Request filtering finished, {0} filter(s) applied.
+opentracing.log.applied.request.filters=Applied request filters: {0}.
+opentracing.log.resource.method.started=Resource method {0} started.
+opentracing.log.response.filtering.started=Response filtering started.
+opentracing.log.resource.method.finished=Resource method finished.
+opentracing.log.response.filtering.finished=Response filtering finished, {0} filter(s) applied.
+opentracing.log.applied.response.filters=Applied reqsponse filters: {0}.
+opentracing.log.exception=Exception while processing the request: {0}.
+opentracing.log.exception.mapper.found=Exception mapper found: {0}.
+opentracing.log.exception.mapping.finished=Exception mapping finished:
+opentracing.log.exception.mapping.success=successfully mapped
+opentracing.log.exception.mapping.noexception.or.failed=no exception or mapping failed.
+opentracing.tag.request.headers=requestHeaders
+opentracing.tag.response.headers=responseHeaders
+opentracing.tag.has.request.entity=hasRequestEntity
+opentracing.tag.has.response.entity=hasResponseEntity
+opentracing.tag.response.length=responseLength
+opentracing.tag.acceptable.media.types=acceptableMediaTypes
+opentracing.span.prefix.client=jersey-client-
+