Skip to content

Commit

Permalink
Adds Spring Boot 1.5 (webmvc4) example (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriancole authored Aug 30, 2018
1 parent 0541db7 commit 1ed6fa6
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 0 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Next, you can view traces that went through the backend via http://localhost:941
* This is a locally run zipkin service which keeps traces in memory

## Starting the Services

### Servlet Container Option
In a separate tab or window, start each of [brave.webmvc.Frontend](/webmvc4/src/main/java/brave/webmvc/Frontend.java)
and [brave.webmvc.Backend](/webmvc4/src/main/java/brave/webmvc/Backend.java):
```bash
Expand All @@ -39,6 +41,15 @@ $ mvn jetty:run -Pfrontend
$ mvn jetty:run -Pbackend
```

### Spring Boot Option
In a separate tab or window, start each of [brave.webmvc.Frontend](/webmvc4-boot/src/main/java/brave/webmvc/Frontend.java)
and [brave.webmvc.Backend](/webmvc4-boot/src/main/java/brave/webmvc/Backend.java):
```bash
$ cd webmvc4-boot
$ mvn compile exec:java -Dexec.mainClass=brave.webmvc.Backend
$ mvn compile exec:java -Dexec.mainClass=brave.webmvc.Frontend
```

Next, run [Zipkin](http://zipkin.io/), which stores and queries traces
reported by the above services.

Expand Down
18 changes: 18 additions & 0 deletions webmvc4-boot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## WebMVC 4 Boot Example

Instead of servlet, this uses Spring Boot 1.5 to create a self-contained
application that runs Spring WebMVC 4 controllers.

* brave.webmvc.Frontend and Backend : Rest controllers with no tracing configuration
* brave.webmvc.TracingConfiguration : This adds tracing by configuring the tracer, server and client tracing interceptors.

`TracingConfiguration` is automatically loaded due to `META-INF/spring.factories`
This allows the `Frontend` and `Backend` controllers to have no tracing
code. Inside the tracing configuration, you'll notice the rest template
is setup via a `RestTemplateCustomizer`, which ensures application-level
interceptors are not affected. Also, you'll notice layered tracing for
server requests. First, `TracingFilter` creates a span, then later,
`SpanCustomizingAsyncHandlerInterceptor` adds MVC tags to it.

*Note* This only lightly configures tracing. When doing anything serious,
consider [Spring Cloud Sleuth](https://github.com/spring-cloud/spring-cloud-sleuth) instead.
69 changes: 69 additions & 0 deletions webmvc4-boot/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<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">
<modelVersion>4.0.0</modelVersion>

<groupId>io.zipkin.brave</groupId>
<artifactId>brave-webmvc4-boot-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>brave-webmvc4-boot-example</name>
<description>Example using Brave to trace RPCs from Spring Web MVC</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>

<spring-boot.version>1.5.14.RELEASE</spring-boot.version>
<brave.version>5.1.3</brave.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-bom</artifactId>
<version>${brave.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-context-slf4j</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-spring-webmvc</artifactId>
</dependency>
</dependencies>
</project>
28 changes: 28 additions & 0 deletions webmvc4-boot/src/main/java/brave/webmvc/Backend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package brave.webmvc;

import java.util.Date;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@EnableAutoConfiguration
@RestController
public class Backend {

@RequestMapping("/api")
public String printDate(@RequestHeader(name = "user-name", required = false) String username) {
if (username != null) {
return new Date().toString() + " " + username;
}
return new Date().toString();
}

public static void main(String[] args) {
SpringApplication.run(Backend.class,
"--spring.application.name=backend",
"--server.port=9000"
);
}
}
33 changes: 33 additions & 0 deletions webmvc4-boot/src/main/java/brave/webmvc/Frontend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package brave.webmvc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@EnableAutoConfiguration
@RestController
@CrossOrigin // So that javascript can be hosted elsewhere
public class Frontend {

@Autowired RestTemplate restTemplate;

@RequestMapping("/") public String callBackend() {
return restTemplate.getForObject("http://localhost:9000/api", String.class);
}

@Bean RestTemplate restTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(Frontend.class,
"--spring.application.name=frontend",
"--server.port=8081"
);
}
}
86 changes: 86 additions & 0 deletions webmvc4-boot/src/main/java/brave/webmvc/TracingConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package brave.webmvc;

import brave.Tracing;
import brave.context.slf4j.MDCCurrentTraceContext;
import brave.http.HttpTracing;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
import brave.servlet.TracingFilter;
import brave.spring.web.TracingClientHttpRequestInterceptor;
import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import zipkin2.Span;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.Sender;
import zipkin2.reporter.okhttp3.OkHttpSender;

/**
* This adds tracing configuration to any web mvc controllers or rest template clients.
*/
@Configuration
// Importing a class is effectively the same as declaring bean methods
@Import(SpanCustomizingAsyncHandlerInterceptor.class)
public class TracingConfiguration extends WebMvcConfigurerAdapter {

/** Configuration for how to send spans to Zipkin */
@Bean Sender sender() {
return OkHttpSender.create("http://127.0.0.1:9411/api/v2/spans");
}

/** Configuration for how to buffer spans into messages for Zipkin */
@Bean AsyncReporter<Span> spanReporter() {
return AsyncReporter.create(sender());
}

/** Controls aspects of tracing such as the name that shows up in the UI */
@Bean Tracing tracing(@Value("${spring.application.name}") String serviceName) {
return Tracing.newBuilder()
.localServiceName(serviceName)
.propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
.currentTraceContext(MDCCurrentTraceContext.create()) // puts trace IDs into logs
.spanReporter(spanReporter()).build();
}

/** decides how to name and tag spans. By default they are named the same as the http method. */
@Bean HttpTracing httpTracing(Tracing tracing) {
return HttpTracing.create(tracing);
}

/** Creates client spans for http requests */
@Bean @Order(Ordered.HIGHEST_PRECEDENCE)
RestTemplateCustomizer tracingRestTemplateCustomizer(final HttpTracing httpTracing) {
return new RestTemplateCustomizer() {
@Override public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> interceptors =
new ArrayList<>(restTemplate.getInterceptors());
interceptors.add(0, TracingClientHttpRequestInterceptor.create(httpTracing));
}
};
}

/** Creates server spans for http requests */
@Bean Filter tracingFilter(HttpTracing httpTracing) {
return TracingFilter.create(httpTracing);
}

@Autowired SpanCustomizingAsyncHandlerInterceptor webMvcTracingCustomizer;

/** Decorates server spans with application-defined web tags */
@Override public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webMvcTracingCustomizer);
}
}
2 changes: 2 additions & 0 deletions webmvc4-boot/src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
brave.webmvc.TracingConfiguration
5 changes: 5 additions & 0 deletions webmvc4-boot/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# spring.application.name and server.port are set in the main methods,
# so not done here
logging.level.org.springframework.web=DEBUG
# Adds trace and span IDs to logs (when a trace is in progress)
logging.pattern.level=%d{ABSOLUTE} [%X{traceId}/%X{spanId}] %-5p [%t] %C{2} - %m%n

0 comments on commit 1ed6fa6

Please sign in to comment.