Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions brave-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@
<artifactId>brave-instrumentation-jersey-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-jersey-server-jakarta</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-jms</artifactId>
Expand Down
70 changes: 70 additions & 0 deletions instrumentation/jersey-server-jakarta/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# brave-instrumentation-jersey-server-jakarta
This module contains application event listeners for [Jersey Server 3.x](https://jersey.github.io/documentation/latest/monitoring_tracing.html#d0e16007).

These instrumentation is an alternative to the [jaxrs2](../jaxrs2) container
instrumentation. Do *not* use both.

`TracingApplicationEventListener` extracts trace state from incoming
requests. Then, it reports Zipkin how long each request takes, along
with relevant tags like the http url and the resource.

`SpanCustomizingApplicationEventListener` layers over [servlet](../servlet),
adding resource tags and route information to servlet-originated spans.

When in a servlet environment, use `SpanCustomizingApplicationEventListener`.
When not, use `TracingApplicationEventListener`. Don't use both!

`TracingApplicationEventListener` extracts trace state from incoming
requests. Then, it reports Zipkin how long each request takes, along
with relevant tags like the http url.

To enable tracing, you need to register the `TracingApplicationEventListener`.

## Configuration

### Normal configuration (`TracingApplicationEventListener`)

The `TracingApplicationEventListener` requires an instance of
`HttpTracing` to operate. With that in mind, use [standard means](https://jersey.github.io/apidocs/2.26/jersey/org/glassfish/jersey/server/monitoring/ApplicationEventListener.html)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this link needs an update, too

to register the listener.

For example, you could wire up like this:
```java
public class MyApplication extends Application {

public Set<Object> getSingletons() {
HttpTracing httpTracing = // configure me!
return new LinkedHashSet<>(Arrays.asList(
TracingApplicationEventListener.create(httpTracing),
new MyResource()
));
}
}
```

### Servlet-based configuration (`SpanCustomizingApplicationEventListener`)

When using `jersey-container-servlet`, setup [servlet tracing](../servlet),
an register `SpanCustomizingContainerFilter`.

```java
public class MyApplication extends Application {
public Set<Object> getSingletons() {
HttpTracing httpTracing = // configure me!
return new LinkedHashSet<>(Arrays.asList(
SpanCustomizingApplicationEventListener.create(),
new MyResource()
));
}
```


## Customizing Span data based on resources
`EventParser` decides which resource-specific (data beyond normal
http tags) end up on the span. You can override this to change what's
parsed, or use `NOOP` to disable controller-specific data.

Ex. If you want less tags, you can disable the JAX-RS resource ones.
```java
SpanCustomizingApplicationEventListener.create(EventParser.NOOP);
```
6 changes: 6 additions & 0 deletions instrumentation/jersey-server-jakarta/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# We use brave.internal.Nullable, but it is not used at runtime.
Import-Package: \
!brave.internal*,\
*
Export-Package: \
brave.jakarta.jersey.server
79 changes: 79 additions & 0 deletions instrumentation/jersey-server-jakarta/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0"?>
<!--

Copyright The OpenZipkin Authors
SPDX-License-Identifier: Apache-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/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-parent</artifactId>
<version>6.1.1-SNAPSHOT</version>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to bump all poms to 6.2.0-SNAPSHOT and verify the since javadoc on public types are since 6.2

</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>brave-instrumentation-jersey-server-jakarta</artifactId>
<name>Brave Instrumentation: Jersey Server 3.x (Jakarta)</name>

<properties>
<!-- Matches Export-Package in bnd.bnd -->
<module.name>brave.jakarta.jersey.server</module.name>

<main.basedir>${project.basedir}/../..</main.basedir>
</properties>

<dependencies>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey3.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-http</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-http-tests-jakarta</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>brave-instrumentation-servlet-jakarta</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty11.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey3.version}</version>
<scope>test</scope>
</dependency>
<!-- Avoid InjectionManagerFactory not found -->
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey3.version}</version>
<scope>test</scope>
</dependency>

<!-- to test @Inject annotations -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice6.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenZipkin Authors
* SPDX-License-Identifier: Apache-2.0
*/
package brave.jakarta.jersey.server;

import brave.SpanCustomizer;
import brave.http.HttpTracing;
import org.glassfish.jersey.server.model.Invocable;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;

