diff --git a/README.md b/README.md index 5b3bb30..c757044 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/webmvc4-boot/README.md b/webmvc4-boot/README.md new file mode 100644 index 0000000..bf14563 --- /dev/null +++ b/webmvc4-boot/README.md @@ -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. diff --git a/webmvc4-boot/pom.xml b/webmvc4-boot/pom.xml new file mode 100644 index 0000000..d66633d --- /dev/null +++ b/webmvc4-boot/pom.xml @@ -0,0 +1,69 @@ + + 4.0.0 + + io.zipkin.brave + brave-webmvc4-boot-example + 1.0-SNAPSHOT + jar + + brave-webmvc4-boot-example + Example using Brave to trace RPCs from Spring Web MVC + + + UTF-8 + 1.7 + 1.7 + + 1.5.14.RELEASE + 5.1.3 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + io.zipkin.brave + brave-bom + ${brave.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + io.zipkin.brave + brave + + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + + + io.zipkin.brave + brave-context-slf4j + + + io.zipkin.brave + brave-instrumentation-spring-web + + + io.zipkin.brave + brave-instrumentation-spring-webmvc + + + diff --git a/webmvc4-boot/src/main/java/brave/webmvc/Backend.java b/webmvc4-boot/src/main/java/brave/webmvc/Backend.java new file mode 100644 index 0000000..9dff48c --- /dev/null +++ b/webmvc4-boot/src/main/java/brave/webmvc/Backend.java @@ -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" + ); + } +} diff --git a/webmvc4-boot/src/main/java/brave/webmvc/Frontend.java b/webmvc4-boot/src/main/java/brave/webmvc/Frontend.java new file mode 100644 index 0000000..34aa935 --- /dev/null +++ b/webmvc4-boot/src/main/java/brave/webmvc/Frontend.java @@ -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" + ); + } +} diff --git a/webmvc4-boot/src/main/java/brave/webmvc/TracingConfiguration.java b/webmvc4-boot/src/main/java/brave/webmvc/TracingConfiguration.java new file mode 100644 index 0000000..e3352c6 --- /dev/null +++ b/webmvc4-boot/src/main/java/brave/webmvc/TracingConfiguration.java @@ -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 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 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); + } +} diff --git a/webmvc4-boot/src/main/resources/META-INF/spring.factories b/webmvc4-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..248137f --- /dev/null +++ b/webmvc4-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +brave.webmvc.TracingConfiguration diff --git a/webmvc4-boot/src/main/resources/application.properties b/webmvc4-boot/src/main/resources/application.properties new file mode 100644 index 0000000..12fab19 --- /dev/null +++ b/webmvc4-boot/src/main/resources/application.properties @@ -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