/**
* Jersey specific type used to customize traced requests based on the JAX-RS resource.
*
* <p>Note: This should not duplicate data added by {@link HttpTracing}. For example, this should
* not add the tag "http.url".
*/
// named event parser, not request event parser, in case we want to later support application event.
public class EventParser {
/** Adds no data to the request */
public static final EventParser NOOP = new EventParser() {
@Override protected void requestMatched(RequestEvent event, SpanCustomizer customizer) {
}
};

/** Simple class name that processed the request. ex BookResource */
public static final String RESOURCE_CLASS = "jaxrs.resource.class";
/** Method name that processed the request. ex listOfBooks */
public static final String RESOURCE_METHOD = "jaxrs.resource.method";

/**
* Invoked prior to request invocation during {@link RequestEventListener#onEvent(RequestEvent)}
* where the event type is {@link RequestEvent.Type#REQUEST_MATCHED}
*
* <p>Adds the tags {@link #RESOURCE_CLASS} and {@link #RESOURCE_METHOD}. Override or use {@link
* #NOOP} to change this behavior.
*/
protected void requestMatched(RequestEvent event, SpanCustomizer customizer) {
ResourceMethod method = event.getContainerRequest().getUriInfo().getMatchedResourceMethod();
if (method == null) return; // This case is extremely odd as this is called on REQUEST_MATCHED!
Invocable i = method.getInvocable();
customizer.tag(RESOURCE_CLASS, i.getHandler().getHandlerClass().getSimpleName());
customizer.tag(RESOURCE_METHOD, i.getHandlingMethod().getName());
}

public EventParser() { // intentionally public for @Inject to work without explicit binding
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright The OpenZipkin Authors
* SPDX-License-Identifier: Apache-2.0
*/
package brave.jakarta.jersey.server;

import brave.SpanCustomizer;
import brave.internal.Nullable;
import jakarta.inject.Inject;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.ext.Provider;
import java.util.List;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.server.internal.process.MappableException;
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 org.glassfish.jersey.uri.UriTemplate;

import static org.glassfish.jersey.server.monitoring.RequestEvent.Type.FINISHED;

/**
* Adds application-tier data to an existing http span via {@link EventParser}. This also sets the
* request property "http.route" so that it can be used in naming the http span.
*
* <p>Use this instead of {@link TracingApplicationEventListener} when you start traces at the
* servlet level via {@code brave.servlet.TracingFilter}.
*/
@Provider
public class SpanCustomizingApplicationEventListener
implements ApplicationEventListener, RequestEventListener {
public static SpanCustomizingApplicationEventListener create() {
return new SpanCustomizingApplicationEventListener(new EventParser());
}

public static SpanCustomizingApplicationEventListener create(EventParser parser) {
return new SpanCustomizingApplicationEventListener(parser);
}

final EventParser parser;

@Inject
SpanCustomizingApplicationEventListener(EventParser parser) {
if (parser == null) throw new NullPointerException("parser == null");
this.parser = parser;
}

@Override public void onEvent(ApplicationEvent event) {
// only onRequest is used
}

@Override public RequestEventListener onRequest(RequestEvent requestEvent) {
if (requestEvent.getType() == RequestEvent.Type.START) return this;
return null;
}

@Override public void onEvent(RequestEvent event) {
// Note: until REQUEST_MATCHED, we don't know metadata such as if the request is async or not
if (event.getType() != FINISHED) return;
ContainerRequest request = event.getContainerRequest();
Object maybeSpan = request.getProperty(SpanCustomizer.class.getName());
if (!(maybeSpan instanceof SpanCustomizer)) return;

// Set the HTTP route attribute so that TracingFilter can see it
request.setProperty("http.route", route(request));

Throwable error = unwrapError(event);
// Set the error attribute so that TracingFilter can see it
if (error != null && request.getProperty("error") == null) request.setProperty("error", error);

parser.requestMatched(event, (SpanCustomizer) maybeSpan);
}

@Nullable static Throwable unwrapError(RequestEvent event) {
Throwable error = event.getException();
// For example, if thrown in an async controller
if (error instanceof MappableException && error.getCause() != null) {
error = error.getCause();
}
// Don't create error messages for normal HTTP status codes.
if (error instanceof WebApplicationException) return error.getCause();
return error;
}

/**
* This returns the matched template as defined by a base URL and path expressions.
*
* <p>Matched templates are pairs of (resource path, method path) added with
* {@link org.glassfish.jersey.server.internal.routing.RoutingContext#pushTemplates(UriTemplate,
* UriTemplate)}. This code skips redundant slashes from either source caused by Path("/") or
* Path("").
*/
@Nullable static String route(ContainerRequest request) {
ExtendedUriInfo uriInfo = request.getUriInfo();
List<UriTemplate> templates = uriInfo.getMatchedTemplates();
int templateCount = templates.size();
if (templateCount == 0) return "";
StringBuilder builder = null; // don't allocate unless you need it!
String basePath = uriInfo.getBaseUri().getPath();
String result = null;
if (!"/" .equals(basePath)) { // skip empty base paths
result = basePath;
}
for (int i = templateCount - 1; i >= 0; i--) {
String template = templates.get(i).getTemplate();
if ("/" .equals(template)) continue; // skip allocation
if (builder != null) {
builder.append(template);
} else if (result != null) {
builder = new StringBuilder(result).append(template);
result = null;
} else {
result = template;
}
}
return result != null ? result : builder != null ? builder.toString() : "";
}
}
Loading