diff --git a/README.adoc b/README.adoc index 93ed92af27..416f90ec13 100644 --- a/README.adoc +++ b/README.adoc @@ -8,7 +8,7 @@ :github-code: https://github.com/{github-repo}/tree/{github-tag} image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/{github-tag}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth @@ -66,8 +66,12 @@ of that span is equal to trace id. *Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed big-data store, a trace might be formed by a put request. -*Annotation:* is used to record existence of an event in time. Some of the core annotations used to define -the start and stop of a request are: +*Annotation:* is used to record existence of an event in time. With +https://github.com/openzipkin/brave[Brave] instrumentation we no longer need to set special events +for https://zipkin.io/[Zipkin] to understand who the client and server are and where +the request started and where it has ended. For learning purposes +however we will mark these events to highlight what kind +of an action took place. - *cs* - Client Sent - The client has made a request. This annotation depicts the start of the span. - *sr* - Server Received - The server side got the request and will start processing it. @@ -90,8 +94,8 @@ Trace Id = X Span Id = D Client Sent -That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. It also has emitted - *Client Sent* event. +That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. Also, the + *Client Sent* event took place. This is how the visualization of the parent / child relationship of spans would look like: @@ -118,14 +122,14 @@ annotations then they will presented as a single span. Why is there a difference between the 7 and 4 spans in this case? - 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations. - - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service1` side. Server Received (SR) and Server Sent (SS) events took place on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. - - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. - - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2` @@ -150,11 +154,25 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc As you can see you can easily see the reason for an error and the whole stacktrace related to it. +==== Distributed tracing with Brave + +Starting with version `2.0.0`, Spring Cloud Sleuth uses +https://github.com/openzipkin/brave[Brave] as the tracing library. That means +that Sleuth no longer takes care of storing the context but it delegates +that work to Brave. + +Due to the fact that Sleuth had different naming / tagging +conventions than Brave, we've decided to follow the Brave's +conventions from now on. However, if you want to use the legacy +Sleuth approaches, it's enough to set the `spring.sleuth.http.legacy.enabled` property +to `true`. + ==== Live examples .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +http://docssleuth-zipkin-server.cfapps.io/[Click here to see it live!] The dependency graph in Zipkin would look like this: @@ -163,7 +181,7 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] - +http://docssleuth-zipkin-server.cfapps.io/dependency[Click here to see it live!] ==== Log correlation @@ -322,9 +340,8 @@ Example of setting baggage on a span: [source,java] ---- -Span initialSpan = this.tracer.createSpan("span"); -initialSpan.setBaggageItem("foo", "bar"); -initialSpan.setBaggageItem("UPPER_CASE", "someValue"); +Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +} ---- ===== Baggage vs. Span Tags @@ -337,15 +354,11 @@ can search by tag to find the trace, where there exists a span having the search If you want to be able to lookup a span based on baggage, you should add corresponding entry as a tag in the root span. +IMPORTANT: Remember that the span needs to be in scope! + [source,java] ---- -@Autowired Tracer tracer; - -Span span = tracer.getCurrentSpan(); -String baggageKey = "key"; -String baggageValue = "foo"; -span.setBaggageItem(baggageKey, baggageValue); -tracer.addTag(baggageKey, baggageValue); +Unresolved directive in intro.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/master/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0] ---- === Adding to the project @@ -361,7 +374,7 @@ the `spring-cloud-starter-sleuth` module to your project. [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud @@ -406,7 +419,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud @@ -457,7 +470,7 @@ dependencies. [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java index 71612b2336..ec6092e19e 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; - import javax.annotation.PreDestroy; import org.apache.commons.logging.Log; @@ -30,9 +29,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.Tracer; @@ -40,9 +39,7 @@ import org.springframework.cloud.sleuth.annotation.NewSpan; import org.springframework.cloud.sleuth.annotation.SpanTag; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; @@ -102,9 +99,9 @@ public void onApplicationEvent(ServletWebServerInitializedEvent event) { } @Bean - public EmbeddedServletContainerFactory servletContainer(@Value("${server.port:0}") int serverPort) { + public ServletWebServerFactory servletContainer(@Value("${server.port:0}") int serverPort) { log.info("Starting container at port [" + serverPort + "]"); - return new TomcatEmbeddedServletContainerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort); + return new TomcatServletWebServerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort); } @PreDestroy diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AnnotationBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AnnotationBenchmarks.java index 88e59d2326..38c3d81155 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AnnotationBenchmarks.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AnnotationBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java index b0b95fd3e5..36655d45c2 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java index 0c98665621..65ff051b6f 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java index 9467681ab6..f48b5a6e2f 100644 --- a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java index 2fd00ed612..9abfb7af70 100644 --- a/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java +++ b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/benchmarks/src/test/jmeter/jmeter.properties b/benchmarks/src/test/jmeter/jmeter.properties index 5aceab5fa5..70a8f7fc75 100644 --- a/benchmarks/src/test/jmeter/jmeter.properties +++ b/benchmarks/src/test/jmeter/jmeter.properties @@ -1,5 +1,5 @@ # -# Copyright 2013-2017 the original author or authors. +# Copyright 2013-2018 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index ff97ea095e..4510ffd19e 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -6,7 +6,7 @@ :github-code: https://github.com/{github-repo}/tree/{github-tag} image::https://circleci.com/gh/spring-cloud/spring-cloud-sleuth.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-sleuth"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/master/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] +image::https://codecov.io/gh/spring-cloud/spring-cloud-sleuth/branch/{github-tag}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-sleuth"] image::https://badges.gitter.im/spring-cloud/spring-cloud-sleuth.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-sleuth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] == Spring Cloud Sleuth diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/src/main/asciidoc/intro.adoc index af64c5dd25..67243ab0e5 100644 --- a/docs/src/main/asciidoc/intro.adoc +++ b/docs/src/main/asciidoc/intro.adoc @@ -22,8 +22,12 @@ of that span is equal to trace id. *Trace:* A set of spans forming a tree-like structure. For example, if you are running a distributed big-data store, a trace might be formed by a put request. -*Annotation:* is used to record existence of an event in time. Some of the core annotations used to define -the start and stop of a request are: +*Annotation:* is used to record existence of an event in time. With +https://github.com/openzipkin/brave[Brave] instrumentation we no longer need to set special events +for https://zipkin.io/[Zipkin] to understand who the client and server are and where +the request started and where it has ended. For learning purposes +however we will mark these events to highlight what kind +of an action took place. - *cs* - Client Sent - The client has made a request. This annotation depicts the start of the span. - *sr* - Server Received - The server side got the request and will start processing it. @@ -46,8 +50,8 @@ Trace Id = X Span Id = D Client Sent -That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. It also has emitted - *Client Sent* event. +That means that the current span has *Trace-Id* set to *X*, *Span-Id* set to *D*. Also, the + *Client Sent* event took place. This is how the visualization of the parent / child relationship of spans would look like: @@ -74,14 +78,14 @@ annotations then they will presented as a single span. Why is there a difference between the 7 and 4 spans in this case? - 2 spans come from `http:/start` span. It has the Server Received (SR) and Server Sent (SS) annotations. - - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service1` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service1` to `service2` to the `http:/foo` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service1` side. Server Received (SR) and Server Sent (SS) events took place on the `service2` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. - - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service2` to `service3` to the `http:/bar` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place on the `service3` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. - - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. It has the Client Sent (CS) - and Client Received (CR) annotations on `service2` side. It also has Server Received (SR) and Server Sent (SS) annotations + - 2 spans come from the RPC call from `service2` to `service4` to the `http:/baz` endpoint. The Client Sent (CS) + and Client Received (CR) events took place on `service2` side. Server Received (SR) and Server Sent (SS) events took place on the `service4` side. Physically there are 2 spans but they form 1 logical span related to an RPC call. So if we count the physical spans we have *1* from `http:/start`, *2* from `service1` calling `service2`, *2* form `service2` @@ -106,11 +110,25 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc As you can see you can easily see the reason for an error and the whole stacktrace related to it. +==== Distributed tracing with Brave + +Starting with version `2.0.0`, Spring Cloud Sleuth uses +https://github.com/openzipkin/brave[Brave] as the tracing library. That means +that Sleuth no longer takes care of storing the context but it delegates +that work to Brave. + +Due to the fact that Sleuth had different naming / tagging +conventions than Brave, we've decided to follow the Brave's +conventions from now on. However, if you want to use the legacy +Sleuth approaches, it's enough to set the `spring.sleuth.http.legacy.enabled` property +to `true`. + ==== Live examples .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/", width=150, height=74] +http://docssleuth-zipkin-server.cfapps.io/[Click here to see it live!] The dependency graph in Zipkin would look like this: @@ -119,7 +137,7 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branc .Click Pivotal Web Services icon to see it live! [caption="Click Pivotal Web Services icon to see it live!"] image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/pws.png["Zipkin deployed on Pivotal Web Services", link="http://docssleuth-zipkin-server.cfapps.io/dependency", width=150, height=74] - +http://docssleuth-zipkin-server.cfapps.io/dependency[Click here to see it live!] ==== Log correlation @@ -203,7 +221,8 @@ Example of setting baggage on a span: [source,java] ---- -include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage,indent=0] +} ---- ===== Baggage vs. Span Tags @@ -216,15 +235,11 @@ can search by tag to find the trace, where there exists a span having the search If you want to be able to lookup a span based on baggage, you should add corresponding entry as a tag in the root span. +IMPORTANT: Remember that the span needs to be in scope! + [source,java] ---- -@Autowired Tracer tracer; - -Span span = tracer.getCurrentSpan(); -String baggageKey = "key"; -String baggageValue = "foo"; -span.setBaggageItem(baggageKey, baggageValue); -tracer.addTag(baggageKey, baggageValue); +include::{github-raw}/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/brave/instrument/web/multiple/MultipleHopsIntegrationTests.java[tags=baggage_tag,indent=0] ---- === Adding to the project @@ -240,7 +255,7 @@ the `spring-cloud-starter-sleuth` module to your project. [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud @@ -285,7 +300,7 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud @@ -336,7 +351,7 @@ dependencies. [source,xml,indent=0,subs="verbatim,attributes",role="primary"] .Maven ---- - <1> + <1> org.springframework.cloud diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index 1b5c420984..b24bf63aa3 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -19,29 +19,287 @@ include::intro.adoc[] include::features.adoc[] +=== Introduction to Brave + +IMPORTANT: Starting with version `2.0.0` Spring Cloud Sleuth uses +https://github.com/openzipkin/brave[Brave] as the tracing library. +For your convenience we're embedding part of the Brave's docs here. + +Brave is a library used to capture and report latency information about +distributed operations to Zipkin. Most users won't use Brave directly, +rather libraries or frameworks than employ Brave on their behalf. + +This module includes tracer creates and joins spans that model the +latency of potentially distributed work. It also includes libraries to +propagate the trace context over network boundaries, for example, via +http headers. + +==== Tracing + +Most importantly, you need a `brave.Tracer`, configured to [report to Zipkin] +(https://github.com/openzipkin/zipkin-reporter-java). + +Here's an example setup that sends trace data (spans) to Zipkin over +http (as opposed to Kafka). + +```java + +class MyClass { + + private final Tracer tracer; + + // Tracer will be autowired + MyClass(Tracer tracer) { + this.tracer = tracer; + } + + void doSth() { + Span span = tracer.newTrace().name("encode").start(); + // ... + } +} +``` + +IMPORTANT: If your span contains a name greater than 50 chars, then that name will +be truncated to 50 chars. Your names have to be explicit and concrete. Big names lead to +latency issues and sometimes even thrown exceptions. + +==== Tracing + +The tracer creates and joins spans that model the latency of potentially +distributed work. It can employ sampling to reduce overhead in process +or to reduce the amount of data sent to Zipkin. + +Spans returned by a tracer report data to Zipkin when finished, or do +nothing if unsampled. After starting a span, you can annotate events of +interest or add tags containing details or lookup keys. + +Spans have a context which includes trace identifiers that place it at +the correct spot in the tree representing the distributed operation. + +==== Local Tracing + +When tracing local code, just run it inside a span. + +```java +Span span = tracer.newTrace().name("encode").start(); +try { + doSomethingExpensive(); +} finally { + span.finish(); +} +``` + +In the above example, the span is the root of the trace. In many cases, +you will be a part of an existing trace. When this is the case, call +`newChild` instead of `newTrace` + +```java +Span span = tracer.newChild(root.context()).name("encode").start(); +try { + doSomethingExpensive(); +} finally { + span.finish(); +} +``` + +==== Customizing spans + +Once you have a span, you can add tags to it, which can be used as lookup +keys or details. For example, you might add a tag with your runtime +version. + +```java +span.tag("clnt/finagle.version", "6.36.0"); +``` + +When exposing the ability to customize spans to third parties, prefer +`brave.SpanCustomizer` as opposed to `brave.Span`. The former is simpler to +understand and test, and doesn't tempt users with span lifecycle hooks. + +```java +interface MyTraceCallback { + void request(Request request, SpanCustomizer customizer); +} +``` + +Since `brave.Span` implements `brave.SpanCustomizer`, it is just as easy for you +to pass to users. + +Ex. +```java +for (MyTraceCallback callback : userCallbacks) { + callback.request(request, span); +} +``` + +==== Implicitly looking up the current span + +Sometimes you won't know if a trace is in progress or not, and you don't +want users to do null checks. `brave.CurrentSpanCustomizer` adds to any +span that's in progress or drops data accordingly. + +Ex. +```java +// user code can then inject this without a chance of it being null. +@Autowire SpanCustomizer span; + +void userCode() { + span.annotate("tx.started"); + ... +} +``` + +==== RPC tracing + +Check for https://github.com/openzipkin/sleuth/tree/master/instrumentation[instrumentation written here] +and http://zipkin.io/pages/existing_instrumentations.html[Zipkin's list] +before rolling your own RPC instrumentation! + +RPC tracing is often done automatically by interceptors. Under the scenes, +they add tags and events that relate to their role in an RPC operation. + +Here's an example of a client span: + +```java +// before you send a request, add metadata that describes the operation +span = tracer.newTrace().name("get").type(CLIENT); +span.tag("clnt/finagle.version", "6.36.0"); +span.tag(TraceKeys.HTTP_PATH, "/api"); +span.remoteEndpoint(Endpoint.builder() + .serviceName("backend") + .ipv4(127 << 24 | 1) + .port(8080).build()); + +// when the request is scheduled, start the span +span.start(); + +// if you have callbacks for when data is on the wire, note those events +span.annotate(Constants.WIRE_SEND); +span.annotate(Constants.WIRE_RECV); + +// when the response is complete, finish the span +span.finish(); +``` + +===== One-Way tracing + +Sometimes you need to model an asynchronous operation, where there is a +request, but no response. In normal RPC tracing, you use `span.finish()` +which indicates the response was received. In one-way tracing, you use +`span.flush()` instead, as you don't expect a response. + +Here's how a client might model a one-way operation +```java +// start a new span representing a client request +oneWaySend = tracer.newSpan(parent).kind(Span.Kind.CLIENT); + +// Add the trace context to the request, so it can be propagated in-band +tracing.propagation().injector(Request::addHeader) + .inject(oneWaySend.context(), request); + +// fire off the request asynchronously, totally dropping any response +request.execute(); + +// start the client side and flush instead of finish +oneWaySend.start().flush(); +``` + +And here's how a server might handle this.. +```java +// pull the context out of the incoming request +extractor = tracing.propagation().extractor(Request::getHeader); + +// convert that context to a span which you can name and add tags to +oneWayReceive = nextSpan(tracer, extractor.extract(request)) + .name("process-request") + .kind(SERVER) + ... add tags etc. + +// start the server side and flush instead of finish +oneWayReceive.start().flush(); + +// you should not modify this span anymore as it is complete. However, +// you can create children to represent follow-up work. +next = tracer.newSpan(oneWayReceive.context()).name("step2").start(); +``` + +**Note** The above propagation logic is a simplified version of our [http handlers](https://github.com/openzipkin/sleuth/tree/master/instrumentation/http#http-server). + +There's a working example of a one-way span [here](src/test/java/sleuth/features/async/OneWaySpanTest.java). + == Sampling -In distributed tracing the data volumes can be very high so sampling -can be important (you usually don't need to export all spans to get a -good picture of what is happening). Spring Cloud Sleuth has a -`Sampler` strategy that you can implement to take control of the -sampling algorithm. Samplers do not stop span (correlation) ids from -being generated, but they do prevent the tags and events being -attached and exported. By default you get a strategy that continues to -trace if a span is already active, but new ones are always marked as -non-exportable. If all your apps run with this sampler you will see -traces in logs, but not in any remote store. For testing the default -is often enough, and it probably is all you need if you are only using -the logs (e.g. with an ELK aggregator). If you are exporting span data -to Zipkin or Spring Cloud Stream, there is also an `AlwaysSampler` -that exports everything and a `PercentageBasedSampler` that samples a +Sampling may be employed to reduce the data collected and reported out +of process. When a span isn't sampled, it adds no overhead (noop). + +Sampling is an up-front decision, meaning that the decision to report +data is made at the first operation in a trace, and that decision is +propagated downstream. + +By default, there's a global sampler that applies a single rate to all +traced operations. `Tracer.Builder.sampler` is how you indicate this, +and it defaults to trace every request. + +=== Declarative sampling + +Some need to sample based on the type or annotations of a java method. + +Most users will use a framework interceptor which automates this sort of +policy. Here's how they might work internally. + +```java +// derives a sample rate from an annotation on a java method +DeclarativeSampler sampler = DeclarativeSampler.create(Traced::sampleRate); + +@Around("@annotation(traced)") +public Object traceThing(ProceedingJoinPoint pjp, Traced traced) throws Throwable { + Span span = tracing.tracer().newTrace(sampler.sample(traced))... + try { + return pjp.proceed(); + } finally { + span.finish(); + } +} +``` + +=== Custom sampling + +You may want to apply different policies depending on what the operation +is. For example, you might not want to trace requests to static resources +such as images, or you might want to trace all requests to a new api. + +Most users will use a framework interceptor which automates this sort of +policy. Here's how they might work internally. + +```java +Span newTrace(Request input) { + SamplingFlags flags = SamplingFlags.NONE; + if (input.url().startsWith("/experimental")) { + flags = SamplingFlags.SAMPLED; + } else if (input.url().startsWith("/static")) { + flags = SamplingFlags.NOT_SAMPLED; + } + return tracer.newTrace(flags); +} +``` + +Note: the above is the basis for the built-in https://github.com/openzipkin/sleuth/tree/master/instrumentation/http[http sampler] + +=== Sampling in Spring Cloud Sleuth + +Spring Cloud Sleuth by default sets all spans to non-exportable. +That means that you will see traces in logs, but not in any remote store. +For testing the default is often enough, and it probably is all you need +if you are only using the logs (e.g. with an ELK aggregator). If you are +exporting span data to Zipkin, there is also an `Sampler.ALWAYS_SAMPLE` +that exports everything and a `ProbabilityBasedSampler` that samples a fixed fraction of spans. -NOTE: the `PercentageBasedSampler` is the default if you are using -`spring-cloud-sleuth-zipkin` or `spring-cloud-sleuth-stream`. You can -configure the exports using `spring.sleuth.sampler.percentage`. The passed -value needs to be a double from `0.0` to `1.0` so it's not a percentage. -For backwards compatibility reasons we're not changing the property name. +NOTE: The `ProbabilityBasedSampler` is the default if you are using +`spring-cloud-sleuth-zipkin`. You can +configure the exports using `spring.sleuth.sampler.probability`. The passed +value needs to be a double from `0.0` to `1.0`. A sampler can be installed just by creating a bean definition, e.g: @@ -54,6 +312,247 @@ TIP: You can set the HTTP header `X-B3-Flags` to `1` or when doing messaging you set `spanFlags` header to `1`. Then the current span will be forced to be exportable regardless of the sampling decision. +== Propagation + +Propagation is needed to ensure activity originating from the same root +are collected together in the same trace. The most common propagation +approach is to copy a trace context from a client sending an RPC request +to a server receiving it. + +For example, when an downstream Http call is made, its trace context is +sent along with it, encoded as request headers: + +``` + Client Span Server Span +┌──────────────────┐ ┌──────────────────┐ +│ │ │ │ +│ TraceContext │ Http Request Headers │ TraceContext │ +│ ┌──────────────┐ │ ┌───────────────────┐ │ ┌──────────────┐ │ +│ │ TraceId │ │ │ X─B3─TraceId │ │ │ TraceId │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ ParentSpanId │ │ Extract │ X─B3─ParentSpanId │ Inject │ │ ParentSpanId │ │ +│ │ ├─┼─────────>│ ├────────┼>│ │ │ +│ │ SpanId │ │ │ X─B3─SpanId │ │ │ SpanId │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ Sampled │ │ │ X─B3─Sampled │ │ │ Sampled │ │ +│ └──────────────┘ │ └───────────────────┘ │ └──────────────┘ │ +│ │ │ │ +└──────────────────┘ └──────────────────┘ +``` + +The names above are from https://github.com/openzipkin/b3-propagation[B3 Propagation], +which is built-in to Brave and has implementations in many languages and +frameworks. + +Most users will use a framework interceptor which automates propagation. +Here's how they might work internally. + +Here's what client-side propagation might look like + +```java +// configure a function that injects a trace context into a request +injector = tracing.propagation().injector(Request.Builder::addHeader); + +// before a request is sent, add the current span's context to it +injector.inject(span.context(), request); +``` + +Here's what server-side propagation might look like + +```java +// configure a function that extracts the trace context from a request +extracted = tracing.propagation().extractor(Request::getHeader); + +// when a server receives a request, it joins or starts a new trace +span = tracer.nextSpan(extracted, request); +``` + +=== Propagating extra fields + +Sometimes you need to propagate extra fields, such as a request ID or an alternate trace context. +For example, if you are in a Cloud Foundry environment, you might want to pass the request ID: + +```java +// when you initialize the builder, define the extra field you want to propagate +tracingBuilder.propagationFactory( + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-vcap-request-id") +); + +// later, you can tag that request ID or use it in log correlation +requestId = ExtraFieldPropagation.get("x-vcap-request-id"); +``` + +You may also need to propagate a trace context you aren't using. For example, you may be in an +Amazon Web Services environment, but not reporting data to X-Ray. To ensure X-Ray can co-exist +correctly, pass-through its tracing header like so. + +```java +tracingBuilder.propagationFactory( + ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id") +); +``` + +==== Prefixed fields + +You can also prefix fields, if they follow a common pattern. For example, the following will +propagate the field "x-vcap-request-id" as-is, but send the fields "country-code" and "user-id" +on the wire as "x-baggage-country-code" and "x-baggage-user-id" respectively. + +Setup your tracing instance with allowed fields: + +```java +tracingBuilder.propagationFactory( + ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY) + .addField("x-vcap-request-id") + .addPrefixedFields("baggage-", Arrays.asList("country-code", "user-id")) + .build() +); +``` + +Later, you can call below to affect the country code of the current trace context + +```java +ExtraFieldPropagation.set("country-code", "FO"); +String countryCode = ExtraFieldPropagation.get("country-code"); +``` + +Or, if you have a reference to a trace context, use it explicitly + +```java +ExtraFieldPropagation.set(span.context(), "country-code", "FO"); +String countryCode = ExtraFieldPropagation.get(span.context(), "country-code"); +``` + +IMPORTANT: In comparison to previous versions of Sleuth, with +Brave it's required to pass the list of baggage keys. +There are two properties to achieve this. Via the `spring.sleuth.baggage-keys` you set keys +that will get prefixed with `baggage-` for http calls and `baggage_` for messaging. You can also pass +a list of prefixed keys that will be whitelisted without any prefix via +`spring.sleuth.prefixed-keys` property. + +==== Extracting a propagated context + +The `TraceContext.Extractor` reads trace identifiers and sampling status +from an incoming request or message. The carrier is usually a request object +or headers. + +This utility is used in standard instrumentation like [HttpServerHandler](../instrumentation/http/src/main/java/sleuth/http/HttpServerHandler.java), +but can also be used for custom RPC or messaging code. + +`TraceContextOrSamplingFlags` is usually only used with `Tracer.nextSpan(extracted)`, unless you are +sharing span IDs between a client and a server. + +==== Sharing span IDs between client and server + +A normal instrumentation pattern is creating a span representing the server +side of an RPC. `Extractor.extract` might return a complete trace context when +applied to an incoming client request. `Tracer.joinSpan` attempts to continue +the this trace, using the same span ID if supported, or creating a child span +if not. When span ID is shared, data reported includes a flag saying so. + +Here's an example of B3 propagation: + +``` + ┌───────────────────┐ ┌───────────────────┐ + Incoming Headers │ TraceContext │ │ TraceContext │ +┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │ +│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │ +│ │ │ │ │ │ │ │ │ │ +│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │ +│ │ │ │ │ │ │ │ │ │ +│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │ +└───────────────────┘ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ Shared: true │ │ + │ └───────────────┘ │ │ └───────────────┘ │ + └───────────────────┘ └───────────────────┘ +``` + +Some propagation systems only forward the parent span ID, detected when +`Propagation.Factory.supportsJoin() == false`. In this case, a new span ID is +always provisioned and the incoming context determines the parent ID. + +Here's an example of AWS propagation: +``` + ┌───────────────────┐ ┌───────────────────┐ + x-amzn-trace-id │ TraceContext │ │ TraceContext │ +┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │ +│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │ +│ │ │ │ │ │ │ │ │ │ +│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │ +└───────────────────┘ │ └───────────────┘ │ │ │ │ │ + └───────────────────┘ │ │ SpanId: New │ │ + │ └───────────────┘ │ + └───────────────────┘ +``` + +Note: Some span reporters do not support sharing span IDs. For example, if you +set `Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive)`, disable join +via `Tracing.Builder.supportsJoin(false)`. This will force a new child span on +`Tracer.joinSpan()`. + +==== Implementing Propagation + +`TraceContext.Extractor` is implemented by a `Propagation.Factory` plugin. Internally, this code +will create the union type `TraceContextOrSamplingFlags` with one of the following: +* `TraceContext` if trace and span IDs were present. +* `TraceIdContext` if a trace ID was present, but not span IDs. +* `SamplingFlags` if no identifiers were present + +Some `Propagation` implementations carry extra data from point of extraction (ex reading incoming +headers) to injection (ex writing outgoing headers). For example, it might carry a request ID. When +implementations have extra data, here's how they handle it. +* If a `TraceContext` was extracted, add the extra data as `TraceContext.extra()` +* Otherwise, add it as `TraceContextOrSamplingFlags.extra()`, which `Tracer.nextSpan` handles. + +== Current Tracing Component + +Brave supports a "current tracing component" concept which should only +be used when you have no other means to get a reference. This was made +for JDBC connections, as they often initialize prior to the tracing +component. + +The most recent tracing component instantiated is available via +`Tracing.current()`. You there's also a shortcut to get only the tracer +via `Tracing.currentTracer()`. If you use either of these methods, do +noot cache the result. Instead, look them up each time you need them. + +== Current Span + +Brave supports a "current span" concept which represents the in-flight +operation. `Tracer.currentSpan()` can be used to add custom tags to a +span and `Tracer.nextSpan()` can be used to create a child of whatever +is in-flight. + +=== Setting a span in scope manually + +When writing new instrumentation, it is important to place a span you +created in scope as the current span. Not only does this allow users to +access it with `Tracer.currentSpan()`, but it also allows customizations +like SLF4J MDC to see the current trace IDs. + +`Tracer.withSpanInScope(Span)` facilitates this and is most conveniently +employed via the try-with-resources idiom. Whenever external code might +be invoked (such as proceeding an interceptor or otherwise), place the +span in scope like this. + +```java +try (SpanInScope ws = tracer.withSpanInScope(span)) { + return inboundRequest.invoke(); +} finally { // note the scope is independent of the span + span.finish(); +} +``` + +In edge cases, you may need to clear the current span temporarily. For +example, launching a task that should not be associated with the current +request. To do this, simply pass null to `withSpanInScope`. + +```java +try (SpanInScope cleared = tracer.withSpanInScope(null)) { + startBackgroundThread(); +} +``` + == Instrumentation Spring Cloud Sleuth instruments all your Spring application @@ -74,29 +573,24 @@ NOTE: Remember that tags are only collected and exported if there is a danger of accidentally collecting too much data without configuring something). -NOTE: Currently the instrumentation in Spring Cloud Sleuth is eager - it means that -we're actively trying to pass the tracing context between threads. Also timing events -are captured even when sleuth isn't exporting data to a tracing system. -This approach may change in the future towards being lazy on this matter. - == Span lifecycle -You can do the following operations on the Span by means of *org.springframework.cloud.sleuth.Tracer* interface: +You can do the following operations on the Span by means of *brave.Tracer*: -- <> - when you start a span its name is assigned and start timestamp is recorded. -- <> - the span gets finished (the end time of the span is recorded) and if -the span is *exportable* then it will be eligible for collection to Zipkin. -The span is also removed from the current thread. +- <> - when you start a span its name is assigned and start timestamp is recorded. +- <> - the span gets finished (the end time of the span is recorded) and if +the span is *sampled* then it will be eligible for collection to e.g. Zipkin. - <> - a new instance of span will be created whereas it will be a copy of the one that it continues. - <> - the span doesn't get stopped or closed. It only gets removed from the current thread. - <> - you can create a new span and set an explicit parent to it -TIP: Spring creates the instance of `Tracer` for you. In order to use it all you need is to just autowire it. +TIP: Spring Cloud Sleuth creates the instance of `Tracer` for you. In order to use it, +all you need is to just autowire it. -=== Creating and closing spans [[creating-and-closing-spans]] +=== Creating and finishing spans [[creating-and-finishing-spans]] -You can manually create spans by using the *Tracer* interface. +You can manually create spans by using the *Tracer*. [source,java] ---- @@ -106,7 +600,7 @@ include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/ In this example we could see how to create a new instance of span. Assuming that there already was a span present in this thread then it would become the parent of that span. -IMPORTANT: Always clean after you create a span! Don't forget to close a span if you want to send it to Zipkin. +IMPORTANT: Always clean after you create a span! Don't forget to finish a span if you want to send it to Zipkin. IMPORTANT: If your span contains a name greater than 50 chars, then that name will be truncated to 50 chars. Your names have to be explicit and concrete. Big names lead to @@ -121,39 +615,28 @@ situation might be (of course it all depends on the use-case): - *Hystrix* - executing a Hystrix command is most likely a logical part of the current processing. It's in fact only a technical implementation detail that you wouldn't necessarily want to reflect in tracing as a separate being. -The continued instance of span is equal to the one that it continues: -[source,java] ----- -Span continuedSpan = this.tracer.continueSpan(spanToContinue); -assertThat(continuedSpan).isEqualTo(spanToContinue); ----- - -To continue a span you can use the *Tracer* interface. +To continue a span you can use *brave.Tracer*. [source,java] ---- include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_continuation,indent=0] ---- -IMPORTANT: Always clean after you create a span! Don't forget to detach a span if some work was done started in one - thread (e.g. thread X) and it's waiting for other threads (e.g. Y, Z) to finish. - Then the spans in the threads Y, Z should be detached at the end of their work. When the results are collected - the span in thread X should be closed. - === Creating spans with an explicit parent [[creating-spans-with-explicit-parent]] There is a possibility that you want to start a new span and provide an explicit parent of that span. -Let's assume that the parent of a span is in one thread and you want to start a new span in another thread. The -`startSpan` method of the `Tracer` interface is the method you are looking for. +Let's assume that the parent of a span is in one thread and you want to start a new span in another thread. +In Brave, whenever you call `nextSpan()`, it's creating one in reference +to the span being currently in scope. It's enough to just put +the span in scope and then call `nextSpan()`, as presented in the example below: [source,java] ---- include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java[tags=manual_span_joining,indent=0] ---- -IMPORTANT: After having created such a span remember to close it. Otherwise you will see a lot of warnings in your logs - related to the fact that you have a span present in the current thread other than the one you're trying to close. - What's worse your spans won't get closed properly thus will not get collected to Zipkin. +IMPORTANT: After having created such a span remember to finish it, otherwise it will not get +reported to e.g. Zipkin == Naming spans @@ -344,82 +827,14 @@ if executed with a value of `15` will lead to setting of a tag with a String val == Customizations -Thanks to the `SpanInjector` and `SpanExtractor` you can customize the way spans -are created and propagated. - -There are currently two built-in ways to pass tracing information between processes: - - * via Spring Integration - * via HTTP - -Span ids are extracted from Zipkin-compatible (B3) headers (either `Message` -or HTTP headers), to start or join an existing trace. Trace information is -injected into any outbound requests so the next hop can extract them. - -The key change in comparison to the previous versions of Sleuth is that Sleuth is implementing -the Open Tracing's `TextMap` notion. In Sleuth it's called `SpanTextMap`. Basically the idea -is that any means of communication (e.g. message, http request, etc.) can be abstracted via -a `SpanTextMap`. This abstraction defines how one can insert data into the carrier and -how to retrieve it from there. Thanks to this if you want to instrument a new HTTP library -that uses a `FooRequest` as a mean of sending HTTP requests then you have to create an -implementation of a `SpanTextMap` that delegates calls to `FooRequest` in terms of retrieval -and insertion of HTTP headers. +// TODO: Update this === Spring Integration -For Spring Integration there are 2 interfaces responsible for creation of a Span from a `Message`. -These are: - -- `MessagingSpanTextMapExtractor` -- `MessagingSpanTextMapInjector` - -You can override them by providing your own implementation. === HTTP -For HTTP there are 2 interfaces responsible for creation of a Span from a `Message`. -These are: - -- `HttpSpanExtractor` -- `HttpSpanInjector` - -You can override them by providing your own implementation. - -=== Example - -Let's assume that instead of the standard Zipkin compatible tracing HTTP header names -you have - -* for trace id - `correlationId` -* for span id - `mySpanId` - -This is a an example of a `SpanExtractor` - -[source,java] ----- -include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=extractor,indent=0] ----- - -And you could register it like this: - -[source,java] ----- -include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java[tags=configuration,indent=0] ----- - -Spring Cloud Sleuth does not add trace/span related headers to the Http Response for security reasons. If you need the headers then a custom `SpanInjector` -that injects the headers into the Http Response and a Servlet filter which makes use of this can be added the following way: - -[source,java] ----- -include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=injector,indent=0] ----- - -And you could register them like this: -[source,java] ----- -include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java[tags=configuration,indent=0] ----- +// TODO: Update this === TraceFilter @@ -436,19 +851,6 @@ add to the Span a tag with key `custom` and a value `tag`. include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java[tags=response_headers,indent=0] ---- -=== Custom SA tag in Zipkin - -Sometimes you want to create a manual Span that will wrap a call to an external service which is not instrumented. -What you can do is to create a span with the `peer.service` tag that will contain a value of the service that you want to call. -Below you can see an example of a call to Redis that is wrapped in such a span. - -[source,java] ----- -include::../../../..//spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/ZipkinSpanReporterTests.java[tags=service_name,indent=0] ----- - -IMPORTANT: Remember not to add both `peer.service` tag and the `SA` tag! You have to add only `peer.service`. - === Custom service name By default Sleuth assumes that when you send a span to Zipkin, you want the span's service name @@ -467,26 +869,23 @@ spring.zipkin.service.name: foo Before reporting spans to e.g. Zipkin you can be interested in modifying that span in some way. You can achieve that by using the `SpanAdjuster` interface. -Example of usage: - In Sleuth we're generating spans with a fixed name. Some users want to modify the name depending on values of tags. Implementation of the `SpanAdjuster` interface can be used to alter that name. Example: -[source,yaml] +Example. If you register two beans of `SpanAdjuster` type: + +[source,java] ---- -@Bean -SpanAdjuster customSpanAdjuster() { - return span -> span.toBuilder().name(scrub(span.getName())).build(); -} +include::../../../..//spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/SpanAdjusterAspectTests.java[tags=adjuster,indent=0] ---- -This will lead in changing the name of the reported span just before it gets sent to Zipkin. - -IMPORTANT: Your `SpanReporter` should inject the `SpanAdjuster` and - allow span manipulation before the actual reporting is done. +This will lead in changing the name of the reported span to `foo bar`, just before it gets reported (e.g. to Zipkin). === Host locator +IMPORTANT: This section is about defining *host* from service discovery. It's *NOT* +about finding Zipkin in service discovery. + In order to define the host that is corresponding to a particular span we need to resolve the host name and port. The default approach is to take it from server properties. If those for some reason are not set then we're trying to retrieve the host name from the network interfaces. @@ -520,68 +919,15 @@ Zipkin's service id inside the URL (example for `zipkinserver` service id) spring.zipkin.baseUrl: http://zipkinserver/ ---- -== Span Data as Messages +== Zipkin Stream Span Consumer IMPORTANT: The suggested approach is to use the Zipkin's native support for message based span sending. Starting from Edgware Zipkin Stream server is deprecated and in Finchley it got removed. -You can accumulate and send span data over -http://cloud.spring.io/spring-cloud-stream[Spring Cloud Stream] by -including the `spring-cloud-sleuth-stream` jar as a dependency, and -adding a Channel Binder implementation -(e.g. `spring-cloud-starter-stream-rabbit` for RabbitMQ or -`spring-cloud-starter-stream-kafka` for Kafka). This will -automatically turn your app into a producer of messages with payload -type `Spans`. The channel name to which the spans will be sent -is called `sleuth`. - -=== Zipkin Consumer - Please refer to the http://cloud.spring.io/spring-cloud-static/Dalston.SR4/multi/multi__span_data_as_messages.html#_zipkin_consumer[Dalston Documentaion] -on how to create a Stream Zipkin server. That approach has been -deprecated in Edgware and removed in Finchley release. - -=== Custom Consumer - -A custom consumer can also easily be implemented using -`spring-cloud-sleuth-stream` and binding to the `SleuthSink`. Example: - -[source,java] ----- -@EnableBinding(SleuthSink.class) -@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class) -@MessageEndpoint -public class Consumer { - - @ServiceActivator(inputChannel = SleuthSink.INPUT) - public void sink(Spans input) throws Exception { - // ... process spans - } -} ----- - -NOTE: the sample consumer application above explicitly excludes -`SleuthStreamAutoConfiguration` so it doesn't send messages to itself, -but this is optional (you might actually want to trace requests into -the consumer app). - -In order to customize the polling mechanism you can create a bean of `PollerMetadata` type -with name equal to `StreamSpanReporter.POLLER`. Here you can find an example of such a configuration. - -[source,java] ----- -include::../../../../spring-cloud-sleuth-stream/src/test/java/org/springframework/cloud/sleuth/stream/SleuthStreamAutoConfigurationTest.java[tags=custom_poller,indent=0] ----- - -== Metrics - -Currently Spring Cloud Sleuth registers very simple metrics related to spans. -It's using the http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html#production-ready-recording-metrics[Spring Boot's metrics support] -to calculate the number of accepted and dropped spans. Each time a span gets -sent to Zipkin the number of accepted spans will increase. If there's an error then -the number of dropped spans will get increased. +on how to create a Stream Zipkin server. == Integrations @@ -672,7 +1018,6 @@ Via the `TraceWebFilter` all sampled incoming requests result in creation of a S like to skip via the `spring.sleuth.web.skipPattern` property. If you have `ManagementServerProperties` on classpath then its value of `contextPath` gets appended to the provided skip pattern. - === HTTP client integration ==== Synchronous Rest Template @@ -686,19 +1031,9 @@ If you create a `RestTemplate` instance with a `new` keyword then the instrument ==== Asynchronous Rest Template -IMPORTANT: A traced version of an `AsyncRestTemplate` bean is registered for you out of the box. If you -have your own bean you have to wrap it in a `TraceAsyncRestTemplate` representation. The best solution -is to only customize the `ClientHttpRequestFactory` and / or `AsyncClientHttpRequestFactory`. -*If you have your own `AsyncRestTemplate` and you don't wrap it your calls WILL NOT GET TRACED*. - -Custom instrumentation is set to create and close Spans upon sending and receiving requests. You can customize the `ClientHttpRequestFactory` -and the `AsyncClientHttpRequestFactory` by registering your beans. Remember to use tracing compatible implementations (e.g. don't forget to -wrap `ThreadPoolTaskScheduler` in a `TraceAsyncListenableTaskExecutor`). Example of custom request factories: - -[source,java] ----- -include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java[tags=async_template_factories,indent=0] ----- +IMPORTANT: Starting with Sleuth `2.0.0` we no longer register +a bean of `AsyncRestTemplate` type. It's up to you to create such +a bean. Then we will instrument it. To block the `AsyncRestTemplate` features set `spring.sleuth.web.async.client.enabled` to `false`. To disable creation of the default `TraceAsyncClientHttpRequestFactoryWrapper` set `spring.sleuth.web.async.client.factory.enabled` @@ -711,7 +1046,7 @@ can see an example of how to set up such a custom `AsyncRestTemplate`. [source,java] ---- -include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java[tags=custom_async_rest_template,indent=0] +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java[tags=custom_async_rest_template,indent=0] ---- ==== WebClient @@ -800,7 +1135,7 @@ can see an example of how to set up such a custom `Executor`. [source,java] ---- -include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java[tags=custom_executor,indent=0] +include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java[tags=custom_executor,indent=0] ---- === Messaging diff --git a/pom.xml b/pom.xml index da97693785..07f1b31dd0 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,6 @@ spring-cloud-sleuth-dependencies spring-cloud-sleuth-core spring-cloud-sleuth-zipkin - spring-cloud-sleuth-stream spring-cloud-starter-sleuth spring-cloud-starter-zipkin spring-cloud-sleuth-samples @@ -253,6 +252,18 @@ false + + + jfrog-snapshots + JFrog Snapshots + https://oss.jfrog.org/oss-snapshot-local/ + + true + + + false + + spring-milestones Spring Milestones diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index 233448a219..52a8bbee24 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -135,6 +135,24 @@ commons-logging true + + + io.zipkin.brave + brave + + + io.zipkin.brave + brave-context-log4j2 + + + io.zipkin.brave + brave-instrumentation-spring-web + + + io.zipkin.brave + brave-instrumentation-spring-webmvc + + org.springframework.boot spring-boot-starter-test diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java index c8fd215f3d..a902a9fafc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/DefaultSpanNamer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ * @author Marcin Grzejszczak * @since 1.0.0 * - * @see org.springframework.cloud.sleuth.SpanName + * @see SpanName */ public class DefaultSpanNamer implements SpanNamer { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ErrorParser.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ErrorParser.java index 97033d406f..aa5d33ac55 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ErrorParser.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ErrorParser.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth; +import brave.SpanCustomizer; + /** * Contract for hooking into process of adding error response tags. * This interface is only called when an exception is thrown upon receiving a response. @@ -12,11 +14,9 @@ public interface ErrorParser { /** * Allows setting of tags when an exception was thrown when the response was received. - * The implementation should not manipulate the {@link Span} in other way than just - * by adding the tags. * * @param span - current span in context * @param error - error that was thrown upon receiving a response */ - void parseErrorTags(Span span, Throwable error); + void parseErrorTags(SpanCustomizer span, Throwable error); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParser.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParser.java index 68bef847dd..af9a25e62c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParser.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParser.java @@ -1,9 +1,8 @@ package org.springframework.cloud.sleuth; +import brave.SpanCustomizer; +import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.util.ExceptionUtils; - -import java.lang.invoke.MethodHandles; /** * {@link ErrorParser} that sets the error tag for an exportable span. @@ -13,16 +12,20 @@ */ public class ExceptionMessageErrorParser implements ErrorParser { - private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(ExceptionMessageErrorParser.class); @Override - public void parseErrorTags(Span span, Throwable error) { - if (span != null && span.isExportable()) { - String errorMsg = ExceptionUtils.getExceptionMessage(error); + public void parseErrorTags(SpanCustomizer span, Throwable error) { + if (span != null && error != null) { + String errorMsg = getExceptionMessage(error); if (log.isDebugEnabled()) { log.debug("Adding an error tag [" + errorMsg + "] to span " + span); } - span.tag(Span.SPAN_ERROR_TAG_NAME, errorMsg); + span.tag("error", errorMsg); } } + + private String getExceptionMessage(Throwable e) { + return e.getMessage() != null ? e.getMessage() : e.toString(); + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/InternalApi.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/InternalApi.java deleted file mode 100644 index a3db0ce1fe..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/InternalApi.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth; - -/** - * Internal API that can be changed any time so please do not use it! - * - * @author Marcin Grzejszczak - * @since 2.0.0 - */ -public class InternalApi { - - public static void renameSpan(Span span, String newName) { - span.name = newName; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java deleted file mode 100644 index d3e392751d..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Log.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Represents an event in time associated with a span. Every span has zero or more Logs, - * each of which being a timestamped event name. - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class Log { - /** - * The epoch timestamp of the log record; often set via {@link System#currentTimeMillis()}. - */ - private final long timestamp; - - /** - * Event should be the stable name of some notable moment in the lifetime of a span. - * For instance, a span representing a browser page load might add an Event for each of the - * Performance.timing moments here: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming - * - *

While it is not a formal requirement, Event strings will be most useful if they are *not* - * unique; rather, tracing systems should be able to use them to understand how two similar spans - * relate from an internal timing perspective. - */ - private final String event; - - @JsonCreator - public Log( - @JsonProperty(value = "timestamp", required = true) long timestamp, - @JsonProperty(value = "event", required = true) String event - ) { - if (event == null) throw new NullPointerException("event"); - this.timestamp = timestamp; - this.event = event; - } - - public long getTimestamp() { - return this.timestamp; - } - - public String getEvent() { - return this.event; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof Log) { - Log that = (Log) o; - return (this.timestamp == that.timestamp) - && (this.event.equals(that.event)); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (this.timestamp >>> 32) ^ this.timestamp; - h *= 1000003; - h ^= this.event.hashCode(); - return h; - } - - @Override public String toString() { - return "Log{" + - "timestamp=" + this.timestamp + - ", event='" + this.event + '\'' + - '}'; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanAdjuster.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanAdjuster.java deleted file mode 100644 index 52f9bf454e..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanAdjuster.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Span adjuster that does nothing. - * - * @author Marcin Grzejszczak - * @since 1.1.4 - */ -public class NoOpSpanAdjuster implements SpanAdjuster { - @Override public Span adjust(Span span) { - return span; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java deleted file mode 100644 index 377a366fe6..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/NoOpSpanReporter.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Span reporter that does nothing - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class NoOpSpanReporter implements SpanReporter { - @Override - public void report(Span span) { - - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java deleted file mode 100644 index e7a97592d1..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Sampler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Extremely simple callback to determine the frequency that an action should be traced. - * - * @since 1.0.0 - */ -public interface Sampler { - /** - * @return true if the span is not null and should be exported to the tracing system - */ - boolean isSampled(Span span); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java deleted file mode 100644 index 4c254b9e9e..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ /dev/null @@ -1,803 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; - -/** - * Class for gathering and reporting statistics about a block of execution. - *

- * Spans should form a directed acyclic graph structure. It should be possible to keep - * following the parents of a span until you arrive at a span with no parents. - *

- * Spans can be either annotated with tags or logs. - *

- * An Annotation is used to record existence of an event in time. Below you can - * find some of the core annotations used to define the start and stop of a request: - *

- *

    - *
  • cs - Client Sent
  • - *
  • sr - Server Received
  • - *
  • ss - Server Sent
  • - *
  • cr - Client Received
  • - *
- * - * Spring Cloud Sleuth uses Zipkin compatible header names - * - *
    - *
  • X-B3-TraceId: 64 encoded bits
  • - *
  • X-B3-SpanId: 64 encoded bits
  • - *
  • X-B3-ParentSpanId: 64 encoded bits
  • - *
  • X-B3-Sampled: Boolean (either “1” or “0”)
  • - *
- * - * @author Spencer Gibb - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -/* - * OpenTracing spans can affect the trace tree by creating children. In this way, they are - * like scoped tracers. Sleuth spans are DTOs, whose sole responsibility is the current - * span in the trace tree. - */ -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) -@JsonInclude(JsonInclude.Include.NON_DEFAULT) -public class Span implements SpanContext { - - public static final String SAMPLED_NAME = "X-B3-Sampled"; - public static final String PROCESS_ID_NAME = "X-Process-Id"; - public static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; - public static final String TRACE_ID_NAME = "X-B3-TraceId"; - public static final String SPAN_NAME_NAME = "X-Span-Name"; - public static final String SPAN_ID_NAME = "X-B3-SpanId"; - public static final String SPAN_EXPORT_NAME = "X-Span-Export"; - public static final String SPAN_FLAGS = "X-B3-Flags"; - public static final String SPAN_BAGGAGE_HEADER_PREFIX = "baggage"; - public static final Set SPAN_HEADERS = new HashSet<>( - Arrays.asList(SAMPLED_NAME, PROCESS_ID_NAME, PARENT_ID_NAME, TRACE_ID_NAME, - SPAN_ID_NAME, SPAN_NAME_NAME, SPAN_EXPORT_NAME)); - - public static final String SPAN_SAMPLED = "1"; - public static final String SPAN_NOT_SAMPLED = "0"; - - public static final String SPAN_LOCAL_COMPONENT_TAG_NAME = "lc"; - public static final String SPAN_ERROR_TAG_NAME = "error"; - - /** - * cr - Client Receive. Signifies the end of the span. The client has - * successfully received the response from the server side. If one subtracts the cs - * timestamp from this timestamp one will receive the whole time needed by the client - * to receive the response from the server. - */ - public static final String CLIENT_RECV = "cr"; - - /** - * cs - Client Sent. The client has made a request (a client can be e.g. - * {@link org.springframework.web.client.RestTemplate}. This annotation depicts the - * start of the span. - */ - // For an outbound RPC call, it should log a "cs" annotation. - // If possible, it should log a binary annotation of "sa", indicating the - // destination address. - public static final String CLIENT_SEND = "cs"; - - /** - * sr - Server Receive. The server side got the request and will start - * processing it. If one subtracts the cs timestamp from this timestamp one will - * receive the network latency. - */ - // If an inbound RPC call, it should log a "sr" annotation. - // If possible, it should log a binary annotation of "ca", indicating the - // caller's address (ex X-Forwarded-For header) - public static final String SERVER_RECV = "sr"; - - /** - * ss - Server Send. Annotated upon completion of request processing (when the - * response got sent back to the client). If one subtracts the sr timestamp from this - * timestamp one will receive the time needed by the server side to process the - * request. - */ - public static final String SERVER_SEND = "ss"; - - /** - * As - * in Open Tracing - */ - public static final String SPAN_PEER_SERVICE_TAG_NAME = "peer.service"; - - /** - * ID of the instance from which the span was originated. - */ - public static final String INSTANCEID = "spring.instance_id"; - - private final long begin; - private long end = 0; - volatile String name; - private final long traceIdHigh; - private final long traceId; - private List parents = new ArrayList<>(); - private final long spanId; - private boolean remote = false; - private boolean exportable = true; - private final Map tags; - private final String processId; - private final Collection logs; - private final Span savedSpan; - @JsonIgnore - private final Map baggage; - - // Null means we don't know the start tick, so fallback to time - @JsonIgnore - private final Long startNanos; - private Long durationMicros; // serialized in json so micros precision isn't lost - /* - Using B3 propagation, it is most typical to share the same span ID across client and - the server. This has backend implications like who owns the timestamp (hint the - client does). When a SpanReporter receives a completed span, it should know if it - is shared or not. - */ - private final boolean shared; - - @SuppressWarnings("unused") - private Span() { - this(-1, -1, "dummy", 0, Collections.emptyList(), 0, false, false, null); - } - - /** - * Creates a new span that still tracks tags and logs of the current span. This is - * crucial when continuing spans since the changes in those collections done in the - * continued span need to be reflected until the span gets closed. - * - * @deprecated - use {@link SpanBuilder} - */ - @Deprecated - public Span(Span current, Span savedSpan) { - this.begin = current.getBegin(); - this.end = current.getEnd(); - this.name = current.getName(); - this.traceIdHigh = current.getTraceIdHigh(); - this.traceId = current.getTraceId(); - this.parents = current.getParents(); - this.spanId = current.getSpanId(); - this.remote = current.isRemote(); - this.exportable = current.isExportable(); - this.processId = current.getProcessId(); - this.tags = current.tags; - this.logs = current.logs; - this.startNanos = current.startNanos; - this.durationMicros = current.durationMicros; - this.baggage = current.baggage; - this.savedSpan = savedSpan; - this.shared = current.shared; - } - - Span(long begin, long end, String name, long traceId, List parents, - long spanId, boolean remote, boolean exportable, String processId) { - this(begin, end, name, traceId, parents, spanId, remote, exportable, processId, - null, false); - } - - Span(long begin, long end, String name, long traceId, List parents, - long spanId, boolean remote, boolean exportable, String processId, - Span savedSpan, boolean shared) { - this(new SpanBuilder() - .begin(begin) - .end(end) - .name(name) - .traceId(traceId) - .parents(parents) - .spanId(spanId) - .remote(remote) - .exportable(exportable) - .processId(processId) - .savedSpan(savedSpan) - .shared(shared)); - } - - Span(SpanBuilder builder) { - if (builder.begin > 0) { // conventionally, 0 indicates unset - this.startNanos = null; // don't know the start tick - this.begin = builder.begin; - } else { - this.startNanos = nanoTime(); - this.begin = System.currentTimeMillis(); - } - if (builder.end > 0) { - this.end = builder.end; - this.durationMicros = (this.end - this.begin) * 1000; - } - this.name = builder.name != null ? builder.name : ""; - this.traceIdHigh = builder.traceIdHigh; - this.traceId = builder.traceId; - this.parents.addAll(builder.parents); - this.spanId = builder.spanId; - this.remote = builder.remote; - this.exportable = builder.exportable; - this.processId = builder.processId; - this.savedSpan = builder.savedSpan; - this.tags = new ConcurrentHashMap<>(); - this.tags.putAll(builder.tags); - this.logs = new ConcurrentLinkedQueue<>(); - this.logs.addAll(builder.logs); - this.baggage = new ConcurrentHashMap<>(); - this.baggage.putAll(builder.baggage); - this.shared = builder.shared; - } - - public static SpanBuilder builder() { - return new SpanBuilder(); - } - - /** - * The block has completed, stop the clock - */ - public synchronized void stop() { - if (this.durationMicros == null) { - if (this.begin == 0) { - throw new IllegalStateException( - "Span for " + this.name + " has not been started"); - } - if (this.end == 0) { - this.end = System.currentTimeMillis(); - } - if (this.startNanos != null) { // set a precise duration - this.durationMicros = Math.max(1, (nanoTime() - this.startNanos) / 1000); - } else { - this.durationMicros = (this.end - this.begin) * 1000; - } - } - } - - /** - * Return the total amount of time elapsed since start was called, if running, or - * difference between stop and start, in microseconds. - * - * Note that in case of the spans that have CS / CR events we will not - * send to Zipkin the accumulated microseconds but will calculate the - * duration basing on the timestamps of the CS / CR events. - * - * @return zero if not running, or a positive number of microseconds. - */ - @JsonIgnore - public synchronized long getAccumulatedMicros() { - if (this.durationMicros != null) { - return this.durationMicros; - } else { // stop() hasn't yet been called - if (this.begin == 0) { - return 0; - } - if (this.startNanos != null) { - return Math.max(1, (nanoTime() - this.startNanos) / 1000); - } else { - return (System.currentTimeMillis() - this.begin) * 1000; - } - } - } - - // Visible for testing - @JsonIgnore - long nanoTime() { - return System.nanoTime(); - } - - /** - * Has the span been started and not yet stopped? - */ - @JsonIgnore - public synchronized boolean isRunning() { - return this.begin != 0 && this.durationMicros == null; - } - - /** - * Add a tag or data annotation associated with this span. The tag will be added only - * if it has a value. - */ - public void tag(String key, String value) { - if (StringUtils.hasText(value)) { - this.tags.put(key, value); - } - } - - /** - * Add an {@link Log#event event} to the timeline associated with this span. - */ - public void logEvent(String event) { - logEvent(System.currentTimeMillis(), event); - } - - /** - * Add a {@link Log#event event} to a specific point (a timestamp in milliseconds) in the timeline - * associated with this span. - */ - public void logEvent(long timestampMilliseconds, String event) { - this.logs.add(new Log(timestampMilliseconds, event)); - } - - /** - * Sets a baggage item in the Span (and its SpanContext) as a key/value pair. - * - * Baggage enables powerful distributed context propagation functionality where arbitrary application data can be - * carried along the full path of request execution throughout the system. - * - * Note 1: Baggage is only propagated to the future (recursive) children of this SpanContext. - * - * Note 2: Baggage is sent in-band with every subsequent local and remote calls, so this feature must be used with - * care. - * - * @return this Span instance, for chaining - */ - public Span setBaggageItem(String key, String value) { - this.baggage.put(key.toLowerCase(), value); - return this; - } - - /** - * @return the value of the baggage item identified by the given key, or null if no such item could be found - */ - public String getBaggageItem(String key) { - return this.baggage.get(key.toLowerCase()); - } - - @Override - public final Iterable> baggageItems() { - return this.baggage.entrySet(); - } - - public final Map getBaggage() { - return Collections.unmodifiableMap(this.baggage); - } - - /** - * Get tag data associated with this span (read only) - *

- *

- * Will never be null. - */ - public Map tags() { - return Collections.unmodifiableMap(new LinkedHashMap<>(this.tags)); - } - - /** - * Get any timestamped events (read only) - *

- *

- * Will never be null. - */ - public List logs() { - return Collections.unmodifiableList(new ArrayList<>(this.logs)); - } - - /** - * Returns the saved span. The one that was "current" before this span. - *

- * Might be null - */ - @JsonIgnore - public Span getSavedSpan() { - return this.savedSpan; - } - - public boolean hasSavedSpan() { - return this.savedSpan != null; - } - - /** - * A human-readable name assigned to this span instance. - *

- */ - public String getName() { - return this.name; - } - - /** - * A pseudo-unique (random) number assigned to this span instance. - *

- *

- * The span id is immutable and cannot be changed. It is safe to access this from - * multiple threads. - */ - public long getSpanId() { - return this.spanId; - } - - /** - * When non-zero, the trace containing this span uses 128-bit trace identifiers. - * - *

{@code traceIdHigh} corresponds to the high bits in big-endian format and - * {@link #getTraceId()} corresponds to the low bits. - * - *

Ex. to convert the two fields to a 128bit opaque id array, you'd use code like below. - *

{@code
-	 * ByteBuffer traceId128 = ByteBuffer.allocate(16);
-	 * traceId128.putLong(span.getTraceIdHigh());
-	 * traceId128.putLong(span.getTraceId());
-	 * traceBytes = traceId128.array();
-	 * }
- * - * @see #traceIdString() - * @since 1.0.11 - */ - public long getTraceIdHigh() { - return this.traceIdHigh; - } - - /** - * Unique 8-byte identifier for a trace, set on all spans within it. - * - * @see #getTraceIdHigh() for notes about 128-bit trace identifiers - */ - public long getTraceId() { - return this.traceId; - } - - /** - * Return a unique id for the process from which this span originated. - *

- * Might be null - */ - public String getProcessId() { - return this.processId; - } - - /** - * Returns the parent IDs of the span. - *

- *

- * The collection will be empty if there are no parents. - */ - public List getParents() { - return this.parents; - } - - /** - * Flag that tells us whether the span was started in another process. Useful in RPC - * tracing when the receiver actually has to add annotations to the senders span. - */ - public boolean isRemote() { - return this.remote; - } - - /** - * Get the start time, in milliseconds - */ - public long getBegin() { - return this.begin; - } - - /** - * Get the stop time, in milliseconds - */ - public long getEnd() { - return this.end; - } - - /** - * Is the span eligible for export? If not then we may not need accumulate annotations - * (for instance). - */ - public boolean isExportable() { - return this.exportable; - } - - /** - * Span and trace id got extracted from a carrier? - * We are adding data to the same span created by a remote client - * - * @since 1.3.0 - */ - public boolean isShared() { - return this.shared; - } - - /** - * Returns the 16 or 32 character hex representation of the span's trace ID - * - * @since 1.0.11 - */ - public String traceIdString() { - if (this.traceIdHigh != 0) { - char[] result = new char[32]; - writeHexLong(result, 0, this.traceIdHigh); - writeHexLong(result, 16, this.traceId); - return new String(result); - } - char[] result = new char[16]; - writeHexLong(result, 0, this.traceId); - return new String(result); - } - - /** - * Converts the span to a {@link SpanBuilder} format - */ - public SpanBuilder toBuilder() { - return builder().from(this); - } - - /** - * Represents given long id as 16-character lower-hex string - * - * @see #traceIdString() - */ - public static String idToHex(long id) { - char[] data = new char[16]; - writeHexLong(data, 0, id); - return new String(data); - } - - /** Inspired by {@code okio.Buffer.writeLong} */ - static void writeHexLong(char[] data, int pos, long v) { - writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff)); - writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff)); - writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff)); - writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff)); - writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff)); - writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff)); - writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff)); - writeHexByte(data, pos + 14, (byte) (v & 0xff)); - } - - static void writeHexByte(char[] data, int pos, byte b) { - data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf]; - data[pos + 1] = HEX_DIGITS[b & 0xf]; - } - - static final char[] HEX_DIGITS = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - /** - * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any - * bits higher than 64. - */ - public static long hexToId(String hexString) { - Assert.hasText(hexString, "Can't convert empty hex string to long"); - int length = hexString.length(); - if (length < 1 || length > 32) throw new IllegalArgumentException("Malformed id: " + hexString); - - // trim off any high bits - int beginIndex = length > 16 ? length - 16 : 0; - - return hexToId(hexString, beginIndex); - } - - /** - * Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the - * specified index. - * - * @since 1.0.11 - */ - public static long hexToId(String lowerHex, int index) { - Assert.hasText(lowerHex, "Can't convert empty hex string to long"); - long result = 0; - for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) { - char c = lowerHex.charAt(index); - result <<= 4; - if (c >= '0' && c <= '9') { - result |= c - '0'; - } else if (c >= 'a' && c <= 'f') { - result |= c - 'a' + 10; - } else { - throw new IllegalArgumentException("Malformed id: " + lowerHex); - } - } - return result; - } - - @Override - public String toString() { - return "[Trace: " + traceIdString() + ", Span: " + idToHex(this.spanId) - + ", Parent: " + getParentIdIfPresent() + ", exportable:" + this.exportable + "]"; - } - - private String getParentIdIfPresent() { - return this.getParents().isEmpty() ? "null" : idToHex(this.getParents().get(0)); - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (this.traceIdHigh >>> 32) ^ this.traceIdHigh; - h *= 1000003; - h ^= (this.traceId >>> 32) ^ this.traceId; - h *= 1000003; - h ^= (this.spanId >>> 32) ^ this.spanId; - h *= 1000003; - return h; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof Span) { - Span that = (Span) o; - return (this.traceIdHigh == that.traceIdHigh) - && (this.traceId == that.traceId) - && (this.spanId == that.spanId); - } - return false; - } - - public static class SpanBuilder { - private long begin; - private long end; - private String name; - private long traceIdHigh; - private long traceId; - private final ArrayList parents = new ArrayList<>(); - private long spanId; - private boolean remote; - private boolean exportable = true; - private String processId; - private Span savedSpan; - private final List logs = new ArrayList<>(); - private final Map tags = new LinkedHashMap<>(); - private final Map baggage = new LinkedHashMap<>(); - private boolean shared; - - SpanBuilder() { - } - - /** - * Call this to record a begin time of a Span you didn't start. Don't call this when you are - * starting the span. - * - *

In other words, don't call {@code builder.begin(System.currentTimeMillis());}. doing so is - * redundant and will result in less precision when calculating elapsed time. - */ - public Span.SpanBuilder begin(long begin) { - this.begin = begin; - return this; - } - - public Span.SpanBuilder end(long end) { - this.end = end; - return this; - } - - public Span.SpanBuilder name(String name) { - this.name = name; - return this; - } - - public Span.SpanBuilder traceIdHigh(long traceIdHigh) { - this.traceIdHigh = traceIdHigh; - return this; - } - - public Span.SpanBuilder traceId(long traceId) { - this.traceId = traceId; - return this; - } - - public Span.SpanBuilder parent(Long parent) { - this.parents.add(parent); - return this; - } - - public Span.SpanBuilder parents(Collection parents) { - this.parents.clear(); - this.parents.addAll(parents); - return this; - } - - public Span.SpanBuilder log(Log log) { - this.logs.add(log); - return this; - } - - public Span.SpanBuilder logs(Collection logs) { - this.logs.clear(); - this.logs.addAll(logs); - return this; - } - - public Span.SpanBuilder tag(String tagKey, String tagValue) { - this.tags.put(tagKey, tagValue); - return this; - } - - public Span.SpanBuilder tags(Map tags) { - this.tags.clear(); - this.tags.putAll(tags); - return this; - } - - public Span.SpanBuilder baggage(String baggageKey, String baggageValue) { - this.baggage.put(baggageKey.toLowerCase(), baggageValue); - return this; - } - - public Span.SpanBuilder baggage(Map baggage) { - this.baggage.putAll(baggage); - return this; - } - - public Span.SpanBuilder spanId(long spanId) { - this.spanId = spanId; - return this; - } - - public Span.SpanBuilder remote(boolean remote) { - this.remote = remote; - return this; - } - - public Span.SpanBuilder exportable(boolean exportable) { - this.exportable = exportable; - return this; - } - - public Span.SpanBuilder processId(String processId) { - this.processId = processId; - return this; - } - - public Span.SpanBuilder savedSpan(Span savedSpan) { - this.savedSpan = savedSpan; - return this; - } - - public Span.SpanBuilder shared(boolean shared) { - this.shared = shared; - return this; - } - - /** - * Creates a {@link Span.SpanBuilder} from the {@link Span}. - */ - public Span.SpanBuilder from(Span span) { - return begin(span.begin).end(span.end).name(span.name) - .traceIdHigh(span.traceIdHigh).traceId(span.traceId) - .parents(span.getParents()).logs(span.logs).tags(span.tags).baggage(span.baggage) - .spanId(span.spanId).remote(span.remote).exportable(span.exportable) - .processId(span.processId).savedSpan(span.savedSpan); - } - - /** - * Builds a span. All collections lik baggage / tags / logs are *copied*, not continued. - * In other words if you add a tag to the input {@link Span}, the created span - * will not reflect that change. - */ - public Span build() { - return new Span(this); - } - - @Override - public String toString() { - return new Span(this).toString(); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAccessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAccessor.java deleted file mode 100644 index b609a490b2..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Strategy for accessing the current span. This is the primary interface for use by user - * code (if it needs access to spans at all - in general it is better to leave span access - * to specialized and cross-cutting instrumentation code). - * - * @author Dave Syer - * @since 1.0.0 - */ -public interface SpanAccessor { - - /** - * Retrieves the span that is present in the context. If currently there is - * no tracing going on, then this method will return {@code null}. - */ - Span getCurrentSpan(); - - /** - * Returns {@code true} when a span is present in the current context. In other - * words if a span was started or continued then this method returns {@code true}. - */ - boolean isTracing(); - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAdjuster.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAdjuster.java index 5ffb426fbf..e9acee4335 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAdjuster.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanAdjuster.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,29 @@ package org.springframework.cloud.sleuth; +import zipkin2.Span; + /** * Adds ability to adjust a span before reporting it. * - * IMPORTANT: Your {@link SpanReporter} should inject the collection of {@link SpanAdjuster} and - * allow {@link Span} manipulation before the actual reporting is done. + * IMPORTANT - if you override the default {@link brave.Tracing} implementation, + * remember to ensure that you pass to it an adjusted version of the {@link zipkin2.reporter.Reporter} + * bean. In other words you must reuse the list of available {@link SpanAdjuster}s and + * wrap the provided {@link zipkin2.reporter.Reporter} interface with it. * * @author Marcin Grzejszczak * @since 1.1.4 */ public interface SpanAdjuster { /** - * You can adjust the {@link Span} by creating a new one using the {@link Span.SpanBuilder} + * You can adjust the {@link zipkin2.Span} by creating a new one using the {@link Span#toBuilder()} * before reporting it. * - * In Sleuth we're generating spans with a fixed name. Some users want to modify the name + * With the legacy Sleuth approach we're generating spans with a fixed name. Some users want to modify the name * depending on some values of tags. Implementation of this interface can be used to alter * then name. Example: * * {@code span -> span.toBuilder().name(scrub(span.getName())).build();} */ Span adjust(Span span); -} +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java deleted file mode 100644 index 9752d0b005..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanContext.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.cloud.sleuth; - -import java.util.Map; - -/** - * Adopted from: https://github.com/opentracing/opentracing-java/blob/0.16.0/opentracing-api/src/main/java/io/opentracing/SpanContext.java - * - * SpanContext represents Span state that must propagate to descendant Spans and across process boundaries. - * - * SpanContext is logically divided into two pieces: (1) the user-level "Baggage" that propagates across Span - * boundaries and (2) any Tracer-implementation-specific fields that are needed to identify or otherwise contextualize - * the associated Span instance (e.g., a tuple). - * - * The {@link SpanContext#baggageItems()} returns the map of user-level "Baggage". - * - * @see Span#setBaggageItem(String, String) - * @see Span#getBaggageItem(String) - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface SpanContext { - - /** - * @return all zero or more baggage items propagating along with the associated Span - * - * @see Span#setBaggageItem(String, String) - * @see Span#getBaggageItem(String) - */ - Iterable> baggageItems(); -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java deleted file mode 100644 index 6c24f381ee..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanExtractor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Adopted from - * OpenTracing - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public interface SpanExtractor { - /** - * Returns a SpanBuilder provided a “carrier” object from which to extract identifying - * information needed by the new Span instance. - * - * If the carrier object has no such span stored within it, a new Span is created. - * - * Unless there’s an error, it returns a Span. The Span generated from the builder can - * be used in the host process like any other. - * - * (Note that some OpenTracing implementations consider the Spans on either side of an - * RPC to have the same identity, and others consider the caller to be the parent and - * the receiver to be the child). - */ - Span joinTrace(T carrier); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java deleted file mode 100644 index f493b0000e..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanInjector.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Adopted from - * OpenTracing - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public interface SpanInjector { - /** - * Takes two arguments: - *

    - *
  • a Span instance, and
  • - *
  • a “carrier” object in which to inject that Span for cross-process propagation. - *
  • - *
- * - * A “carrier” object is some sort of http or rpc envelope, for example HeaderGroup - * (from Apache HttpComponents). - * - * Attempting to inject to a carrier that has been registered/configured to this - * Tracer will result in a IllegalStateException. - */ - void inject(Span span, T carrier); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java index 7292941118..78115f22fb 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanName.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Annotation to provide the name for the span. You should annotate all your - * custom {@link java.lang.Runnable Runnable} or {@link java.util.concurrent.Callable Callable} classes + * custom {@link Runnable Runnable} or {@link java.util.concurrent.Callable Callable} classes * for the instrumentation logic to pick up how to name the span. *

* diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java index 53a9fb7813..53ec243655 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanNamer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java deleted file mode 100644 index 6eec0c092b..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanReporter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Contract for reporting Sleuth spans for collection. For example to Zipkin. - * - * IMPORTANT: Your {@link SpanReporter} should inject the {@link SpanAdjuster} and - * allow {@link Span} manipulation before the actual reporting is done. - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public interface SpanReporter { - /** - * Reports a completed span out of band, usually out of process. - * This is typically to a trace depot (ex zipkin) or a log file. - */ - void report(Span span); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java deleted file mode 100644 index 3fed538fb1..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/SpanTextMap.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.cloud.sleuth; - -import java.util.Iterator; -import java.util.Map; - -/** - * Adopted from: https://github.com/opentracing/opentracing-java/blob/0.16.0/opentracing-api/src/main/java/io/opentracing/propagation/TextMap.java - * - * TextMap is a built-in carrier for {@link SpanInjector} and {@link SpanExtractor}. TextMap implementations allows Tracers to - * read and write key:value String pairs from arbitrary underlying sources of data. - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface SpanTextMap extends Iterable> { - /** - * Gets an iterator over arbitrary key:value pairs from the TextMapReader. - * - * @return entries in the TextMap backing store; note that for some Formats, the iterator may include entries that - * were never injected by a Tracer implementation (e.g., unrelated HTTP headers) - */ - Iterator> iterator(); - - /** - * Puts a key:value pair into the TextMapWriter's backing store. - * - * @param key a String, possibly with constraints dictated by the particular Format this TextMap is paired with - * @param value a String, possibly with constraints dictated by the particular Format this TextMap is paired with - */ - void put(String key, String value); -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java deleted file mode 100644 index 2e4d4b1df3..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceCallable.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import java.util.concurrent.Callable; - -/** - * Callable that passes Span between threads. The Span name is - * taken either from the passed value or from the {@link SpanNamer} - * interface. - * - * @author Spencer Gibb - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class TraceCallable implements Callable { - - private final Tracer tracer; - private final SpanNamer spanNamer; - private final Callable delegate; - private final String name; - private final Span parent; - - public TraceCallable(Tracer tracer, SpanNamer spanNamer, Callable delegate) { - this(tracer, spanNamer, delegate, null); - } - - public TraceCallable(Tracer tracer, SpanNamer spanNamer, Callable delegate, String name) { - this.tracer = tracer; - this.spanNamer = spanNamer; - this.delegate = delegate; - this.name = name; - this.parent = tracer.getCurrentSpan(); - } - - @Override - public V call() throws Exception { - Span span = startSpan(); - try { - return this.getDelegate().call(); - } - finally { - close(span); - } - } - - protected Span startSpan() { - return this.tracer.createSpan(getSpanName(), this.parent); - } - - protected String getSpanName() { - if (this.name != null) { - return this.name; - } - return this.spanNamer.name(this.delegate, "async"); - } - - protected void close(Span span) { - this.tracer.close(span); - } - - protected Span continueSpan(Span span) { - return this.tracer.continueSpan(span); - } - - protected Span detachSpan(Span span) { - return this.tracer.detach(span); - } - - public Tracer getTracer() { - return this.tracer; - } - - public Callable getDelegate() { - return this.delegate; - } - - public String getName() { - return this.name; - } - - public Span getParent() { - return this.parent; - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java index 3e644b14ca..ad6a6d5730 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceKeys.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; /** - * Well-known {@link org.springframework.cloud.sleuth.Span#tag(String, String) span tag} + * Well-known {@link brave.Span#tag(String, String) span tag} * keys. * *

Overhead of adding Trace Data

diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java deleted file mode 100644 index ef883a1a9e..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/TraceRunnable.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -/** - * Runnable that passes Span between threads. The Span name is - * taken either from the passed value or from the {@link SpanNamer} - * interface. - * - * @author Spencer Gibb - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class TraceRunnable implements Runnable { - - /** - * Since we don't know the exact operation name we provide a default - * name for the Span - */ - private static final String DEFAULT_SPAN_NAME = "async"; - - private final Tracer tracer; - private final SpanNamer spanNamer; - private final Runnable delegate; - private final String name; - private final Span parent; - - public TraceRunnable(Tracer tracer, SpanNamer spanNamer, Runnable delegate) { - this(tracer, spanNamer, delegate, null); - } - - public TraceRunnable(Tracer tracer, SpanNamer spanNamer, Runnable delegate, String name) { - this.tracer = tracer; - this.spanNamer = spanNamer; - this.delegate = delegate; - this.name = name; - this.parent = tracer.getCurrentSpan(); - } - - @Override - public void run() { - Span span = startSpan(); - try { - this.getDelegate().run(); - } - finally { - close(span); - } - } - - protected Span startSpan() { - return this.tracer.createSpan(getSpanName(), this.parent); - } - - protected String getSpanName() { - if (this.name != null) { - return this.name; - } - return this.spanNamer.name(this.delegate, DEFAULT_SPAN_NAME); - } - - protected void close(Span span) { - // race conditions - check #447 - if (!this.tracer.isTracing()) { - this.tracer.continueSpan(span); - } - this.tracer.close(span); - } - - protected Span continueSpan(Span span) { - return this.tracer.continueSpan(span); - } - - protected Span detachSpan(Span span) { - if (this.tracer.isTracing()) { - return this.tracer.detach(span); - } - return span; - } - - public Tracer getTracer() { - return this.tracer; - } - - public Runnable getDelegate() { - return this.delegate; - } - - public String getName() { - return this.name; - } - - public Span getParent() { - return this.parent; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Tracer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Tracer.java deleted file mode 100644 index a3ceec2a40..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Tracer.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import java.util.concurrent.Callable; - -/** - * The Tracer class is the primary way for instrumentation code (note user code) to - * interact with the library. It provides methods to create and manipulate spans. - *

- * - * A 'span' represents a length of time. It has many other attributes such as a name, ID, - * and even potentially a set of key/value strings attached to it. - *

- * - * Each thread in your application has a single currently active currentSpan associated - * with it. When this is non-null, it represents the current operation that the thread is - * doing. spans are NOT thread-safe, and must never be used by multiple threads at once. - * With care, it is possible to safely pass a span object between threads, but in most - * cases this is not necessary. - *

- * - * Most crucial methods in terms of span lifecycle are: - *

    - *
  • The {@linkplain Tracer#createSpan(String) createSpan} method in this class - * starts a new span.
  • - *
  • The {@linkplain Tracer#createSpan(String, Span) createSpan} method creates a new span - * which has this thread's currentSpan as one of its parents
  • - *
  • The {@linkplain Tracer#continueSpan(Span) continueSpan} method creates a - * new instance of span that logically is a continuation of the provided span.
  • - *
- * - * Closing a TraceScope does a few things: - *
    - *
  • It closes the span which the scope was managing.
  • - *
  • Set currentSpan to the previous currentSpan (which may be null).
  • - *
- * - * @since 1.0.0 - */ -public interface Tracer extends SpanAccessor { - - /** - * Creates a new Span. - *

- * If this thread has a currently active span, it will be the parent of the span we - * create here. If there is no currently active trace span, the trace scope we - * create will be empty. - * - * @param name The name field for the new span to create. - */ - Span createSpan(String name); - - /** - * Creates a new Span with a specific parent. The parent might be in another - * process or thread. - *

- * If this thread has a currently active trace span, it must be the 'parent' span that - * you pass in here as a parameter. The trace scope we create here will contain a new - * span which is a child of 'parent'. - * - * @param name The name field for the new span to create. - */ - Span createSpan(String name, Span parent); - - /** - * Start a new span if the sampler allows it or if we are already tracing in this - * thread. A sampler can be used to limit the number of traces created. - * - * @param name the name of the span - * @param sampler a sampler to decide whether to create the span or not - */ - Span createSpan(String name, Sampler sampler); - - /** - * Contributes to a span started in another thread. The returned span shares - * mutable state with the input. - */ - Span continueSpan(Span span); - - /** - * Adds a tag to the current span if tracing is currently on. - *

- * Every span may also have zero or more key/value Tags, which do not have - * timestamps and simply annotate the spans. - * - * Check {@link TraceKeys} for examples of most common tag keys - */ - void addTag(String key, String value); - - /** - * Remove this span from the current thread, but don't stop it yet nor send it for - * collection. This is useful if the span object is then passed to another thread for - * use with {@link Tracer#continueSpan(Span)}. - *

- * Example of usage: - *

{@code
-	 *     // Span "A" was present in thread "X". Let's assume that we're in thread "Y" to which span "A" got passed
-	 *     Span continuedSpan = tracer.continueSpan(spanA);
-	 *     // Now span "A" got continued in thread "Y".
-	 *     ... // Some work is done... state of span "A" could get mutated
-	 *     Span previouslyStoredSpan = tracer.detach(continuedSpan);
-	 *     // Span "A" got removed from the thread Y but it wasn't yet sent for collection.
-	 *     // Additional work can be done on span "A" in thread "X" and finally it can get closed and sent for collection
-	 *     tracer.close(spanA);
-	 * }
- * - * @return the saved trace if there was one before the trace started (null otherwise) - */ - Span detach(Span span); - - /** - * Remove this span from the current thread, stop it and send it for collection. - * - * @param span the span to close - * @return the saved span if there was one before the trace started (null otherwise) - */ - Span close(Span span); - - /** - * Returns a wrapped {@link Callable} which will be recorded as a span - * in the current trace. - */ - Callable wrap(Callable callable); - - /** - * Returns a wrapped {@link Runnable} which will be recorded as a span - * in the current trace. - */ - Runnable wrap(Runnable runnable); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/ContinueSpan.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/ContinueSpan.java index 48b3e69d94..a1c183a044 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/ContinueSpan.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/ContinueSpan.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/DefaultSpanCreator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/DefaultSpanCreator.java index 6ff198861d..7e2d5805fe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/DefaultSpanCreator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/DefaultSpanCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,11 @@ */ package org.springframework.cloud.sleuth.annotation; -import java.lang.invoke.MethodHandles; - +import brave.Span; +import brave.Tracing; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.util.StringUtils; @@ -34,11 +32,11 @@ */ class DefaultSpanCreator implements SpanCreator { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(DefaultSpanCreator.class); - private final Tracer tracer; + private final Tracing tracer; - DefaultSpanCreator(Tracer tracer) { + DefaultSpanCreator(Tracing tracer) { this.tracer = tracer; } @@ -50,14 +48,7 @@ class DefaultSpanCreator implements SpanCreator { log.debug("For the class [" + pjp.getThis().getClass() + "] method " + "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]"); } - return createSpan(changedName); - } - - private Span createSpan(String name) { - if (this.tracer.isTracing()) { - return this.tracer.createSpan(name, this.tracer.getCurrentSpan()); - } - return this.tracer.createSpan(name); + return this.tracer.tracer().nextSpan().name(changedName); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NewSpan.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NewSpan.java index c01cc67adf..34a060dc86 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NewSpan.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NewSpan.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolver.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolver.java index 13a20ab2dc..e33267f31f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolver.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAdvisorConfig.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAdvisorConfig.java index 2079bb8813..226c5cd2ac 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAdvisorConfig.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAdvisorConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,14 @@ package org.springframework.cloud.sleuth.annotation; +import javax.annotation.PostConstruct; import java.lang.annotation.Annotation; -import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.PostConstruct; +import brave.Span; +import brave.Tracer; +import brave.Tracing; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; @@ -37,8 +39,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -154,7 +154,7 @@ public void doWith(Method method) throws IllegalArgumentException, return; } Annotation annotation = AnnotationUtils.findAnnotation(method, - AnnotationMethodsResolver.this.annotationType); + SleuthAdvisorConfig.AnnotationMethodsResolver.this.annotationType); if (annotation != null) { found.set(true); } } }); @@ -168,15 +168,15 @@ public void doWith(Method method) throws IllegalArgumentException, * Interceptor that creates or continues a span depending on the provided * annotation. Also it adds logs and tags if necessary. */ -class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware { +class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware { - private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log logger = LogFactory.getLog(SleuthInterceptor.class); private static final String CLASS_KEY = "class"; private static final String METHOD_KEY = "method"; private BeanFactory beanFactory; private SpanCreator spanCreator; - private Tracer tracer; + private Tracing tracing; private SpanTagAnnotationHandler spanTagAnnotationHandler; private ErrorParser errorParser; @@ -193,13 +193,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable { if (newSpan == null && continueSpan == null) { return invocation.proceed(); } - Span span = tracer().getCurrentSpan(); + Span span = tracing().tracer().currentSpan(); + if (newSpan != null || span == null) { + span = spanCreator().createSpan(invocation, newSpan); + } String log = log(continueSpan); boolean hasLog = StringUtils.hasText(log); - try { - if (newSpan != null) { - span = spanCreator().createSpan(invocation, newSpan); - } + try (Tracer.SpanInScope ws = tracing().tracer().withSpanInScope(span)) { if (hasLog) { logEvent(span, log + ".before"); } @@ -213,7 +213,7 @@ public Object invoke(MethodInvocation invocation) throws Throwable { if (hasLog) { logEvent(span, log + ".afterFailure"); } - errorParser().parseErrorTags(tracer().getCurrentSpan(), e); + errorParser().parseErrorTags(span, e); throw e; } finally { if (span != null) { @@ -221,15 +221,15 @@ public Object invoke(MethodInvocation invocation) throws Throwable { logEvent(span, log + ".after"); } if (newSpan != null) { - tracer().close(span); + span.finish(); } } } } private void addTags(MethodInvocation invocation, Span span) { - tracer().addTag(CLASS_KEY, invocation.getThis().getClass().getSimpleName()); - tracer().addTag(METHOD_KEY, invocation.getMethod().getName()); + span.tag(CLASS_KEY, invocation.getThis().getClass().getSimpleName()); + span.tag(METHOD_KEY, invocation.getMethod().getName()); } private void logEvent(Span span, String name) { @@ -239,7 +239,7 @@ private void logEvent(Span span, String name) { + "the same class then the aspect will not be properly resolved"); return; } - span.logEvent(name); + span.annotate(name); } private String log(ContinueSpan continueSpan) { @@ -249,11 +249,11 @@ private String log(ContinueSpan continueSpan) { return ""; } - private Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + private Tracing tracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(Tracing.class); } - return this.tracer; + return this.tracing; } private SpanCreator spanCreator() { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotatedParameter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotatedParameter.java index cea28a24ef..bbea6c3812 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotatedParameter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotatedParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationAutoConfiguration.java index 3cf1cefa14..940af131a3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ */ package org.springframework.cloud.sleuth.annotation; +import brave.Tracing; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,32 +36,28 @@ * @since 1.2.0 */ @Configuration -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true) @AutoConfigureAfter(TraceAutoConfiguration.class) @EnableConfigurationProperties(SleuthAnnotationProperties.class) public class SleuthAnnotationAutoConfiguration { @Bean - @ConditionalOnMissingBean - SpanCreator spanCreator(Tracer tracer) { - return new DefaultSpanCreator(tracer); + @ConditionalOnMissingBean SpanCreator spanCreator(Tracing tracing) { + return new DefaultSpanCreator(tracing); } @Bean - @ConditionalOnMissingBean - TagValueExpressionResolver spelTagValueExpressionResolver() { + @ConditionalOnMissingBean TagValueExpressionResolver spelTagValueExpressionResolver() { return new SpelTagValueExpressionResolver(); } @Bean - @ConditionalOnMissingBean - TagValueResolver noOpTagValueResolver() { + @ConditionalOnMissingBean TagValueResolver noOpTagValueResolver() { return new NoOpTagValueResolver(); } - @Bean - SleuthAdvisorConfig sleuthAdvisorConfig() { + @Bean SleuthAdvisorConfig sleuthAdvisorConfig() { return new SleuthAdvisorConfig(); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationProperties.java index 203ed2e5ae..87cfa0b708 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationUtils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationUtils.java index 854b907d63..b08b24c7d2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationUtils.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SleuthAnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.annotation; import java.lang.annotation.Annotation; -import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -35,7 +34,7 @@ */ class SleuthAnnotationUtils { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(SleuthAnnotationUtils.class); static boolean isMethodAnnotated(Method method) { return findAnnotation(method, NewSpan.class) != null || diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanCreator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanCreator.java index f071f59535..e1dba87cf3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanCreator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package org.springframework.cloud.sleuth.annotation; +import brave.Span; import org.aopalliance.intercept.MethodInvocation; -import org.springframework.cloud.sleuth.Span; /** * A contract for creating a new span for a given join point diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTag.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTag.java index e1053b0bee..36f4f8e192 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTag.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandler.java index 9689045db1..86a381895b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandler.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,17 @@ package org.springframework.cloud.sleuth.annotation; -import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import brave.Span; +import brave.Tracing; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.util.StringUtils; /** @@ -43,10 +43,10 @@ */ class SpanTagAnnotationHandler { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(SpanTagAnnotationHandler.class); private final BeanFactory beanFactory; - private Tracer tracer; + private Tracing tracing; SpanTagAnnotationHandler(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -122,11 +122,20 @@ private void addAnnotatedArguments(List toBeAdded) { for (SleuthAnnotatedParameter container : toBeAdded) { String tagValue = resolveTagValue(container.annotation, container.argument); String tagKey = resolveTagKey(container); - tracer().addTag(tagKey, tagValue); + span().tag(tagKey, tagValue); } } - private String resolveTagKey(SleuthAnnotatedParameter container) { + private Span span() { + Span span = tracing().tracer().currentSpan(); + if (span != null) { + return span; + } + return tracing().tracer().nextSpan(); + } + + private String resolveTagKey( + SleuthAnnotatedParameter container) { return StringUtils.hasText(container.annotation.value()) ? container.annotation.value() : container.annotation.key(); } @@ -145,11 +154,11 @@ String resolveTagValue(SpanTag annotation, Object argument) { return argument.toString(); } - private Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + private Tracing tracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(Tracing.class); } - return this.tracer; + return this.tracing; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolver.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolver.java index 711e5bd621..0995b68514 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolver.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.cloud.sleuth.annotation; -import java.lang.invoke.MethodHandles; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.expression.Expression; @@ -32,7 +30,7 @@ * @since 1.2.0 */ class SpelTagValueExpressionResolver implements TagValueExpressionResolver { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(SpelTagValueExpressionResolver.class); @Override public String resolve(String expression, Object parameter) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/TagValueExpressionResolver.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/TagValueExpressionResolver.java index ccc81d1222..9e6176ec8e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/TagValueExpressionResolver.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/annotation/TagValueExpressionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java index 01a2e52f2e..51a336c1fe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/SleuthProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.cloud.sleuth.autoconfig; +import java.util.ArrayList; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -27,10 +30,26 @@ public class SleuthProperties { private boolean enabled = true; - /** When true, generate 128-bit trace IDs instead of 64-bit ones. */ - private boolean traceId128 = false; - /** When true, your tracing system allows sharing a span ID between a client and server span */ - private boolean supportsJoin = true; + + /** + * List of baggage key names that should be propagated out of process. + * These keys will be prefixed with `baggage` before the actual key. + * This property is set in order to be backward compatible with previous + * Sleuth versions. + * + * @see brave.propagation.ExtraFieldPropagation.FactoryBuilder#addPrefixedFields(String, java.util.Collection) + */ + private List baggageKeys = new ArrayList<>(); + + /** + * List of fields that are referenced the same in-process as it is on the wire. For example, the + * name "x-vcap-request-id" would be set as-is including the prefix. + * + *

Note: {@code fieldName} will be implicitly lower-cased. + * + * @see brave.propagation.ExtraFieldPropagation.FactoryBuilder#addField(String) + */ + private List propagationKeys = new ArrayList<>(); public boolean isEnabled() { return this.enabled; @@ -40,19 +59,19 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isTraceId128() { - return this.traceId128; + public List getBaggageKeys() { + return this.baggageKeys; } - public void setTraceId128(boolean traceId128) { - this.traceId128 = traceId128; + public void setBaggageKeys(List baggageKeys) { + this.baggageKeys = baggageKeys; } - public boolean isSupportsJoin() { - return this.supportsJoin; + public List getPropagationKeys() { + return this.propagationKeys; } - public void setSupportsJoin(boolean supportsJoin) { - this.supportsJoin = supportsJoin; + public void setPropagationKeys(List propagationKeys) { + this.propagationKeys = propagationKeys; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java index 875304c5ce..84ac26e808 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfiguration.java @@ -1,43 +1,32 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.springframework.cloud.sleuth.autoconfig; -import java.util.Random; +import java.util.ArrayList; +import java.util.List; +import brave.CurrentSpanCustomizer; +import brave.Tracer; +import brave.Tracing; +import brave.context.log4j2.ThreadContextCurrentTraceContext; +import brave.propagation.B3Propagation; +import brave.propagation.CurrentTraceContext; +import brave.propagation.ExtraFieldPropagation; +import brave.propagation.Propagation; +import brave.sampler.Sampler; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.NoOpSpanAdjuster; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.SpanAdjuster; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.SpanLogger; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import zipkin2.Span; +import zipkin2.reporter.Reporter; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -45,58 +34,101 @@ * * @author Spencer Gibb * @author Marcin Grzejszczak - * @since 1.0.0 + * @since 2.0.0 */ @Configuration @ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true) -@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class}) +@EnableConfigurationProperties({ TraceKeys.class, SleuthProperties.class }) public class TraceAutoConfiguration { - @Autowired - SleuthProperties properties; + + @Autowired(required = false) List spanAdjusters = new ArrayList<>(); @Bean @ConditionalOnMissingBean - public Random randomForSpanIds() { - return new Random(); + Tracing sleuthTracing(@Value("${spring.zipkin.service.name:${spring.application.name:default}}") String serviceName, + Propagation.Factory factory, + CurrentTraceContext currentTraceContext, + Reporter reporter, + Sampler sampler) { + return Tracing.newBuilder() + .sampler(sampler) + .localServiceName(serviceName) + .propagationFactory(factory) + .currentTraceContext(currentTraceContext) + .spanReporter(adjustedReporter(reporter)).build(); + } + + private Reporter adjustedReporter(Reporter delegate) { + return span -> { + Span spanToAdjust = span; + for (SpanAdjuster spanAdjuster : this.spanAdjusters) { + spanToAdjust = spanAdjuster.adjust(spanToAdjust); + } + delegate.report(spanToAdjust); + }; } @Bean @ConditionalOnMissingBean - public Sampler defaultTraceSampler() { - return NeverSampler.INSTANCE; + Tracer sleuthTracer(Tracing tracing) { + return tracing.tracer(); } @Bean - @ConditionalOnMissingBean(Tracer.class) - public Tracer sleuthTracer(Sampler sampler, Random random, - SpanNamer spanNamer, SpanLogger spanLogger, - SpanReporter spanReporter, TraceKeys traceKeys) { - return new DefaultTracer(sampler, random, spanNamer, spanLogger, - spanReporter, this.properties.isTraceId128(), traceKeys); + @ConditionalOnMissingBean + Sampler sleuthTraceSampler() { + return Sampler.NEVER_SAMPLE; } @Bean - @ConditionalOnMissingBean - public SpanNamer spanNamer() { + @ConditionalOnMissingBean SpanNamer sleuthSpanNamer() { return new DefaultSpanNamer(); } @Bean @ConditionalOnMissingBean - public SpanReporter defaultSpanReporter() { - return new NoOpSpanReporter(); + Propagation.Factory sleuthPropagation(SleuthProperties sleuthProperties) { + if (sleuthProperties.getBaggageKeys().isEmpty() && sleuthProperties.getPropagationKeys().isEmpty()) { + return B3Propagation.FACTORY; + } + ExtraFieldPropagation.FactoryBuilder factoryBuilder = ExtraFieldPropagation + .newFactoryBuilder(B3Propagation.FACTORY); + if (!sleuthProperties.getBaggageKeys().isEmpty()) { + factoryBuilder = factoryBuilder + // for HTTP + .addPrefixedFields("baggage-", sleuthProperties.getBaggageKeys()) + // for messaging + .addPrefixedFields("baggage_", sleuthProperties.getBaggageKeys()); + } + if (!sleuthProperties.getPropagationKeys().isEmpty()) { + for (String key : sleuthProperties.getPropagationKeys()) { + factoryBuilder = factoryBuilder.addField(key); + } + } + return factoryBuilder.build(); + } + + @Bean + @ConditionalOnMissingBean + CurrentTraceContext sleuthCurrentTraceContext() { + return ThreadContextCurrentTraceContext.create(); } @Bean @ConditionalOnMissingBean - public SpanAdjuster defaultSpanAdjuster() { - return new NoOpSpanAdjuster(); + Reporter noOpSpanReporter() { + return Reporter.NOOP; } @Bean @ConditionalOnMissingBean - public ErrorParser defaultErrorParser() { + ErrorParser sleuthErrorParser() { return new ExceptionMessageErrorParser(); } + @Bean + @ConditionalOnMissingBean + CurrentSpanCustomizer sleuthCurrentSpanCustomizer(Tracing tracing) { + return CurrentSpanCustomizer.create(tracing); + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java index bde8953c81..733a13f9b0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/autoconfig/TraceEnvironmentPostProcessor.java @@ -33,7 +33,8 @@ * * * @author Dave Syer - * @since 1.0.0 + * @author Marcin Grzejszczak + * @since 2.0.0 */ public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfiguration.java index ec8895e24b..ae3a78857c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfiguration.java @@ -54,7 +54,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof AsyncConfigurer) { + if (bean instanceof AsyncConfigurer && !(bean instanceof LazyTraceAsyncCustomizer)) { AsyncConfigurer configurer = (AsyncConfigurer) bean; return new LazyTraceAsyncCustomizer(this.beanFactory, configurer); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index c57005e76c..3316cac7c4 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -18,15 +18,15 @@ import java.util.concurrent.Executor; +import brave.Tracer; +import brave.Tracing; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SimpleAsyncTaskExecutor; @@ -46,8 +46,8 @@ */ @Configuration @ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true) -@ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(AsyncCustomAutoConfiguration.class) +@ConditionalOnBean(Tracing.class) +//@AutoConfigureAfter(AsyncCustomAutoConfiguration.class) public class AsyncDefaultAutoConfiguration { @Autowired private BeanFactory beanFactory; @@ -66,8 +66,8 @@ public Executor getAsyncExecutor() { } @Bean - public TraceAsyncAspect traceAsyncAspect(Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { - return new TraceAsyncAspect(tracer, traceKeys, spanNamer); + public TraceAsyncAspect traceAsyncAspect(Tracer tracer, SpanNamer spanNamer, TraceKeys traceKeys) { + return new TraceAsyncAspect(tracer, spanNamer, traceKeys); } @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java index 1347fa4fd0..9ce03995e0 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/ExecutorBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,8 @@ */ class ExecutorBeanPostProcessor implements BeanPostProcessor { - private static final Log log = LogFactory.getLog(ExecutorBeanPostProcessor.class); + private static final Log log = LogFactory.getLog( + ExecutorBeanPostProcessor.class); private final BeanFactory beanFactory; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizer.java index 5ad6d7dd16..7d0ad3d9a3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,9 @@ public LazyTraceAsyncCustomizer(BeanFactory beanFactory, AsyncConfigurer delegat @Override public Executor getAsyncExecutor() { + if (this.delegate.getAsyncExecutor() instanceof LazyTraceExecutor) { + return this.delegate.getAsyncExecutor(); + } return new LazyTraceExecutor(this.beanFactory, this.delegate.getAsyncExecutor()); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceExecutor.java index 7e77c0cbe4..97bc50d38f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,35 +16,33 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.lang.invoke.MethodHandles; import java.util.concurrent.Executor; +import brave.Tracer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; /** - * {@link Executor} that wraps {@link Runnable} in a - * {@link org.springframework.cloud.sleuth.TraceRunnable TraceRunnable} that sets a - * local component tag on the span. + * {@link Executor} that wraps {@link Runnable} in a trace representation * * @author Dave Syer * @since 1.0.0 */ public class LazyTraceExecutor implements Executor { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(LazyTraceExecutor.class); private Tracer tracer; private final BeanFactory beanFactory; private final Executor delegate; - private TraceKeys traceKeys; private SpanNamer spanNamer; + private ErrorParser errorParser; public LazyTraceExecutor(BeanFactory beanFactory, Executor delegate) { this.beanFactory = beanFactory; @@ -62,35 +60,35 @@ public void execute(Runnable command) { return; } } - this.delegate.execute(new SpanContinuingTraceRunnable(this.tracer, traceKeys(), spanNamer(), command)); + this.delegate.execute(new TraceRunnable(this.tracer, spanNamer(), errorParser(), command)); } // due to some race conditions trace keys might not be ready yet - private TraceKeys traceKeys() { - if (this.traceKeys == null) { + private SpanNamer spanNamer() { + if (this.spanNamer == null) { try { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + this.spanNamer = this.beanFactory.getBean(SpanNamer.class); } catch (NoSuchBeanDefinitionException e) { - log.warn("TraceKeys bean not found - will provide a manually created instance"); - return new TraceKeys(); + log.warn("SpanNamer bean not found - will provide a manually created instance"); + return new DefaultSpanNamer(); } } - return this.traceKeys; + return this.spanNamer; } // due to some race conditions trace keys might not be ready yet - private SpanNamer spanNamer() { - if (this.spanNamer == null) { + private ErrorParser errorParser() { + if (this.errorParser == null) { try { - this.spanNamer = this.beanFactory.getBean(SpanNamer.class); + this.errorParser = this.beanFactory.getBean(ErrorParser.class); } catch (NoSuchBeanDefinitionException e) { - log.warn("SpanNamer bean not found - will provide a manually created instance"); - return new DefaultSpanNamer(); + log.warn("ErrorParser bean not found - will provide a manually created instance"); + return new ExceptionMessageErrorParser(); } } - return this.spanNamer; + return this.errorParser; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java index 415d7d8058..4355397ff1 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,27 +16,27 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.lang.invoke.MethodHandles; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; +import brave.Tracer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.core.task.TaskDecorator; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.concurrent.ListenableFuture; /** - * {@link ThreadPoolTaskExecutor} that continues a span if one was passed or creates a new one + * Trace representation of {@link ThreadPoolTaskExecutor} * * @author Marcin Grzejszczak * @since 1.0.10 @@ -44,13 +44,13 @@ @SuppressWarnings("serial") public class LazyTraceThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(LazyTraceThreadPoolTaskExecutor.class); private Tracer tracer; private final BeanFactory beanFactory; private final ThreadPoolTaskExecutor delegate; - private TraceKeys traceKeys; private SpanNamer spanNamer; + private ErrorParser errorParser; public LazyTraceThreadPoolTaskExecutor(BeanFactory beanFactory, ThreadPoolTaskExecutor delegate) { @@ -60,32 +60,32 @@ public LazyTraceThreadPoolTaskExecutor(BeanFactory beanFactory, @Override public void execute(Runnable task) { - this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + this.delegate.execute(new TraceRunnable(tracer(), spanNamer(), errorParser(), task)); } @Override public void execute(Runnable task, long startTimeout) { - this.delegate.execute(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task), startTimeout); + this.delegate.execute(new TraceRunnable(tracer(), spanNamer(), errorParser(), task), startTimeout); } @Override public Future submit(Runnable task) { - return this.delegate.submit(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + return this.delegate.submit(new TraceRunnable(tracer(), spanNamer(), errorParser(), task)); } @Override public Future submit(Callable task) { - return this.delegate.submit(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); + return this.delegate.submit(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task)); } @Override public ListenableFuture submitListenable(Runnable task) { - return this.delegate.submitListenable(new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task)); + return this.delegate.submitListenable(new TraceRunnable(tracer(), spanNamer(), errorParser(), task)); } @Override public ListenableFuture submitListenable(Callable task) { - return this.delegate.submitListenable(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), spanNamer(), task)); + return this.delegate.submitListenable(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task)); } @Override public boolean prefersShortLivedTasks() { @@ -236,19 +236,6 @@ private Tracer tracer() { return this.tracer; } - private TraceKeys traceKeys() { - if (this.traceKeys == null) { - try { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - catch (NoSuchBeanDefinitionException e) { - log.warn("TraceKeys bean not found - will provide a manually created instance"); - return new TraceKeys(); - } - } - return this.traceKeys; - } - private SpanNamer spanNamer() { if (this.spanNamer == null) { try { @@ -261,4 +248,17 @@ private SpanNamer spanNamer() { } return this.spanNamer; } + + private ErrorParser errorParser() { + if (this.errorParser == null) { + try { + this.errorParser = this.beanFactory.getBean(ErrorParser.class); + } + catch (NoSuchBeanDefinitionException e) { + log.warn("ErrorParser bean not found - will provide a manually created instance"); + return new ExceptionMessageErrorParser(); + } + } + return this.errorParser; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java deleted file mode 100644 index 844165caf4..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallable.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.async; - -import java.util.concurrent.Callable; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceCallable; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - -/** - * Callable that starts a span that is a local component span. - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class LocalComponentTraceCallable extends TraceCallable { - - protected static final String ASYNC_COMPONENT = "async"; - - private final TraceKeys traceKeys; - - public LocalComponentTraceCallable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Callable delegate) { - super(tracer, spanNamer, delegate); - this.traceKeys = traceKeys; - } - - public LocalComponentTraceCallable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, String name, Callable delegate) { - super(tracer, spanNamer, delegate, name); - this.traceKeys = traceKeys; - } - - @Override - public V call() throws Exception { - Span span = startSpan(); - try { - return this.getDelegate().call(); - } - finally { - close(span); - } - } - - @Override - protected Span startSpan() { - Span span = getTracer().createSpan(getSpanName(), getParent()); - getTracer().addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT); - getTracer().addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName()); - return span; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java deleted file mode 100644 index 1ecb39b434..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceRunnable.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.async; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceRunnable; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - -/** - * Runnable that starts a span that is a local component span. - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class LocalComponentTraceRunnable extends TraceRunnable { - - protected static final String ASYNC_COMPONENT = "async"; - - private final TraceKeys traceKeys; - - public LocalComponentTraceRunnable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Runnable delegate) { - super(tracer, spanNamer, delegate); - this.traceKeys = traceKeys; - } - - public LocalComponentTraceRunnable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Runnable delegate, String name) { - super(tracer, spanNamer, delegate, name); - this.traceKeys = traceKeys; - } - - @Override - public void run() { - Span span = startSpan(); - try { - this.getDelegate().run(); - } - finally { - close(span); - } - } - - @Override - protected Span startSpan() { - Span span = getTracer().createSpan(getSpanName(), getParent()); - getTracer().addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT); - getTracer().addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName()); - return span; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java deleted file mode 100644 index f91c1721dd..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceCallable.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.async; - -import java.util.concurrent.Callable; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceCallable; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - -/** - * Runnable that continues a span if there is one and creates new that is a - * local component span if there was no tracing present. - * - * @author Marcin Grzejszczak - * @since 1.0.10 - */ -public class SpanContinuingTraceCallable extends TraceCallable { - - private final LocalComponentTraceCallable traceCallable; - - public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Callable delegate) { - super(tracer, spanNamer, delegate); - this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, delegate); - } - - public SpanContinuingTraceCallable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, String name, Callable delegate) { - super(tracer, spanNamer, delegate, name); - this.traceCallable = new LocalComponentTraceCallable<>(tracer, traceKeys, spanNamer, name, delegate); - } - - @Override - public V call() throws Exception { - Span span = startSpan(); - try { - return this.getDelegate().call(); - } - finally { - close(span); - } - } - - @Override - protected Span startSpan() { - Span span = this.getParent(); - if (span == null) { - return this.traceCallable.startSpan(); - } - return continueSpan(span); - } - - @Override protected void close(Span span) { - if (this.getParent() == null) { - super.close(span); - } else { - super.detachSpan(span); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java deleted file mode 100644 index a9b307a942..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/SpanContinuingTraceRunnable.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.async; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.TraceRunnable; -import org.springframework.cloud.sleuth.Tracer; - -/** - * Runnable that continues a span if there is one and creates new that is a - * local component span if there was no tracing present. - * - * @author Marcin Grzejszczak - * @since 1.0.10 - */ -public class SpanContinuingTraceRunnable extends TraceRunnable { - - private final LocalComponentTraceRunnable traceRunnable; - - public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Runnable delegate) { - super(tracer, spanNamer, delegate); - this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate); - } - - public SpanContinuingTraceRunnable(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, Runnable delegate, String name) { - super(tracer, spanNamer, delegate, name); - this.traceRunnable = new LocalComponentTraceRunnable(tracer, traceKeys, spanNamer, delegate, name); - } - - @Override - public void run() { - Span span = startSpan(); - try { - this.getDelegate().run(); - } - finally { - close(span); - } - } - - @Override - protected Span startSpan() { - Span span = this.getParent(); - if (span == null) { - return this.traceRunnable.startSpan(); - } - return continueSpan(span); - } - - @Override protected void close(Span span) { - if (this.getParent() == null) { - super.close(span); - } else { - super.detachSpan(span); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java index 1ea139f947..d716357a2f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,14 @@ import java.lang.reflect.Method; +import brave.Span; +import brave.Tracer; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.InternalApi; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.util.ReflectionUtils; @@ -43,59 +41,30 @@ @Aspect public class TraceAsyncAspect { - private static final String ASYNC_COMPONENT = "async"; - private final Tracer tracer; + private final SpanNamer spanNamer; private final TraceKeys traceKeys; - private final BeanFactory beanFactory; - private SpanNamer spanNamer; - - @Deprecated - public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, BeanFactory beanFactory) { - this.tracer = tracer; - this.traceKeys = traceKeys; - this.beanFactory = beanFactory; - } - public TraceAsyncAspect(Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { + public TraceAsyncAspect(Tracer tracer, SpanNamer spanNamer, TraceKeys traceKeys) { this.tracer = tracer; - this.traceKeys = traceKeys; this.spanNamer = spanNamer; - this.beanFactory = null; + this.traceKeys = traceKeys; } @Around("execution (@org.springframework.scheduling.annotation.Async * *.*(..))") public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable { - String spanName = spanNamer().name(getMethod(pjp, pjp.getTarget()), + String spanName = this.spanNamer.name(getMethod(pjp, pjp.getTarget()), SpanNameUtil.toLowerHyphen(pjp.getSignature().getName())); - Span span = span(spanName); - renameAsyncSpan(spanName, span); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ASYNC_COMPONENT); - this.tracer.addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName()); - this.tracer.addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName()); - try { + Span span = this.tracer.currentSpan().name(spanName); + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + span.tag(this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName()); + span.tag(this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName()); return pjp.proceed(); } finally { - this.tracer.close(span); - } - } - - private void renameAsyncSpan(String spanName, Span span) { - // if there's a tag "lc" -> "async", that means the span came from - // a LazyTraceExecutor component that creates a span that contains very few - // information. If that's the case we want to rename it to have a different name - if (ASYNC_COMPONENT.equals(span.tags().get(Span.SPAN_LOCAL_COMPONENT_TAG_NAME))) { - InternalApi.renameSpan(span, spanName); - } - } - - private Span span(String spanName) { - if (this.tracer.isTracing()) { - return this.tracer.getCurrentSpan(); + span.finish(); } - return this.tracer.createSpan(spanName); } private Method getMethod(ProceedingJoinPoint pjp, Object object) { @@ -105,11 +74,4 @@ private Method getMethod(ProceedingJoinPoint pjp, Object object) { .findMethod(object.getClass(), method.getName(), method.getParameterTypes()); } - SpanNamer spanNamer() { - if (this.spanNamer == null && this.beanFactory != null) { - this.spanNamer = this.beanFactory.getBean(SpanNamer.class); - } - return this.spanNamer; - } - } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutor.java similarity index 65% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutor.java index 53faec96bd..508320e0a3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.web.client; +package org.springframework.cloud.sleuth.instrument.async; import java.util.concurrent.Callable; import java.util.concurrent.Future; -import org.springframework.cloud.sleuth.Tracer; +import brave.Tracing; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.util.concurrent.ListenableFuture; @@ -29,48 +29,48 @@ * * @since 1.0.0 * - * @see Tracer#wrap(Runnable) - * @see Tracer#wrap(Callable) + * @see brave.propagation.CurrentTraceContext#wrap(Runnable) + * @see brave.propagation.CurrentTraceContext#wrap(Callable) */ public class TraceAsyncListenableTaskExecutor implements AsyncListenableTaskExecutor { private final AsyncListenableTaskExecutor delegate; - private final Tracer tracer; + private final Tracing tracing; TraceAsyncListenableTaskExecutor(AsyncListenableTaskExecutor delegate, - Tracer tracer) { + Tracing tracing) { this.delegate = delegate; - this.tracer = tracer; + this.tracing = tracing; } @Override public ListenableFuture submitListenable(Runnable task) { - return this.delegate.submitListenable(this.tracer.wrap(task)); + return this.delegate.submitListenable(this.tracing.currentTraceContext().wrap(task)); } @Override public ListenableFuture submitListenable(Callable task) { - return this.delegate.submitListenable(this.tracer.wrap(task)); + return this.delegate.submitListenable(this.tracing.currentTraceContext().wrap(task)); } @Override public void execute(Runnable task, long startTimeout) { - this.delegate.execute(this.tracer.wrap(task), startTimeout); + this.delegate.execute(this.tracing.currentTraceContext().wrap(task), startTimeout); } @Override public Future submit(Runnable task) { - return this.delegate.submit(this.tracer.wrap(task)); + return this.delegate.submit(this.tracing.currentTraceContext().wrap(task)); } @Override public Future submit(Callable task) { - return this.delegate.submit(this.tracer.wrap(task)); + return this.delegate.submit(this.tracing.currentTraceContext().wrap(task)); } @Override public void execute(Runnable task) { - this.delegate.execute(this.tracer.wrap(task)); + this.delegate.execute(this.tracing.currentTraceContext().wrap(task)); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceCallable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceCallable.java new file mode 100644 index 0000000000..d9aa4a0b7e --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceCallable.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.async; + +import java.util.concurrent.Callable; + +import brave.Span; +import brave.Tracer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.SpanNamer; + +/** + * Callable that passes Span between threads. The Span name is + * taken either from the passed value or from the {@link SpanNamer} + * interface. + * + * @author Spencer Gibb + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class TraceCallable implements Callable { + + /** + * Since we don't know the exact operation name we provide a default + * name for the Span + */ + private static final String DEFAULT_SPAN_NAME = "async"; + + private final Tracer tracer; + private final Callable delegate; + private final Span span; + private final ErrorParser errorParser; + + public TraceCallable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Callable delegate) { + this(tracer, spanNamer, errorParser, delegate, null); + } + + public TraceCallable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Callable delegate, String name) { + this.tracer = tracer; + this.delegate = delegate; + String spanName = name != null ? name : spanNamer.name(delegate, DEFAULT_SPAN_NAME); + this.span = this.tracer.nextSpan().name(spanName); + this.errorParser = errorParser; + } + + @Override public V call() throws Exception { + Throwable error = null; + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span.start())) { + return this.delegate.call(); + } catch (Exception | Error e) { + error = e; + throw e; + } finally { + this.errorParser.parseErrorTags(this.span, error); + this.span.finish(); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnable.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnable.java new file mode 100644 index 0000000000..4ef430c828 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnable.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.async; + +import brave.Span; +import brave.Tracer; +import brave.Tracer.SpanInScope; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.SpanNamer; + +/** + * Runnable that passes Span between threads. The Span name is + * taken either from the passed value or from the {@link SpanNamer} + * interface. + * + * @author Spencer Gibb + * @author Marcin Grzejszczak + * @since 1.0.0 + */ +public class TraceRunnable implements Runnable { + + /** + * Since we don't know the exact operation name we provide a default + * name for the Span + */ + private static final String DEFAULT_SPAN_NAME = "async"; + + private final Tracer tracer; + private final Runnable delegate; + private final Span span; + private final ErrorParser errorParser; + + public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate) { + this(tracer, spanNamer, errorParser, delegate, null); + } + + public TraceRunnable(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser, Runnable delegate, String name) { + this.tracer = tracer; + this.delegate = delegate; + String spanName = name != null ? name : spanNamer.name(delegate, DEFAULT_SPAN_NAME); + this.span = this.tracer.nextSpan().name(spanName); + this.errorParser = errorParser; + } + + @Override + public void run() { + Throwable error = null; + try (SpanInScope ws = this.tracer.withSpanInScope(this.span.start())) { + this.delegate.run(); + } catch (RuntimeException | Error e) { + error = e; + throw e; + } finally { + this.errorParser.parseErrorTags(this.span, error); + this.span.finish(); + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java index 61ff81a02a..acb2e8a8d7 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorService.java @@ -25,10 +25,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import brave.Tracer; import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; /** * A decorator class for {@link ExecutorService} to support tracing in Executors @@ -40,34 +40,23 @@ public class TraceableExecutorService implements ExecutorService { final ExecutorService delegate; Tracer tracer; private final String spanName; - TraceKeys traceKeys; SpanNamer spanNamer; BeanFactory beanFactory; - - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer) { - this(delegate, tracer, traceKeys, spanNamer, null); - } + ErrorParser errorParser; public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - this.spanName = null; + this(beanFactory, delegate, null); } - public TraceableExecutorService(final ExecutorService delegate, final Tracer tracer, - TraceKeys traceKeys, SpanNamer spanNamer, String spanName) { + public TraceableExecutorService(BeanFactory beanFactory, final ExecutorService delegate, String spanName) { this.delegate = delegate; - this.tracer = tracer; + this.beanFactory = beanFactory; this.spanName = spanName; - this.traceKeys = traceKeys; - this.spanNamer = spanNamer; } @Override public void execute(Runnable command) { - final Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), command, this.spanName); + final Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command, this.spanName); this.delegate.execute(r); } @@ -98,22 +87,19 @@ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedE @Override public Future submit(Callable task) { - Callable c = new SpanContinuingTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task); + Callable c = new TraceCallable<>(tracer(), spanNamer(), errorParser(), task, this.spanName); return this.delegate.submit(c); } @Override public Future submit(Runnable task, T result) { - Runnable r = new SpanContinuingTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); + Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), task, this.spanName); return this.delegate.submit(r, result); } @Override public Future submit(Runnable task) { - Runnable r = new LocalComponentTraceRunnable(tracer(), traceKeys(), - spanNamer(), task, this.spanName); + Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), task, this.spanName); return this.delegate.submit(r); } @@ -142,9 +128,8 @@ public T invokeAny(Collection> tasks, long timeout, Ti private Collection> wrapCallableCollection(Collection> tasks) { List> ts = new ArrayList<>(); for (Callable task : tasks) { - if (!(task instanceof SpanContinuingTraceCallable)) { - ts.add(new SpanContinuingTraceCallable<>(tracer(), traceKeys(), - spanNamer(), this.spanName, task)); + if (!(task instanceof TraceCallable)) { + ts.add(new TraceCallable<>(tracer(), spanNamer(), errorParser(), task, this.spanName)); } } return ts; @@ -157,18 +142,17 @@ Tracer tracer() { return this.tracer; } - TraceKeys traceKeys() { - if (this.traceKeys == null && this.beanFactory != null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - SpanNamer spanNamer() { if (this.spanNamer == null && this.beanFactory != null) { this.spanNamer = this.beanFactory.getBean(SpanNamer.class); } return this.spanNamer; } - + + ErrorParser errorParser() { + if (this.errorParser == null) { + this.errorParser = this.beanFactory.getBean(ErrorParser.class); + } + return this.errorParser; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java index 39ee5690f4..a1a2906480 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorService.java @@ -17,13 +17,12 @@ package org.springframework.cloud.sleuth.instrument.async; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.beans.factory.BeanFactory; /** * A decorator class for {@link ScheduledExecutorService} to support tracing in Executors @@ -33,9 +32,8 @@ */ public class TraceableScheduledExecutorService extends TraceableExecutorService implements ScheduledExecutorService { - public TraceableScheduledExecutorService(ScheduledExecutorService delegate, - Tracer tracer, TraceKeys traceKeys, SpanNamer spanNamer) { - super(delegate, tracer, traceKeys, spanNamer); + public TraceableScheduledExecutorService(BeanFactory beanFactory, final ExecutorService delegate) { + super(beanFactory, delegate); } private ScheduledExecutorService getScheduledExecutorService() { @@ -44,25 +42,25 @@ private ScheduledExecutorService getScheduledExecutorService() { @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command); return getScheduledExecutorService().schedule(r, delay, unit); } @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - Callable c = new SpanContinuingTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); + Callable c = new TraceCallable<>(tracer(), spanNamer(), errorParser(), callable); return getScheduledExecutorService().schedule(c, delay, unit); } @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command); return getScheduledExecutorService().scheduleAtFixedRate(r, initialDelay, period, unit); } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - Runnable r = new SpanContinuingTraceRunnable(this.tracer, this.traceKeys, this.spanNamer, command); + Runnable r = new TraceRunnable(tracer(), spanNamer(), errorParser(), command); return getScheduledExecutorService().scheduleWithFixedDelay(r, initialDelay, delay, unit); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixAutoConfiguration.java index 8a9f1df35e..7cd0d86eb2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixAutoConfiguration.java @@ -1,11 +1,13 @@ package org.springframework.cloud.sleuth.instrument.hystrix; +import brave.Tracer; +import brave.Tracing; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.SpanNamer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,13 +26,14 @@ @Configuration @AutoConfigureAfter(TraceAutoConfiguration.class) @ConditionalOnClass(HystrixCommand.class) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @ConditionalOnProperty(value = "spring.sleuth.hystrix.strategy.enabled", matchIfMissing = true) public class SleuthHystrixAutoConfiguration { - @Bean - SleuthHystrixConcurrencyStrategy sleuthHystrixConcurrencyStrategy(Tracer tracer, TraceKeys traceKeys) { - return new SleuthHystrixConcurrencyStrategy(tracer, traceKeys); + @Bean SleuthHystrixConcurrencyStrategy sleuthHystrixConcurrencyStrategy(Tracer tracer, + SpanNamer spanNamer, ErrorParser errorParser) { + return new SleuthHystrixConcurrencyStrategy(tracer, spanNamer, + errorParser); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java index 1c66316755..48a329a4be 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ package org.springframework.cloud.sleuth.instrument.hystrix; -import java.lang.invoke.MethodHandles; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import brave.Tracer; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; @@ -35,9 +35,9 @@ import com.netflix.hystrix.strategy.properties.HystrixProperty; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.instrument.async.TraceCallable; /** * A {@link HystrixConcurrencyStrategy} that wraps a {@link Callable} in a @@ -54,12 +54,15 @@ public class SleuthHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy .getLog(SleuthHystrixConcurrencyStrategy.class); private final Tracer tracer; - private final TraceKeys traceKeys; + private final SpanNamer spanNamer; + private final ErrorParser errorParser; private HystrixConcurrencyStrategy delegate; - public SleuthHystrixConcurrencyStrategy(Tracer tracer, TraceKeys traceKeys) { + public SleuthHystrixConcurrencyStrategy(Tracer tracer, + SpanNamer spanNamer, ErrorParser errorParser) { this.tracer = tracer; - this.traceKeys = traceKeys; + this.spanNamer = spanNamer; + this.errorParser = errorParser; try { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if (this.delegate instanceof SleuthHystrixConcurrencyStrategy) { @@ -103,15 +106,16 @@ private void logCurrentStateOfHysrixPlugins(HystrixEventNotifier eventNotifier, @Override public Callable wrapCallable(Callable callable) { - if (callable instanceof HystrixTraceCallable) { + if (callable instanceof TraceCallable) { return callable; } Callable wrappedCallable = this.delegate != null ? this.delegate.wrapCallable(callable) : callable; - if (wrappedCallable instanceof HystrixTraceCallable) { + if (wrappedCallable instanceof TraceCallable) { return wrappedCallable; } - return new HystrixTraceCallable<>(this.tracer, this.traceKeys, wrappedCallable); + return new TraceCallable<>(this.tracer, this.spanNamer, + this.errorParser, wrappedCallable, HYSTRIX_COMPONENT); } @Override @@ -140,68 +144,4 @@ public HystrixRequestVariable getRequestVariable( HystrixRequestVariableLifecycle rv) { return this.delegate.getRequestVariable(rv); } - - // Visible for testing - static class HystrixTraceCallable implements Callable { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Tracer tracer; - private final TraceKeys traceKeys; - private final Callable callable; - private final Span parent; - - public HystrixTraceCallable(Tracer tracer, TraceKeys traceKeys, - Callable callable) { - this.tracer = tracer; - this.traceKeys = traceKeys; - this.callable = callable; - this.parent = tracer.getCurrentSpan(); - } - - @Override - public S call() throws Exception { - Span span = this.parent; - boolean created = false; - if (span != null) { - span = this.tracer.continueSpan(span); - if (log.isDebugEnabled()) { - log.debug("Continuing span " + span); - } - } - else { - span = this.tracer.createSpan(HYSTRIX_COMPONENT); - created = true; - if (log.isDebugEnabled()) { - log.debug("Creating new span " + span); - } - } - if (!span.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) { - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT); - } - String asyncKey = this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(); - if (!span.tags().containsKey(asyncKey)) { - this.tracer.addTag(asyncKey, Thread.currentThread().getName()); - } - try { - return this.callable.call(); - } - finally { - if (created) { - if (log.isDebugEnabled()) { - log.debug("Closing span since it was created" + span); - } - this.tracer.close(span); - } - else if(this.tracer.isTracing()) { - if (log.isDebugEnabled()) { - log.debug("Detaching span since it was continued " + span); - } - this.tracer.detach(span); - } - } - } - - } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommand.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommand.java index fc8d10b642..3c1dfcc978 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommand.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,10 @@ package org.springframework.cloud.sleuth.instrument.hystrix; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.TraceKeys; - +import brave.Span; +import brave.Tracer; import com.netflix.hystrix.HystrixCommand; +import org.springframework.cloud.sleuth.TraceKeys; /** * Abstraction over {@code HystrixCommand} that wraps command execution with Trace setting @@ -29,57 +28,38 @@ * @see Tracer * * @author Tomasz Nurkiewicz, 4financeIT - * @author Marcin Grzejszczak, 4financeIT + * @author Marcin Grzejszczak * @author Spencer Gibb * @since 1.0.0 */ public abstract class TraceCommand extends HystrixCommand { - private static final String HYSTRIX_COMPONENT = "hystrix"; - private final Tracer tracer; private final TraceKeys traceKeys; - private final Span parentSpan; + private final Span span; protected TraceCommand(Tracer tracer, TraceKeys traceKeys, Setter setter) { super(setter); this.tracer = tracer; this.traceKeys = traceKeys; - this.parentSpan = tracer.getCurrentSpan(); + this.span = this.tracer.nextSpan(); } @Override protected R run() throws Exception { String commandKeyName = getCommandKey().name(); - Span span = startSpan(commandKeyName); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, HYSTRIX_COMPONENT); - this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() + + Span span = this.span.name(commandKeyName); + span.tag(this.traceKeys.getHystrix().getPrefix() + this.traceKeys.getHystrix().getCommandKey(), commandKeyName); - this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() + + span.tag(this.traceKeys.getHystrix().getPrefix() + this.traceKeys.getHystrix().getCommandGroup(), getCommandGroup().name()); - this.tracer.addTag(this.traceKeys.getHystrix().getPrefix() + + span.tag(this.traceKeys.getHystrix().getPrefix() + this.traceKeys.getHystrix().getThreadPoolKey(), getThreadPoolKey().name()); - try { + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { return doRun(); } finally { - close(span); - } - } - - private Span startSpan(String commandKeyName) { - Span span = this.parentSpan; - if (span == null) { - return this.tracer.createSpan(commandKeyName, this.parentSpan); - } - return this.tracer.continueSpan(span); - } - - private void close(Span span) { - if (this.parentSpan == null) { - this.tracer.close(span); - } else { - this.tracer.detach(span); + span.finish(); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java deleted file mode 100644 index ab98834198..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/AbstractTraceChannelInterceptor.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.util.SpanNameUtil; -import org.springframework.integration.channel.AbstractMessageChannel; -import org.springframework.integration.context.IntegrationObjectSupport; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.ChannelInterceptorAdapter; -import org.springframework.messaging.support.ExecutorChannelInterceptor; -import org.springframework.util.ClassUtils; - -import java.lang.invoke.MethodHandles; - -/** - * Abstraction over classes related to channel intercepting - * - * @author Marcin Grzejszczak - */ -abstract class AbstractTraceChannelInterceptor extends ChannelInterceptorAdapter - implements ExecutorChannelInterceptor { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - /** - * If a span comes from messaging components then it will have this value as a prefix - * to its name. - *

- * Example of a Span name: {@code message:foo} - *

- * Where {@code message} is the prefix and {@code foo} is the channel name - */ - protected static final String MESSAGE_COMPONENT = "message"; - - private Tracer tracer; - private TraceKeys traceKeys; - private MessagingSpanTextMapExtractor spanExtractor; - private MessagingSpanTextMapInjector spanInjector; - private ErrorParser errorParser; - private final BeanFactory beanFactory; - - protected AbstractTraceChannelInterceptor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - protected Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } - - protected TraceKeys getTraceKeys() { - if (this.traceKeys == null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } - - protected MessagingSpanTextMapExtractor getSpanExtractor() { - if (this.spanExtractor == null) { - this.spanExtractor = this.beanFactory.getBean(MessagingSpanTextMapExtractor.class); - } - return this.spanExtractor; - } - - protected MessagingSpanTextMapInjector getSpanInjector() { - if (this.spanInjector == null) { - this.spanInjector = this.beanFactory.getBean(MessagingSpanTextMapInjector.class); - } - return this.spanInjector; - } - - protected ErrorParser getErrorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); - } - return this.errorParser; - } - - /** - * Returns a span given the message and a channel. Returns {@code null} if ids are - * missing. - */ - protected Span buildSpan(SpanTextMap carrier) { - try { - return getSpanExtractor().joinTrace(carrier); - } catch (Exception e) { - log.error("Exception occurred while trying to extract span from carrier", e); - return null; - } - } - - String getChannelName(MessageChannel channel) { - String name = null; - if (ClassUtils.isPresent( - "org.springframework.integration.context.IntegrationObjectSupport", - null)) { - if (channel instanceof IntegrationObjectSupport) { - name = ((IntegrationObjectSupport) channel).getComponentName(); - } - if (name == null && channel instanceof AbstractMessageChannel) { - name = ((AbstractMessageChannel) channel).getFullChannelName(); - } - } - if (name == null) { - name = channel.toString(); - } - return name; - } - - String getMessageChannelName(MessageChannel channel) { - return SpanNameUtil.shorten(MESSAGE_COMPONENT + ":" + getChannelName(channel)); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java deleted file mode 100644 index ff0712e895..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractor.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.Map; -import java.util.Random; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.util.TextMapUtil; - -/** - * Default implementation for messaging - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public class HeaderBasedMessagingExtractor implements MessagingSpanTextMapExtractor { - - private final Random random = new Random(); - - @Override - public Span joinTrace(SpanTextMap textMap) { - Map carrier = TextMapUtil.asMap(textMap); - boolean spanIdMissing = !hasHeader(carrier, TraceMessageHeaders.SPAN_ID_NAME); - boolean traceIdMissing = !hasHeader(carrier, TraceMessageHeaders.TRACE_ID_NAME); - if (Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME))) { - String traceId = generateTraceIdIfMissing(carrier, traceIdMissing); - if (spanIdMissing) { - carrier.put(TraceMessageHeaders.SPAN_ID_NAME, traceId); - } - } else if (spanIdMissing) { - return null; - // TODO: Consider throwing IllegalArgumentException; - } - boolean idMissing = spanIdMissing || traceIdMissing; - return extractSpanFromHeaders(carrier, Span.builder(), idMissing); - } - - private String generateTraceIdIfMissing(Map carrier, - boolean traceIdMissing) { - if (traceIdMissing) { - carrier.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(this.random.nextLong())); - } - return carrier.get(TraceMessageHeaders.TRACE_ID_NAME); - } - - private Span extractSpanFromHeaders(Map carrier, - Span.SpanBuilder spanBuilder, boolean idMissing) { - String traceId = carrier.get(TraceMessageHeaders.TRACE_ID_NAME); - spanBuilder = spanBuilder - .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) - .traceId(Span.hexToId(traceId)) - .spanId(Span.hexToId(carrier.get(TraceMessageHeaders.SPAN_ID_NAME))); - String flags = carrier.get(TraceMessageHeaders.SPAN_FLAGS_NAME); - boolean debug = Span.SPAN_SAMPLED.equals(flags); - boolean spanSampled = Span.SPAN_SAMPLED.equals(carrier.get(TraceMessageHeaders.SAMPLED_NAME)); - if (debug) { - spanBuilder.exportable(true); - } else { - spanBuilder.exportable(spanSampled); - } - String processId = carrier.get(TraceMessageHeaders.PROCESS_ID_NAME); - String spanName = carrier.get(TraceMessageHeaders.SPAN_NAME_NAME); - if (spanName != null) { - spanBuilder.name(spanName); - } - if (processId != null) { - spanBuilder.processId(processId); - } - setParentIdIfApplicable(carrier, spanBuilder, TraceMessageHeaders.PARENT_ID_NAME); - spanBuilder.remote(true); - spanBuilder.shared((debug || spanSampled) && !idMissing); - for (Map.Entry entry : carrier.entrySet()) { - if (entry.getKey().toLowerCase().startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER)) { - spanBuilder.baggage(unprefixedKey(entry.getKey()), entry.getValue()); - } - } - return spanBuilder.build(); - } - - boolean hasHeader(Map message, String name) { - return message.containsKey(name); - } - - private void setParentIdIfApplicable(Map carrier, Span.SpanBuilder spanBuilder, - String spanParentIdHeader) { - String parentId = carrier.get(spanParentIdHeader); - if (parentId != null) { - spanBuilder.parent(Span.hexToId(parentId)); - } - } - - private String unprefixedKey(String key) { - return key.substring(key.indexOf(TraceMessageHeaders.HEADER_DELIMITER) + 1).toLowerCase(); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java deleted file mode 100644 index 17db826ab7..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjector.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.util.TextMapUtil; -import org.springframework.util.StringUtils; - -/** - * Default implementation for messaging - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public class HeaderBasedMessagingInjector implements MessagingSpanTextMapInjector { - - private final TraceKeys traceKeys; - - public HeaderBasedMessagingInjector(TraceKeys traceKeys) { - this.traceKeys = traceKeys; - } - - @Override - public void inject(Span span, SpanTextMap carrier) { - Map map = TextMapUtil.asMap(carrier); - if (span == null) { - if (!isSampled(map, TraceMessageHeaders.SAMPLED_NAME)) { - carrier.put(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - return; - } - return; - } - addHeaders(map, span, carrier); - } - - private boolean isSampled(Map initialMessage, String sampledHeaderName) { - return Span.SPAN_SAMPLED.equals(initialMessage.get(sampledHeaderName)); - } - - private void addHeaders(Map map, Span span, SpanTextMap textMap) { - addHeader(map, textMap, TraceMessageHeaders.TRACE_ID_NAME, span.traceIdString()); - addHeader(map, textMap, TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - if (span.isExportable()) { - addAnnotations(this.traceKeys, textMap, span); - Long parentId = getFirst(span.getParents()); - if (parentId != null) { - addHeader(map, textMap, TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(parentId)); - } - addHeader(map, textMap, TraceMessageHeaders.SPAN_NAME_NAME, span.getName()); - addHeader(map, textMap, TraceMessageHeaders.PROCESS_ID_NAME, span.getProcessId()); - addHeader(map, textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_SAMPLED); - } - else { - addHeader(map, textMap, TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - } - for (Map.Entry entry : span.baggageItems()) { - textMap.put(prefixedKey(entry.getKey()), entry.getValue()); - } - } - - private void addAnnotations(TraceKeys traceKeys, SpanTextMap spanTextMap, Span span) { - Map map = TextMapUtil.asMap(spanTextMap); - for (String name : traceKeys.getMessage().getHeaders()) { - if (map.containsKey(name)) { - String key = traceKeys.getMessage().getPrefix() + name.toLowerCase(); - Object value = map.get(name); - if (value == null) { - value = "null"; - } - // TODO: better way to serialize? - tagIfEntryMissing(span, key, value.toString()); - } - } - addPayloadAnnotations(traceKeys, map, span); - } - - private void addPayloadAnnotations(TraceKeys traceKeys, Map map, Span span) { - if (map.containsKey(traceKeys.getMessage().getPayload().getType())) { - tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getType(), - map.get(traceKeys.getMessage().getPayload().getType())); - tagIfEntryMissing(span, traceKeys.getMessage().getPayload().getSize(), - map.get(traceKeys.getMessage().getPayload().getSize())); - } - } - - private void tagIfEntryMissing(Span span, String key, String value) { - if (!span.tags().containsKey(key)) { - span.tag(key, value); - } - } - - private void addHeader(Map map, SpanTextMap textMap, String name, String value) { - if (StringUtils.hasText(value) && !map.containsKey(name)) { - textMap.put(name, value); - } - } - - private Long getFirst(List parents) { - return parents.isEmpty() ? null : parents.get(0); - } - - private String prefixedKey(String key) { - if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER )) { - return key; - } - return Span.SPAN_BAGGAGE_HEADER_PREFIX + TraceMessageHeaders.HEADER_DELIMITER + key; - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java deleted file mode 100644 index 1bdf18e4e7..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/IntegrationTraceChannelInterceptor.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.integration.channel.ChannelInterceptorAware; -import org.springframework.integration.channel.interceptor.VetoCapableInterceptor; -import org.springframework.messaging.support.ChannelInterceptor; - -/** - * @author Dave Syer - * - */ -class IntegrationTraceChannelInterceptor extends TraceChannelInterceptor implements VetoCapableInterceptor { - - IntegrationTraceChannelInterceptor(BeanFactory beanFactory) { - super(beanFactory); - } - - @Override - public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) { - for (ChannelInterceptor interceptor : channel.getChannelInterceptors()) { - if (interceptor instanceof AbstractTraceChannelInterceptor) { - return false; - } - } - return true; - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java new file mode 100644 index 0000000000..7554a7b8d3 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation.java @@ -0,0 +1,138 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import brave.propagation.Propagation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.messaging.support.NativeMessageHeaderAccessor; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.StringUtils; + +import static org.springframework.messaging.support.NativeMessageHeaderAccessor.NATIVE_HEADERS; + +/** + * This always sets native headers in defence of STOMP issues discussed here + */ +enum MessageHeaderPropagation + implements Propagation.Setter, + Propagation.Getter { + INSTANCE; + + private static final Log log = LogFactory.getLog(MessageHeaderPropagation.class); + + private static final Map LEGACY_HEADER_MAPPING = new HashMap<>(); + + private static final String TRACE_ID_NAME = "X-B3-TraceId"; + private static final String SPAN_ID_NAME = "X-B3-SpanId"; + private static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; + private static final String SAMPLED_NAME = "X-B3-Sampled"; + private static final String FLAGS_NAME = "X-B3-Flags"; + + static { + LEGACY_HEADER_MAPPING.put(TRACE_ID_NAME, TraceMessageHeaders.TRACE_ID_NAME); + LEGACY_HEADER_MAPPING.put(SPAN_ID_NAME, TraceMessageHeaders.SPAN_ID_NAME); + LEGACY_HEADER_MAPPING.put(PARENT_SPAN_ID_NAME, TraceMessageHeaders.PARENT_ID_NAME); + LEGACY_HEADER_MAPPING.put(SAMPLED_NAME, TraceMessageHeaders.SAMPLED_NAME); + LEGACY_HEADER_MAPPING.put(FLAGS_NAME, TraceMessageHeaders.SPAN_FLAGS_NAME); + } + + @Override public void put(MessageHeaderAccessor accessor, String key, String value) { + try { + doPut(accessor, key, value); + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("An exception happened when we tried to retrieve the [" + key + "] from message", e); + } + } + String legacyKey = LEGACY_HEADER_MAPPING.get(key); + if (legacyKey != null) { + doPut(accessor, legacyKey, value); + } + } + + private void doPut(MessageHeaderAccessor accessor, String key, String value) { + accessor.setHeader(key, value); + if (accessor instanceof NativeMessageHeaderAccessor) { + NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; + nativeAccessor.setNativeHeader(key, value); + } + else { + Map> nativeHeaders = (Map) accessor + .getHeader(NATIVE_HEADERS); + if (nativeHeaders == null) { + accessor.setHeader(NATIVE_HEADERS, + nativeHeaders = new LinkedMultiValueMap<>()); + } + nativeHeaders.put(key, Collections.singletonList(value)); + } + } + + @Override public String get(MessageHeaderAccessor accessor, String key) { + try { + String value = doGet(accessor, key); + if (StringUtils.hasText(value)) { + return value; + } + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("An exception happened when we tried to retrieve the [" + key + "] from message", e); + } + } + return legacyValue(accessor, key); + } + + private String legacyValue(MessageHeaderAccessor accessor, String key) { + String legacyKey = LEGACY_HEADER_MAPPING.get(key); + if (legacyKey != null) { + return doGet(accessor, legacyKey); + } + return null; + } + + private String doGet(MessageHeaderAccessor accessor, String key) { + if (accessor instanceof NativeMessageHeaderAccessor) { + NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; + String result = nativeAccessor.getFirstNativeHeader(key); + if (result != null) + return result; + } + else { + Map> nativeHeaders = (Map) accessor + .getHeader(NATIVE_HEADERS); + if (nativeHeaders != null) { + List result = nativeHeaders.get(key); + if (result != null && !result.isEmpty()) + return result.get(0); + } + } + Object result = accessor.getHeader(key); + return result != null ? result.toString() : null; + } + + static void removeAnyTraceHeaders(MessageHeaderAccessor accessor, + List keysToRemove) { + for (String keyToRemove : keysToRemove) { + accessor.removeHeader(keyToRemove); + if (accessor instanceof NativeMessageHeaderAccessor) { + NativeMessageHeaderAccessor nativeAccessor = (NativeMessageHeaderAccessor) accessor; + nativeAccessor.removeNativeHeader(keyToRemove); + } + else { + Map> nativeHeaders = (Map) accessor + .getHeader(NATIVE_HEADERS); + if (nativeHeaders == null) + continue; + nativeHeaders.remove(keyToRemove); + } + } + } + + @Override public String toString() { + return "MessageHeaderPropagation{}"; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java deleted file mode 100644 index 4ac3a24903..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapExtractor.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * Contract for extracting tracing headers from a {@link SpanTextMap} - * via message headers - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface MessagingSpanTextMapExtractor extends SpanExtractor { -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java deleted file mode 100644 index ad32edaa5d..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanTextMapInjector.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * Contract for injecting tracing headers from a {@link SpanTextMap} - * via message headers - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface MessagingSpanTextMapInjector extends SpanInjector { -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java deleted file mode 100644 index 49e67e164a..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMap.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.messaging.Message; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.messaging.support.NativeMessageHeaderAccessor; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -/** - * A {@link SpanTextMap} abstraction over {@link MessageBuilder} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -class MessagingTextMap implements SpanTextMap { - - private final MessageBuilder delegate; - - public MessagingTextMap(MessageBuilder delegate) { - this.delegate = delegate; - } - - @Override - public Iterator> iterator() { - Map map = new HashMap<>(); - for (Map.Entry entry : this.delegate.build().getHeaders() - .entrySet()) { - if (!NativeMessageHeaderAccessor.NATIVE_HEADERS.equals(entry.getKey())) { - map.put(entry.getKey(), String.valueOf(entry.getValue())); - } - } - return map.entrySet().iterator(); - } - - @Override - @SuppressWarnings("unchecked") - public void put(String key, String value) { - if (!StringUtils.hasText(value)) { - return; - } - Message initialMessage = this.delegate.build(); - MessageHeaderAccessor accessor = MessageHeaderAccessor - .getMutableAccessor(initialMessage); - accessor.setHeader(key, value); - if (accessor instanceof SimpMessageHeaderAccessor) { - SimpMessageHeaderAccessor nativeAccessor = (SimpMessageHeaderAccessor) accessor; - nativeAccessor.setNativeHeader(key, value); - } - else if (accessor.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS) != null) { - if (accessor.getHeader( - NativeMessageHeaderAccessor.NATIVE_HEADERS) instanceof MultiValueMap) { - MultiValueMap map = (MultiValueMap) accessor - .getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS); - map.add(key, value); - } - } - else { - MultiValueMap map = new LinkedMultiValueMap<>(); - accessor.setHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS, map); - map.add(key, value); - } - this.delegate.copyHeaders(accessor.toMessageHeaders()); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java deleted file mode 100644 index 88187592b5..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptor.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.Log; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.messaging.support.MessageHeaderAccessor; - -/** - * A channel interceptor that automatically starts / continues / closes and detaches - * spans. - * - * @author Dave Syer - * @since 1.0.0 - */ -public class TraceChannelInterceptor extends AbstractTraceChannelInterceptor { - - private static final org.apache.commons.logging.Log log = LogFactory - .getLog(TraceChannelInterceptor.class); - - public TraceChannelInterceptor(BeanFactory beanFactory) { - super(beanFactory); - } - - @Override - public void afterSendCompletion(Message message, MessageChannel channel, - boolean sent, Exception ex) { - Message retrievedMessage = getMessage(message); - MessageBuilder messageBuilder = MessageBuilder.fromMessage(retrievedMessage); - Span currentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan() - : buildSpan(new MessagingTextMap(messageBuilder)); - if (log.isDebugEnabled()) { - log.debug("Completed sending and current span is " + currentSpan); - } - getTracer().continueSpan(currentSpan); - if (containsServerReceived(currentSpan)) { - if (log.isDebugEnabled()) { - log.debug("Marking span with server send"); - } - currentSpan.logEvent(Span.SERVER_SEND); - } - else if (currentSpan != null) { - if (log.isDebugEnabled()) { - log.debug("Marking span with client received"); - } - currentSpan.logEvent(Span.CLIENT_RECV); - } - addErrorTag(ex); - if (log.isDebugEnabled()) { - log.debug("Closing messaging span " + currentSpan); - } - getTracer().close(currentSpan); - if (log.isDebugEnabled()) { - log.debug("Messaging span " + currentSpan + " successfully closed"); - } - } - - private boolean containsServerReceived(Span span) { - if (span == null) { - return false; - } - for (Log log : span.logs()) { - if (Span.SERVER_RECV.equals(log.getEvent())) { - return true; - } - } - return false; - } - - @Override - public Message preSend(Message message, MessageChannel channel) { - if (log.isDebugEnabled()) { - log.debug("Processing message before sending it to the channel"); - } - Message retrievedMessage = getMessage(message); - MessageBuilder messageBuilder = MessageBuilder.fromMessage(retrievedMessage); - Span parentSpan = getTracer().isTracing() ? getTracer().getCurrentSpan() - : buildSpan(new MessagingTextMap(messageBuilder)); - // Do not continue the parent (assume that this is handled by caller) - // getTracer().continueSpan(parentSpan); - if (log.isDebugEnabled()) { - log.debug("Parent span is " + parentSpan); - } - String name = getMessageChannelName(channel); - if (log.isDebugEnabled()) { - log.debug("Name of the span will be [" + name + "]"); - } - Span span = startSpan(parentSpan, name, message); - if (message.getHeaders() - .containsKey(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT)) { - if (log.isDebugEnabled()) { - log.debug("Marking span with server received"); - } - span.logEvent(Span.SERVER_RECV); - } - else { - if (log.isDebugEnabled()) { - log.debug("Marking span with client send"); - } - span.logEvent(Span.CLIENT_SEND); - messageBuilder.setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true); - } - getSpanInjector().inject(span, new MessagingTextMap(messageBuilder)); - MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); - if (message instanceof ErrorMessage) { - headers.copyHeaders(sleuthHeaders(messageBuilder.build().getHeaders())); - return new ErrorMessage((Throwable) message.getPayload(), headers.getMessageHeaders()); - } - headers.copyHeaders(messageBuilder.build().getHeaders()); - return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders()); - } - - private Map sleuthHeaders(Map headers) { - Map headersToCopy = new HashMap<>(); - for (Map.Entry entry : headers.entrySet()) { - if (TraceMessageHeaders.ALL_HEADERS.contains(entry.getKey())) { - headersToCopy.put(entry.getKey(), entry.getValue()); - } - } - return headersToCopy; - } - - private Message getMessage(Message message) { - Object payload = message.getPayload(); - if (payload instanceof MessagingException) { - MessagingException e = (MessagingException) payload; - return e.getFailedMessage(); - } - return message; - } - - private Span startSpan(Span span, String name, Message message) { - if (span != null) { - return getTracer().createSpan(name, span); - } - if (Span.SPAN_NOT_SAMPLED - .equals(message.getHeaders().get(TraceMessageHeaders.SAMPLED_NAME))) { - return getTracer().createSpan(name, NeverSampler.INSTANCE); - } - return getTracer().createSpan(name); - } - - @Override - public Message beforeHandle(Message message, MessageChannel channel, - MessageHandler handler) { - Message retrievedMessage = getMessage(message); - MessageBuilder messageBuilder = MessageBuilder.fromMessage(retrievedMessage); - Span spanFromHeader = getTracer().isTracing() ? getTracer().getCurrentSpan() - : buildSpan(new MessagingTextMap(messageBuilder)); - if (log.isDebugEnabled()) { - log.debug("Continuing span " + spanFromHeader + " before handling message"); - } - if (spanFromHeader != null) { - if (log.isDebugEnabled()) { - log.debug("Marking span with server received"); - } - spanFromHeader.logEvent(Span.SERVER_RECV); - } - getTracer().continueSpan(spanFromHeader); - if (log.isDebugEnabled()) { - log.debug("Span " + spanFromHeader + " successfully continued"); - } - return message; - } - - @Override - public void afterMessageHandled(Message message, MessageChannel channel, - MessageHandler handler, Exception ex) { - Span spanFromHeader = getTracer().getCurrentSpan(); - if (log.isDebugEnabled()) { - log.debug("Continuing span " + spanFromHeader + " after message handled"); - } - if (spanFromHeader != null) { - if (log.isDebugEnabled()) { - log.debug("Marking span with server send"); - } - spanFromHeader.logEvent(Span.SERVER_SEND); - addErrorTag(ex); - } - // related to #447 - if (getTracer().isTracing()) { - getTracer().detach(spanFromHeader); - if (log.isDebugEnabled()) { - log.debug("Detached " + spanFromHeader + " from current thread"); - } - } - } - - private void addErrorTag(Exception ex) { - if (ex != null) { - getErrorParser().parseErrorTags(getTracer().getCurrentSpan(), ex); - } - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java index 6767f5ec27..d47165f8ed 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceMessageHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,6 @@ package org.springframework.cloud.sleuth.instrument.messaging; -import java.util.Arrays; -import java.util.List; - /** * Contains trace related messaging headers. The deprecated headers contained `-` which * for example in the JMS specs is invalid. That's why the public constants in this class @@ -31,16 +28,10 @@ public class TraceMessageHeaders { public static final String SPAN_ID_NAME = "spanId"; public static final String SAMPLED_NAME = "spanSampled"; - public static final String PROCESS_ID_NAME = "spanProcessId"; public static final String PARENT_ID_NAME = "spanParentSpanId"; public static final String TRACE_ID_NAME = "spanTraceId"; public static final String SPAN_NAME_NAME = "spanName"; public static final String SPAN_FLAGS_NAME = "spanFlags"; - static final List ALL_HEADERS = Arrays.asList(SPAN_ID_NAME, SAMPLED_NAME, - PROCESS_ID_NAME, PARENT_ID_NAME, TRACE_ID_NAME, SPAN_NAME_NAME, SPAN_FLAGS_NAME); - - static final String MESSAGE_SENT_FROM_CLIENT = "messageSent"; - static final String HEADER_DELIMITER = "_"; private TraceMessageHeaders() {} } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java deleted file mode 100644 index 4283332ff8..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpanMessagingAutoConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.Message; - -/** - * AutoConfiguration containing Span extractor and injector for messaging. Will be reused - * by Messaging and WebSockets - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -@Configuration -@ConditionalOnClass(Message.class) -@ConditionalOnBean(Tracer.class) -public class TraceSpanMessagingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public MessagingSpanTextMapExtractor messagingSpanExtractor() { - return new HeaderBasedMessagingExtractor(); - } - - @Bean - @ConditionalOnMissingBean - public MessagingSpanTextMapInjector messagingSpanInjector(TraceKeys traceKeys) { - return new HeaderBasedMessagingInjector(traceKeys); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java index 104083f433..cec06b7a56 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TraceSpringIntegrationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,13 @@ package org.springframework.cloud.sleuth.instrument.messaging; -import org.springframework.beans.factory.BeanFactory; +import brave.Tracing; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,21 +36,20 @@ * @author Spencer Gibb * @since 1.0.0 * - * @see TraceChannelInterceptor + * @see TracingChannelInterceptor */ @Configuration @ConditionalOnClass(GlobalChannelInterceptor.class) -@ConditionalOnBean(Tracer.class) -@AutoConfigureAfter({ TraceAutoConfiguration.class, - TraceSpanMessagingAutoConfiguration.class }) +@ConditionalOnBean(Tracing.class) +@AutoConfigureAfter({ TraceAutoConfiguration.class }) @ConditionalOnProperty(value = "spring.sleuth.integration.enabled", matchIfMissing = true) @EnableConfigurationProperties(TraceKeys.class) public class TraceSpringIntegrationAutoConfiguration { @Bean @GlobalChannelInterceptor(patterns = "${spring.sleuth.integration.patterns:*}") - public TraceChannelInterceptor traceChannelInterceptor(BeanFactory beanFactory) { - return new IntegrationTraceChannelInterceptor(beanFactory); + public TracingChannelInterceptor traceChannelInterceptor(Tracing tracing) { + return new TracingChannelInterceptor(tracing); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java new file mode 100644 index 0000000000..6fe47b4b33 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java @@ -0,0 +1,245 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import brave.Span; +import brave.SpanCustomizer; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.ThreadLocalSpan; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.sleuth.util.SpanNameUtil; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.ChannelInterceptorAdapter; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.messaging.support.ExecutorChannelInterceptor; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.util.ClassUtils; + +/** + * This starts and propagates {@link Span.Kind#PRODUCER} span for each message sent (via native + * headers. It also extracts or creates a {@link Span.Kind#CONSUMER} span for each message + * received. This span is injected onto each message so it becomes the parent when a handler later + * calls {@link MessageHandler#handleMessage(Message)}, or a another processing library calls {@link #nextSpan(Message)}. + *

+ *

This implementation uses {@link ThreadLocalSpan} to propagate context between callbacks. This + * is an alternative to {@code ThreadStatePropagationChannelInterceptor} which is less sensitive + * to message manipulation by other interceptors. + */ +public final class TracingChannelInterceptor extends ChannelInterceptorAdapter + implements ExecutorChannelInterceptor { + + private static final Log log = LogFactory.getLog(TracingChannelInterceptor.class); + + public static TracingChannelInterceptor create(Tracing tracing) { + return new TracingChannelInterceptor(tracing); + } + + final Tracing tracing; + final Tracer tracer; + final ThreadLocalSpan threadLocalSpan; + final TraceContext.Injector injector; + final TraceContext.Extractor extractor; + + TracingChannelInterceptor(Tracing tracing) { + this.tracing = tracing; + this.tracer = tracing.tracer(); + this.threadLocalSpan = ThreadLocalSpan.create(this.tracer); + this.injector = tracing.propagation().injector(MessageHeaderPropagation.INSTANCE); + this.extractor = tracing.propagation() + .extractor(MessageHeaderPropagation.INSTANCE); + } + + /** + * Use this to create a span for processing the given message. Note: the result has no name and is + * not started. + *

+ *

This creates a child from identifiers extracted from the message headers, or a new span if + * one couldn't be extracted. + */ + public Span nextSpan(Message message) { + MessageHeaderAccessor headers = mutableHeaderAccessor(message); + TraceContextOrSamplingFlags extracted = this.extractor.extract(headers); + headers.setImmutable(); + Span result = this.tracer.nextSpan(extracted); + if (extracted.context() == null && !result.isNoop()) { + addTags(message, result, null); + } + if (log.isDebugEnabled()) { + log.debug("Created a new span " + result); + } + return result; + } + + /** + * Starts and propagates {@link Span.Kind#PRODUCER} span for each message sent. + */ + @Override public Message preSend(Message message, MessageChannel channel) { + MessageHeaderAccessor headers = mutableHeaderAccessor(message); + TraceContextOrSamplingFlags extracted = this.extractor.extract(headers); + Span span = this.threadLocalSpan.next(extracted); + + MessageHeaderPropagation + .removeAnyTraceHeaders(headers, this.tracing.propagation().keys()); + this.injector.inject(span.context(), headers); + if (!span.isNoop()) { + span.kind(Span.Kind.PRODUCER).name("send").start(); + addTags(message, span, channel); + } + if (log.isDebugEnabled()) { + log.debug("Created a new span in pre send" + span); + } + headers.setImmutable(); + return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders()); + } + + @Override public void afterSendCompletion(Message message, MessageChannel channel, + boolean sent, Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Will finish the current span after completion " + this.tracer.currentSpan()); + } + finishSpan(ex); + } + + /** + * This starts a consumer span as a child of the incoming message or the current trace context, + * placing it in scope until the receive completes. + */ + @Override public Message postReceive(Message message, MessageChannel channel) { + MessageHeaderAccessor headers = mutableHeaderAccessor(message); + TraceContextOrSamplingFlags extracted = this.extractor.extract(headers); + Span span = this.threadLocalSpan.next(extracted); + + MessageHeaderPropagation + .removeAnyTraceHeaders(headers, this.tracing.propagation().keys()); + this.injector.inject(span.context(), headers); + if (!span.isNoop()) { + span.kind(Span.Kind.CONSUMER).name("receive").start(); + addTags(message, span, channel); + } + if (log.isDebugEnabled()) { + log.debug("Created a new span in post receive " + span); + } + headers.setImmutable(); + return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders()); + } + + @Override + public void afterReceiveCompletion(Message message, MessageChannel channel, + Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Will finish the current span after receive completion " + this.tracer.currentSpan()); + } + finishSpan(ex); + } + + /** + * This starts a consumer span as a child of the incoming message or the current trace context. + * It then creates a span for the handler, placing it in scope. + */ + @Override public Message beforeHandle(Message message, MessageChannel channel, + MessageHandler handler) { + MessageHeaderAccessor headers = mutableHeaderAccessor(message); + TraceContextOrSamplingFlags extracted = this.extractor.extract(headers); + + // Start and finish a consumer span as we will immediately process it. + Span consumerSpan = this.tracer.nextSpan(extracted); + if (!consumerSpan.isNoop()) { + consumerSpan.kind(Span.Kind.CONSUMER).start(); + addTags(message, consumerSpan, channel); + consumerSpan.finish(); + } + + // create and scope a span for the message processor + this.threadLocalSpan.next(TraceContextOrSamplingFlags.create(consumerSpan.context())) + .name("handle").start(); + + // remove any trace headers, but don't re-inject as we are synchronously processing the + // message and can rely on scoping to access this span later. + MessageHeaderPropagation + .removeAnyTraceHeaders(headers, this.tracing.propagation().keys()); + if (log.isDebugEnabled()) { + log.debug("Created a new span in before handle" + consumerSpan); + } + if (message instanceof ErrorMessage) { + return new ErrorMessage((Throwable) message.getPayload(), headers.getMessageHeaders()); + } + headers.setImmutable(); + return new GenericMessage<>(message.getPayload(), headers.getMessageHeaders()); + } + + @Override public void afterMessageHandled(Message message, MessageChannel channel, + MessageHandler handler, Exception ex) { + if (log.isDebugEnabled()) { + log.debug("Will finish the current span after message handled " + this.tracer.currentSpan()); + } + finishSpan(ex); + } + + /** + * When an upstream context was not present, lookup keys are unlikely added + */ + static void addTags(Message message, SpanCustomizer result, MessageChannel channel) { + // TODO topic etc + if (channel != null) { + result.tag("channel", messageChannelName(channel)); + } + } + + private static String channelName(MessageChannel channel) { + String name = null; + if (ClassUtils.isPresent( + "org.springframework.integration.context.IntegrationObjectSupport", + null)) { + if (channel instanceof IntegrationObjectSupport) { + name = ((IntegrationObjectSupport) channel).getComponentName(); + } + if (name == null && channel instanceof AbstractMessageChannel) { + name = ((AbstractMessageChannel) channel).getFullChannelName(); + } + } + if (name == null) { + name = channel.toString(); + } + return name; + } + + private static String messageChannelName(MessageChannel channel) { + return SpanNameUtil.shorten("send:" + channelName(channel)); + } + + void finishSpan(Exception error) { + Span span = this.threadLocalSpan.remove(); + if (span == null || span.isNoop()) + return; + if (error != null) { // an error occurred, adding error to span + String message = error.getMessage(); + if (message == null) + message = error.getClass().getSimpleName(); + span.tag("error", message); + } + span.finish(); + } + + private MessageHeaderAccessor mutableHeaderAccessor(Message message) { + MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(getMessage(message)); + headers.setLeaveMutable(true); + return headers; + } + + private Message getMessage(Message message) { + Object payload = message.getPayload(); + if (payload instanceof MessagingException) { + MessagingException e = (MessagingException) payload; + return e.getFailedMessage(); + } + return message; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java index e9b3f2388c..9c0bdb29bf 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfiguration.java @@ -1,21 +1,14 @@ package org.springframework.cloud.sleuth.instrument.messaging.websocket; -import org.springframework.beans.factory.BeanFactory; +import brave.Tracing; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanTextMapExtractor; -import org.springframework.cloud.sleuth.instrument.messaging.MessagingSpanTextMapInjector; -import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor; -import org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.messaging.TracingChannelInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.stereotype.Component; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @@ -29,25 +22,15 @@ * * @see AbstractWebSocketMessageBrokerConfigurer */ -@Component @Configuration -@AutoConfigureAfter(TraceSpanMessagingAutoConfiguration.class) @ConditionalOnClass(DelegatingWebSocketMessageBrokerConfiguration.class) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @ConditionalOnProperty(value = "spring.sleuth.integration.websockets.enabled", matchIfMissing = true) public class TraceWebSocketAutoConfiguration extends AbstractWebSocketMessageBrokerConfigurer { @Autowired - BeanFactory beanFactory; - @Autowired - Tracer tracer; - @Autowired - TraceKeys traceKeys; - @Autowired - MessagingSpanTextMapExtractor spanExtractor; - @Autowired - MessagingSpanTextMapInjector spanInjector; + Tracing tracing; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { @@ -56,16 +39,16 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { @Override public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.configureBrokerChannel().setInterceptors(new TraceChannelInterceptor(this.beanFactory)); + registry.configureBrokerChannel().setInterceptors(TracingChannelInterceptor.create(this.tracing)); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { - registration.setInterceptors(new TraceChannelInterceptor(this.beanFactory)); + registration.setInterceptors(TracingChannelInterceptor.create(this.tracing)); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { - registration.setInterceptors(new TraceChannelInterceptor(this.beanFactory)); + registration.setInterceptors(TracingChannelInterceptor.create(this.tracing)); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java index e75a4fc51a..04b6e3bc63 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java @@ -3,12 +3,11 @@ import java.util.function.Function; import java.util.function.Predicate; -import org.reactivestreams.Publisher; -import org.springframework.cloud.sleuth.Tracer; - +import brave.Tracing; import reactor.core.Fuseable; import reactor.core.Scannable; import reactor.core.publisher.Operators; +import org.reactivestreams.Publisher; /** * Reactive Span pointcuts factories @@ -19,18 +18,19 @@ public abstract class ReactorSleuth { /** - * Return a span operator pointcut given a {@link Tracer}. This can be used in reactor + * Return a span operator pointcut given a {@link Tracing}. This can be used in reactor * via {@link reactor.core.publisher.Flux#transform(Function)}, {@link * reactor.core.publisher.Mono#transform(Function)}, {@link * reactor.core.publisher.Hooks#onEachOperator(Function)} or {@link * reactor.core.publisher.Hooks#onLastOperator(Function)}. * - * @param tracer the {@link Tracer} instance to use in this span operator + * @param tracing the {@link Tracing} instance to use in this span operator * @param an arbitrary type that is left unchanged by the span operator * * @return a new Span operator pointcut */ - public static Function, ? extends Publisher> spanOperator(Tracer tracer) { + public static Function, ? extends Publisher> spanOperator( + Tracing tracing) { return Operators.lift(POINTCUT_FILTER, ((scannable, sub) -> { //do not trace fused flows if(scannable instanceof Fuseable && sub instanceof Fuseable.QueueSubscription){ @@ -39,7 +39,7 @@ public abstract class ReactorSleuth { return new SpanSubscriber<>( sub, sub.currentContext(), - tracer, + tracing, scannable.name()); })); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java index 3be48c36cb..122f27d154 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriber.java @@ -2,14 +2,16 @@ import java.util.concurrent.atomic.AtomicBoolean; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.TraceContextOrSamplingFlags; import reactor.core.CoreSubscriber; import reactor.util.Logger; import reactor.util.Loggers; import reactor.util.context.Context; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; /** * A trace representation of the {@link Subscriber} @@ -21,7 +23,8 @@ final class SpanSubscriber extends AtomicBoolean implements Subscription, CoreSubscriber { - private static final Logger log = Loggers.getLogger(SpanSubscriber.class); + private static final Logger log = Loggers.getLogger( + SpanSubscriber.class); private final Span span; private final Span rootSpan; @@ -30,11 +33,11 @@ final class SpanSubscriber extends AtomicBoolean implements Subscription, private final Tracer tracer; private Subscription s; - SpanSubscriber(Subscriber subscriber, Context ctx, Tracer tracer, + SpanSubscriber(Subscriber subscriber, Context ctx, Tracing tracing, String name) { this.subscriber = subscriber; - this.tracer = tracer; - Span root = ctx.getOrDefault(Span.class, tracer.getCurrentSpan()); + this.tracer = tracing.tracer(); + Span root = ctx.getOrDefault(Span.class, this.tracer.currentSpan()); if (log.isTraceEnabled()) { log.trace("Span from context [{}]", root); } @@ -42,7 +45,9 @@ final class SpanSubscriber extends AtomicBoolean implements Subscription, if (log.isTraceEnabled()) { log.trace("Stored context root span [{}]", this.rootSpan); } - this.span = tracer.createSpan(name, root); + this.span = root != null ? + this.tracer.nextSpan(TraceContextOrSamplingFlags.create(root.context())) + .name(name) : this.tracer.nextSpan().name(name); if (log.isTraceEnabled()) { log.trace("Created span [{}], with name [{}]", this.span, name); } @@ -54,55 +59,29 @@ final class SpanSubscriber extends AtomicBoolean implements Subscription, log.trace("On subscribe"); } this.s = subscription; - this.tracer.continueSpan(this.span); - if (log.isTraceEnabled()) { - log.trace("On subscribe - span continued"); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span)) { + if (log.isTraceEnabled()) { + log.trace("On subscribe - span continued"); + } + this.subscriber.onSubscribe(this); } - this.subscriber.onSubscribe(this); } @Override public void request(long n) { if (log.isTraceEnabled()) { log.trace("Request"); } - this.tracer.continueSpan(this.span); - if (log.isTraceEnabled()) { - log.trace("Request - continued"); - } - this.s.request(n); - // We're in the main thread so we don't want to pollute it with wrong spans - // that's why we need to detach the current one and continue with its parent - Span localRootSpan = this.span; - while (localRootSpan != null) { - if (this.rootSpan != null) { - if (localRootSpan.getSpanId() != this.rootSpan.getSpanId() && - !isRootParentSpan(localRootSpan)) { - localRootSpan = continueDetachedSpan(localRootSpan); - } else { - localRootSpan = null; - } - } else if (!isRootParentSpan(localRootSpan)) { - localRootSpan = continueDetachedSpan(localRootSpan); - } else { - localRootSpan = null; + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(this.span)) { + if (log.isTraceEnabled()) { + log.trace("Request - continued"); + } + this.s.request(n); + // no additional cleaning is required cause we operate on scopes + if (log.isTraceEnabled()) { + log.trace("Request after cleaning. Current span [{}]", + this.tracer.currentSpan()); } } - if (log.isTraceEnabled()) { - log.trace("Request after cleaning. Current span [{}]", - this.tracer.getCurrentSpan()); - } - } - - private boolean isRootParentSpan(Span localRootSpan) { - return localRootSpan.getSpanId() == localRootSpan.getTraceId(); - } - - private Span continueDetachedSpan(Span localRootSpan) { - if (log.isTraceEnabled()) { - log.trace("Will detach span {}", localRootSpan); - } - Span detachedSpan = this.tracer.detach(localRootSpan); - return this.tracer.continueSpan(detachedSpan); } @Override public void cancel() { @@ -144,12 +123,12 @@ void cleanup() { if (log.isTraceEnabled()) { log.trace("Cleaning up"); } - if (this.tracer.getCurrentSpan() != this.span) { + Tracer.SpanInScope ws = null; + if (this.tracer.currentSpan() != this.span) { if (log.isTraceEnabled()) { log.trace("Detaching span"); } - this.tracer.detach(this.tracer.getCurrentSpan()); - this.tracer.continueSpan(this.span); + ws = this.tracer.withSpanInScope(this.span); if (log.isTraceEnabled()) { log.trace("Continuing span"); } @@ -157,13 +136,15 @@ void cleanup() { if (log.isTraceEnabled()) { log.trace("Closing span"); } - this.tracer.close(this.span); + this.span.finish(); + if (ws != null) { + ws.close(); + } if (log.isTraceEnabled()) { log.trace("Span closed"); } if (this.rootSpan != null) { - this.tracer.continueSpan(this.rootSpan); - this.tracer.close(this.rootSpan); + this.rootSpan.finish(); if (log.isTraceEnabled()) { log.trace("Closed root span"); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java index b437928cd0..4cd3f4a9df 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/TraceReactorAutoConfiguration.java @@ -1,10 +1,15 @@ package org.springframework.cloud.sleuth.instrument.reactor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Supplier; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Supplier; +import brave.Tracing; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -12,16 +17,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.TraceableScheduledExecutorService; import org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -38,42 +37,31 @@ public class TraceReactorAutoConfiguration { @Configuration - @ConditionalOnBean(Tracer.class) + @ConditionalOnBean(Tracing.class) static class TraceReactorConfiguration { - @Autowired Tracer tracer; - @Autowired TraceKeys traceKeys; - @Autowired SpanNamer spanNamer; + @Autowired Tracing tracing; + @Autowired BeanFactory beanFactory; @Autowired LastOperatorWrapper lastOperatorWrapper; @Bean - @ConditionalOnNotWebApplication - LastOperatorWrapper spanOperator() { - return new LastOperatorWrapper() { - @Override public void wrapLastOperator(Tracer tracer) { - Hooks.onLastOperator(ReactorSleuth.spanOperator(tracer)); - } - }; + @ConditionalOnNotWebApplication LastOperatorWrapper spanOperator() { + return tracer -> Hooks.onLastOperator(ReactorSleuth.spanOperator(tracer)); } @Bean - @ConditionalOnWebApplication - LastOperatorWrapper noOpLastOperatorWrapper() { - return new LastOperatorWrapper() { - @Override public void wrapLastOperator(Tracer tracer) { - } - }; + @ConditionalOnWebApplication LastOperatorWrapper noOpLastOperatorWrapper() { + return tracer -> { }; } @PostConstruct public void setupHooks() { - this.lastOperatorWrapper.wrapLastOperator(this.tracer); + this.lastOperatorWrapper.wrapLastOperator(this.tracing); Schedulers.setFactory(new Schedulers.Factory() { @Override public ScheduledExecutorService decorateExecutorService(String schedulerType, Supplier actual) { - return new TraceableScheduledExecutorService(actual.get(), - TraceReactorConfiguration.this.tracer, - TraceReactorConfiguration.this.traceKeys, - TraceReactorConfiguration.this.spanNamer); + return new TraceableScheduledExecutorService( + TraceReactorConfiguration.this.beanFactory, + actual.get()); } }); } @@ -87,5 +75,5 @@ public void cleanupHooks() { } interface LastOperatorWrapper { - void wrapLastOperator(Tracer tracer); + void wrapLastOperator(Tracing tracer); } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java index 286dc47530..8383caf847 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/RxJavaAutoConfiguration.java @@ -2,19 +2,19 @@ import java.util.Arrays; +import brave.Tracer; +import brave.Tracing; +import rx.plugins.RxJavaSchedulersHook; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import rx.plugins.RxJavaSchedulersHook; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} that * enables support for RxJava via {@link RxJavaSchedulersHook}. @@ -24,7 +24,7 @@ */ @Configuration @AutoConfigureAfter(TraceAutoConfiguration.class) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @ConditionalOnClass(RxJavaSchedulersHook.class) @ConditionalOnProperty(value = "spring.sleuth.rxjava.schedulers.hook.enabled", matchIfMissing = true) @EnableConfigurationProperties(SleuthRxJavaSchedulersProperties.class) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java index f049705c03..797264e2dc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHook.java @@ -2,12 +2,11 @@ import java.util.List; +import brave.Span; +import brave.Tracer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - import rx.functions.Action0; import rx.plugins.RxJavaErrorHandler; import rx.plugins.RxJavaObservableExecutionHook; @@ -23,7 +22,8 @@ */ class SleuthRxJavaSchedulersHook extends RxJavaSchedulersHook { - private static final Log log = LogFactory.getLog(SleuthRxJavaSchedulersHook.class); + private static final Log log = LogFactory.getLog( + SleuthRxJavaSchedulersHook.class); private static final String RXJAVA_COMPONENT = "rxjava"; private final Tracer tracer; @@ -93,7 +93,7 @@ public TraceAction(Tracer tracer, TraceKeys traceKeys, Action0 actual, this.tracer = tracer; this.traceKeys = traceKeys; this.threadsToIgnore = threadsToIgnore; - this.parent = tracer.getCurrentSpan(); + this.parent = this.tracer.currentSpan(); this.actual = actual; } @@ -116,21 +116,19 @@ public void call() { Span span = this.parent; boolean created = false; if (span != null) { - span = this.tracer.continueSpan(span); + span = this.tracer.joinSpan(this.parent.context()); } else { - span = this.tracer.createSpan(RXJAVA_COMPONENT); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, RXJAVA_COMPONENT); - this.tracer.addTag(this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(), Thread.currentThread().getName()); + span = this.tracer.nextSpan().name(RXJAVA_COMPONENT).start(); + span.tag(this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getThreadNameKey(), + Thread.currentThread().getName()); created = true; } - try { + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { this.actual.call(); } finally { if (created) { - this.tracer.close(span); - } else if (this.tracer.isTracing()) { - this.tracer.detach(span); + span.finish(); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java index 75214ce20c..c3a9699656 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,13 @@ import java.util.regex.Pattern; +import brave.Span; +import brave.Tracer; +import brave.Tracing; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; /** @@ -39,21 +40,20 @@ * @author Spencer Gibb * @since 1.0.0 * - * @see Tracer + * @see Tracing */ @Aspect public class TraceSchedulingAspect { - private static final String SCHEDULED_COMPONENT = "scheduled"; - private final Tracer tracer; - private final TraceKeys traceKeys; private final Pattern skipPattern; + private final TraceKeys traceKeys; - public TraceSchedulingAspect(Tracer tracer, TraceKeys traceKeys, Pattern skipPattern) { + public TraceSchedulingAspect(Tracer tracer, Pattern skipPattern, + TraceKeys traceKeys) { this.tracer = tracer; - this.traceKeys = traceKeys; this.skipPattern = skipPattern; + this.traceKeys = traceKeys; } @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))") @@ -62,18 +62,24 @@ public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwa return pjp.proceed(); } String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName()); - Span span = this.tracer.createSpan(spanName); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, SCHEDULED_COMPONENT); - this.tracer.addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName()); - this.tracer.addTag(this.traceKeys.getAsync().getPrefix() + - this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName()); - try { + Span span = startOrContinueRenamedSpan(spanName); + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + span.tag(this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getClassNameKey(), pjp.getTarget().getClass().getSimpleName()); + span.tag(this.traceKeys.getAsync().getPrefix() + + this.traceKeys.getAsync().getMethodNameKey(), pjp.getSignature().getName()); return pjp.proceed(); + } finally { + span.finish(); } - finally { - this.tracer.close(span); + } + + private Span startOrContinueRenamedSpan(String spanName) { + Span currentSpan = this.tracer.currentSpan(); + if (currentSpan != null) { + return currentSpan.name(spanName); } + return this.tracer.nextSpan().name(spanName); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java index fbbcc50578..b9496291c2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/scheduling/TraceSchedulingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,21 @@ package org.springframework.cloud.sleuth.instrument.scheduling; +import java.util.regex.Pattern; + +import brave.Tracer; +import brave.Tracing; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; -import java.util.regex.Pattern; - /** * Registers beans related to task scheduling. * @@ -42,15 +43,16 @@ @Configuration @EnableAspectJAutoProxy @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @AutoConfigureAfter(TraceAutoConfiguration.class) @EnableConfigurationProperties(SleuthSchedulingProperties.class) public class TraceSchedulingAutoConfiguration { - @ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint") @Bean - public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, TraceKeys traceKeys, - SleuthSchedulingProperties sleuthSchedulingProperties) { - return new TraceSchedulingAspect(tracer, traceKeys, Pattern.compile(sleuthSchedulingProperties.getSkipPattern())); + @ConditionalOnClass(name = "org.aspectj.lang.ProceedingJoinPoint") + public TraceSchedulingAspect traceSchedulingAspect(Tracer tracer, + SleuthSchedulingProperties sleuthSchedulingProperties, TraceKeys traceKeys) { + return new TraceSchedulingAspect(tracer, + Pattern.compile(sleuthSchedulingProperties.getSkipPattern()), traceKeys); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java deleted file mode 100644 index f3c2da620c..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestTextMap.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.AbstractMap; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.web.util.UrlPathHelper; - -/** - * A {@link SpanTextMap} abstraction over {@link HttpServletRequest} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -class HttpServletRequestTextMap implements SpanTextMap { - - private final HttpServletRequest delegate; - private final UrlPathHelper urlPathHelper; - - HttpServletRequestTextMap(HttpServletRequest delegate) { - this.delegate = delegate; - this.urlPathHelper = new UrlPathHelper(); - } - - @Override - public Iterator> iterator() { - final Enumeration headerNames = this.delegate.getHeaderNames(); - - return new Iterator>() { - - private boolean useAdditionalHeader = true; - - @Override - public boolean hasNext() { - return useAdditionalHeader - || (headerNames != null && headerNames.hasMoreElements()); - } - - @Override - public Map.Entry next() { - if (useAdditionalHeader) { - useAdditionalHeader = false; - return new AbstractMap.SimpleImmutableEntry<>( - ZipkinHttpSpanMapper.URI_HEADER, - HttpServletRequestTextMap.this.urlPathHelper - .getPathWithinApplication( - HttpServletRequestTextMap.this.delegate)); - } - - String name = headerNames.nextElement(); - String value = HttpServletRequestTextMap.this.delegate.getHeader(name); - return new AbstractMap.SimpleEntry<>(name, value); - } - }; - } - - @Override - public void put(String key, String value) { - throw new UnsupportedOperationException("change servlet request isn't supported"); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanExtractor.java deleted file mode 100644 index ba0eaa1eae..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanExtractor.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import org.springframework.cloud.sleuth.SpanExtractor; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * Contract for extracting tracing headers from a {@link SpanTextMap} - * via HTTP headers - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface HttpSpanExtractor extends SpanExtractor { -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanInjector.java deleted file mode 100644 index 9a47482276..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpSpanInjector.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * Contract for injecting tracing headers from a {@link SpanTextMap} - * via HTTP headers - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public interface HttpSpanInjector extends SpanInjector { -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java deleted file mode 100644 index d730c14821..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjector.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.net.URI; -import java.util.Collection; -import java.util.Map; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.util.StringUtils; - -/** - * Injects HTTP related keys to the current span. - * - * @author Marcin Grzejszczak - * - * @since 1.0.1 - */ -public class HttpTraceKeysInjector { - - private final Tracer tracer; - private final TraceKeys traceKeys; - - public HttpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { - this.tracer = tracer; - this.traceKeys = traceKeys; - } - - /** - * Adds tags from the HTTP request to the current Span - */ - public void addRequestTags(String url, String host, String path, String method) { - this.tracer.addTag(this.traceKeys.getHttp().getUrl(), url); - this.tracer.addTag(this.traceKeys.getHttp().getHost(), host); - this.tracer.addTag(this.traceKeys.getHttp().getPath(), path); - this.tracer.addTag(this.traceKeys.getHttp().getMethod(), method); - } - - /** - * Adds tags from the HTTP request to the given Span - */ - public void addRequestTags(Span span, String url, String host, String path, String method) { - tagSpan(span, this.traceKeys.getHttp().getUrl(), url); - tagSpan(span, this.traceKeys.getHttp().getHost(), host); - tagSpan(span, this.traceKeys.getHttp().getPath(), path); - tagSpan(span, this.traceKeys.getHttp().getMethod(), method); - } - - /** - * Adds tags from the HTTP request to the given Span - */ - public void addRequestTags(Span span, URI uri, String method) { - addRequestTags(span, uri.toString(), uri.getHost(), uri.getPath(), method); - } - - /** - * Adds tags from the HTTP request together with headers to the current Span - */ - public void addRequestTags(String url, String host, String path, String method, - Map> headers) { - addRequestTags(url, host, path, method); - addRequestTagsFromHeaders(headers); - } - - /** - * Add a tag to the given, exportable Span - */ - public void tagSpan(Span span, String key, String value) { - if (span != null && span.isExportable()) { - span.tag(key, value); - } - } - - private void addRequestTagsFromHeaders(Map> headers) { - for (String name : this.traceKeys.getHttp().getHeaders()) { - Collection values = headers.get(name); - if (values != null) { - addTagForEntry(name, values); - } - } - } - - private void addTagForEntry(String name, Collection list) { - String key = this.traceKeys.getHttp().getPrefix() + name.toLowerCase(); - String value = list.size() == 1 ? list.iterator().next() - : StringUtils.collectionToDelimitedString(list, ",", "'", "'"); - this.tracer.addTag(key, value); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java deleted file mode 100644 index a7b4db3f63..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServerHttpRequestTextMap.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.http.server.reactive.ServerHttpRequest; - -/** - * Created by mgrzejszczak. - */ -class ServerHttpRequestTextMap implements SpanTextMap { - - private final ServerHttpRequest delegate; - private final Map additionalHeaders = new HashMap<>(); - - ServerHttpRequestTextMap(ServerHttpRequest delegate) { - this.delegate = delegate; - this.additionalHeaders.put(ZipkinHttpSpanMapper.URI_HEADER, - delegate.getPath().pathWithinApplication().value()); - } - - @Override - public Iterator> iterator() { - Map map = new HashMap<>(); - for (Map.Entry> entry : this.delegate.getHeaders() - .entrySet()) { - map.put(entry.getKey(), entry.getValue() != null ? - entry.getValue().isEmpty() ? "" : entry.getValue().get(0) : ""); - } - map.putAll(this.additionalHeaders); - return map.entrySet().iterator(); - } - - @Override - public void put(String key, String value) { - this.additionalHeaders.put(key, value); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java index 6159920aeb..5c0acdb71b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ServletUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParser.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParser.java new file mode 100644 index 0000000000..fae109af8c --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParser.java @@ -0,0 +1,84 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.net.URI; + +import brave.SpanCustomizer; +import brave.http.HttpAdapter; +import brave.http.HttpClientParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.util.SpanNameUtil; + +/** + * An {@link HttpClientParser} that behaves like Sleuth in versions 1.x + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +class SleuthHttpClientParser extends HttpClientParser { + + private final TraceKeys traceKeys; + + public SleuthHttpClientParser(TraceKeys traceKeys) { + this.traceKeys = traceKeys; + } + + @Override protected String spanName(HttpAdapter adapter, + Req req) { + return getName(URI.create(adapter.url(req))); + } + + @Override public void request(HttpAdapter adapter, Req req, + SpanCustomizer customizer) { + super.request(adapter, req, customizer); + String url = adapter.url(req); + URI uri = URI.create(url); + addRequestTags(customizer, url, uri.getHost(), uri.getPath(), adapter.method(req)); + this.traceKeys.getHttp().getHeaders() + .forEach(s -> { + String headerValue = adapter.requestHeader(req, s); + if (headerValue != null) { + customizer.tag(key(s), headerValue); + } + }); + } + + private String key(String key) { + return this.traceKeys.getHttp().getPrefix() + key.toLowerCase(); + } + + private String getName(URI uri) { + // The returned name should comply with RFC 882 - Section 3.1.2. + // i.e Header values must composed of printable ASCII values. + return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getRawPath()); + } + + private String uriScheme(URI uri) { + return uri.getScheme() == null ? "http" : uri.getScheme(); + } + + private void addRequestTags(SpanCustomizer customizer, String url, String host, + String path, String method) { + customizer.tag(this.traceKeys.getHttp().getUrl(), url); + if (host != null) { + customizer.tag(this.traceKeys.getHttp().getHost(), host); + } + customizer.tag(this.traceKeys.getHttp().getPath(), path); + customizer.tag(this.traceKeys.getHttp().getMethod(), method); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpProperties.java new file mode 100644 index 0000000000..792f539203 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpProperties.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Sleuth HTTP settings + * + * @since 2.0.0 + */ +@ConfigurationProperties("spring.sleuth.http") +public class SleuthHttpProperties { + + private boolean enabled = true; + + private Legacy legacy = new Legacy(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Legacy getLegacy() { + return this.legacy; + } + + public void setLegacy(Legacy legacy) { + this.legacy = legacy; + } + + /** + * Legacy Sleuth support. Related to the way headers are parsed and tags are set + */ + public static class Legacy { + + private boolean enabled = false; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpServerParser.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpServerParser.java new file mode 100644 index 0000000000..2149614ef5 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpServerParser.java @@ -0,0 +1,82 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import javax.servlet.http.HttpServletResponse; + +import brave.SpanCustomizer; +import brave.http.HttpAdapter; +import brave.http.HttpClientParser; +import brave.http.HttpServerParser; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; + +/** + * An {@link HttpClientParser} that behaves like Sleuth in versions 1.x + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +class SleuthHttpServerParser extends HttpServerParser { + + private final SleuthHttpClientParser clientParser; + private final ErrorParser errorParser; + private final TraceKeys traceKeys; + + SleuthHttpServerParser(TraceKeys traceKeys, ErrorParser errorParser) { + this.clientParser = new SleuthHttpClientParser(traceKeys); + this.errorParser = errorParser; + this.traceKeys = traceKeys; + } + + @Override protected String spanName(HttpAdapter adapter, + Req req) { + return this.clientParser.spanName(adapter, req); + } + + @Override public void request(HttpAdapter adapter, Req req, + SpanCustomizer customizer) { + this.clientParser.request(adapter, req, customizer); + } + + @Override + protected void error(Integer httpStatus, Throwable error, SpanCustomizer customizer) { + this.errorParser.parseErrorTags(customizer, error); + } + + @Override + public void response(HttpAdapter adapter, Resp res, Throwable error, + SpanCustomizer customizer) { + if (res == null) { + error(null, error, customizer); + return; + } + int httpStatus = adapter.statusCode(res); + if (httpStatus == HttpServletResponse.SC_OK && error != null) { + // Filter chain threw exception but the response status may not have been set + // yet, so we have to guess. + customizer.tag(this.traceKeys.getHttp().getStatusCode(), + String.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + } + // only tag valid http statuses + else if (httpStatus >= 100 && (httpStatus < 200) || (httpStatus > 399)) { + customizer.tag(this.traceKeys.getHttp().getStatusCode(), + String.valueOf(httpStatus)); + } + error(httpStatus, error, customizer); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java deleted file mode 100644 index 4504b04f8b..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/SsLogSetter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.lang.invoke.MethodHandles; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; - -/** - * Utility class to set SS log if it wasn't already set - * - * @author Marcin Grzejszczak - */ -class SsLogSetter { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - static void annotateWithServerSendIfLogIsNotAlreadyPresent(Span span) { - if (span == null) { - return; - } - for (org.springframework.cloud.sleuth.Log log1 : span.logs()) { - if (Span.SERVER_SEND.equals(log1.getEvent())) { - if (log.isTraceEnabled()) { - log.trace("Span was already annotated with SS, will not do it again"); - } - return; - } - } - if (log.isTraceEnabled()) { - log.trace("Will set SS on the span"); - } - span.logEvent(Span.SERVER_SEND); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java index fd5b701a77..0e9889403d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,6 @@ package org.springframework.cloud.sleuth.instrument.web; import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -28,35 +24,33 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import brave.Span; +import brave.Tracer; +import brave.http.HttpServerHandler; +import brave.http.HttpTracing; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContextOrSamplingFlags; +import brave.servlet.HttpServletAdapter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; +import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; -import org.springframework.util.StringUtils; import org.springframework.web.context.request.async.WebAsyncUtils; import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.util.UrlPathHelper; /** - * Filter that takes the value of the {@link Span#SPAN_ID_NAME} and - * {@link Span#TRACE_ID_NAME} header from either request or response and uses them to + * Filter that takes the value of the headers from either request and uses them to * create a new span. * *

* In order to keep the size of spans manageable, this only add tags defined in - * {@link TraceKeys}. If you need to add additional tags, such as headers subtype this and - * override {@link #addRequestTags} or {@link #addResponseTags}. + * {@link TraceKeys}. * * @author Jakub Nabrdalik, 4financeIT * @author Tomasz Nurkiewicz, 4financeIT @@ -72,7 +66,7 @@ @Order(TraceFilter.ORDER) public class TraceFilter extends GenericFilterBean { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(TraceFilter.class); private static final String HTTP_COMPONENT = "http"; @@ -95,15 +89,18 @@ public class TraceFilter extends GenericFilterBean { private static final String TRACE_SPAN_WITHOUT_PARENT = TraceFilter.class.getName() + ".SPAN_WITH_NO_PARENT"; - private Tracer tracer; + private static final String TRACE_EXCEPTION_REQUEST_ATTR = TraceFilter.class.getName() + + ".EXCEPTION"; + + private static final String SAMPLED_NAME = "X-B3-Sampled"; + private static final String SPAN_NOT_SAMPLED = "0"; + + private HttpTracing tracing; private TraceKeys traceKeys; private final Pattern skipPattern; - private final boolean supportsJoin; - private SpanReporter spanReporter; - private HttpSpanExtractor spanExtractor; - private HttpTraceKeysInjector httpTraceKeysInjector; - private ErrorParser errorParser; private final BeanFactory beanFactory; + private HttpServerHandler handler; + private Boolean hasErrorController; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @@ -113,7 +110,6 @@ public TraceFilter(BeanFactory beanFactory) { public TraceFilter(BeanFactory beanFactory, Pattern skipPattern) { this.beanFactory = beanFactory; - this.supportsJoin = beanFactory.getBean(SleuthProperties.class).isSupportsJoin(); this.skipPattern = skipPattern; } @@ -136,63 +132,58 @@ private static Pattern skipPattern(BeanFactory beanFactory) { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) { + if (!(servletRequest instanceof HttpServletRequest) || + !(servletResponse instanceof HttpServletResponse)) { throw new ServletException("Filter just supports HTTP requests"); } HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String uri = this.urlPathHelper.getPathWithinApplication(request); boolean skip = this.skipPattern.matcher(uri).matches() - || Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME)); + || SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, SAMPLED_NAME)); Span spanFromRequest = getSpanFromAttribute(request); + Tracer.SpanInScope ws = null; if (spanFromRequest != null) { - continueSpan(request, spanFromRequest); + ws = continueSpan(request, spanFromRequest); } if (log.isDebugEnabled()) { log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]"); } // in case of a response with exception status a exception controller will close the span if (!httpStatusSuccessful(response) && isSpanContinued(request)) { - Span parentSpan = parentSpan(spanFromRequest); - processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest); + processErrorRequest(filterChain, request, response, spanFromRequest, ws); return; } String name = HTTP_COMPONENT + ":" + uri; + SpanAndScope spanAndScope = new SpanAndScope(); Throwable exception = null; try { - spanFromRequest = createSpan(request, skip, spanFromRequest, name); - filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest)); + spanAndScope = createSpan(request, skip, spanFromRequest, name, ws); + filterChain.doFilter(request, response); } catch (Throwable e) { exception = e; - errorParser().parseErrorTags(tracer().getCurrentSpan(), e); if (log.isErrorEnabled()) { log.error("Uncaught exception thrown", e); } + request.setAttribute(TRACE_EXCEPTION_REQUEST_ATTR, e); throw e; } finally { if (isAsyncStarted(request) || request.isAsyncStarted()) { if (log.isDebugEnabled()) { - log.debug("The span " + spanFromRequest + " will get detached by a HandleInterceptor"); + log.debug("The span " + spanFromRequest + " was created for async"); } // TODO: how to deal with response annotations and async? - return; + } else { + detachOrCloseSpans(request, response, spanAndScope, exception); + } + if (spanAndScope.scope != null) { + spanAndScope.scope.close(); } - detachOrCloseSpans(request, response, spanFromRequest, exception); - } - } - - private Span parentSpan(Span span) { - if (span == null) { - return null; - } - if (span.hasSavedSpan()) { - return span.getSavedSpan(); } - return span; } private void processErrorRequest(FilterChain filterChain, HttpServletRequest request, - HttpServletResponse response, Span spanFromRequest) + HttpServletResponse response, Span spanFromRequest, Tracer.SpanInScope ws) throws IOException, ServletException { if (log.isDebugEnabled()) { log.debug("The span " + spanFromRequest + " was already detached once and we're processing an error"); @@ -201,19 +192,23 @@ private void processErrorRequest(FilterChain filterChain, HttpServletRequest req filterChain.doFilter(request, response); } finally { request.setAttribute(TRACE_ERROR_HANDLED_REQUEST_ATTR, true); - addResponseTags(response, null); if (request.getAttribute(TraceRequestAttributes.ERROR_HANDLED_SPAN_REQUEST_ATTR) == null) { - tracer().close(spanFromRequest); + handler().handleSend(response, + (Throwable) request.getAttribute(TRACE_EXCEPTION_REQUEST_ATTR), spanFromRequest); + request.setAttribute(TRACE_EXCEPTION_REQUEST_ATTR, null); + } + if (ws != null) { + ws.close(); } } } - private void continueSpan(HttpServletRequest request, Span spanFromRequest) { - tracer().continueSpan(spanFromRequest); + private Tracer.SpanInScope continueSpan(HttpServletRequest request, Span spanFromRequest) { request.setAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true"); if (log.isDebugEnabled()) { log.debug("There has already been a span in the request " + spanFromRequest); } + return httpTracing().tracing().tracer().withSpanInScope(spanFromRequest); } private boolean requestHasAlreadyBeenHandled(HttpServletRequest request) { @@ -221,48 +216,49 @@ private boolean requestHasAlreadyBeenHandled(HttpServletRequest request) { } private void detachOrCloseSpans(HttpServletRequest request, - HttpServletResponse response, Span spanFromRequest, Throwable exception) { - Span span = spanFromRequest; + HttpServletResponse response, SpanAndScope spanFromRequest, Throwable exception) { + Span span = spanFromRequest.span; if (span != null) { - addResponseTags(response, exception); - addResponseTagsForSpanWithoutParent(request, response); - if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) { - recordParentSpan(span.getSavedSpan()); - } - recordParentSpan(span); + addResponseTagsForSpanWithoutParent(exception, request, response, span); // in case of a response with exception status will close the span when exception dispatch is handled // checking if tracing is in progress due to async / different order of view controller processing - if (httpStatusSuccessful(response) && tracer().isTracing()) { + if (httpStatusSuccessful(response)) { if (log.isDebugEnabled()) { log.debug("Closing the span " + span + " since the response was successful"); } - tracer().close(span); - clearTraceAttribute(request); - } else if (errorAlreadyHandled(request) && tracer().isTracing() && !shouldCloseSpan(request)) { + if (exception == null || !hasErrorController()) { + clearTraceAttribute(request); + handler().handleSend(response, exception, span); + } + } else if (errorAlreadyHandled(request) && !shouldCloseSpan(request)) { if (log.isDebugEnabled()) { log.debug( "Won't detach the span " + span + " since error has already been handled"); } - } else if ((shouldCloseSpan(request) || isRootSpan(span)) && tracer().isTracing() && stillTracingCurrentSpan(span)) { + } else if ((shouldCloseSpan(request) || isRootSpan(span)) && stillTracingCurrentSpan(span)) { if (log.isDebugEnabled()) { log.debug("Will close span " + span + " since " + (shouldCloseSpan(request) ? "some component marked it for closure" : "response was unsuccessful for the root span")); } - tracer().close(span); + handler().handleSend(response, exception, span); clearTraceAttribute(request); - } else if (tracer().isTracing()) { + } else if (span != null || requestHasAlreadyBeenHandled(request)) { if (log.isDebugEnabled()) { log.debug("Detaching the span " + span + " since the response was unsuccessful"); } - tracer().detach(span); clearTraceAttribute(request); + if (exception == null || !hasErrorController()) { + handler().handleSend(response, exception, span); + } else { + span.abandon(); + } } } } - private void addResponseTagsForSpanWithoutParent(HttpServletRequest request, - HttpServletResponse response) { - if (spanWithoutParent(request) && response.getStatus() >= 100) { - tracer().addTag(traceKeys().getHttp().getStatusCode(), + private void addResponseTagsForSpanWithoutParent(Throwable exception, + HttpServletRequest request, HttpServletResponse response, Span span) { + if (exception == null && spanWithoutParent(request) && response.getStatus() >= 100) { + span.tag(traceKeys().getHttp().getStatusCode(), String.valueOf(response.getStatus())); } } @@ -272,29 +268,12 @@ private boolean spanWithoutParent(HttpServletRequest request) { } private boolean isRootSpan(Span span) { - return span.getTraceId() == span.getSpanId(); + return span.context().traceId() == span.context().spanId(); } private boolean stillTracingCurrentSpan(Span span) { - return tracer().getCurrentSpan().equals(span); - } - - private void recordParentSpan(Span parent) { - if (parent == null) { - return; - } - if (parent.isRemote()) { - if (log.isDebugEnabled()) { - log.debug("Trying to send the parent span " + parent + " to Zipkin"); - } - parent.stop(); - // should be already done by HttpServletResponse wrappers - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); - spanReporter().report(parent); - } else { - // should be already done by HttpServletResponse wrappers - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); - } + Span currentSpan = httpTracing().tracing().tracer().currentSpan(); + return currentSpan != null && currentSpan.equals(span); } private boolean httpStatusSuccessful(HttpServletResponse response) { @@ -327,99 +306,72 @@ private boolean isSpanContinued(HttpServletRequest request) { return getSpanFromAttribute(request) != null; } - /** - * In order not to send unnecessary data we're not adding request tags to the server - * side spans. All the tags are there on the client side. - */ - private void addRequestTagsForParentSpan(HttpServletRequest request, Span spanFromRequest) { - if (spanFromRequest.getName().contains("parent")) { - addRequestTags(spanFromRequest, request); - } - } - /** * Creates a span and appends it as the current request's attribute */ - private Span createSpan(HttpServletRequest request, - boolean skip, Span spanFromRequest, String name) { + private SpanAndScope createSpan(HttpServletRequest request, + boolean skip, Span spanFromRequest, String name, Tracer.SpanInScope ws) { if (spanFromRequest != null) { if (log.isDebugEnabled()) { log.debug("Span has already been created - continuing with the previous one"); } - return spanFromRequest; + return new SpanAndScope(spanFromRequest, ws); } - Span parent = spanExtractor().joinTrace(new HttpServletRequestTextMap(request)); - if (parent != null) { - if (log.isDebugEnabled()) { - log.debug("Found a parent span " + parent + " in the request"); - } - if (!this.supportsJoin) { // create a child span for this side of the RPC - spanFromRequest = tracer().createSpan(parent.getName(), parent); + try { + // TODO: Try to use Brave's mechanism for sampling + if (skip) { + spanFromRequest = unsampledSpan(name); } else { - spanFromRequest = parent; + spanFromRequest = handler().handleReceive(httpTracing().tracing() + .propagation().extractor(HttpServletRequest::getHeader), request); } - addRequestTagsForParentSpan(request, spanFromRequest); - tracer().continueSpan(spanFromRequest); - if (parent.isRemote()) { // then we are in a server span - parent.logEvent(Span.SERVER_RECV); + if (log.isDebugEnabled()) { + log.debug("Found a parent span " + spanFromRequest.context() + " in the request"); } request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); if (log.isDebugEnabled()) { - log.debug("Parent span is " + parent + ""); + log.debug("Parent span is " + spanFromRequest + ""); } - } else { + } catch (Exception e) { + log.error("Exception occurred while trying to extract tracing context from request. " + + "Falling back to manual span creation", e); if (skip) { - spanFromRequest = tracer().createSpan(name, NeverSampler.INSTANCE); + spanFromRequest = unsampledSpan(name); } else { - String header = request.getHeader(Span.SPAN_FLAGS); - if (Span.SPAN_SAMPLED.equals(header)) { - spanFromRequest = tracer().createSpan(name, new AlwaysSampler()); - } else { - spanFromRequest = tracer().createSpan(name); - } - addRequestTags(spanFromRequest, request); + spanFromRequest = httpTracing().tracing().tracer().nextSpan() + .kind(Span.Kind.SERVER) + .name(name).start(); request.setAttribute(TRACE_SPAN_WITHOUT_PARENT, spanFromRequest); } - spanFromRequest.logEvent(Span.SERVER_RECV); request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest); if (log.isDebugEnabled()) { log.debug("No parent span present - creating a new span"); } } - return spanFromRequest; + return new SpanAndScope(spanFromRequest, httpTracing().tracing() + .tracer().withSpanInScope(spanFromRequest)); } - /** Override to add annotations not defined in {@link TraceKeys}. */ - protected void addRequestTags(Span span, HttpServletRequest request) { - String uri = this.urlPathHelper.getPathWithinApplication(request); - keysInjector().addRequestTags(span, getFullUrl(request), - request.getServerName(), uri, request.getMethod()); - for (String name : traceKeys().getHttp().getHeaders()) { - Enumeration values = request.getHeaders(name); - if (values.hasMoreElements()) { - String key = traceKeys().getHttp().getPrefix() + name.toLowerCase(); - ArrayList list = Collections.list(values); - String value = list.size() == 1 ? list.get(0) - : StringUtils.collectionToDelimitedString(list, ",", "'", "'"); - keysInjector().tagSpan(span, key, value); - } - } + private Span unsampledSpan(String name) { + return httpTracing().tracing().tracer() + .nextSpan(TraceContextOrSamplingFlags.create(SamplingFlags.NOT_SAMPLED)) + .kind(Span.Kind.SERVER) + .name(name).start(); } - /** Override to add annotations not defined in {@link TraceKeys}. */ - protected void addResponseTags(HttpServletResponse response, Throwable e) { - int httpStatus = response.getStatus(); - if (httpStatus == HttpServletResponse.SC_OK && e != null) { - // Filter chain threw exception but the response status may not have been set - // yet, so we have to guess. - tracer().addTag(traceKeys().getHttp().getStatusCode(), - String.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + class SpanAndScope { + + final Span span; + final Tracer.SpanInScope scope; + SpanAndScope(Span span, Tracer.SpanInScope scope) { + this.span = span; + this.scope = scope; } - // only tag valid http statuses - else if (httpStatus >= 100 && (httpStatus < 200) || (httpStatus > 399)) { - tracer().addTag(traceKeys().getHttp().getStatusCode(), - String.valueOf(response.getStatus())); + + SpanAndScope() { + this.span = null; + this.scope = null; } } @@ -427,21 +379,13 @@ protected boolean isAsyncStarted(HttpServletRequest request) { return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted(); } - private String getFullUrl(HttpServletRequest request) { - StringBuffer requestURI = request.getRequestURL(); - String queryString = request.getQueryString(); - if (queryString == null) { - return requestURI.toString(); - } else { - return requestURI.append('?').append(queryString).toString(); - } - } - - Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + @SuppressWarnings("unchecked") + HttpServerHandler handler() { + if (this.handler == null) { + this.handler = HttpServerHandler.create(this.beanFactory.getBean(HttpTracing.class), + new HttpServletAdapter()); } - return this.tracer; + return this.handler; } TraceKeys traceKeys() { @@ -451,32 +395,23 @@ TraceKeys traceKeys() { return this.traceKeys; } - SpanReporter spanReporter() { - if (this.spanReporter == null) { - this.spanReporter = this.beanFactory.getBean(SpanReporter.class); - } - return this.spanReporter; - } - - HttpSpanExtractor spanExtractor() { - if (this.spanExtractor == null) { - this.spanExtractor = this.beanFactory.getBean(HttpSpanExtractor.class); - } - return this.spanExtractor; - } - - HttpTraceKeysInjector keysInjector() { - if (this.httpTraceKeysInjector == null) { - this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + HttpTracing httpTracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(HttpTracing.class); } - return this.httpTraceKeysInjector; + return this.tracing; } - ErrorParser errorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); + // null check is only for tests + private boolean hasErrorController() { + if (this.hasErrorController == null) { + try { + this.hasErrorController = this.beanFactory.getBean(ErrorController.class) != null; + } catch (NoSuchBeanDefinitionException e) { + this.hasErrorController = false; + } } - return this.errorParser; + return this.hasErrorController; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java index 584df00e4a..09ea317ad2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,22 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.lang.invoke.MethodHandles; -import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.concurrent.atomic.AtomicReference; +import brave.Span; +import brave.Tracer; +import brave.http.HttpServerHandler; +import brave.http.HttpTracing; +import brave.servlet.HttpServletAdapter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.util.SpanNameUtil; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -49,14 +51,15 @@ */ public class TraceHandlerInterceptor extends HandlerInterceptorAdapter { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(TraceHandlerInterceptor.class); private final BeanFactory beanFactory; - private Tracer tracer; + private HttpTracing tracing; private TraceKeys traceKeys; private ErrorParser errorParser; private AtomicReference errorController; + private HttpServerHandler handler; public TraceHandlerInterceptor(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -67,28 +70,31 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons Object handler) throws Exception { String spanName = spanName(handler); boolean continueSpan = getRootSpanFromAttribute(request) != null; - Span span = continueSpan ? getRootSpanFromAttribute(request) : getTracer().createSpan(spanName); - if (log.isDebugEnabled()) { - log.debug("Handling span " + span); - } - addClassMethodTag(handler, span); - addClassNameTag(handler, span); - setSpanInAttribute(request, span); - if (!continueSpan) { - setNewSpanCreatedAttribute(request, span); + Span span = continueSpan ? getRootSpanFromAttribute(request) : + httpTracing().tracing().tracer().nextSpan().name(spanName).start(); + try (Tracer.SpanInScope ws = httpTracing().tracing().tracer().withSpanInScope(span)) { + if (log.isDebugEnabled()) { + log.debug("Handling span " + span); + } + addClassMethodTag(handler, span); + addClassNameTag(handler, span); + setSpanInAttribute(request, span); + if (!continueSpan) { + setNewSpanCreatedAttribute(request, span); + } } return true; } private boolean isErrorControllerRelated(HttpServletRequest request) { - return getErrorController() != null && getErrorController().getErrorPath() + return errorController() != null && errorController().getErrorPath() .equals(request.getRequestURI()); } private void addClassMethodTag(Object handler, Span span) { if (handler instanceof HandlerMethod) { String methodName = ((HandlerMethod) handler).getMethod().getName(); - getTracer().addTag(getTraceKeys().getMvc().getControllerMethod(), methodName); + span.tag(traceKeys().getMvc().getControllerMethod(), methodName); if (log.isDebugEnabled()) { log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); } @@ -105,7 +111,7 @@ private void addClassNameTag(Object handler, Span span) { if (log.isDebugEnabled()) { log.debug("Adding a class tag with value [" + className + "] to a span " + span); } - getTracer().addTag(getTraceKeys().getMvc().getControllerClass(), className); + span.tag(traceKeys().getMvc().getControllerClass(), className); } private String spanName(Object handler) { @@ -119,12 +125,15 @@ private String spanName(Object handler) { public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Span spanFromRequest = getNewSpanFromAttribute(request); - Span rootSpanFromRequest = getRootSpanFromAttribute(request); - if (log.isDebugEnabled()) { - log.debug("Closing the span " + spanFromRequest + " and detaching its parent " + rootSpanFromRequest + " since the request is asynchronous"); + if (spanFromRequest != null) { + try (Tracer.SpanInScope ws = httpTracing().tracing().tracer().withSpanInScope(spanFromRequest)) { + if (log.isDebugEnabled()) { + log.debug("Closing the span " + spanFromRequest); + } + } finally { + spanFromRequest.finish(); + } } - getTracer().close(spanFromRequest); - getTracer().detach(rootSpanFromRequest); } @Override @@ -138,15 +147,14 @@ public void afterCompletion(HttpServletRequest request, HttpServletResponse resp } Span span = getRootSpanFromAttribute(request); if (ex != null) { - getErrorParser().parseErrorTags(span, ex); + errorParser().parseErrorTags(span, ex); } if (getNewSpanFromAttribute(request) != null) { if (log.isDebugEnabled()) { log.debug("Closing span " + span); } Span newSpan = getNewSpanFromAttribute(request); - getTracer().continueSpan(newSpan); - getTracer().close(newSpan); + handler().handleSend(response, ex, newSpan); clearNewSpanCreatedAttribute(request); } } @@ -171,28 +179,37 @@ private void clearNewSpanCreatedAttribute(HttpServletRequest request) { request.removeAttribute(TraceRequestAttributes.NEW_SPAN_REQUEST_ATTR); } - private Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + private HttpTracing httpTracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(HttpTracing.class); } - return this.tracer; + return this.tracing; } - private TraceKeys getTraceKeys() { + private TraceKeys traceKeys() { if (this.traceKeys == null) { this.traceKeys = this.beanFactory.getBean(TraceKeys.class); } return this.traceKeys; } - private ErrorParser getErrorParser() { + @SuppressWarnings("unchecked") + HttpServerHandler handler() { + if (this.handler == null) { + this.handler = HttpServerHandler.create(this.beanFactory.getBean(HttpTracing.class), + new HttpServletAdapter()); + } + return this.handler; + } + + private ErrorParser errorParser() { if (this.errorParser == null) { this.errorParser = this.beanFactory.getBean(ErrorParser.class); } return this.errorParser; } - ErrorController getErrorController() { + ErrorController errorController() { if (this.errorController == null) { try { ErrorController errorController = this.beanFactory.getBean(ErrorController.class); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java index 7ecd2bd281..4f0d9ce802 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpAutoConfiguration.java @@ -1,28 +1,13 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.springframework.cloud.sleuth.instrument.web; -import java.util.regex.Pattern; - +import brave.Tracing; +import brave.http.HttpTracing; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -32,29 +17,28 @@ * related to HTTP based communication. * * @author Marcin Grzejszczak - * @since 1.0.12 + * @since 2.0.0 */ @Configuration -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) +@ConditionalOnProperty(name = "spring.sleuth.http.enabled", havingValue = "true", matchIfMissing = true) @AutoConfigureAfter(TraceAutoConfiguration.class) -@EnableConfigurationProperties({ TraceKeys.class, SleuthWebProperties.class }) public class TraceHttpAutoConfiguration { @Bean @ConditionalOnMissingBean - public HttpTraceKeysInjector httpTraceKeysInjector(Tracer tracer, TraceKeys traceKeys) { - return new HttpTraceKeysInjector(tracer, traceKeys); - } - - @Bean - @ConditionalOnMissingBean - public HttpSpanExtractor httpSpanExtractor(SleuthWebProperties sleuthWebProperties) { - return new ZipkinHttpSpanExtractor(Pattern.compile(sleuthWebProperties.getSkipPattern())); + @ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled", havingValue = "false", matchIfMissing = true) + HttpTracing sleuthHttpTracing(Tracing tracing) { + return HttpTracing.create(tracing); } @Bean @ConditionalOnMissingBean - public HttpSpanInjector httpSpanInjector() { - return new ZipkinHttpSpanInjector(); + @ConditionalOnProperty(name = "spring.sleuth.http.legacy.enabled", havingValue = "true") + HttpTracing legacySleuthHttpTracing(Tracing tracing, TraceKeys traceKeys, ErrorParser errorParser) { + return HttpTracing.newBuilder(tracing) + .clientParser(new SleuthHttpClientParser(traceKeys)) + .serverParser(new SleuthHttpServerParser(traceKeys, errorParser)) + .build(); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java deleted file mode 100644 index d423bfbcd0..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceHttpServletResponse.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.invoke.MethodHandles; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; - -/** - * We want to set SS as fast as possible after the response was sent back. The response - * can be sent back by calling either an {@link ServletOutputStream} or {@link PrintWriter}. - */ -class TraceHttpServletResponse extends HttpServletResponseWrapper { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Span span; - - TraceHttpServletResponse(HttpServletResponse response, Span span) { - super(response); - this.span = span; - } - - @Override public void flushBuffer() throws IOException { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the response is flushed"); - } - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - super.flushBuffer(); - } - - @Override public ServletOutputStream getOutputStream() throws IOException { - return new TraceServletOutputStream(super.getOutputStream(), this.span); - } - - @Override public PrintWriter getWriter() throws IOException { - return new TracePrintWriter(super.getWriter(), this.span); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java deleted file mode 100644 index 426ac04c7c..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TracePrintWriter.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.io.PrintWriter; -import java.lang.invoke.MethodHandles; -import java.util.Locale; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; - -/** - * @author Marcin Grzejszczak - */ -class TracePrintWriter extends PrintWriter { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final PrintWriter delegate; - private final Span span; - - TracePrintWriter(PrintWriter delegate, Span span) { - super(delegate); - this.delegate = delegate; - this.span = span; - } - - @Override public void flush() { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the response is flushed"); - } - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - this.delegate.flush(); - } - - @Override public void close() { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the stream is closed"); - } - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - this.delegate.close(); - } - - @Override public boolean checkError() { - return this.delegate.checkError(); - } - - @Override public void write(int c) { - this.delegate.write(c); - } - - @Override public void write(char[] buf, int off, int len) { - this.delegate.write(buf, off, len); - } - - @Override public void write(char[] buf) { - this.delegate.write(buf); - } - - @Override public void write(String s, int off, int len) { - this.delegate.write(s, off, len); - } - - @Override public void write(String s) { - this.delegate.write(s); - } - - @Override public void print(boolean b) { - this.delegate.print(b); - } - - @Override public void print(char c) { - this.delegate.print(c); - } - - @Override public void print(int i) { - this.delegate.print(i); - } - - @Override public void print(long l) { - this.delegate.print(l); - } - - @Override public void print(float f) { - this.delegate.print(f); - } - - @Override public void print(double d) { - this.delegate.print(d); - } - - @Override public void print(char[] s) { - this.delegate.print(s); - } - - @Override public void print(String s) { - this.delegate.print(s); - } - - @Override public void print(Object obj) { - this.delegate.print(obj); - } - - @Override public void println() { - this.delegate.println(); - } - - @Override public void println(boolean x) { - this.delegate.println(x); - } - - @Override public void println(char x) { - this.delegate.println(x); - } - - @Override public void println(int x) { - this.delegate.println(x); - } - - @Override public void println(long x) { - this.delegate.println(x); - } - - @Override public void println(float x) { - this.delegate.println(x); - } - - @Override public void println(double x) { - this.delegate.println(x); - } - - @Override public void println(char[] x) { - this.delegate.println(x); - } - - @Override public void println(String x) { - this.delegate.println(x); - } - - @Override public void println(Object x) { - this.delegate.println(x); - } - - @Override public PrintWriter printf(String format, Object... args) { - return this.delegate.printf(format, args); - } - - @Override public PrintWriter printf(Locale l, String format, Object... args) { - return this.delegate.printf(l, format, args); - } - - @Override public PrintWriter format(String format, Object... args) { - return this.delegate.format(format, args); - } - - @Override public PrintWriter format(Locale l, String format, Object... args) { - return this.delegate.format(l, format, args); - } - - @Override public PrintWriter append(CharSequence csq) { - return this.delegate.append(csq); - } - - @Override public PrintWriter append(CharSequence csq, int start, int end) { - return this.delegate.append(csq, start, end); - } - - @Override public PrintWriter append(char c) { - return this.delegate.append(c); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java index eebf18acbf..d5a336db8f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceRequestAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java deleted file mode 100644 index 4acdaf9f94..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceServletOutputStream.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import javax.servlet.ServletOutputStream; -import javax.servlet.WriteListener; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; - -/** - * @author Marcin Grzejszczak - */ -class TraceServletOutputStream extends ServletOutputStream { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final ServletOutputStream delegate; - private final Span span; - - TraceServletOutputStream(ServletOutputStream delegate, Span span) { - this.delegate = delegate; - this.span = span; - } - - @Override public boolean isReady() { - return this.delegate.isReady(); - } - - @Override public void setWriteListener(WriteListener listener) { - this.delegate.setWriteListener(listener); - } - - @Override public void write(int b) throws IOException { - this.delegate.write(b); - } - - @Override public void print(String s) throws IOException { - this.delegate.print(s); - } - - @Override public void print(boolean b) throws IOException { - this.delegate.print(b); - } - - @Override public void print(char c) throws IOException { - this.delegate.print(c); - } - - @Override public void print(int i) throws IOException { - this.delegate.print(i); - } - - @Override public void print(long l) throws IOException { - this.delegate.print(l); - } - - @Override public void print(float f) throws IOException { - this.delegate.print(f); - } - - @Override public void print(double d) throws IOException { - this.delegate.print(d); - } - - @Override public void println() throws IOException { - this.delegate.println(); - } - - @Override public void println(String s) throws IOException { - this.delegate.println(s); - } - - @Override public void println(boolean b) throws IOException { - this.delegate.println(b); - } - - @Override public void println(char c) throws IOException { - this.delegate.println(c); - } - - @Override public void println(int i) throws IOException { - this.delegate.println(i); - } - - @Override public void println(long l) throws IOException { - this.delegate.println(l); - } - - @Override public void println(float f) throws IOException { - this.delegate.println(f); - } - - @Override public void println(double d) throws IOException { - this.delegate.println(d); - } - - @Override public void write(byte[] b) throws IOException { - this.delegate.write(b); - } - - @Override public void write(byte[] b, int off, int len) throws IOException { - this.delegate.write(b, off, len); - } - - @Override public void flush() throws IOException { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the stream is flushed"); - } - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - this.delegate.flush(); - } - - @Override public void close() throws IOException { - if (log.isTraceEnabled()) { - log.trace("Will annotate SS once the stream is closed"); - } - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(this.span); - this.delegate.close(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java index 993d35874f..804eb20f13 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceSpringDataBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package org.springframework.cloud.sleuth.instrument.web; import javax.servlet.http.HttpServletRequest; -import java.lang.invoke.MethodHandles; import java.util.Collections; import org.apache.commons.logging.Log; @@ -37,7 +36,7 @@ */ class TraceSpringDataBeanPostProcessor implements BeanPostProcessor { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(TraceSpringDataBeanPostProcessor.class); private final BeanFactory beanFactory; diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java index cef5f22549..32f8892933 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,23 @@ package org.springframework.cloud.sleuth.instrument.web; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Field; +import java.util.concurrent.Callable; + +import brave.Tracer; import org.apache.commons.logging.Log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceCallable; +import org.springframework.cloud.sleuth.instrument.async.TraceCallable; import org.springframework.web.context.request.async.WebAsyncTask; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.lang.reflect.Field; -import java.util.concurrent.Callable; +import brave.Span; /** * Aspect that adds tracing to @@ -63,9 +63,6 @@ * * @see org.springframework.stereotype.Controller * @see org.springframework.web.client.RestOperations - * @see org.springframework.cloud.sleuth.TraceCallable - * @see org.springframework.cloud.sleuth.Tracer - * @see org.springframework.cloud.sleuth.instrument.web.TraceFilter */ @SuppressWarnings("ArgNamesWarningsInspection") @Aspect @@ -76,14 +73,14 @@ public class TraceWebAspect { private final Tracer tracer; private final SpanNamer spanNamer; - private final TraceKeys traceKeys; + //private final TraceKeys traceKeys; private final ErrorParser errorParser; - public TraceWebAspect(Tracer tracer, SpanNamer spanNamer, TraceKeys traceKeys, + public TraceWebAspect(Tracer tracer, SpanNamer spanNamer, //TraceKeys traceKeys, ErrorParser errorParser) { this.tracer = tracer; this.spanNamer = spanNamer; - this.traceKeys = traceKeys; + //this.traceKeys = traceKeys; this.errorParser = errorParser; } @@ -112,11 +109,11 @@ private void anyControllerOrRestControllerWithPublicWebAsyncTaskMethod() { } // @SuppressWarnings("unchecked") public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { Callable callable = (Callable) pjp.proceed(); - if (this.tracer.isTracing()) { + if (this.tracer.currentSpan() != null) { if (log.isDebugEnabled()) { - log.debug("Wrapping callable with span [" + this.tracer.getCurrentSpan() + "]"); + log.debug("Wrapping callable with span [" + this.tracer.currentSpan() + "]"); } - return new SpanContinuingTraceCallable<>(this.tracer, this.traceKeys, this.spanNamer, callable); + return new TraceCallable<>(this.tracer, this.spanNamer, this.errorParser, callable); } else { return callable; @@ -126,16 +123,16 @@ public Object wrapWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { @Around("anyControllerOrRestControllerWithPublicWebAsyncTaskMethod()") public Object wrapWebAsyncTaskWithCorrelationId(ProceedingJoinPoint pjp) throws Throwable { final WebAsyncTask webAsyncTask = (WebAsyncTask) pjp.proceed(); - if (this.tracer.isTracing()) { + if (this.tracer.currentSpan() != null) { try { if (log.isDebugEnabled()) { - log.debug("Wrapping callable with span [" + this.tracer.getCurrentSpan() + log.debug("Wrapping callable with span [" + this.tracer.currentSpan() + "]"); } Field callableField = WebAsyncTask.class.getDeclaredField("callable"); callableField.setAccessible(true); - callableField.set(webAsyncTask, new SpanContinuingTraceCallable<>(this.tracer, - this.traceKeys, this.spanNamer, webAsyncTask.getCallable())); + callableField.set(webAsyncTask, new TraceCallable<>(this.tracer, this.spanNamer, + this.errorParser, webAsyncTask.getCallable())); } catch (NoSuchFieldException ex) { log.warn("Cannot wrap webAsyncTask's callable with TraceCallable", ex); } @@ -146,17 +143,9 @@ public Object wrapWebAsyncTaskWithCorrelationId(ProceedingJoinPoint pjp) throws @Around("anyHandlerExceptionResolver(request, response, handler, ex)") public Object markRequestForSpanClosing(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Throwable { - Span currentSpan = this.tracer.getCurrentSpan(); - try { - if (currentSpan != null && !currentSpan.tags().containsKey(Span.SPAN_ERROR_TAG_NAME)) { - this.errorParser.parseErrorTags(currentSpan, ex); - } + Span currentSpan = this.tracer.currentSpan(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(currentSpan)){ return pjp.proceed(); - } finally { - if (log.isDebugEnabled()) { - log.debug("Marking span " + currentSpan + " for closure by Trace Filter"); - } - request.setAttribute(TraceFilter.TRACE_CLOSE_SPAN_REQUEST_ATTR, true); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index c17177aa31..45c3426ef3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import java.util.regex.Pattern; +import brave.Tracing; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -26,7 +27,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; @@ -42,13 +42,15 @@ @Configuration @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.ANY) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @AutoConfigureAfter(TraceHttpAutoConfiguration.class) +@EnableConfigurationProperties(SleuthWebProperties.class) public class TraceWebAutoConfiguration { @Configuration @ConditionalOnClass(ManagementServerProperties.class) - @ConditionalOnMissingBean(SkipPatternProvider.class) + @ConditionalOnMissingBean( + SkipPatternProvider.class) @EnableConfigurationProperties(SleuthWebProperties.class) protected static class SkipPatternProviderConfig { @@ -68,7 +70,7 @@ public Pattern skipPattern() { } /** - * Sets or appends {@link ManagementServerProperties.Servlet#getContextPath()} to the skip + * Sets or appends {@link ManagementServerProperties#getServlet()#getContextPath()} to the skip * pattern. If neither is available then sets the default one */ static Pattern getPatternForManagementServerProperties( @@ -95,7 +97,8 @@ public SkipPatternProvider defaultSkipPatternBeanIfManagementServerPropsArePrese @Bean @ConditionalOnMissingClass("org.springframework.boot.actuate.autoconfigure.ManagementServerProperties") - @ConditionalOnMissingBean(SkipPatternProvider.class) + @ConditionalOnMissingBean( + SkipPatternProvider.class) public SkipPatternProvider defaultSkipPatternBean(SleuthWebProperties sleuthWebProperties) { return defaultSkipPatternProvider(sleuthWebProperties.getSkipPattern()); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java index 23a515a425..57f0320d6f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFilter.java @@ -1,25 +1,24 @@ package org.springframework.cloud.sleuth.instrument.web; -import reactor.core.publisher.Mono; - -import java.util.List; import java.util.regex.Pattern; +import brave.Span; +import brave.Tracer; +import brave.http.HttpServerHandler; +import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContext; +import brave.propagation.TraceContextOrSamplingFlags; +import reactor.core.publisher.Mono; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.sampler.NeverSampler; import org.springframework.core.Ordered; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.server.ServerWebExchange; @@ -37,26 +36,41 @@ public class TraceWebFilter implements WebFilter, Ordered { private static final Log log = LogFactory.getLog(TraceWebFilter.class); + private static final String HTTP_COMPONENT = "http"; protected static final String TRACE_REQUEST_ATTR = TraceWebFilter.class.getName() + ".TRACE"; private static final String TRACE_SPAN_WITHOUT_PARENT = TraceWebFilter.class.getName() + ".SPAN_WITH_NO_PARENT"; - private static final String HTTP_COMPONENT = "http";/** + /** * If you register your filter before the {@link TraceWebFilter} then you will not * have the tracing context passed for you out of the box. That means that e.g. your * logs will not get correlated. */ public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 5; - private Tracer tracer; - private TraceKeys traceKeys; - private final Pattern skipPattern; - private SpanReporter spanReporter; - private HttpSpanExtractor spanExtractor; - private HttpTraceKeysInjector httpTraceKeysInjector; - private ErrorParser errorParser; + static final Propagation.Getter GETTER = + new Propagation.Getter() { + + @Override public String get(HttpHeaders carrier, String key) { + return carrier.getFirst(key); + } + + @Override public String toString() { + return "HttpHeaders::getFirst"; + } + }; + + public static WebFilter create(BeanFactory beanFactory, SkipPatternProvider skipPatternProvider) { + return new TraceWebFilter(beanFactory, skipPatternProvider.skipPattern()); + } + + TraceKeys traceKeys; + Tracer tracer; + HttpServerHandler handler; + TraceContext.Extractor extractor; private final BeanFactory beanFactory; + private final Pattern skipPattern; TraceWebFilter(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -68,40 +82,64 @@ public class TraceWebFilter implements WebFilter, Ordered { this.skipPattern = skipPattern; } - @Override public Mono filter(final ServerWebExchange exchange, WebFilterChain chain) { + @SuppressWarnings("unchecked") + HttpServerHandler handler() { + if (this.handler == null) { + this.handler = HttpServerHandler + .create(this.beanFactory.getBean(HttpTracing.class), + new TraceWebFilter.HttpAdapter()); + } + return this.handler; + } + + Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(HttpTracing.class).tracing().tracer(); + } + return this.tracer; + } + + TraceKeys traceKeys() { + if (this.traceKeys == null) { + this.traceKeys = this.beanFactory.getBean(TraceKeys.class); + } + return this.traceKeys; + } + + TraceContext.Extractor extractor() { + if (this.extractor == null) { + this.extractor = this.beanFactory.getBean(HttpTracing.class) + .tracing().propagation().extractor(GETTER); + } + return this.extractor; + } + + @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String uri = request.getPath().pathWithinApplication().value(); boolean skip = this.skipPattern.matcher(uri).matches() - || Span.SPAN_NOT_SAMPLED.equals(sampledHeader(request)); + || "0".equals(request.getHeaders().getFirst("X-B3-Sampled")); if (log.isDebugEnabled()) { log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]"); } Span spanFromAttribute = getSpanFromAttribute(exchange); - if (spanFromAttribute != null) { - continueSpan(exchange, spanFromAttribute); - } String name = HTTP_COMPONENT + ":" + uri; final String CONTEXT_ERROR = "sleuth.webfilter.context.error"; return chain .filter(exchange) .compose(f -> f.then(Mono.subscriberContext()) - .onErrorResume(t -> Mono.subscriberContext().map(c -> c.put(CONTEXT_ERROR, t))) + .onErrorResume(t -> Mono.subscriberContext() + .map(c -> c.put(CONTEXT_ERROR, t))) .flatMap(c -> { //reactivate span from context - Span span = c.getOrDefault(Span.class, null); - if (span != null) { - tracer().continueSpan(span); - } + Span span = c.getOrDefault(Span.class, tracer().nextSpan().start()); Mono continuation; - + Throwable t = null; if (c.hasKey(CONTEXT_ERROR)) { - Throwable t = c.get(CONTEXT_ERROR); - errorParser().parseErrorTags(tracer().getCurrentSpan(), t); - addResponseTags(response, t); + t = c.get(CONTEXT_ERROR); continuation = Mono.error(t); } else { - addResponseTags(response, null); continuation = Mono.empty(); } Object attribute = exchange @@ -111,36 +149,72 @@ public class TraceWebFilter implements WebFilter, Ordered { addClassMethodTag(handlerMethod, span); addClassNameTag(handlerMethod, span); } - addResponseTagsForSpanWithoutParent(exchange, response); - detachOrCloseSpans(span); - + addResponseTagsForSpanWithoutParent(exchange, response, span); + handler().handleSend(response, t, span); return continuation; }) .subscriberContext(c -> { Span span; if (c.hasKey(Span.class)) { Span parent = c.get(Span.class); - span = createSpan(request, exchange, skip, parent, name); + span = tracer() + .nextSpan(TraceContextOrSamplingFlags.create(parent.context())) + .start(); } else { - span = createSpan(request, exchange, skip, spanFromAttribute, name); + try { + if (skip) { + span = unsampledSpan(name); + } else { + if (spanFromAttribute != null) { + span = spanFromAttribute; + } else { + span = handler().handleReceive(extractor(), + request.getHeaders(), request); + } + } + exchange.getAttributes().put(TRACE_REQUEST_ATTR, span); + } catch (Exception e) { + log.error("Exception occurred while trying to parse the request. " + + "Will fallback to manual span setting", e); + if (skip) { + span = unsampledSpan(name); + } else { + span = tracer().nextSpan().name(name).start(); + exchange.getAttributes().put(TRACE_SPAN_WITHOUT_PARENT, span); + } + } } - return c.put(Span.class, span); })); } private void addResponseTagsForSpanWithoutParent(ServerWebExchange exchange, - ServerHttpResponse response) { - if (spanWithoutParent(exchange) && response.getStatusCode() != null) { - tracer().addTag(traceKeys().getHttp().getStatusCode(), + ServerHttpResponse response, Span span) { + if (spanWithoutParent(exchange) && response.getStatusCode() != null + && span != null) { + span.tag(traceKeys().getHttp().getStatusCode(), String.valueOf(response.getStatusCode().value())); } } + private Span unsampledSpan(String name) { + return tracer().nextSpan(TraceContextOrSamplingFlags.create( + SamplingFlags.NOT_SAMPLED)).name(name) + .kind(Span.Kind.SERVER).start(); + } + + private Span getSpanFromAttribute(ServerWebExchange exchange) { + return exchange.getAttribute(TRACE_REQUEST_ATTR); + } + + private boolean spanWithoutParent(ServerWebExchange exchange) { + return exchange.getAttribute(TRACE_SPAN_WITHOUT_PARENT) != null; + } + private void addClassMethodTag(Object handler, Span span) { if (handler instanceof HandlerMethod) { String methodName = ((HandlerMethod) handler).getMethod().getName(); - tracer().addTag(traceKeys().getMvc().getControllerMethod(), methodName); + span.tag(traceKeys().getMvc().getControllerMethod(), methodName); if (log.isDebugEnabled()) { log.debug("Adding a method tag with value [" + methodName + "] to a span " + span); } @@ -157,197 +231,33 @@ private void addClassNameTag(Object handler, Span span) { if (log.isDebugEnabled()) { log.debug("Adding a class tag with value [" + className + "] to a span " + span); } - tracer().addTag(traceKeys().getMvc().getControllerClass(), className); - } - - private String sampledHeader(ServerHttpRequest request) { - return getHeader(request, Span.SAMPLED_NAME); - } - - private void continueSpan(ServerWebExchange exchange, Span spanFromRequest) { - tracer().continueSpan(spanFromRequest); - exchange.getAttributes().put(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true"); - if (log.isDebugEnabled()) { - log.debug("There has already been a span in the request " + spanFromRequest); - } + span.tag(traceKeys().getMvc().getControllerClass(), className); } - /** - * Creates a span and appends it as the current request's attribute - */ - private Span createSpan(ServerHttpRequest request, ServerWebExchange exchange, - boolean skip, Span spanFromAttribute, String name) { - Span spanFromRequest = null; - if (spanFromAttribute != null) { - if (log.isDebugEnabled()) { - log.debug("Span has already been created - continuing with the previous one"); - } - return spanFromAttribute; - } - Span parent = spanExtractor().joinTrace(new ServerHttpRequestTextMap(request)); - if (parent != null) { - if (log.isDebugEnabled()) { - log.debug("Found a parent span " + parent + " in the request"); - } - addRequestTagsForParentSpan(request, parent); - spanFromRequest = parent; - tracer().continueSpan(spanFromRequest); - if (parent.isRemote()) { - parent.logEvent(Span.SERVER_RECV); - } - exchange.getAttributes().put(TRACE_REQUEST_ATTR, spanFromRequest); - if (log.isDebugEnabled()) { - log.debug("Parent span is " + parent + ""); - } - } else { - if (skip) { - spanFromRequest = tracer().createSpan(name, NeverSampler.INSTANCE); - } - else { - String header = getHeader(request, Span.SPAN_FLAGS); - if (Span.SPAN_SAMPLED.equals(header)) { - spanFromRequest = tracer().createSpan(name, new AlwaysSampler()); - } else { - spanFromRequest = tracer().createSpan(name); - } - addRequestTags(spanFromRequest, request); - } - spanFromRequest.logEvent(Span.SERVER_RECV); - exchange.getAttributes().put(TRACE_REQUEST_ATTR, spanFromRequest); - exchange.getAttributes().put(TRACE_SPAN_WITHOUT_PARENT, spanFromRequest); - if (log.isDebugEnabled()) { - log.debug("No parent span present - creating a new span"); - } - } - return spanFromRequest; - } - - private String getHeader(ServerHttpRequest request, String headerName) { - List list = request.getHeaders().get(headerName); - return list == null ? "" : list.isEmpty() ? "" : list.get(0); - } - - /** Override to add annotations not defined in {@link TraceKeys}. */ - protected void addRequestTags(Span span, ServerHttpRequest request) { - keysInjector().addRequestTags(span, request.getURI(), request.getMethod().toString()); - for (String name : traceKeys().getHttp().getHeaders()) { - List values = request.getHeaders().get(name); - if (values != null && !values.isEmpty()) { - String key = traceKeys().getHttp().getPrefix() + name.toLowerCase(); - String value = values.size() == 1 ? values.get(0) - : StringUtils.collectionToDelimitedString(values, ",", "'", "'"); - keysInjector().tagSpan(span, key, value); - } - } - } - - /** Override to add annotations not defined in {@link TraceKeys}. */ - protected void addResponseTags(ServerHttpResponse response, Throwable e) { - HttpStatus httpStatus = response.getStatusCode(); - if (httpStatus != null && httpStatus == HttpStatus.OK && e != null) { - // Filter chain threw exception but the response status may not have been set - // yet, so we have to guess. - tracer().addTag(traceKeys().getHttp().getStatusCode(), - String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value())); - } - // only tag valid http statuses - else if (httpStatus != null && - (httpStatus.value() >= 100 && (httpStatus.value() < 200) || (httpStatus.value() > 399))) { - tracer().addTag(traceKeys().getHttp().getStatusCode(), - String.valueOf(response.getStatusCode().value())); - } - } - - /** - * In order not to send unnecessary data we're not adding request tags to the server - * side spans. All the tags are there on the client side. - */ - private void addRequestTagsForParentSpan(ServerHttpRequest request, Span spanFromRequest) { - if (spanFromRequest.getName().contains("parent")) { - addRequestTags(spanFromRequest, request); - } - } - - private Span getSpanFromAttribute(ServerWebExchange exchange) { - return exchange.getAttribute(TRACE_REQUEST_ATTR); - } - - private boolean spanWithoutParent(ServerWebExchange exchange) { - return exchange.getAttribute(TRACE_SPAN_WITHOUT_PARENT) != null; - } - - private void detachOrCloseSpans(Span spanFromRequest) { - Span span = spanFromRequest; - if (span != null) { - if (span.hasSavedSpan()) { - recordParentSpan(span.getSavedSpan()); - } - recordParentSpan(span); - tracer().close(span); - } - } - - private void recordParentSpan(Span parent) { - if (parent == null) { - return; - } - if (parent.isRemote()) { - if (log.isDebugEnabled()) { - log.debug("Trying to send the parent span " + parent + " to Zipkin"); - } - parent.stop(); - // should be already done by HttpServletResponse wrappers - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); - spanReporter().report(parent); - } else { - // should be already done by HttpServletResponse wrappers - SsLogSetter.annotateWithServerSendIfLogIsNotAlreadyPresent(parent); - } - } - - Tracer tracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; + @Override public int getOrder() { + return ORDER; } - TraceKeys traceKeys() { - if (this.traceKeys == null) { - this.traceKeys = this.beanFactory.getBean(TraceKeys.class); - } - return this.traceKeys; - } + static final class HttpAdapter + extends brave.http.HttpServerAdapter { - SpanReporter spanReporter() { - if (this.spanReporter == null) { - this.spanReporter = this.beanFactory.getBean(SpanReporter.class); + @Override public String method(ServerHttpRequest request) { + return request.getMethodValue(); } - return this.spanReporter; - } - HttpSpanExtractor spanExtractor() { - if (this.spanExtractor == null) { - this.spanExtractor = this.beanFactory.getBean(HttpSpanExtractor.class); + @Override public String url(ServerHttpRequest request) { + return request.getURI().toString(); } - return this.spanExtractor; - } - HttpTraceKeysInjector keysInjector() { - if (this.httpTraceKeysInjector == null) { - this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + @Override public String requestHeader(ServerHttpRequest request, String name) { + Object result = request.getHeaders().getFirst(name); + return result != null ? result.toString() : null; } - return this.httpTraceKeysInjector; - } - ErrorParser errorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); + @Override public Integer statusCode(ServerHttpResponse response) { + return response.getStatusCode() != null ? + response.getStatusCode().value() : null; } - return this.errorParser; - } - - @Override public int getOrder() { - return ORDER; } } + diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java index bb5867a270..d9ea7ab807 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ */ package org.springframework.cloud.sleuth.instrument.web; +import brave.Tracing; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,7 +34,7 @@ @Configuration @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(Tracing.class) @AutoConfigureAfter(TraceWebAutoConfiguration.class) public class TraceWebFluxAutoConfiguration { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java index 58975d74a6..b0b96dcb6f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebMvcConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * MVC Adapter that adds the {@link TraceHandlerInterceptor} @@ -31,7 +31,7 @@ * @since 1.0.3 */ @Configuration -class TraceWebMvcConfigurer extends WebMvcConfigurerAdapter { +class TraceWebMvcConfigurer implements WebMvcConfigurer { @Autowired BeanFactory beanFactory; @Bean diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java index 740c760a31..d787fff643 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebServletAutoConfiguration.java @@ -1,20 +1,7 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.springframework.cloud.sleuth.instrument.web; +import brave.Tracer; +import brave.http.HttpTracing; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -25,12 +12,10 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; @@ -42,8 +27,6 @@ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} enables tracing to HTTP requests. * - * @author Tomasz Nurkewicz, 4financeIT - * @author Michal Chmielarz, 4financeIT * @author Marcin Grzejszczak * @author Spencer Gibb * @since 1.0.0 @@ -51,7 +34,7 @@ @Configuration @ConditionalOnProperty(value = "spring.sleuth.web.enabled", matchIfMissing = true) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(HttpTracing.class) @AutoConfigureAfter(TraceHttpAutoConfiguration.class) public class TraceWebServletAutoConfiguration { @@ -60,15 +43,14 @@ public class TraceWebServletAutoConfiguration { * dependency to it) */ @Configuration - @ConditionalOnClass(WebMvcConfigurerAdapter.class) + @ConditionalOnClass(WebMvcConfigurer.class) @Import(TraceWebMvcConfigurer.class) protected static class TraceWebMvcAutoConfiguration { } @Bean - public TraceWebAspect traceWebAspect(Tracer tracer, TraceKeys traceKeys, - SpanNamer spanNamer, ErrorParser errorParser) { - return new TraceWebAspect(tracer, spanNamer, traceKeys, errorParser); + TraceWebAspect traceWebAspect(Tracer tracer, SpanNamer spanNamer, ErrorParser errorParser) { + return new TraceWebAspect(tracer, spanNamer, errorParser); } @Bean @@ -77,13 +59,12 @@ public TraceSpringDataBeanPostProcessor traceSpringDataBeanPostProcessor( BeanFactory beanFactory) { return new TraceSpringDataBeanPostProcessor(beanFactory); } - + @Bean - public FilterRegistrationBean traceWebFilter(TraceFilter traceFilter) { - FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean( - traceFilter); - filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, - REQUEST); + public FilterRegistrationBean traceWebFilter( + TraceFilter traceFilter) { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(traceFilter); + filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); filterRegistrationBean.setOrder(TraceFilter.ORDER); return filterRegistrationBean; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java deleted file mode 100644 index 19512f78d3..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanExtractor.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.lang.invoke.MethodHandles; -import java.util.Map; -import java.util.Random; -import java.util.regex.Pattern; - -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.util.StringUtils; - -/** - * Default implementation, compatible with Zipkin propagation. - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public class ZipkinHttpSpanExtractor implements HttpSpanExtractor { - - private static final org.apache.commons.logging.Log log = LogFactory.getLog( - MethodHandles.lookup().lookupClass()); - - private static final String HTTP_COMPONENT = "http"; - - private static final ZipkinHttpSpanMapper SPAN_CARRIER_MAPPER = new ZipkinHttpSpanMapper(); - - private final Pattern skipPattern; - private final Random random; - - public ZipkinHttpSpanExtractor(Pattern skipPattern) { - this.skipPattern = skipPattern; - this.random = new Random(); - } - - @Override - public Span joinTrace(SpanTextMap textMap) { - Map carrier = SPAN_CARRIER_MAPPER.convert(textMap); - - boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS)); - boolean idToBeGenerated = debug && onlySpanIdIsPresent(carrier); - // we're only generating Trace ID since if there's no Span ID will assume - // that it's equal to Trace ID - we're trying to fix a malformed request - if (!idToBeGenerated && traceIdIsMissing(carrier)) { - // can't build a Span without trace id - return null; - } - try { - return buildParentSpan(carrier, idToBeGenerated); - } catch (Exception e) { - log.error("Exception occurred while trying to extract span from carrier", e); - return null; - } - } - - private boolean onlySpanIdIsPresent(Map carrier) { - return traceIdIsMissing(carrier) && spanIdIsPresent(carrier); - } - - private boolean traceIdIsMissing(Map carrier) { - return carrier.get(Span.TRACE_ID_NAME) == null; - } - - private boolean spanIdIsPresent(Map carrier) { - return carrier.get(Span.SPAN_ID_NAME) != null; - } - - private String generateId() { - return Span.idToHex(this.random.nextLong()); - } - - private long spanId(String spanId, String traceId) { - if (spanId == null) { - if (log.isDebugEnabled()) { - log.debug("Request is missing a span id but it has a trace id. We'll assume that this is " - + "a root span with span id equal to the lower 64-bits of the trace id"); - } - return Span.hexToId(traceId); - } else { - return Span.hexToId(spanId); - } - } - - private Span buildParentSpan(Map carrier, boolean idToBeGenerated) { - String traceId = carrier.get(Span.TRACE_ID_NAME); - if (traceId == null) { - traceId = generateId(); - } - Span.SpanBuilder span = Span.builder() - .traceIdHigh(traceId.length() == 32 ? Span.hexToId(traceId, 0) : 0) - .traceId(Span.hexToId(traceId)) - .spanId(spanId(carrier.get(Span.SPAN_ID_NAME), traceId)); - String parentName = carrier.get(Span.SPAN_NAME_NAME); - if (StringUtils.hasText(parentName)) { - span.name(parentName); - } else { - span.name(HTTP_COMPONENT + ":/parent" - + carrier.get(ZipkinHttpSpanMapper.URI_HEADER)); - } - String processId = carrier.get(Span.PROCESS_ID_NAME); - if (StringUtils.hasText(processId)) { - span.processId(processId); - } - String parentId = carrier.get(Span.PARENT_ID_NAME); - if (parentId != null) { - span.parent(Span.hexToId(parentId)); - } - span.remote(true); - - boolean skip = this.skipPattern - .matcher(carrier.get(ZipkinHttpSpanMapper.URI_HEADER)).matches() - || Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME)); - // trace, span id were retrieved from the headers and span is sampled - span.shared(!(skip || idToBeGenerated)); - boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS)); - if (debug) { - span.exportable(true); - } else if (skip) { - span.exportable(false); - } - for (Map.Entry entry : carrier.entrySet()) { - if (entry.getKey().toLowerCase() - .startsWith(ZipkinHttpSpanMapper.BAGGAGE_PREFIX)) { - span.baggage(unprefixedKey(entry.getKey()), entry.getValue()); - } - } - return span.build(); - } - - private String unprefixedKey(String key) { - return key.substring(key.indexOf(ZipkinHttpSpanMapper.HEADER_DELIMITER) + 1) - .toLowerCase(); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java deleted file mode 100644 index 6767a1e140..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjector.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.Map; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.util.StringUtils; - -/** - * Default implementation of {@link HttpSpanInjector}, compatible with Zipkin propagation. - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public class ZipkinHttpSpanInjector implements HttpSpanInjector { - - private static final ZipkinHttpSpanMapper SPAN_CARRIER_MAPPER = new ZipkinHttpSpanMapper(); - - @Override - public void inject(Span span, SpanTextMap map) { - Map carrier = SPAN_CARRIER_MAPPER.convert(map); - setHeader(map, carrier, Span.TRACE_ID_NAME, span.traceIdString()); - setIdHeader(map, carrier, Span.SPAN_ID_NAME, span.getSpanId()); - setHeader(map, carrier, Span.SAMPLED_NAME, span.isExportable() ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - setHeader(map, carrier, Span.SPAN_NAME_NAME, span.getName()); - setIdHeader(map, carrier, Span.PARENT_ID_NAME, getParentId(span)); - setHeader(map, carrier, Span.PROCESS_ID_NAME, span.getProcessId()); - for (Map.Entry entry : span.baggageItems()) { - map.put(prefixedKey(entry.getKey()), entry.getValue()); - } - } - - private String prefixedKey(String key) { - if (key.startsWith(Span.SPAN_BAGGAGE_HEADER_PREFIX - + ZipkinHttpSpanMapper.HEADER_DELIMITER)) { - return key; - } - return Span.SPAN_BAGGAGE_HEADER_PREFIX + ZipkinHttpSpanMapper.HEADER_DELIMITER - + key; - } - - private Long getParentId(Span span) { - return !span.getParents().isEmpty() ? span.getParents().get(0) : null; - } - - private void setIdHeader(SpanTextMap map, Map carrier, String name, Long value) { - if (value != null) { - setHeader(map, carrier, name, Span.idToHex(value)); - } - } - - private void setHeader(SpanTextMap map, Map carrier, String name, String value) { - if (StringUtils.hasText(value) && !carrier.containsKey(name)) { - map.put(name, value); - } - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java deleted file mode 100644 index c2c2d90761..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapper.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * Mapper util for filter Zipkin compatible carrier values only from {@link SpanTextMap} - * - * @author Anton Kislitsyn - */ -class ZipkinHttpSpanMapper { - - static final String HEADER_DELIMITER = "-"; - static final String BAGGAGE_PREFIX = Span.SPAN_BAGGAGE_HEADER_PREFIX - + HEADER_DELIMITER; - static final String URI_HEADER = "X-Span-Uri"; - - private static Comparator IGNORE_CASE_COMPARATOR = new Comparator() { - @Override - public int compare(String o1, String o2) { - return o1.toLowerCase().compareTo(o2.toLowerCase()); - } - }; - - /** - * Acceptable span fields - */ - private static final Set SPAN_FIELDS; - - static { - TreeSet fields = new TreeSet<>(IGNORE_CASE_COMPARATOR); - Collections.addAll(fields, Span.SPAN_FLAGS, Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, - Span.PROCESS_ID_NAME, Span.SPAN_NAME_NAME, Span.PARENT_ID_NAME, - Span.SAMPLED_NAME, URI_HEADER); - SPAN_FIELDS = Collections.unmodifiableSet(fields); - } - - /** - * Create new Map of carrier values - */ - Map convert(SpanTextMap textMap) { - Map carrier = new TreeMap<>(IGNORE_CASE_COMPARATOR); - for (Map.Entry entry : textMap) { - if (isAcceptable(entry.getKey())) { - carrier.put(entry.getKey(), entry.getValue()); - } - } - return Collections.unmodifiableMap(carrier); - } - - private boolean isAcceptable(String key) { - return SPAN_FIELDS.contains(key) || key.startsWith(BAGGAGE_PREFIX); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java deleted file mode 100644 index 57b267ac45..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/AbstractTraceHttpRequestInterceptor.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.lang.invoke.MethodHandles; -import java.net.URI; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.util.SpanNameUtil; -import org.springframework.http.HttpRequest; -/** - * Abstraction over classes that interact with Http requests. Allows you - * to enrich the request headers with trace related information. - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -abstract class AbstractTraceHttpRequestInterceptor { - - protected static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - protected final Tracer tracer; - protected final HttpSpanInjector spanInjector; - protected final HttpTraceKeysInjector keysInjector; - - protected AbstractTraceHttpRequestInterceptor(Tracer tracer, - HttpSpanInjector spanInjector, HttpTraceKeysInjector keysInjector) { - this.tracer = tracer; - this.spanInjector = spanInjector; - this.keysInjector = keysInjector; - } - - /** - * Enriches the request with proper headers and publishes - * the client sent event - */ - protected void publishStartEvent(HttpRequest request) { - URI uri = request.getURI(); - String spanName = getName(uri); - Span newSpan = this.tracer.createSpan(spanName); - this.spanInjector.inject(newSpan, new HttpRequestTextMap(request)); - addRequestTags(request); - newSpan.logEvent(Span.CLIENT_SEND); - if (log.isDebugEnabled()) { - log.debug("Starting new client span [" + newSpan + "]"); - } - } - - private String getName(URI uri) { - // The returned name should comply with RFC 882 - Section 3.1.2. - // i.e Header values must composed of printable ASCII values. - return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getRawPath()); - } - - private String uriScheme(URI uri) { - return uri.getScheme() == null ? "http" : uri.getScheme(); - } - - /** - * Adds HTTP tags to the client side span - */ - protected void addRequestTags(HttpRequest request) { - this.keysInjector.addRequestTags(request.getURI().toString(), - request.getURI().getHost(), - request.getURI().getPath(), - request.getMethod().name(), - request.getHeaders()); - } - - /** - * Close the current span and log the client received event - */ - public void finish() { - if (!isTracing()) { - return; - } - currentSpan().logEvent(Span.CLIENT_RECV); - this.tracer.close(this.currentSpan()); - } - - protected Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - - protected boolean isTracing() { - return this.tracer.isTracing(); - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java deleted file mode 100644 index 4176814907..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/HttpRequestTextMap.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.http.HttpRequest; -import org.springframework.util.StringUtils; - -/** - * A {@link SpanTextMap} abstraction over {@link HttpRequest} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -class HttpRequestTextMap implements SpanTextMap { - - private final HttpRequest delegate; - - HttpRequestTextMap(HttpRequest delegate) { - this.delegate = delegate; - } - - @Override - public Iterator> iterator() { - final Iterator>> iterator = this.delegate.getHeaders() - .entrySet().iterator(); - return new Iterator>() { - @Override public boolean hasNext() { - return iterator.hasNext(); - } - - @Override public Map.Entry next() { - Map.Entry> next = iterator.next(); - List value = next.getValue(); - return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.get(0)); - } - }; - } - - @Override - public void put(String key, String value) { - if (!StringUtils.hasText(value)) { - return; - } - this.delegate.getHeaders().put(key, Collections.singletonList(value)); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java index 3c127edee4..b36c484a5f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/SleuthWebClientEnabled.java @@ -1,8 +1,12 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -import java.lang.annotation.*; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; /** * Helper annotation to enable Sleuth web client diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java deleted file mode 100644 index f07475ae9a..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncClientHttpRequestFactoryWrapper.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.io.IOException; -import java.net.URI; - -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.core.task.AsyncListenableTaskExecutor; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.AsyncClientHttpRequest; -import org.springframework.http.client.AsyncClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; - -/** - * Wrapper that adds trace related headers to the created {@link AsyncClientHttpRequest} - * and to the {@link ClientHttpRequest} - * - * @author Marcin Grzejszczak - * @author Spencer Gibb - * @since 1.0.0 - */ -public class TraceAsyncClientHttpRequestFactoryWrapper extends AbstractTraceHttpRequestInterceptor - implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { - - final AsyncClientHttpRequestFactory asyncDelegate; - final ClientHttpRequestFactory syncDelegate; - - /** - * According to the JavaDocs all Spring {@link AsyncClientHttpRequestFactory} implement - * the {@link ClientHttpRequestFactory} interface. - * - * In case that it's not true we're setting the {@link SimpleClientHttpRequestFactory} - * as a default for sync request processing. - * - * @see org.springframework.web.client.AsyncRestTemplate#AsyncRestTemplate(AsyncClientHttpRequestFactory) - */ - public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - HttpSpanInjector spanInjector, - AsyncClientHttpRequestFactory asyncDelegate, - HttpTraceKeysInjector httpTraceKeysInjector) { - super(tracer, spanInjector, httpTraceKeysInjector); - this.asyncDelegate = asyncDelegate; - this.syncDelegate = asyncDelegate instanceof ClientHttpRequestFactory ? - (ClientHttpRequestFactory) asyncDelegate : defaultClientHttpRequestFactory(); - } - - /** - * Default implementation that creates a {@link SimpleClientHttpRequestFactory} that - * has a wrapped task executor via the {@link TraceAsyncListenableTaskExecutor} - */ - public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector) { - super(tracer, spanInjector, httpTraceKeysInjector); - SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = defaultClientHttpRequestFactory(); - this.asyncDelegate = simpleClientHttpRequestFactory; - this.syncDelegate = simpleClientHttpRequestFactory; - } - - public TraceAsyncClientHttpRequestFactoryWrapper(Tracer tracer, - HttpSpanInjector spanInjector, - AsyncClientHttpRequestFactory asyncDelegate, - ClientHttpRequestFactory syncDelegate, - HttpTraceKeysInjector httpTraceKeysInjector) { - super(tracer, spanInjector, httpTraceKeysInjector); - this.asyncDelegate = asyncDelegate; - this.syncDelegate = syncDelegate; - } - - private SimpleClientHttpRequestFactory defaultClientHttpRequestFactory() { - SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - simpleClientHttpRequestFactory.setTaskExecutor(asyncListenableTaskExecutor(this.tracer)); - return simpleClientHttpRequestFactory; - } - - private AsyncListenableTaskExecutor asyncListenableTaskExecutor(Tracer tracer) { - ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); - threadPoolTaskScheduler.initialize(); - return new TraceAsyncListenableTaskExecutor(threadPoolTaskScheduler, tracer); - } - - @Override - public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) - throws IOException { - AsyncClientHttpRequest request = this.asyncDelegate - .createAsyncRequest(uri, httpMethod); - addRequestTags(request); - publishStartEvent(request); - return request; - } - - @Override - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) - throws IOException { - ClientHttpRequest request = this.syncDelegate.createRequest(uri, httpMethod); - addRequestTags(request); - publishStartEvent(request); - return request; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java deleted file mode 100644 index 583bb44544..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncRestTemplate.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.lang.invoke.MethodHandles; -import java.net.URI; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.core.task.AsyncListenableTaskExecutor; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.AsyncClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.util.concurrent.FailureCallback; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.util.concurrent.SuccessCallback; -import org.springframework.web.client.AsyncRequestCallback; -import org.springframework.web.client.AsyncRestTemplate; -import org.springframework.web.client.ResponseExtractor; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -/** - * An {@link AsyncRestTemplate} that closes started spans when a response has been - * successfully received. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -public class TraceAsyncRestTemplate extends AsyncRestTemplate { - - private final Tracer tracer; - private final ErrorParser errorParser; - - public TraceAsyncRestTemplate(Tracer tracer, ErrorParser errorParser) { - super(); - this.tracer = tracer; - this.errorParser = errorParser; - } - - public TraceAsyncRestTemplate(AsyncListenableTaskExecutor taskExecutor, - Tracer tracer, ErrorParser errorParser) { - super(taskExecutor); - this.tracer = tracer; - this.errorParser = errorParser; - } - - public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory asyncRequestFactory, - Tracer tracer, ErrorParser errorParser) { - super(asyncRequestFactory); - this.tracer = tracer; - this.errorParser = errorParser; - } - - public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory asyncRequestFactory, - ClientHttpRequestFactory syncRequestFactory, Tracer tracer, ErrorParser errorParser) { - super(asyncRequestFactory, syncRequestFactory); - this.tracer = tracer; - this.errorParser = errorParser; - } - - public TraceAsyncRestTemplate(AsyncClientHttpRequestFactory requestFactory, - RestTemplate restTemplate, Tracer tracer, ErrorParser errorParser) { - super(requestFactory, restTemplate); - this.tracer = tracer; - this.errorParser = errorParser; - } - - @Override - protected ListenableFuture doExecute(URI url, HttpMethod method, - AsyncRequestCallback requestCallback, ResponseExtractor responseExtractor) - throws RestClientException { - final ListenableFuture future = super.doExecute(url, method, requestCallback, responseExtractor); - final Span span = this.tracer.getCurrentSpan(); - future.addCallback(new TraceListenableFutureCallback<>(this.tracer, span, - this.errorParser)); - // potential race can happen here - if (span != null && span.equals(this.tracer.getCurrentSpan())) { - Span parent = this.tracer.detach(span); - if (parent != null) { - this.tracer.continueSpan(parent); - } - } - return new ListenableFuture() { - - @Override public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - @Override public boolean isCancelled() { - return future.isCancelled(); - } - - @Override public boolean isDone() { - return future.isDone(); - } - - @Override public T get() throws InterruptedException, ExecutionException { - return future.get(); - } - - @Override public T get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return future.get(timeout, unit); - } - - @Override - public void addCallback(ListenableFutureCallback callback) { - future.addCallback(new TraceListenableFutureCallbackWrapper<>(TraceAsyncRestTemplate.this.tracer, span, callback)); - } - - @Override public void addCallback(SuccessCallback successCallback, - FailureCallback failureCallback) { - future.addCallback( - new TraceSuccessCallback<>(TraceAsyncRestTemplate.this.tracer, span, successCallback), - new TraceFailureCallback(TraceAsyncRestTemplate.this.tracer, span, failureCallback)); - } - }; - } - - private static class TraceSuccessCallback implements SuccessCallback { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Tracer tracer; - private final Span parent; - private final SuccessCallback delegate; - - private TraceSuccessCallback(Tracer tracer, Span parent, - SuccessCallback delegate) { - this.tracer = tracer; - this.parent = parent; - this.delegate = delegate; - } - - @Override public void onSuccess(T result) { - continueSpan(); - if (log.isDebugEnabled()) { - log.debug("Calling on success of the delegate"); - } - this.delegate.onSuccess(result); - finish(); - } - - private void continueSpan() { - this.tracer.continueSpan(this.parent); - } - - private void finish() { - this.tracer.detach(currentSpan()); - } - - private Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - } - - private static class TraceFailureCallback implements FailureCallback { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Tracer tracer; - private final Span parent; - private final FailureCallback delegate; - - private TraceFailureCallback(Tracer tracer, Span parent, - FailureCallback delegate) { - this.tracer = tracer; - this.parent = parent; - this.delegate = delegate; - } - - @Override public void onFailure(Throwable ex) { - continueSpan(); - if (log.isDebugEnabled()) { - log.debug("Calling on failure of the delegate"); - } - this.delegate.onFailure(ex); - finish(); - } - - private void continueSpan() { - this.tracer.continueSpan(this.parent); - } - - private void finish() { - this.tracer.detach(currentSpan()); - } - - private Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - } - - private static class TraceListenableFutureCallbackWrapper implements ListenableFutureCallback { - - private final Tracer tracer; - private final Span parent; - private final ListenableFutureCallback delegate; - - private TraceListenableFutureCallbackWrapper(Tracer tracer, Span parent, - ListenableFutureCallback delegate) { - this.tracer = tracer; - this.parent = parent; - this.delegate = delegate; - } - - @Override public void onFailure(Throwable ex) { - new TraceFailureCallback(this.tracer, this.parent, this.delegate).onFailure(ex); - } - - @Override public void onSuccess(T result) { - new TraceSuccessCallback<>(this.tracer, this.parent, this.delegate).onSuccess(result); - } - } - - private static class TraceListenableFutureCallback implements ListenableFutureCallback { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Tracer tracer; - private final Span parent; - private final ErrorParser errorParser; - - private TraceListenableFutureCallback(Tracer tracer, Span parent, - ErrorParser errorParser) { - this.tracer = tracer; - this.parent = parent; - this.errorParser = errorParser; - } - - @Override - public void onFailure(Throwable ex) { - continueSpan(); - if (log.isDebugEnabled()) { - log.debug("The callback failed - will close the span"); - } - this.errorParser.parseErrorTags(currentSpan(), ex); - finish(); - } - - @Override - public void onSuccess(T result) { - continueSpan(); - if (log.isDebugEnabled()) { - log.debug("The callback succeeded - will close the span"); - } - finish(); - } - - private void continueSpan() { - this.tracer.continueSpan(this.parent); - } - - private void finish() { - if (!isTracing()) { - return; - } - currentSpan().logEvent(Span.CLIENT_RECV); - this.tracer.close(currentSpan()); - } - - private Span currentSpan() { - return this.tracer.getCurrentSpan(); - } - - private boolean isTracing() { - return this.tracer.isTracing(); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceHttpResponse.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceHttpResponse.java deleted file mode 100644 index b7514d052a..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceHttpResponse.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.io.IOException; -import java.io.InputStream; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; - -/** - * Implementation of {@link ClientHttpResponse} that upon - * {@link ClientHttpResponse#close() closing the response} - * {@link TraceRestTemplateInterceptor#finish() closes the span} - * - * @author Dave Syer - * @since 1.0.0 - */ -public class TraceHttpResponse implements ClientHttpResponse { - - private final ClientHttpResponse delegate; - private final TraceRestTemplateInterceptor interceptor; - - public TraceHttpResponse(TraceRestTemplateInterceptor interceptor, - ClientHttpResponse delegate) { - this.interceptor = interceptor; - this.delegate = delegate; - } - - @Override - public HttpHeaders getHeaders() { - return this.delegate.getHeaders(); - } - - @Override - public InputStream getBody() throws IOException { - return this.delegate.getBody(); - } - - @Override - public HttpStatus getStatusCode() throws IOException { - return this.delegate.getStatusCode(); - } - - @Override - public int getRawStatusCode() throws IOException { - return this.delegate.getRawStatusCode(); - } - - @Override - public String getStatusText() throws IOException { - return this.delegate.getStatusText(); - } - - @Override - public void close() { - try { - this.delegate.close(); - } - finally { - this.interceptor.finish(); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java deleted file mode 100644 index afadd5d751..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptor.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; - -import java.io.IOException; - -/** - * Interceptor that verifies whether the trance and span id has been set on the request - * and sets them if one or both of them are missing. - * - * @author Marcin Grzejszczak - * @author Spencer Gibb - * @since 1.0.0 - * - * @see org.springframework.web.client.RestTemplate - */ -public class TraceRestTemplateInterceptor extends AbstractTraceHttpRequestInterceptor - implements ClientHttpRequestInterceptor { - - private final ErrorParser errorParser; - - public TraceRestTemplateInterceptor(Tracer tracer, HttpSpanInjector spanInjector, - HttpTraceKeysInjector httpTraceKeysInjector, ErrorParser errorParser) { - super(tracer, spanInjector, httpTraceKeysInjector); - this.errorParser = errorParser; - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { - publishStartEvent(request); - return response(request, body, execution); - } - - private ClientHttpResponse response(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { - try { - return new TraceHttpResponse(this, execution.execute(request, body)); - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Exception occurred while trying to execute the request. Will close the span [" + currentSpan() + "]", e); - } - this.errorParser.parseErrorTags(currentSpan(), e); - finish(); - throw e; - } - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java index 1f08e42204..fbbccdd0b8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,23 @@ package org.springframework.cloud.sleuth.instrument.web.client; +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import brave.http.HttpTracing; +import brave.spring.web.TracingAsyncClientHttpRequestInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.http.client.AsyncClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.http.client.AsyncClientHttpRequestInterceptor; import org.springframework.web.client.AsyncRestTemplate; /** @@ -48,47 +47,38 @@ @SleuthWebClientEnabled @ConditionalOnProperty(value = "spring.sleuth.web.async.client.enabled", matchIfMissing = true) @ConditionalOnClass(AsyncRestTemplate.class) -@ConditionalOnBean(HttpTraceKeysInjector.class) +@ConditionalOnBean(HttpTracing.class) @AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceWebAsyncClientAutoConfiguration { - @Autowired Tracer tracer; - @Autowired private HttpTraceKeysInjector httpTraceKeysInjector; - @Autowired private HttpSpanInjector spanInjector; - @Autowired(required = false) private ClientHttpRequestFactory clientHttpRequestFactory; - @Autowired(required = false) private AsyncClientHttpRequestFactory asyncClientHttpRequestFactory; + @ConditionalOnBean(AsyncRestTemplate.class) + static class AsyncRestTemplateConfig { - private TraceAsyncClientHttpRequestFactoryWrapper traceAsyncClientHttpRequestFactory() { - ClientHttpRequestFactory clientFactory = this.clientHttpRequestFactory; - AsyncClientHttpRequestFactory asyncClientFactory = this.asyncClientHttpRequestFactory; - if (clientFactory == null) { - clientFactory = defaultClientHttpRequestFactory(this.tracer); - } - if (asyncClientFactory == null) { - asyncClientFactory = clientFactory instanceof AsyncClientHttpRequestFactory ? - (AsyncClientHttpRequestFactory) clientFactory : defaultClientHttpRequestFactory(this.tracer); + @Bean + public TracingAsyncClientHttpRequestInterceptor asyncTracingClientHttpRequestInterceptor(HttpTracing httpTracing) { + return (TracingAsyncClientHttpRequestInterceptor) TracingAsyncClientHttpRequestInterceptor.create(httpTracing); } - return new TraceAsyncClientHttpRequestFactoryWrapper(this.tracer, this.spanInjector, - asyncClientFactory, clientFactory, this.httpTraceKeysInjector); - } - private SimpleClientHttpRequestFactory defaultClientHttpRequestFactory(Tracer tracer) { - SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - simpleClientHttpRequestFactory.setTaskExecutor(asyncListenableTaskExecutor(tracer)); - return simpleClientHttpRequestFactory; - } + @Configuration + protected static class TraceInterceptorConfiguration { - private AsyncListenableTaskExecutor asyncListenableTaskExecutor(Tracer tracer) { - ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); - threadPoolTaskScheduler.initialize(); - return new TraceAsyncListenableTaskExecutor(threadPoolTaskScheduler, tracer); - } + @Autowired(required = false) + private Collection restTemplates; - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(value = "spring.sleuth.web.async.client.template.enabled", matchIfMissing = true) - public AsyncRestTemplate traceAsyncRestTemplate(ErrorParser errorParser) { - return new TraceAsyncRestTemplate(traceAsyncClientHttpRequestFactory(), this.tracer, errorParser); - } + @Autowired + private TracingAsyncClientHttpRequestInterceptor clientInterceptor; + @PostConstruct + public void init() { + if (this.restTemplates != null) { + for (AsyncRestTemplate restTemplate : this.restTemplates) { + List interceptors = new ArrayList<>( + restTemplate.getInterceptors()); + interceptors.add(this.clientInterceptor); + restTemplate.setInterceptors(interceptors); + } + } + } + } + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 41bb7b00d4..472926757e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.List; import javax.annotation.PostConstruct; +import brave.http.HttpTracing; +import brave.spring.web.TracingClientHttpRequestInterceptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,12 +30,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -51,19 +48,16 @@ */ @Configuration @SleuthWebClientEnabled -@ConditionalOnBean(HttpTraceKeysInjector.class) +@ConditionalOnBean(HttpTracing.class) @AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceWebClientAutoConfiguration { @ConditionalOnClass(RestTemplate.class) static class RestTemplateConfig { + @Bean - @ConditionalOnMissingBean - public TraceRestTemplateInterceptor traceRestTemplateInterceptor(Tracer tracer, - HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector, - ErrorParser errorParser) { - return new TraceRestTemplateInterceptor(tracer, spanInjector, - httpTraceKeysInjector, errorParser); + public TracingClientHttpRequestInterceptor tracingClientHttpRequestInterceptor(HttpTracing httpTracing) { + return (TracingClientHttpRequestInterceptor) TracingClientHttpRequestInterceptor.create(httpTracing); } @Configuration @@ -73,7 +67,7 @@ protected static class TraceInterceptorConfiguration { private Collection restTemplates; @Autowired - private TraceRestTemplateInterceptor traceRestTemplateInterceptor; + private TracingClientHttpRequestInterceptor clientInterceptor; @PostConstruct public void init() { @@ -81,7 +75,7 @@ public void init() { for (RestTemplate restTemplate : this.restTemplates) { List interceptors = new ArrayList( restTemplate.getInterceptors()); - interceptors.add(this.traceRestTemplateInterceptor); + interceptors.add(this.clientInterceptor); restTemplate.setInterceptors(interceptors); } } @@ -109,7 +103,8 @@ private TraceRestTemplateBuilderBPP(BeanFactory beanFactory) { throws BeansException { if (o instanceof RestTemplateBuilder) { RestTemplateBuilder builder = (RestTemplateBuilder) o; - return builder.additionalInterceptors(this.beanFactory.getBean(TraceRestTemplateInterceptor.class)); + return builder.additionalInterceptors( + this.beanFactory.getBean(TracingClientHttpRequestInterceptor.class)); } return o; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java index 9163c7d157..7bb13d7d43 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java @@ -1,31 +1,23 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import java.net.URI; -import java.util.AbstractMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - +import brave.Span; +import brave.Tracer; +import brave.http.HttpClientHandler; +import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import reactor.core.publisher.Mono; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.util.SpanNameUtil; -import org.springframework.util.StringUtils; import org.springframework.web.client.RestClientException; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; /** * {@link BeanPostProcessor} to wrap a {@link WebClient} instance into @@ -65,15 +57,29 @@ class TraceWebClientBeanPostProcessor implements BeanPostProcessor { class TraceExchangeFilterFunction implements ExchangeFilterFunction { + private static final Log log = LogFactory.getLog( + TraceExchangeFilterFunction.class); private static final String CLIENT_SPAN_KEY = "sleuth.webclient.clientSpan"; - private static final Log log = LogFactory.getLog(TraceExchangeFilterFunction.class); + static final Propagation.Setter SETTER = + new Propagation.Setter() { + @Override public void put(ClientRequest.Builder carrier, String key, String value) { + carrier.header(key, value); + } - private Tracer tracer; - private HttpSpanInjector spanInjector; - private HttpTraceKeysInjector keysInjector; - private ErrorParser errorParser; - private final BeanFactory beanFactory; + @Override public String toString() { + return "ClientRequest.Builder::header"; + } + }; + + public static ExchangeFilterFunction create(BeanFactory beanFactory) { + return new TraceExchangeFilterFunction(beanFactory); + } + + final BeanFactory beanFactory; + Tracer tracer; + HttpClientHandler handler; + TraceContext.Injector injector; TraceExchangeFilterFunction(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -82,7 +88,6 @@ class TraceExchangeFilterFunction implements ExchangeFilterFunction { @Override public Mono filter(ClientRequest request, ExchangeFunction next) { final ClientRequest.Builder builder = ClientRequest.from(request); - Mono exchange = Mono .defer(() -> next.exchange(builder.build())) .cast(Object.class) @@ -91,32 +96,33 @@ class TraceExchangeFilterFunction implements ExchangeFilterFunction { .flatMap(anyAndContext -> { Object any = anyAndContext.getT1(); Span clientSpan = anyAndContext.getT2().get(CLIENT_SPAN_KEY); - - tracer().continueSpan(clientSpan); - Mono continuation; - if (any instanceof Throwable) { - Throwable throwable = (Throwable) any; - errorParser().parseErrorTags(clientSpan, throwable); - continuation = Mono.error(throwable); - } else { - ClientResponse response = (ClientResponse) any; - boolean error = response.statusCode().is4xxClientError() || response - .statusCode().is5xxServerError(); - if (error) { - if (log.isDebugEnabled()) { - log.debug( - "Non positive status code was returned from the call. Will close the span [" - + clientSpan + "]"); + Throwable throwable = null; + ClientResponse response = null; + try (Tracer.SpanInScope ws = tracer().withSpanInScope(clientSpan)) { + if (any instanceof Throwable) { + throwable = (Throwable) any; + continuation = Mono.error(throwable); + } else { + response = (ClientResponse) any; + boolean error = response.statusCode().is4xxClientError() || + response.statusCode().is5xxServerError(); + if (error) { + if (log.isDebugEnabled()) { + log.debug( + "Non positive status code was returned from the call. Will close the span [" + + clientSpan + "]"); + } + throwable = new RestClientException( + "Status code of the response is [" + response.statusCode() + .value() + "] and the reason is [" + response + .statusCode().getReasonPhrase() + "]"); } - errorParser().parseErrorTags(clientSpan, new RestClientException( - "Status code of the response is [" + response.statusCode() - .value() + "] and the reason is [" + response - .statusCode().getReasonPhrase() + "]")); + continuation = Mono.just(response); } - continuation = Mono.just(response); + } finally { + handler().handleReceive(response, throwable, clientSpan); } - finish(clientSpan); return continuation; }) .subscriberContext(c -> { @@ -124,142 +130,62 @@ class TraceExchangeFilterFunction implements ExchangeFilterFunction { log.debug("Creating a client span for the WebClient"); } Span parent = c.getOrDefault(Span.class, null); - Span clientSpan = createNewSpan(request, parent); - tracer().continueSpan(clientSpan); - - httpSpanInjector().inject(clientSpan, new ClientRequestTextMap(request, builder)); - if (log.isDebugEnabled()) { - log.debug("Headers got injected from the client span " + clientSpan); - } - + Span clientSpan = handler().handleSend(injector(), builder, request, + parent != null ? parent : tracer().nextSpan()); if (parent == null) { c = c.put(Span.class, clientSpan); if (log.isDebugEnabled()) { log.debug("Reactor Context got injected with the client span " + clientSpan); } } - if (clientSpan != null && clientSpan.equals(tracer().getCurrentSpan())) { - tracer().continueSpan(tracer().detach(clientSpan)); - } return c.put(CLIENT_SPAN_KEY, clientSpan); }); return exchange; } - /** - * Enriches the request with proper headers and publishes - * the client sent event - */ - private Span createNewSpan(ClientRequest request, Span optionalParent) { - URI uri = request.url(); - String spanName = getName(uri); - Span newSpan; - if (optionalParent == null) { - newSpan = tracer().createSpan(spanName); - } else { - newSpan = tracer().createSpan(spanName, optionalParent); + @SuppressWarnings("unchecked") + HttpClientHandler handler() { + if (this.handler == null) { + this.handler = HttpClientHandler + .create(this.beanFactory.getBean(HttpTracing.class), new TraceExchangeFilterFunction.HttpAdapter()); } - addRequestTags(request); - newSpan.logEvent(Span.CLIENT_SEND); - if (log.isDebugEnabled()) { - log.debug("Starting new client span [" + newSpan + "]"); - } - return newSpan; - } - - private String getName(URI uri) { - return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getPath()); + return this.handler; } - private String uriScheme(URI uri) { - return uri.getScheme() == null ? "http" : uri.getScheme(); - } - - /** - * Adds HTTP tags to the client side span - */ - private void addRequestTags(ClientRequest request) { - keysInjector().addRequestTags(request.url().toString(), - request.url().getHost(), - request.url().getPath(), - request.method().name(), - request.headers()); - } - - /** - * Close the current span and log the client received event - */ - private void finish(Span span) { - tracer().continueSpan(span); - if (log.isDebugEnabled()) { - log.debug("Will close span and mark it with Client Received" + span); - } - span.logEvent(Span.CLIENT_RECV); - tracer().close(span); - } - - private Tracer tracer() { + Tracer tracer() { if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + this.tracer = this.beanFactory.getBean(HttpTracing.class).tracing().tracer(); } return this.tracer; } - private HttpSpanInjector httpSpanInjector() { - if (this.spanInjector == null) { - this.spanInjector = this.beanFactory.getBean(HttpSpanInjector.class); - } - return this.spanInjector; - } - - private HttpTraceKeysInjector keysInjector() { - if (this.keysInjector == null) { - this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); + TraceContext.Injector injector() { + if (this.injector == null) { + this.injector = this.beanFactory.getBean(HttpTracing.class) + .tracing().propagation().injector(SETTER); } - return this.keysInjector; + return this.injector; } - private ErrorParser errorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); - } - return this.errorParser; - } -} -class ClientRequestTextMap implements SpanTextMap { + static final class HttpAdapter + extends brave.http.HttpClientAdapter { - private final ClientRequest.Builder writeDelegate; - private final ClientRequest readDelegate; - - ClientRequestTextMap(ClientRequest readDelegate, - ClientRequest.Builder writeDelegate) { - this.readDelegate = readDelegate; - this.writeDelegate = writeDelegate; - } + @Override public String method(ClientRequest request) { + return request.method().name(); + } - @Override - public Iterator> iterator() { - final Iterator>> iterator = this.readDelegate.headers() - .entrySet().iterator(); - return new Iterator>() { - @Override public boolean hasNext() { - return iterator.hasNext(); - } + @Override public String url(ClientRequest request) { + return request.url().toString(); + } - @Override public Map.Entry next() { - Map.Entry> next = iterator.next(); - List value = next.getValue(); - return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.get(0)); - } - }; - } + @Override public String requestHeader(ClientRequest request, String name) { + Object result = request.headers().getFirst(name); + return result != null ? result.toString() : null; + } - @Override - public void put(String key, String value) { - if (!StringUtils.hasText(value)) { - return; + @Override public Integer statusCode(ClientResponse response) { + return response.statusCode().value(); } - this.writeDelegate.header(key, value); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java index f17909087f..ae525b28f9 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignContextBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java deleted file mode 100644 index 20c6da8efd..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestInjector.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client.feign; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.util.StringUtils; - -import feign.Request; - -/** - * Span injector that injects tracing info to {@link Request} via {@link AtomicReference} - * since {@link Request} is immutable. - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class FeignRequestInjector implements SpanInjector> { - - @Override - public void inject(Span span, AtomicReference carrier) { - String method = carrier.get().method(); - String url = carrier.get().url(); - Map> headers = new HashMap<>(carrier.get().headers()); - byte[] body = carrier.get().body(); - Charset charset = carrier.get().charset(); - if (span == null) { - setHeader(headers, Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); - carrier.set(Request.create(method, url, headers, body, charset)); - return; - } - setHeader(headers, Span.TRACE_ID_NAME, Span.idToHex(span.getTraceId())); - setHeader(headers, Span.SPAN_NAME_NAME, span.getName()); - setHeader(headers, Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - setHeader(headers, Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - Long parentId = getParentId(span); - if (parentId != null) { - setHeader(headers, Span.PARENT_ID_NAME, Span.idToHex(parentId)); - } - setHeader(headers, Span.PROCESS_ID_NAME, span.getProcessId()); - carrier.set(Request.create(method, url, headers, body, charset)); - } - - private Long getParentId(Span span) { - return !span.getParents().isEmpty() ? span.getParents().get(0) : null; - } - - protected void setHeader(Map> headers, String name, String value) { - if (StringUtils.hasText(value) && !headers.containsKey(name)) { - List list = new ArrayList<>(); - list.add(value); - headers.put(name, list); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java deleted file mode 100644 index 287603d043..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRequestTextMap.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client.feign; - -import java.nio.charset.Charset; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.util.StringUtils; - -import feign.Request; - -/** - * A {@link SpanTextMap} abstraction over {@link AtomicReference} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -class FeignRequestTextMap implements SpanTextMap { - - private final AtomicReference delegate; - - FeignRequestTextMap(AtomicReference delegate) { - this.delegate = delegate; - } - - @Override - public Iterator> iterator() { - final Iterator>> iterator = this.delegate.get().headers().entrySet().iterator(); - return new Iterator>() { - @Override public boolean hasNext() { - return iterator.hasNext(); - } - - @Override public Map.Entry next() { - Map.Entry> next = iterator.next(); - Collection value = next.getValue(); - return new AbstractMap.SimpleEntry<>(next.getKey(), value.isEmpty() ? "" : value.iterator().next()); - } - }; - } - - @Override - public void put(String key, String value) { - if (!StringUtils.hasText(value)) { - return; - } - String method = this.delegate.get().method(); - String url = this.delegate.get().url(); - Map> headers = new HashMap<>(this.delegate.get().headers()); - byte[] body = this.delegate.get().body(); - Charset charset = this.delegate.get().charset(); - addHeader(key, value, headers); - this.delegate.set(Request.create(method, url, headers, body, charset)); - } - - private void addHeader(String key, String value, - Map> headers) { - if (!headers.containsKey(key)) { - List list = new ArrayList<>(); - list.add(value); - headers.put(key, list); - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java index e11f364e2e..7e5620f0d2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignResponseHeadersHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/LazyTracingFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/LazyTracingFeignClient.java new file mode 100644 index 0000000000..59836e9a27 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/LazyTracingFeignClient.java @@ -0,0 +1,47 @@ +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import java.io.IOException; + +import brave.http.HttpTracing; +import feign.Client; +import feign.Request; +import feign.Response; +import org.springframework.beans.factory.BeanFactory; + +/** + * Lazilly resolves the Trace Feign Client + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +class LazyTracingFeignClient implements Client { + + private Client tracingFeignClient; + private HttpTracing httpTracing; + private final BeanFactory beanFactory; + private final Client delegate; + + LazyTracingFeignClient(BeanFactory beanFactory, Client delegate) { + this.beanFactory = beanFactory; + this.delegate = delegate; + } + + @Override public Response execute(Request request, Request.Options options) + throws IOException { + return tracingFeignClient().execute(request, options); + } + + private Client tracingFeignClient() { + if (this.tracingFeignClient == null) { + this.tracingFeignClient = TracingFeignClient.create(httpTracing(), this.delegate); + } + return this.tracingFeignClient; + } + + private HttpTracing httpTracing() { + if (this.httpTracing == null) { + this.httpTracing = this.beanFactory.getBean(HttpTracing.class); + } + return this.httpTracing; + } +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java index 6d12b43dab..51c992913b 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,11 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import feign.okhttp.OkHttpClient; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import feign.okhttp.OkHttpClient; - /** * Post processor that wraps takes care of the OkHttp Feign Client instrumentation * diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java index 226babb6cc..67804c213f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthFeignBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import brave.http.HttpTracing; +import feign.Client; +import feign.Feign; import feign.Retryer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import feign.Client; -import feign.Feign; - /** - * Contains {@link feign.Feign.Builder} implementation with tracing components + * Contains {@link Feign.Builder} implementation with tracing components * that close spans on completion of request processing. * * @author Marcin Grzejszczak @@ -45,7 +45,8 @@ private static Client client(BeanFactory beanFactory) { Client client = beanFactory.getBean(Client.class); return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(client); } catch (BeansException e) { - return new TraceFeignClient(beanFactory); + return TracingFeignClient.create(beanFactory.getBean(HttpTracing.class), + new Client.Default(null, null)); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java index 68a0274e6a..99a05e493a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/SleuthHystrixFeignBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; -import feign.Retryer; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; - +import brave.http.HttpTracing; import feign.Client; import feign.Feign; +import feign.Retryer; import feign.hystrix.HystrixFeign; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; /** * Contains {@link Feign.Builder} implementation that delegates execution @@ -47,7 +47,8 @@ private static Client client(BeanFactory beanFactory) { Client client = beanFactory.getBean(Client.class); return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(client); } catch (BeansException e) { - return new TraceFeignClient(beanFactory); + return TracingFeignClient.create(beanFactory.getBean(HttpTracing.class), + new Client.Default(null, null)); } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java index 76a476ad70..ab3ec6f256 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspect.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,13 @@ import java.io.IOException; +import feign.Client; +import feign.Request; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.BeanFactory; -import feign.Client; -import feign.Request; - /** * Aspect for Feign clients so that you can autowire your custom components * diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java deleted file mode 100644 index d4c730b8b4..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClient.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client.feign; - -import feign.Client; -import feign.Request; -import feign.Response; - -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.net.URI; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.util.SpanNameUtil; - -/** - * A Feign Client that closes a Span if there is no response body. In other cases Span - * will get closed because the Decoder will be called - * - * @author Marcin Grzejszczak - * - * @since 1.0.0 - */ -class TraceFeignClient implements Client { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Client delegate; - private HttpTraceKeysInjector keysInjector; - private final BeanFactory beanFactory; - private Tracer tracer; - private HttpSpanInjector spanInjector; - private ErrorParser errorParser; - - TraceFeignClient(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - this.delegate = client(beanFactory); - } - - private Client client(BeanFactory beanFactory) { - try { - return beanFactory.getBean(Client.class); - } catch (NoSuchBeanDefinitionException e) { - return new Client.Default(null, null); - } - } - - TraceFeignClient(BeanFactory beanFactory, Client delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - } - - @Override - public Response execute(Request request, Request.Options options) throws IOException { - String spanName = getSpanName(request); - Span span = getTracer().createSpan(spanName); - if (log.isDebugEnabled()) { - log.debug("Created new Feign span " + span); - } - try { - AtomicReference feignRequest = new AtomicReference<>(request); - spanInjector().inject(span, new FeignRequestTextMap(feignRequest)); - span.logEvent(Span.CLIENT_SEND); - addRequestTags(request); - Request modifiedRequest = feignRequest.get(); - if (log.isDebugEnabled()) { - log.debug("The modified request equals " + modifiedRequest); - } - Response response = this.delegate.execute(modifiedRequest, options); - logCr(); - return response; - } catch (RuntimeException | IOException e) { - logCr(); - logError(e); - throw e; - } finally { - closeSpan(span); - } - } - - private String getSpanName(Request request) { - URI uri = URI.create(request.url()); - return SpanNameUtil.shorten(uriScheme(uri) + ":" + uri.getPath()); - } - - private String uriScheme(URI uri) { - return uri.getScheme() == null ? "http" : uri.getScheme(); - } - - /** - * Adds HTTP tags to the client side span - */ - private void addRequestTags(Request request) { - URI uri = URI.create(request.url()); - keysInjector().addRequestTags(uri.toString(), uri.getHost(), uri.getPath(), - request.method(), request.headers()); - } - - private HttpTraceKeysInjector keysInjector() { - if (this.keysInjector == null) { - this.keysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); - } - return this.keysInjector; - } - - private HttpSpanInjector spanInjector() { - if (this.spanInjector == null) { - this.spanInjector = this.beanFactory.getBean(HttpSpanInjector.class); - } - return this.spanInjector; - } - - private void closeSpan(Span span) { - if (span != null) { - if (log.isDebugEnabled()) { - log.debug("Closing Feign span " + span); - } - getTracer().close(span); - } - } - - private void logCr() { - Span span = getTracer().getCurrentSpan(); - if (span != null) { - if (log.isDebugEnabled()) { - log.debug("Closing Feign span and logging CR " + span); - } - span.logEvent(Span.CLIENT_RECV); - } - } - - private void logError(Exception e) { - Span span = getTracer().getCurrentSpan(); - if (span != null) { - getErrorParser().parseErrorTags(span, e); - } - } - - private Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); - } - return this.tracer; - } - - private ErrorParser getErrorParser() { - if (this.errorParser == null) { - this.errorParser = this.beanFactory.getBean(ErrorParser.class); - } - return this.errorParser; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java index 3c1b4d0bf2..7ad4edfc81 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import brave.http.HttpTracing; +import feign.Client; +import feign.Feign; +import feign.okhttp.OkHttpClient; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -23,18 +27,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration; +import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; -import feign.Client; -import feign.Feign; -import feign.okhttp.OkHttpClient; - /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * Auto-configuration} enables span information propagation when using Feign. @@ -45,9 +44,9 @@ @Configuration @ConditionalOnProperty(value = "spring.sleuth.feign.enabled", matchIfMissing = true) @ConditionalOnClass(Client.class) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(HttpTracing.class) @AutoConfigureBefore(FeignAutoConfiguration.class) -@AutoConfigureAfter({SleuthHystrixAutoConfiguration.class, TraceWebServletAutoConfiguration.class}) +@AutoConfigureAfter({SleuthHystrixAutoConfiguration.class, TraceHttpAutoConfiguration.class}) public class TraceFeignClientAutoConfiguration { @Bean @@ -70,8 +69,7 @@ Feign.Builder feignBuilder(BeanFactory beanFactory) { @ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true) protected static class FeignBeanPostProcessorConfiguration { - @Bean - FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) { + @Bean FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFactory) { return new FeignContextBeanPostProcessor(beanFactory); } } @@ -80,8 +78,7 @@ FeignContextBeanPostProcessor feignContextBeanPostProcessor(BeanFactory beanFact @ConditionalOnClass(OkHttpClient.class) protected static class OkHttpClientFeignBeanPostProcessorConfiguration { - @Bean - OkHttpFeignClientBeanPostProcessor okHttpFeignClientBeanPostProcessor(BeanFactory beanFactory) { + @Bean OkHttpFeignClientBeanPostProcessor okHttpFeignClientBeanPostProcessor(BeanFactory beanFactory) { return new OkHttpFeignClientBeanPostProcessor(beanFactory); } } @@ -91,8 +88,7 @@ TraceFeignObjectWrapper traceFeignObjectWrapper(BeanFactory beanFactory) { return new TraceFeignObjectWrapper(beanFactory); } - @Bean - TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) { + @Bean TraceFeignAspect traceFeignAspect(BeanFactory beanFactory) { return new TraceFeignAspect(beanFactory); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java index 5319f8514d..591e50bf5a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapper.java @@ -1,12 +1,11 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import feign.Client; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import feign.Client; - /** * Class that wraps Feign related classes into their Trace representative * @@ -25,16 +24,17 @@ final class TraceFeignObjectWrapper { } Object wrap(Object bean) { - if (bean instanceof Client && !(bean instanceof TraceFeignClient)) { + if (bean instanceof Client && !(bean instanceof TracingFeignClient)) { if (bean instanceof LoadBalancerFeignClient && !(bean instanceof TraceLoadBalancerFeignClient)) { LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); return new TraceLoadBalancerFeignClient( - client.getDelegate(), factory(), - clientFactory(), this.beanFactory); + (Client) new TraceFeignObjectWrapper(this.beanFactory) + .wrap(client.getDelegate()), + factory(), clientFactory(), this.beanFactory); } else if (bean instanceof TraceLoadBalancerFeignClient) { return bean; } - return new TraceFeignClient(this.beanFactory, (Client) bean); + return new LazyTracingFeignClient(this.beanFactory, (Client) bean); } return bean; } @@ -54,4 +54,5 @@ private SpringClientFactory clientFactory() { } return this.springClientFactory; } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java index 4efc576384..8595265c9c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceLoadBalancerFeignClient.java @@ -1,12 +1,15 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import java.io.IOException; + +import feign.Client; +import feign.Request; +import feign.Response; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; -import feign.Client; - /** * We need to wrap the {@link LoadBalancerFeignClient} into a trace representation * due to casts in {@link org.springframework.cloud.netflix.feign.FeignClientFactoryBean}. @@ -16,13 +19,19 @@ */ class TraceLoadBalancerFeignClient extends LoadBalancerFeignClient { + private final BeanFactory beanFactory; + TraceLoadBalancerFeignClient(Client delegate, CachingSpringLoadBalancerFactory lbClientFactory, SpringClientFactory clientFactory, BeanFactory beanFactory) { - super(wrap(delegate, beanFactory), lbClientFactory, clientFactory); + super(delegate, lbClientFactory, clientFactory); + this.beanFactory = beanFactory; } - private static Client wrap(Client delegate, BeanFactory beanFactory) { - return (Client) new TraceFeignObjectWrapper(beanFactory).wrap(delegate); + @Override public Response execute(Request request, Request.Options options) + throws IOException { + return ((Client) new TraceFeignObjectWrapper(this.beanFactory).wrap( + (Client) TraceLoadBalancerFeignClient.super::execute)).execute(request, options); } + } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java new file mode 100644 index 0000000000..f943dfb0ce --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java @@ -0,0 +1,113 @@ +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpClientHandler; +import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; +import feign.Client; +import feign.Request; +import feign.Response; + +/** + * Feign client wrapper + * + * @author Marcin Grzejsczak + * @since 2.0.0 + */ +final class TracingFeignClient implements Client { + static final Propagation.Setter>, String> SETTER = + new Propagation.Setter>, String>() { + @Override public void put(Map> carrier, String key, + String value) { + if (!carrier.containsKey(key)) { + List list = new ArrayList<>(); + list.add(value); + carrier.put(key, list); + } + } + + @Override public String toString() { + return "Map::set"; + } + }; + + public static Client create(Tracing tracing, Client delegate) { + return create(HttpTracing.create(tracing), delegate); + } + + public static Client create(HttpTracing httpTracing, Client delegate) { + return new TracingFeignClient(httpTracing, delegate); + } + + final Tracer tracer; + final Client delegate; + final HttpClientHandler handler; + final TraceContext.Injector>> injector; + + TracingFeignClient(HttpTracing httpTracing, Client delegate) { + this.tracer = httpTracing.tracing().tracer(); + this.handler = HttpClientHandler.create(httpTracing, new HttpAdapter()); + this.injector = httpTracing.tracing().propagation().injector(SETTER); + this.delegate = delegate; + } + + @Override public Response execute(Request request, Request.Options options) + throws IOException { + Map> headers = new HashMap<>(request.headers()); + Span span = this.handler.handleSend(this.injector, headers, request); + Response response = null; + Throwable error = null; + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + return response = this.delegate.execute(modifiedRequest(request, headers), options); + } + catch (IOException | RuntimeException | Error e) { + error = e; + throw e; + } + finally { + this.handler.handleReceive(response, error, span); + } + } + + private Request modifiedRequest(Request request, Map> headers) { + String method = request.method(); + String url = request.url(); + byte[] body = request.body(); + Charset charset = request.charset(); + return Request.create(method, url, headers, body, charset); + } + + static final class HttpAdapter + extends brave.http.HttpClientAdapter { + + @Override public String method(Request request) { + return request.method(); + } + + @Override public String url(Request request) { + return request.url(); + } + + @Override public String requestHeader(Request request, String name) { + Collection result = request.headers().get(name); + return result != null && result.iterator().hasNext() ? + result.iterator().next() : + null; + } + + @Override public Integer statusCode(Response response) { + return response.status(); + } + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java new file mode 100644 index 0000000000..0b2c9aec7a --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/AbstractTraceZuulFilter.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.zuul; + +import javax.servlet.http.HttpServletResponse; + +import brave.Tracer; +import brave.http.HttpClientHandler; +import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * The pre and post filters use the same handler logic + * + * @author Marcin Grzejszczak + * @since 2.0.0 + */ +abstract class AbstractTraceZuulFilter extends ZuulFilter { + + static final String ZUUL_CURRENT_SPAN = + AbstractTraceZuulFilter.class.getName() + ".CURRENT_SPAN"; + + static final Propagation.Setter SETTER = new Propagation.Setter() { + @Override public void put(RequestContext carrier, String key, String value) { + carrier.getZuulRequestHeaders().put(key, value); + } + + @Override public String toString() { + return "RequestContext::getZuulRequestHeaders::put"; + } + }; + + final Tracer tracer; + HttpClientHandler handler; + TraceContext.Injector injector; + + AbstractTraceZuulFilter(HttpTracing httpTracing) { + this.tracer = httpTracing.tracing().tracer(); + this.handler = HttpClientHandler + .create(httpTracing, new AbstractTraceZuulFilter.HttpAdapter()); + this.injector = httpTracing.tracing().propagation().injector(SETTER); + } + + static final class HttpAdapter + extends brave.http.HttpClientAdapter { + + @Override public String method(RequestContext request) { + return request.getRequest().getMethod(); + } + + @Override public String url(RequestContext request) { + return request.getRequest().getRequestURI(); + } + + @Override public String requestHeader(RequestContext request, String name) { + Object result = request.getZuulRequestHeaders().get(name); + return result != null ? result.toString() : null; + } + + @Override public Integer statusCode(HttpServletResponse response) { + return response.getStatus(); + } + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java index 2119d0065a..2a2e6d4300 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - +import brave.http.HttpClientAdapter; +import brave.http.HttpTracing; +import brave.propagation.Propagation; import org.apache.http.Header; import org.apache.http.client.methods.RequestBuilder; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; /** * Customization of a Ribbon request for Apache HttpClient @@ -31,9 +28,23 @@ * @author Marcin Grzejszczak * @since 1.1.0 */ -class ApacheHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { +class ApacheHttpClientRibbonRequestCustomizer extends + SpanInjectingRibbonRequestCustomizer { + + static final Propagation.Setter SETTER = new Propagation.Setter() { + @Override public void put(RequestBuilder carrier, String key, String value) { + if (carrier.getFirstHeader(key) != null) { + return; + } + carrier.addHeader(key, value); + } - ApacheHttpClientRibbonRequestCustomizer(Tracer tracer) { + @Override public String toString() { + return "RequestBuilder::addHeader"; + } + }; + + ApacheHttpClientRibbonRequestCustomizer(HttpTracing tracer) { super(tracer); } @@ -43,20 +54,31 @@ public boolean accepts(Class aClass) { } @Override - protected SpanTextMap toSpanTextMap(final RequestBuilder context) { - return new SpanTextMap() { - @Override public Iterator> iterator() { - Map map = new HashMap<>(); - for (Header header : context.build().getAllHeaders()) { - map.put(header.getName(), header.getValue()); + protected HttpClientAdapter handlerClientAdapter() { + return new HttpClientAdapter() { + @Override public String method(RequestBuilder request) { + return request.getMethod(); + } + + @Override public String url(RequestBuilder request) { + return request.getUri().toString(); + } + + @Override public String requestHeader(RequestBuilder request, String name) { + Header header = request.getFirstHeader(name); + if (header == null) { + return null; } - return map.entrySet().iterator(); + return header.getValue(); } - @Override public void put(String key, String value) { - context.setHeader(key, value); + @Override public Integer statusCode(RequestBuilder response) { + throw new UnsupportedOperationException("response not supported"); } }; } + @Override protected Propagation.Setter setter() { + return SETTER; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java new file mode 100644 index 0000000000..9e41f00ea8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/HttpAdapter.java @@ -0,0 +1,26 @@ +package org.springframework.cloud.sleuth.instrument.zuul; + +import javax.servlet.http.HttpServletResponse; + +import com.netflix.zuul.context.RequestContext; + +final class HttpAdapter + extends brave.http.HttpClientAdapter { + + @Override public String method(RequestContext request) { + return request.getRequest().getMethod(); + } + + @Override public String url(RequestContext request) { + return request.getRequest().getRequestURI(); + } + + @Override public String requestHeader(RequestContext request, String name) { + Object result = request.getZuulRequestHeaders().get(name); + return result != null ? result.toString() : null; + } + + @Override public Integer statusCode(HttpServletResponse response) { + return response.getStatus(); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java index 282020596e..6bc3f40ab3 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,9 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; - +import brave.http.HttpClientAdapter; +import brave.http.HttpTracing; +import brave.propagation.Propagation; import okhttp3.Request; /** @@ -32,9 +27,24 @@ * @author Marcin Grzejszczak * @since 1.1.0 */ -class OkHttpClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { +class OkHttpClientRibbonRequestCustomizer extends + SpanInjectingRibbonRequestCustomizer { + + static final Propagation.Setter SETTER = + new Propagation.Setter() { + @Override public void put(Request.Builder carrier, String key, String value) { + if (carrier.build().header(key) != null) { + return; + } + carrier.addHeader(key, value); + } + + @Override public String toString() { + return "RequestBuilder::addHeader"; + } + }; - OkHttpClientRibbonRequestCustomizer(Tracer tracer) { + OkHttpClientRibbonRequestCustomizer(HttpTracing tracer) { super(tracer); } @@ -44,21 +54,27 @@ public boolean accepts(Class aClass) { } @Override - protected SpanTextMap toSpanTextMap(final Request.Builder context) { - return new SpanTextMap() { - @Override public Iterator> iterator() { - Map map = new HashMap<>(); - for (Map.Entry> entry : context.build().headers().toMultimap().entrySet()) { - if (!entry.getValue().isEmpty()) { - map.put(entry.getKey(), entry.getValue().get(0)); - } - } - return map.entrySet().iterator(); + protected HttpClientAdapter handlerClientAdapter() { + return new HttpClientAdapter() { + @Override public String method(Request.Builder request) { + return request.build().method(); } - @Override public void put(String key, String value) { - context.header(key, value); + @Override public String url(Request.Builder request) { + return request.build().url().uri().toString(); + } + + @Override public String requestHeader(Request.Builder request, String name) { + return request.build().header(name); + } + + @Override public Integer statusCode(Request.Builder response) { + throw new UnsupportedOperationException("response not supported"); } }; } + + @Override protected Propagation.Setter setter() { + return SETTER; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java deleted file mode 100644 index 43110d2517..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RequestContextTextMap.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.zuul; - -import java.util.Iterator; -import java.util.Map; - -import com.netflix.zuul.context.RequestContext; - -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * A {@link SpanTextMap} abstraction over {@link RequestContext} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -class RequestContextTextMap implements SpanTextMap { - - private final RequestContext carrier; - - RequestContextTextMap(RequestContext carrier) { - this.carrier = carrier; - } - - @Override - public Iterator> iterator() { - return this.carrier.getZuulRequestHeaders().entrySet().iterator(); - } - - @Override - public void put(String key, String value) { - this.carrier.getZuulRequestHeaders().put(key, value); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java index 3b882e2555..7912a07ca8 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,36 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.util.Iterator; -import java.util.Map; +import brave.http.HttpClientAdapter; +import brave.http.HttpTracing; +import brave.propagation.Propagation; import com.netflix.client.http.HttpRequest; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; - /** * Customization of a Ribbon request for Netflix HttpClient * * @author Marcin Grzejszczak * @since 1.1.0 */ -class RestClientRibbonRequestCustomizer extends SpanInjectingRibbonRequestCustomizer { +class RestClientRibbonRequestCustomizer extends + SpanInjectingRibbonRequestCustomizer { + + static final Propagation.Setter SETTER = + new Propagation.Setter() { + @Override public void put(HttpRequest.Builder carrier, String key, String value) { + if (carrier.build().getHttpHeaders().containsHeader(key)) { + return; + } + carrier.header(key, value); + } - RestClientRibbonRequestCustomizer(Tracer tracer) { + @Override public String toString() { + return "RequestBuilder::addHeader"; + } + }; + + RestClientRibbonRequestCustomizer(HttpTracing tracer) { super(tracer); } @@ -42,16 +55,28 @@ public boolean accepts(Class aClass) { } @Override - protected SpanTextMap toSpanTextMap(final HttpRequest.Builder context) { - context.build().getHttpHeaders(); - return new SpanTextMap() { - @Override public Iterator> iterator() { - return context.build().getHttpHeaders().getAllHeaders().iterator(); + protected HttpClientAdapter handlerClientAdapter() { + return new HttpClientAdapter() { + @Override public String method(HttpRequest.Builder request) { + return request.build().getVerb().verb(); + } + + @Override public String url(HttpRequest.Builder request) { + return request.build().getUri().toString(); + } + + @Override + public String requestHeader(HttpRequest.Builder request, String name) { + return request.build().getHttpHeaders().getFirstValue(name); } - @Override public void put(String key, String value) { - context.header(key, value); + @Override public Integer statusCode(HttpRequest.Builder response) { + throw new UnsupportedOperationException("response not supported"); } }; } + + @Override protected Propagation.Setter setter() { + return SETTER; + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java index c0b9181626..5b6162cd62 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/SpanInjectingRibbonRequestCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,73 +16,60 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.lang.invoke.MethodHandles; - +import brave.Span; +import brave.Tracer; +import brave.http.HttpClientHandler; +import brave.http.HttpTracing; +import brave.propagation.Propagation; +import brave.propagation.TraceContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanInjector; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; /** * Abstraction over customization of Ribbon Requests. All clients will inject the span * into their respective context. The only difference is how those contexts set the headers. - * In order to add a new implementation of the {@link RibbonRequestCustomizer} it's - * necessary only to provide the {@link RibbonRequestCustomizer#accepts(Class)} method - * with the context class name and {@link SpanInjectingRibbonRequestCustomizer#toSpanTextMap(Object)} - * to tell Sleuth how to set a header using the particular library. * * @author Marcin Grzejszczak * @since 1.1.0 */ -abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer, - SpanInjector { +abstract class SpanInjectingRibbonRequestCustomizer implements RibbonRequestCustomizer { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(SpanInjectingRibbonRequestCustomizer.class); private final Tracer tracer; + HttpClientHandler handler; + TraceContext.Injector injector; - SpanInjectingRibbonRequestCustomizer(Tracer tracer) { - this.tracer = tracer; + SpanInjectingRibbonRequestCustomizer(HttpTracing httpTracing) { + this.tracer = httpTracing.tracing().tracer(); + this.handler = HttpClientHandler + .create(httpTracing, handlerClientAdapter()); + this.injector = httpTracing.tracing().propagation().injector(setter()); } @Override public void customize(T context) { Span span = getCurrentSpan(); - inject(span, toSpanTextMap(context)); - span.logEvent(Span.CLIENT_SEND); - if (log.isDebugEnabled()) { - log.debug("Span in the RibbonRequestCustomizer is" + span); - } - } - - protected abstract SpanTextMap toSpanTextMap(T context); - - @Override - public void inject(Span span, SpanTextMap carrier) { if (span == null) { - carrier.put(Span.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED); + this.handler.handleSend(this.injector, context); return; } - carrier.put(Span.SAMPLED_NAME, span.isExportable() ? - Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - carrier.put(Span.TRACE_ID_NAME, span.traceIdString()); - carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - carrier.put(Span.SPAN_NAME_NAME, span.getName()); - if (getParentId(span) != null) { - carrier.put(Span.PARENT_ID_NAME, Span.idToHex(getParentId(span))); + Span childSpan = this.handler.handleSend(this.injector, context, span); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(childSpan)) { + if (log.isDebugEnabled()) { + log.debug("Span in the RibbonRequestCustomizer is" + span); + } + } finally { + childSpan.finish(); } - carrier.put(Span.PROCESS_ID_NAME, span.getProcessId()); } + + protected abstract brave.http.HttpClientAdapter handlerClientAdapter(); - private Long getParentId(Span span) { - return !span.getParents().isEmpty() - ? span.getParents().get(0) : null; - } + protected abstract Propagation.Setter setter(); - private Span getCurrentSpan() { - return this.tracer.getCurrentSpan(); + Span getCurrentSpan() { + return this.tracer.currentSpan(); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java index 25ff205520..f3cd9c5347 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java @@ -16,13 +16,14 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.lang.invoke.MethodHandles; +import javax.servlet.http.HttpServletResponse; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; @@ -33,16 +34,20 @@ * @author Dave Syer * @since 1.0.0 */ -public class TracePostZuulFilter extends ZuulFilter { +public class TracePostZuulFilter extends AbstractTraceZuulFilter { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(TracePostZuulFilter.class); - private final Tracer tracer; - private final TraceKeys traceKeys; + public static ZuulFilter create(Tracing tracing) { + return new TracePostZuulFilter(HttpTracing.create(tracing)); + } + + public static ZuulFilter create(HttpTracing httpTracing) { + return new TracePostZuulFilter(httpTracing); + } - public TracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) { - this.tracer = tracer; - this.traceKeys = traceKeys; + TracePostZuulFilter(HttpTracing httpTracing) { + super(httpTracing); } @Override @@ -52,21 +57,30 @@ public boolean shouldFilter() { @Override public Object run() { - this.tracer.continueSpan(getCurrentSpan()); - // TODO: the client sent event should come from the client not the filter! - getCurrentSpan().logEvent(Span.CLIENT_RECV); - if (log.isDebugEnabled()) { - log.debug("Closing current client span " + getCurrentSpan()); + Span span = getCurrentSpan(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + if (log.isDebugEnabled()) { + log.debug("Closing current client span " + span); + } + HttpServletResponse response = RequestContext.getCurrentContext() + .getResponse(); + this.handler.handleReceive(response, null, span); + } finally { + if (span != null) { + span.finish(); + } } - int httpStatus = RequestContext.getCurrentContext().getResponse().getStatus(); - if (httpStatus > 0) { - this.tracer.addTag(this.traceKeys.getHttp().getStatusCode(), - String.valueOf(httpStatus)); - } - this.tracer.close(getCurrentSpan()); return null; } + private Span getCurrentSpan() { + RequestContext ctx = RequestContext.getCurrentContext(); + if (ctx == null || ctx.getRequest() == null) { + return null; + } + return (Span) ctx.getRequest().getAttribute(ZUUL_CURRENT_SPAN); + } + @Override public String filterType() { return "post"; @@ -77,7 +91,4 @@ public int filterOrder() { return 0; } - private Span getCurrentSpan() { - return this.tracer.getCurrentSpan(); - } -} +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java index a9e59c3590..30fdd81582 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilter.java @@ -16,22 +16,20 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import com.netflix.zuul.ExecutionStatus; -import com.netflix.zuul.ZuulFilter; -import com.netflix.zuul.ZuulFilterResult; -import com.netflix.zuul.context.RequestContext; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceFilter; import org.springframework.cloud.sleuth.instrument.web.TraceRequestAttributes; -import java.lang.invoke.MethodHandles; -import java.net.URI; +import com.netflix.zuul.ExecutionStatus; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.ZuulFilterResult; +import com.netflix.zuul.context.RequestContext; /** * A pre request {@link ZuulFilter} that sets tracing related headers on the request @@ -40,95 +38,77 @@ * @author Dave Syer * @since 1.0.0 */ -public class TracePreZuulFilter extends ZuulFilter { +public class TracePreZuulFilter extends AbstractTraceZuulFilter { - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(TracePreZuulFilter.class); + private static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() + ".TRACE"; + private static final String TRACE_CLOSE_SPAN_REQUEST_ATTR = + TraceFilter.class.getName() + ".CLOSE_SPAN"; - private static final String TRACE_REQUEST_ATTR = TraceFilter.class.getName() - + ".TRACE"; - private static final String TRACE_CLOSE_SPAN_REQUEST_ATTR = TraceFilter.class.getName() - + ".CLOSE_SPAN"; + public static ZuulFilter create(Tracing tracing, ErrorParser errorParser) { + return new TracePreZuulFilter(HttpTracing.create(tracing), errorParser); + } - private static final String ZUUL_COMPONENT = "zuul"; + public static ZuulFilter create(HttpTracing httpTracing, ErrorParser errorParser) { + return new TracePreZuulFilter(httpTracing, errorParser); + } - private final Tracer tracer; - private final HttpSpanInjector spanInjector; - private final HttpTraceKeysInjector httpTraceKeysInjector; private final ErrorParser errorParser; - public TracePreZuulFilter(Tracer tracer, HttpSpanInjector spanInjector, - HttpTraceKeysInjector httpTraceKeysInjector, ErrorParser errorParser) { - this.tracer = tracer; - this.spanInjector = spanInjector; - this.httpTraceKeysInjector = httpTraceKeysInjector; + TracePreZuulFilter(HttpTracing httpTracing, ErrorParser errorParser) { + super(httpTracing); this.errorParser = errorParser; } - @Override - public boolean shouldFilter() { - return true; - } - - @Override - public Object run() { - getCurrentSpan().logEvent(Span.CLIENT_SEND); - return null; - } - - @Override - public ZuulFilterResult runFilter() { + @Override public ZuulFilterResult runFilter() { RequestContext ctx = RequestContext.getCurrentContext(); - Span span = getCurrentSpan(); - if (log.isDebugEnabled()) { - log.debug("Current span is " + span + ""); - } - markRequestAsHandled(ctx); - Span newSpan = this.tracer.createSpan(span.getName(), span); - newSpan.tag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, ZUUL_COMPONENT); - this.spanInjector.inject(newSpan, new RequestContextTextMap(ctx)); - this.httpTraceKeysInjector.addRequestTags(newSpan, URI.create(ctx.getRequest().getRequestURI()), ctx.getRequest().getMethod()); - if (log.isDebugEnabled()) { - log.debug("New Zuul Span is " + newSpan + ""); - } - if (log.isDebugEnabled()) { - log.debug("Setting attributes for TraceFilter to pick up later"); - } - RequestContext.getCurrentContext().getRequest().setAttribute(TRACE_REQUEST_ATTR, this.tracer.getCurrentSpan()); - RequestContext.getCurrentContext().getRequest().setAttribute(TRACE_CLOSE_SPAN_REQUEST_ATTR, true); - ZuulFilterResult result = super.runFilter(); - if (log.isDebugEnabled()) { - log.debug("Result of Zuul filter is [" + result.getStatus() + "]"); - } - if (ExecutionStatus.SUCCESS != result.getStatus()) { + Span span = this.handler.handleSend(this.injector, ctx); + ZuulFilterResult result = null; + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + markRequestAsHandled(ctx, span); if (log.isDebugEnabled()) { - log.debug("The result of Zuul filter execution was not successful thus " - + "will close the current span " + newSpan); + log.debug("New Zuul Span is " + span + ""); + } + result = super.runFilter(); + return result; + } + finally { + if (result != null && ExecutionStatus.SUCCESS != result.getStatus()) { + if (log.isDebugEnabled()) { + log.debug( + "The result of Zuul filter execution was not successful thus " + + "will close the current span " + span); + } + this.errorParser.parseErrorTags(span, result.getException()); + span.finish(); } - this.errorParser.parseErrorTags(newSpan, result.getException()); - this.tracer.close(newSpan); } - return result; } // TraceFilter will not create the "fallback" span - private void markRequestAsHandled(RequestContext ctx) { - ctx.getRequest().setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); - ctx.getRequest().setAttribute(TraceRequestAttributes.ERROR_HANDLED_SPAN_REQUEST_ATTR, "true"); + private void markRequestAsHandled(RequestContext ctx, Span span) { + ctx.getRequest() + .setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, "true"); + ctx.getRequest().setAttribute(TraceRequestAttributes.ERROR_HANDLED_SPAN_REQUEST_ATTR, + "true"); + ctx.getRequest().setAttribute(TRACE_REQUEST_ATTR, span); + ctx.getRequest().setAttribute(TRACE_CLOSE_SPAN_REQUEST_ATTR, true); + ctx.getRequest().setAttribute(ZUUL_CURRENT_SPAN, span); } - private Span getCurrentSpan() { - return this.tracer.getCurrentSpan(); - } - - - @Override - public String filterType() { + @Override public String filterType() { return "pre"; } - @Override - public int filterOrder() { + @Override public int filterOrder() { return 0; } -} + @Override public boolean shouldFilter() { + return true; + } + + @Override public Object run() { + return null; + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java index 9f1ced58e0..2b755e9fb2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,11 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import brave.Span; +import brave.http.HttpTracing; import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; /** * Propagates traces downstream via http headers that contain trace metadata. @@ -33,22 +32,40 @@ class TraceRibbonCommandFactory implements RibbonCommandFactory { private final RibbonCommandFactory delegate; - private final Tracer tracer; - private final HttpTraceKeysInjector httpTraceKeysInjector; + private final HttpTracing tracing; public TraceRibbonCommandFactory(RibbonCommandFactory delegate, - Tracer tracer, HttpTraceKeysInjector httpTraceKeysInjector) { + HttpTracing tracing) { this.delegate = delegate; - this.tracer = tracer; - this.httpTraceKeysInjector = httpTraceKeysInjector; + this.tracing = tracing; } @Override public RibbonCommand create(RibbonCommandContext context) { RibbonCommand ribbonCommand = this.delegate.create(context); - Span span = this.tracer.getCurrentSpan(); - this.httpTraceKeysInjector.addRequestTags(span, context.uri(), context.getMethod()); + Span span = this.tracing.tracing().tracer().currentSpan(); + this.tracing.clientParser().request(new TraceRibbonCommandFactory.HttpAdapter(), context, span); return ribbonCommand; } + static final class HttpAdapter + extends brave.http.HttpClientAdapter { + + @Override public String method(RibbonCommandContext request) { + return request.getMethod(); + } + + @Override public String url(RibbonCommandContext request) { + return request.getUri(); + } + + @Override public String requestHeader(RibbonCommandContext request, String name) { + Object result = request.getHeaders().getFirst(name); + return result != null ? result.toString() : null; + } + + @Override public Integer statusCode(RibbonCommand response) { + throw new UnsupportedOperationException("RibbonCommand doesn't support status code"); + } + } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java index a506059d5c..c4799b578a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,14 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import brave.http.HttpTracing; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; /** - * Post processor that wraps a {@link org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory} + * Post processor that wraps a {@link RibbonCommandFactory} * in its trace representation. * * @author Marcin Grzejszczak @@ -34,8 +33,7 @@ final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProcessor { private final BeanFactory beanFactory; - private Tracer tracer; - private HttpTraceKeysInjector httpTraceKeysInjector; + private HttpTracing tracing; TraceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -45,7 +43,7 @@ final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProces public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RibbonCommandFactory) { - return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, getTracer(), getHttpTraceKeysInjector()); + return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, tracing()); } return bean; } @@ -56,17 +54,10 @@ public Object postProcessAfterInitialization(Object bean, String beanName) return bean; } - Tracer getTracer() { - if (this.tracer == null) { - this.tracer = this.beanFactory.getBean(Tracer.class); + HttpTracing tracing() { + if (this.tracing == null) { + this.tracing = this.beanFactory.getBean(HttpTracing.class); } - return this.tracer; - } - - HttpTraceKeysInjector getHttpTraceKeysInjector() { - if (this.httpTraceKeysInjector == null) { - this.httpTraceKeysInjector = this.beanFactory.getBean(HttpTraceKeysInjector.class); - } - return this.httpTraceKeysInjector; + return this.tracing; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index bbb508e142..a72bbeaddc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,8 @@ */ package org.springframework.cloud.sleuth.instrument.zuul; -import com.netflix.client.http.HttpRequest; -import com.netflix.zuul.ZuulFilter; - +import brave.http.HttpTracing; +import okhttp3.Request; import org.apache.http.client.methods.RequestBuilder; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -26,17 +25,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import okhttp3.Request; +import com.netflix.client.http.HttpRequest; +import com.netflix.zuul.ZuulFilter; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} @@ -49,22 +45,21 @@ @ConditionalOnProperty(value = "spring.sleuth.zuul.enabled", matchIfMissing = true) @ConditionalOnWebApplication @ConditionalOnClass(ZuulFilter.class) -@ConditionalOnBean(Tracer.class) +@ConditionalOnBean(HttpTracing.class) @AutoConfigureAfter(TraceWebServletAutoConfiguration.class) public class TraceZuulAutoConfiguration { @Bean @ConditionalOnMissingBean - public TracePreZuulFilter tracePreZuulFilter(Tracer tracer, - HttpSpanInjector spanInjector, HttpTraceKeysInjector httpTraceKeysInjector, + public ZuulFilter tracePreZuulFilter(HttpTracing tracer, ErrorParser errorParser) { - return new TracePreZuulFilter(tracer, spanInjector, httpTraceKeysInjector, errorParser); + return TracePreZuulFilter.create(tracer, errorParser); } @Bean @ConditionalOnMissingBean - public TracePostZuulFilter tracePostZuulFilter(Tracer tracer, TraceKeys traceKeys) { - return new TracePostZuulFilter(tracer, traceKeys); + public ZuulFilter tracePostZuulFilter(HttpTracing tracer) { + return TracePostZuulFilter.create(tracer); } @Bean @@ -74,19 +69,19 @@ public TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanP @Bean @ConditionalOnClass(name = "com.netflix.client.http.HttpRequest.Builder") - public RibbonRequestCustomizer restClientRibbonRequestCustomizer(Tracer tracer) { + public RibbonRequestCustomizer restClientRibbonRequestCustomizer(HttpTracing tracer) { return new RestClientRibbonRequestCustomizer(tracer); } @Bean @ConditionalOnClass(name = "org.apache.http.client.methods.RequestBuilder") - public RibbonRequestCustomizer apacheHttpRibbonRequestCustomizer(Tracer tracer) { + public RibbonRequestCustomizer apacheHttpRibbonRequestCustomizer(HttpTracing tracer) { return new ApacheHttpClientRibbonRequestCustomizer(tracer); } @Bean @ConditionalOnClass(name = "okhttp3.Request.Builder") - public RibbonRequestCustomizer okHttpRibbonRequestCustomizer(Tracer tracer) { + public RibbonRequestCustomizer okHttpRibbonRequestCustomizer(HttpTracing tracer) { return new OkHttpClientRibbonRequestCustomizer(tracer); } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java index 567709569e..e16b0e6863 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulHandlerMappingBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; import org.springframework.cloud.sleuth.instrument.web.TraceHandlerInterceptor; +import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping; /** * Bean post processor that wraps {@link ZuulHandlerMapping} in its diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java deleted file mode 100644 index ff0a48bb82..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/NoOpSpanLogger.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.log; - -import org.springframework.cloud.sleuth.Span; - -/** - * Logger of Spans that does nothing - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class NoOpSpanLogger implements SpanLogger { - @Override - public void logStartedSpan(Span parent, Span span) { - - } - - @Override - public void logContinuedSpan(Span span) { - - } - - @Override - public void logStoppedSpan(Span parent, Span span) { - - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java index a1560b2c24..68e0a6550d 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthLogAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,32 @@ package org.springframework.cloud.sleuth.log; +import brave.propagation.CurrentTraceContext; import org.slf4j.MDC; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} - * enables a {@link Slf4jSpanLogger} that prints tracing information in the logs. + * enables a {@link Slf4jCurrentTraceContext} that prints tracing information in the logs. *

- * Note: this is only available for Slf4j * * @author Spencer Gibb - * @since 1.0.0 + * @author Marcin Grzejszczak + * @since 2.0.0 */ @Configuration @ConditionalOnProperty(value="spring.sleuth.enabled", matchIfMissing=true) +@AutoConfigureBefore(TraceAutoConfiguration.class) public class SleuthLogAutoConfiguration { @Configuration @@ -46,23 +52,31 @@ protected static class Slf4jConfiguration { @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) @ConditionalOnMissingBean - public SpanLogger slf4jSpanLogger(SleuthSlf4jProperties sleuthSlf4jProperties) { - // Sets up MDC entries X-B3-TraceId and X-B3-SpanId - return new Slf4jSpanLogger(sleuthSlf4jProperties.getNameSkipPattern()); + public CurrentTraceContext slf4jSpanLogger() { + return Slf4jCurrentTraceContext.create(); } @Bean - @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", havingValue = "false") - @ConditionalOnMissingBean - public SpanLogger noOpSlf4jSpanLogger() { - return new NoOpSpanLogger(); + @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) + @ConditionalOnBean(CurrentTraceContext.class) + public BeanPostProcessor slf4jSpanLoggerBPP() { + return new Slf4jBeanPostProcessor(); } - } - @Bean - @ConditionalOnMissingClass("org.slf4j.MDC") - @ConditionalOnMissingBean - public SpanLogger defaultLoggedSpansHandler() { - return new NoOpSpanLogger(); + class Slf4jBeanPostProcessor implements BeanPostProcessor { + + @Override public Object postProcessBeforeInitialization(Object bean, + String beanName) throws BeansException { + return bean; + } + + @Override public Object postProcessAfterInitialization(Object bean, + String beanName) throws BeansException { + if (bean instanceof CurrentTraceContext && !(bean instanceof Slf4jCurrentTraceContext)) { + return Slf4jCurrentTraceContext.create((CurrentTraceContext) bean); + } + return bean; + } + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java index 1e11d8431f..b9e180b83c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SleuthSlf4jProperties.java @@ -12,15 +12,10 @@ public class SleuthSlf4jProperties { /** - * Enable a {@link Slf4jSpanLogger} that prints tracing information in the logs. + * Enable a {@link Slf4jCurrentTraceContext} that prints tracing information in the logs. */ private boolean enabled = true; - /** - * Name pattern for which span should not be printed in the logs. - */ - private String nameSkipPattern = ""; - public boolean isEnabled() { return this.enabled; } @@ -28,12 +23,4 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } - - public String getNameSkipPattern() { - return this.nameSkipPattern; - } - - public void setNameSkipPattern(String nameSkipPattern) { - this.nameSkipPattern = nameSkipPattern; - } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java new file mode 100644 index 0000000000..c520f3e217 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java @@ -0,0 +1,131 @@ +package org.springframework.cloud.sleuth.log; + +import brave.internal.HexCodec; +import brave.internal.Nullable; +import brave.propagation.CurrentTraceContext; +import brave.propagation.TraceContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * Adds {@linkplain org.slf4j.MDC} properties "traceId", "parentId", "spanId" and "spanExportable" when a {@link + * brave.Tracer#currentSpan() span is current}. These can be used in log correlation. + * Supports backward compatibility of MDC entries by adding legacy "X-B3" entries to MDC context + * "X-B3-TraceId", "X-B3-ParentSpanId", "X-B3-SpanId" and "X-B3-Sampled" + * + * @author Marcin Grzejszczak + * + * @since 2.0.0 + */ +public final class Slf4jCurrentTraceContext extends CurrentTraceContext { + + // Backward compatibility for all logging patterns + private static final String LEGACY_EXPORTABLE_NAME = "X-Span-Export"; + private static final String LEGACY_PARENT_ID_NAME = "X-B3-ParentSpanId"; + private static final String LEGACY_TRACE_ID_NAME = "X-B3-TraceId"; + private static final String LEGACY_SPAN_ID_NAME = "X-B3-SpanId"; + + private static final Logger log = LoggerFactory + .getLogger(Slf4jCurrentTraceContext.class); + + public static Slf4jCurrentTraceContext create() { + return create(CurrentTraceContext.Default.inheritable()); + } + + public static Slf4jCurrentTraceContext create(CurrentTraceContext delegate) { + return new Slf4jCurrentTraceContext(delegate); + } + + final CurrentTraceContext delegate; + + Slf4jCurrentTraceContext(CurrentTraceContext delegate) { + if (delegate == null) + throw new NullPointerException("delegate == null"); + this.delegate = delegate; + } + + @Override public TraceContext get() { + return this.delegate.get(); + } + + @Override public Scope newScope(@Nullable TraceContext currentSpan) { + final String previousTraceId = MDC.get("traceId"); + final String previousParentId = MDC.get("parentId"); + final String previousSpanId = MDC.get("spanId"); + final String spanExportable = MDC.get("spanExportable"); + final String legacyPreviousTraceId = MDC.get(LEGACY_TRACE_ID_NAME); + final String legacyPreviousParentId = MDC.get(LEGACY_PARENT_ID_NAME); + final String legacyPreviousSpanId = MDC.get(LEGACY_SPAN_ID_NAME); + final String legacySpanExportable = MDC.get(LEGACY_EXPORTABLE_NAME); + + if (currentSpan != null) { + String traceIdString = currentSpan.traceIdString(); + MDC.put("traceId", traceIdString); + MDC.put(LEGACY_TRACE_ID_NAME, traceIdString); + String parentId = currentSpan.parentId() != null ? + HexCodec.toLowerHex(currentSpan.parentId()) : + null; + replace("parentId", parentId); + replace(LEGACY_PARENT_ID_NAME, parentId); + String spanId = HexCodec.toLowerHex(currentSpan.spanId()); + MDC.put("spanId", spanId); + MDC.put(LEGACY_SPAN_ID_NAME, spanId); + String sampled = String.valueOf(currentSpan.sampled()); + MDC.put("spanExportable", sampled); + MDC.put(LEGACY_EXPORTABLE_NAME, sampled); + log("Starting span: {}", currentSpan); + if (currentSpan.parentId() != null) { + if (log.isTraceEnabled()) { + log.trace("With parent: {}", currentSpan.parentId()); + } + } + } + else { + MDC.remove("traceId"); + MDC.remove("parentId"); + MDC.remove("spanId"); + MDC.remove("spanExportable"); + MDC.remove(LEGACY_TRACE_ID_NAME); + MDC.remove(LEGACY_PARENT_ID_NAME); + MDC.remove(LEGACY_SPAN_ID_NAME); + MDC.remove(LEGACY_EXPORTABLE_NAME); + } + + Scope scope = this.delegate.newScope(currentSpan); + + class ThreadContextCurrentTraceContextScope implements Scope { + @Override public void close() { + log("Closing span: {}", currentSpan); + scope.close(); + replace("traceId", previousTraceId); + replace("parentId", previousParentId); + replace("spanId", previousSpanId); + replace("spanExportable", spanExportable); + replace(LEGACY_TRACE_ID_NAME, legacyPreviousTraceId); + replace(LEGACY_PARENT_ID_NAME, legacyPreviousParentId); + replace(LEGACY_SPAN_ID_NAME, legacyPreviousSpanId); + replace(LEGACY_EXPORTABLE_NAME, legacySpanExportable); + } + } + return new ThreadContextCurrentTraceContextScope(); + } + + private void log(String text, TraceContext span) { + if (span != null) { + return; + } + if (log.isTraceEnabled()) { + log.trace(text, span); + } + } + + static void replace(String key, @Nullable String value) { + if (value != null) { + MDC.put(key, value); + } + else { + MDC.remove(key); + } + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java deleted file mode 100644 index ecc1e41bdd..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.log; - -import java.util.regex.Pattern; - -import org.slf4j.Logger; -import org.slf4j.MDC; -import org.springframework.cloud.sleuth.Span; - -/** - * Span listener that logs to the console when a span got started / stopped / continued. - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class Slf4jSpanLogger implements SpanLogger { - - private final Logger log; - private final Pattern nameSkipPattern; - - public Slf4jSpanLogger(String nameSkipPattern) { - this.nameSkipPattern = Pattern.compile(nameSkipPattern); - this.log = org.slf4j.LoggerFactory.getLogger(Slf4jSpanLogger.class); - } - - Slf4jSpanLogger(String nameSkipPattern, Logger log) { - this.nameSkipPattern = Pattern.compile(nameSkipPattern); - this.log = log; - } - - @Override - public void logStartedSpan(Span parent, Span span) { - MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); - MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); - log("Starting span: {}", span); - if (parent != null) { - log("With parent: {}", parent); - MDC.put(Span.PARENT_ID_NAME, Span.idToHex(parent.getSpanId())); - } - } - - @Override - public void logContinuedSpan(Span span) { - MDC.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - MDC.put(Span.TRACE_ID_NAME, span.traceIdString()); - MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(span.isExportable())); - setParentIdIfPresent(span); - log("Continued span: {}", span); - } - - private void setParentIdIfPresent(Span span) { - if (!span.getParents().isEmpty()) { - MDC.put(Span.PARENT_ID_NAME, Span.idToHex(span.getParents().get(0))); - } - } - - @Override - public void logStoppedSpan(Span parent, Span span) { - if (span != null) { - log("Stopped span: {}", span); - } - if (span != null && parent != null) { - log("With parent: {}", parent); - MDC.put(Span.SPAN_ID_NAME, Span.idToHex(parent.getSpanId())); - MDC.put(Span.SPAN_EXPORT_NAME, String.valueOf(parent.isExportable())); - setParentIdIfPresent(parent); - } - else { - MDC.remove(Span.SPAN_ID_NAME); - MDC.remove(Span.SPAN_EXPORT_NAME); - MDC.remove(Span.TRACE_ID_NAME); - MDC.remove(Span.PARENT_ID_NAME); - } - } - - private void log(String text, Span span) { - if (span != null && this.nameSkipPattern.matcher(span.getName()).matches()) { - return; - } - if (this.log.isTraceEnabled()) { - this.log.trace(text, span); - } - } - -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java deleted file mode 100644 index 4bed01f22c..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/SpanLogger.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.log; - -import org.springframework.cloud.sleuth.Span; - -/** - * Contract for implementations responsible for logging Spans - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public interface SpanLogger { - - /** - * Logic to run when a Span gets started - * - * @param parent - maybe be nullable - * @param span - current span - */ - void logStartedSpan(Span parent, Span span); - - /** - * Logic to run when a Span gets continued - */ - void logContinuedSpan(Span span); - - /** - * Logic to run when a Span gets stopped (closed or detached) - * - * @param parent - maybe be nullable - * @param span - current span - */ - void logStoppedSpan(Span parent, Span span); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/CounterServiceBasedSpanMetricReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/CounterServiceBasedSpanMetricReporter.java deleted file mode 100644 index fa90434820..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/CounterServiceBasedSpanMetricReporter.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.cloud.sleuth.metric; - -import io.micrometer.core.instrument.Counter; - -/** - * Service to operate on accepted and dropped spans statistics. - * Operates on a {@link Counter} underneath - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class CounterServiceBasedSpanMetricReporter implements SpanMetricReporter { - private final Counter acceptedSpansCounter; - private final Counter droppedSpansCounter; - - public CounterServiceBasedSpanMetricReporter(Counter acceptedSpansCounter, - Counter droppedSpansCounter) { - this.acceptedSpansCounter = acceptedSpansCounter; - this.droppedSpansCounter = droppedSpansCounter; - } - - @Override - public void incrementAcceptedSpans(long quantity) { - this.acceptedSpansCounter.increment(quantity); - } - - @Override - public void incrementDroppedSpans(long quantity) { - this.droppedSpansCounter.increment(quantity); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/NoOpSpanMetricReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/NoOpSpanMetricReporter.java deleted file mode 100644 index 1c40396f05..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/NoOpSpanMetricReporter.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.springframework.cloud.sleuth.metric; - -/** - * {@link SpanMetricReporter} that does nothing - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public class NoOpSpanMetricReporter implements SpanMetricReporter { - - public void incrementAcceptedSpans(long quantity) { - - } - - public void incrementDroppedSpans(long quantity) { - - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java deleted file mode 100644 index 0d88d0a45d..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SleuthMetricProperties.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.springframework.cloud.sleuth.metric; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Sleuth related metrics - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -@ConfigurationProperties("spring.sleuth.metric") -public class SleuthMetricProperties { - - /** - * Enable calculation of accepted and dropped spans through {@link org.springframework.boot.actuate.metrics.CounterService} - */ - private boolean enabled = true; - - private Span span = new Span(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public Span getSpan() { - return this.span; - } - - public void setSpan(Span span) { - this.span = span; - } - - public static class Span { - - private String acceptedName = "counter.span.accepted"; - - private String droppedName = "counter.span.dropped"; - - public String getAcceptedName() { - return this.acceptedName; - } - - public void setAcceptedName(String acceptedName) { - this.acceptedName = acceptedName; - } - - public String getDroppedName() { - return this.droppedName; - } - - public void setDroppedName(String droppedName) { - this.droppedName = droppedName; - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SpanMetricReporter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SpanMetricReporter.java deleted file mode 100644 index 9da29873cc..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/SpanMetricReporter.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.springframework.cloud.sleuth.metric; - -/** - * Contract for a service that measures the number of accepted / dropped spans. - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -public interface SpanMetricReporter { - - /** - * Called when spans are submitted to span collector for processing. - * - * @param quantity the number of spans accepted. - */ - void incrementAcceptedSpans(long quantity); - - /** - * Called when spans become lost for any reason and won't be delivered to the target collector. - * - * @param quantity the number of spans dropped. - */ - void incrementDroppedSpans(long quantity); -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java deleted file mode 100644 index 4744769489..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/metric/TraceMetricsAutoConfiguration.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.metric; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} - * enables Sleuth related metrics reporting - * - * @author Marcin Grzejszczak - * @since 1.0.0 - */ -@Configuration -@ConditionalOnProperty(value = "spring.sleuth.metric.enabled", matchIfMissing = true) -@EnableConfigurationProperties -public class TraceMetricsAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public SleuthMetricProperties sleuthMetricProperties() { - return new SleuthMetricProperties(); - } - - @Configuration - @ConditionalOnClass(MeterRegistry.class) - @ConditionalOnMissingBean(SpanMetricReporter.class) - protected static class CounterServiceSpanReporterConfig { - @Bean - @ConditionalOnBean(MeterRegistry.class) - public SpanMetricReporter spanReporterCounterService(SleuthMetricProperties sleuthMetricProperties, - MeterRegistry meterRegistry) { - Counter acceptedSpansCounter = Counter.builder( - sleuthMetricProperties.getSpan().getAcceptedName()).register(meterRegistry); - Counter droppedSpansCounter = Counter.builder( - sleuthMetricProperties.getSpan().getDroppedName()).register(meterRegistry); - return new CounterServiceBasedSpanMetricReporter(acceptedSpansCounter, droppedSpansCounter); - } - - @Bean - @ConditionalOnMissingBean(MeterRegistry.class) - public SpanMetricReporter noOpSpanReporterCounterService() { - return new NoOpSpanMetricReporter(); - } - } - - @Bean - @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry") - @ConditionalOnMissingBean(SpanMetricReporter.class) - public SpanMetricReporter noOpSpanReporterCounterService() { - return new NoOpSpanMetricReporter(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java deleted file mode 100644 index 1600d76510..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/AlwaysSampler.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.sampler; - -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; - -/** - * {@link Sampler} that traces each action - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class AlwaysSampler implements Sampler { - @Override - public boolean isSampled(Span span) { - return true; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java deleted file mode 100644 index 27ba35a1f2..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/IsTracingSampler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.sampler; - -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanAccessor; - -/** - * {@link Sampler} that traces only if there is already some tracing going on. - * - * @author Spencer Gibb - * @since 1.0.0 - * - * @see SpanAccessor#isTracing() - */ -public class IsTracingSampler implements Sampler { - - private final SpanAccessor accessor; - - public IsTracingSampler(SpanAccessor accessor) { - this.accessor = accessor; - } - - @Override - public boolean isSampled(Span span) { - return this.accessor.isTracing(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/NeverSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/NeverSampler.java deleted file mode 100644 index 513f2efee5..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/NeverSampler.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.sampler; - -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; - -/** - * {@link Sampler} that never traces - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class NeverSampler implements Sampler { - - public static final NeverSampler INSTANCE = new NeverSampler(); - - @Override - public boolean isSampled(Span span) { - return false; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSampler.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSampler.java similarity index 80% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSampler.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSampler.java index 10ac50815d..a3608008fe 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSampler.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSampler.java @@ -1,12 +1,11 @@ package org.springframework.cloud.sleuth.sampler; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; - import java.util.BitSet; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; +import brave.sampler.Sampler; + /** * This sampler is appropriate for low-traffic instrumentation (ex servers that each receive <100K * requests), or those who do not provision random trace ids. It not appropriate for collectors as @@ -23,23 +22,23 @@ * @author Adrian Cole * @since 1.0.0 */ -public class PercentageBasedSampler implements Sampler { +public class ProbabilityBasedSampler extends Sampler { private final AtomicInteger counter = new AtomicInteger(0); private final BitSet sampleDecisions; private final SamplerProperties configuration; - public PercentageBasedSampler(SamplerProperties configuration) { - int outOf100 = (int) (configuration.getPercentage() * 100.0f); + public ProbabilityBasedSampler(SamplerProperties configuration) { + int outOf100 = (int) (configuration.getProbability() * 100.0f); this.sampleDecisions = randomBitSet(100, outOf100, new Random()); this.configuration = configuration; } @Override - public boolean isSampled(Span currentSpan) { - if (this.configuration.getPercentage() == 0 || currentSpan == null) { + public boolean isSampled(long traceId) { + if (this.configuration.getProbability() == 0) { return false; - } else if (this.configuration.getPercentage() == 1.0f) { + } else if (this.configuration.getProbability() == 1.0f) { return true; } synchronized (this) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerProperties.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerProperties.java index 20c6c5d3f1..d4a4819c16 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerProperties.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/sampler/SamplerProperties.java @@ -17,13 +17,13 @@ public class SamplerProperties { * sampled. The precision is whole-numbers only (i.e. there's no support for 0.1% of * the traces). */ - private float percentage = 0.1f; + private float probability = 0.1f; - public float getPercentage() { - return this.percentage; + public float getProbability() { + return this.probability; } - public void setPercentage(float percentage) { - this.percentage = percentage; + public void setProbability(float probability) { + this.probability = probability; } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java deleted file mode 100644 index c96e963311..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/DefaultTracer.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.trace; - -import java.lang.invoke.MethodHandles; -import java.util.Random; -import java.util.concurrent.Callable; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceCallable; -import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceRunnable; -import org.springframework.cloud.sleuth.log.SpanLogger; -import org.springframework.cloud.sleuth.util.ExceptionUtils; -import org.springframework.cloud.sleuth.util.SpanNameUtil; - -/** - * Default implementation of {@link Tracer} - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public class DefaultTracer implements Tracer { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - private final Sampler defaultSampler; - - private final Random random; - - private final SpanNamer spanNamer; - - private final SpanLogger spanLogger; - - private final SpanReporter spanReporter; - - private final TraceKeys traceKeys; - - private final boolean traceId128; - - public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, - SpanLogger spanLogger, SpanReporter spanReporter, TraceKeys traceKeys) { - this(defaultSampler, random, spanNamer, spanLogger, spanReporter, false, traceKeys); - } - - public DefaultTracer(Sampler defaultSampler, Random random, SpanNamer spanNamer, - SpanLogger spanLogger, SpanReporter spanReporter, boolean traceId128, - TraceKeys traceKeys) { - this.defaultSampler = defaultSampler; - this.random = random; - this.spanNamer = spanNamer; - this.spanLogger = spanLogger; - this.spanReporter = spanReporter; - this.traceId128 = traceId128; - this.traceKeys = traceKeys != null ? traceKeys : new TraceKeys(); - } - - @Override - public Span createSpan(String name, Span parent) { - if (parent == null) { - return createSpan(name); - } - return continueSpan(createChild(parent, name)); - } - - @Override - public Span createSpan(String name) { - return this.createSpan(name, this.defaultSampler); - } - - @Override - public Span createSpan(String name, Sampler sampler) { - String shortenedName = SpanNameUtil.shorten(name); - Span span; - if (isTracing()) { - span = createChild(getCurrentSpan(), shortenedName); - } - else { - long id = createId(); - span = Span.builder().name(shortenedName) - .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L) - .traceId(id) - .spanId(id).build(); - if (sampler == null) { - sampler = this.defaultSampler; - } - span = sampledSpan(span, sampler); - this.spanLogger.logStartedSpan(null, span); - } - return continueSpan(span); - } - - @Override - public Span detach(Span span) { - if (span == null) { - return null; - } - Span cur = SpanContextHolder.getCurrentSpan(); - if (cur == null) { - if (log.isTraceEnabled()) { - log.trace("Span in the context is null so something has already detached the span. Won't do anything about it"); - } - return null; - } - if (!span.equals(cur)) { - ExceptionUtils.warn("Tried to detach trace span but " - + "it is not the current span: " + span - + ". You may have forgotten to close or detach " + cur); - } - else { - SpanContextHolder.removeCurrentSpan(); - } - return span.getSavedSpan(); - } - - @Override - public Span close(Span span) { - if (span == null) { - return null; - } - Span cur = SpanContextHolder.getCurrentSpan(); - final Span savedSpan = span.getSavedSpan(); - if (!span.equals(cur)) { - ExceptionUtils.warn( - "Tried to close span but it is not the current span: " + span - + ". You may have forgotten to close or detach " + cur); - } - else { - span.stop(); - if (savedSpan != null && span.getParents().contains(savedSpan.getSpanId())) { - this.spanReporter.report(span); - this.spanLogger.logStoppedSpan(savedSpan, span); - } - else { - if (!span.isRemote()) { - this.spanReporter.report(span); - this.spanLogger.logStoppedSpan(null, span); - } - } - SpanContextHolder.close(new SpanContextHolder.SpanFunction() { - @Override public void apply(Span span) { - DefaultTracer.this.spanLogger.logStoppedSpan(savedSpan, span); - } - }); - } - return savedSpan; - } - - Span createChild(Span parent, String name) { - String shortenedName = SpanNameUtil.shorten(name); - long id = createId(); - if (parent == null) { - Span span = Span.builder().name(shortenedName) - .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L) - .traceId(id) - .spanId(id).build(); - span = sampledSpan(span, this.defaultSampler); - this.spanLogger.logStartedSpan(null, span); - return span; - } - else { - if (!isTracing()) { - SpanContextHolder.push(parent, true); - } - Span span = Span.builder().name(shortenedName) - .traceIdHigh(parent.getTraceIdHigh()) - .traceId(parent.getTraceId()).parent(parent.getSpanId()).spanId(id) - .processId(parent.getProcessId()).savedSpan(parent) - .exportable(parent.isExportable()) - .baggage(parent.getBaggage()) - .build(); - this.spanLogger.logStartedSpan(parent, span); - return span; - } - } - - private Span sampledSpan(Span span, Sampler sampler) { - if (!sampler.isSampled(span)) { - // Copy everything, except set exportable to false - return Span.builder() - .begin(span.getBegin()) - .traceIdHigh(span.getTraceIdHigh()) - .traceId(span.getTraceId()) - .spanId(span.getSpanId()) - .name(span.getName()) - .exportable(false).build(); - } - return span; - } - - /** - * Encodes a timestamp into the upper 32-bits, so that it can be converted to an Amazon trace ID. - * - *

For example, an Amazon trace ID is composed of the following: {@code |-- 32 bits for epoch - * seconds -- | -- 96 bits for random data -- |} - * - *

To support this, {@link Span#getTraceIdHigh() traceIdHigh} holds the epoch seconds and first - * 32 random bits: and {@link Span#getTraceId()} traceId} holds the remaining 64 random bits. - */ - private long createTraceIdHigh() { - long epochSeconds = System.currentTimeMillis() / 1000; - int random = this.random.nextInt(); - return (epochSeconds & 0xffffffffL) << 32 | (random & 0xffffffffL); - } - - private long createId() { - return this.random.nextLong(); - } - - @Override - public Span continueSpan(Span span) { - if (span != null) { - this.spanLogger.logContinuedSpan(span); - } else { - return null; - } - Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan()); - SpanContextHolder.setCurrentSpan(newSpan); - return newSpan; - } - - private Span createContinuedSpan(Span span, Span saved) { - if (saved == null && span.getSavedSpan() != null) { - saved = span.getSavedSpan(); - } - return new Span(span, saved); - } - - @Override - public Span getCurrentSpan() { - return SpanContextHolder.getCurrentSpan(); - } - - @Override - public boolean isTracing() { - return SpanContextHolder.isTracing(); - } - - @Override - public void addTag(String key, String value) { - Span s = getCurrentSpan(); - if (s != null && s.isExportable()) { - s.tag(key, value); - } - } - - /** - * Wrap the callable in a TraceCallable, if tracing. - * - * @return The callable provided, wrapped if tracing, 'callable' if not. - */ - @Override - public Callable wrap(Callable callable) { - if (isTracing()) { - return new SpanContinuingTraceCallable<>(this, this.traceKeys, this.spanNamer, callable); - } - return callable; - } - - /** - * Wrap the runnable in a TraceRunnable, if tracing. - * - * @return The runnable provided, wrapped if tracing, 'runnable' if not. - */ - @Override - public Runnable wrap(Runnable runnable) { - if (isTracing()) { - return new SpanContinuingTraceRunnable(this, this.traceKeys, this.spanNamer, runnable); - } - return runnable; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanContextHolder.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanContextHolder.java deleted file mode 100644 index 7bfd3a59ee..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/trace/SpanContextHolder.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.trace; - -import org.apache.commons.logging.Log; -import org.springframework.cloud.sleuth.Span; -import org.springframework.core.NamedThreadLocal; - -/** - * Utility for managing the thread local state for the {@link DefaultTracer}. - * - * @author Spencer Gibb - * @author Dave Syer - */ -class SpanContextHolder { - - private static final Log log = org.apache.commons.logging.LogFactory - .getLog(SpanContextHolder.class); - private static final ThreadLocal CURRENT_SPAN = new NamedThreadLocal<>( - "Trace Context"); - - /** - * Get the current span out of the thread context - */ - static Span getCurrentSpan() { - return isTracing() ? CURRENT_SPAN.get().span : null; - } - - /** - * Set the current span in the thread context - */ - static void setCurrentSpan(Span span) { - if (log.isTraceEnabled()) { - log.trace("Setting current span " + span); - } - push(span, false); - } - - /** - * Remove all thread context relating to spans (useful for testing). - * - * @see #close() for a better alternative in instrumetation - */ - static void removeCurrentSpan() { - CURRENT_SPAN.remove(); - } - - /** - * Check if there is already a span in the current thread - */ - static boolean isTracing() { - return CURRENT_SPAN.get() != null; - } - - /** - * Close the current span and all parents that can be auto closed. - * On every iteration a function will be applied on the closed Span. - */ - static void close(SpanFunction spanFunction) { - SpanContext current = CURRENT_SPAN.get(); - CURRENT_SPAN.remove(); - while (current != null) { - current = current.parent; - spanFunction.apply(current != null ? current.span : null); - if (current != null) { - if (!current.autoClose) { - CURRENT_SPAN.set(current); - current = null; - } - } - } - } - - /** - * Close the current span and all parents that can be auto closed. - */ - static void close() { - close(new NoOpFunction()); - } - - /** - * Push a span into the thread context, with the option to have it auto close if any - * child spans are themselves closed. Use autoClose=true if you start a new span with - * a parent that wasn't already in thread context. - */ - static void push(Span span, boolean autoClose) { - if (isCurrent(span)) { - return; - } - CURRENT_SPAN.set(new SpanContext(span, autoClose)); - } - - private static boolean isCurrent(Span span) { - if (span == null || CURRENT_SPAN.get() == null) { - return false; - } - return span.equals(CURRENT_SPAN.get().span); - } - - private static class SpanContext { - final Span span; - final boolean autoClose; - final SpanContext parent; - - public SpanContext(Span span, boolean autoClose) { - this.span = span; - this.autoClose = autoClose; - this.parent = CURRENT_SPAN.get(); - } - } - - interface SpanFunction { - void apply(Span span); - } - - private static class NoOpFunction implements SpanFunction { - @Override public void apply(Span span) { } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanReporter.java similarity index 74% rename from spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java rename to spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanReporter.java index 9fe09e6948..9648d50cb2 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanAccumulator.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ArrayListSpanReporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,21 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; +import zipkin2.Span; +import zipkin2.reporter.Reporter; /** - * Accumulator of {@link org.springframework.cloud.sleuth.Tracer#close(Span) - * closed spans}. + * Accumulator of closed spans. * - * @author Spencer Gibb - * @since 1.0.0 + * @author Marcin Grzejszczak + * @since 2.0.0 */ -public class ArrayListSpanAccumulator implements SpanReporter { +public class ArrayListSpanReporter implements Reporter { private final List spans = new ArrayList<>(); public List getSpans() { synchronized (this.spans) { - return this.spans; + return new ArrayList<>(this.spans); } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java deleted file mode 100644 index 2c0182a203..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/ExceptionUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.util; - -import org.apache.commons.logging.Log; - -/** - * Utility class for logging exceptions. Useful for test purposes - when a warning message - * should be presented an exception can be thrown. - *

- * The purpose of this class is not to throw exceptions from the user's code when there - * are some issues with tracing. - * - * @author Spencer Gibb - * @since 1.0.0 - */ -public final class ExceptionUtils { - private static final Log log = org.apache.commons.logging.LogFactory - .getLog(ExceptionUtils.class); - private static boolean fail = false; - private static Exception lastException = null; - - private ExceptionUtils() { - throw new IllegalStateException("Utility class can't be instantiated"); - } - - public static void warn(String msg) { - log.warn(msg); - if (fail) { - IllegalStateException exception = new IllegalStateException(msg); - ExceptionUtils.lastException = exception; - throw exception; - } - } - - public static Exception getLastException() { - return ExceptionUtils.lastException; - } - - public static void setFail(boolean fail) { - ExceptionUtils.fail = fail; - ExceptionUtils.lastException = null; - } - - public static String getExceptionMessage(Throwable e) { - return e.getMessage() != null ? e.getMessage() : e.toString(); - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java index 32220f1074..9883ae722e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/SpanNameUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java deleted file mode 100644 index 5ecaa239cc..0000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/util/TextMapUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.springframework.cloud.sleuth.util; - -import java.util.Comparator; -import java.util.Map; -import java.util.TreeMap; - -/** - * Utility class related to {@link org.springframework.cloud.sleuth.SpanTextMap} - * - * @author Marcin Grzejszczak - * @since 1.2.0 - */ -public final class TextMapUtil { - - private TextMapUtil() {} - - public static Map asMap(Iterable> iterable) { - Map map = new TreeMap<>(new Comparator() { - @Override public int compare(String o1, String o2) { - return o1.toLowerCase().compareTo(o2.toLowerCase()); - } - }); - for (Map.Entry entry : iterable) { - map.put(entry.getKey(), entry.getValue()); - } - return map; - } -} diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index 16dd38a412..c69d343444 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -1,26 +1,24 @@ # Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration,\ -org.springframework.cloud.sleuth.metric.TraceMetricsAutoConfiguration,\ org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.messaging.TraceSpanMessagingAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.async.AsyncCustomAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.async.AsyncDefaultAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.scheduling.TraceSchedulingAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.rxjava.RxJavaAutoConfiguration,\ -org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration +org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.web.TraceWebFluxAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.zuul.TraceZuulAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration # Environment Post Processor org.springframework.boot.env.EnvironmentPostProcessor=\ diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java deleted file mode 100644 index b2bbc570c2..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/AdhocTestSuite.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import org.junit.Ignore; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; -import org.springframework.cloud.sleuth.instrument.web.RestTemplateTraceAspectIntegrationTests; -import org.springframework.cloud.sleuth.instrument.web.client.discoveryexception.WebClientDiscoveryExceptionTests; - -/** - * A test suite for probing weird ordering problems in the tests. - * - * @author Dave Syer - */ -@RunWith(Suite.class) -@SuiteClasses({ WebClientDiscoveryExceptionTests.class, - RestTemplateTraceAspectIntegrationTests.class }) -@Ignore -public class AdhocTestSuite { - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java deleted file mode 100644 index cc865e7975..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/DefaultSpanNamerTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import java.lang.reflect.Method; - -import org.junit.Test; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class DefaultSpanNamerTests { - - DefaultSpanNamer defaultSpanNamer = new DefaultSpanNamer(); - - @Test - public void should_return_value_of_span_name_from_annotation() throws Exception { - then(this.defaultSpanNamer.name(new ClassWithAnnotation(), "default")).isEqualTo("somevalue"); - } - - @Test - public void should_return_value_of_span_name_from_to_string_if_annotation_is_missing() throws Exception { - then(this.defaultSpanNamer.name(fromAnonymousClassWithCustomToString(), "default")).isEqualTo("some-other-value"); - } - - @Test - public void should_return_default_value_if_tostring_wasnt_overridden() throws Exception { - then(this.defaultSpanNamer.name(new ClassWithoutToString(), "default")).isEqualTo("default"); - } - - @Test - public void should_return_value_of_span_name_from_annotation_on_method() throws Exception { - Method method = ReflectionUtils.findMethod(ClassWithAnnotatedMethod.class, "method"); - then(this.defaultSpanNamer.name(method, "default")).isEqualTo("foo"); - } - - @Test - public void should_return_default_value_of_span_name_from_annotation_on_method() throws Exception { - Method method = ReflectionUtils.findMethod(ClassWithNonAnnotatedMethod.class, "method"); - then(this.defaultSpanNamer.name(method, "default")).isEqualTo("default"); - } - - @SpanName("somevalue") - static class ClassWithAnnotation {} - - private Runnable fromAnonymousClassWithCustomToString() { - return new Runnable() { - @Override - public void run() { - - } - - @Override - public String toString() { - return "some-other-value"; - } - }; - } - - static class ClassWithoutToString {} - - static class ClassWithAnnotatedMethod { - @SpanName("foo") - void method() {} - } - - static class ClassWithNonAnnotatedMethod { - void method() {} - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParserTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParserTests.java index e0148ae69c..3555b8afaf 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParserTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/ExceptionMessageErrorParserTests.java @@ -1,37 +1,61 @@ package org.springframework.cloud.sleuth; +import java.util.AbstractMap; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.junit.Before; import org.junit.Test; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ public class ExceptionMessageErrorParserTests { + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + + @Before + public void setup() { + this.reporter.clear(); + } + @Test public void should_append_tag_for_exportable_span() throws Exception { Throwable e = new RuntimeException("foo"); - Span span = new Span.SpanBuilder().exportable(true).build(); + Span span = this.tracer.nextSpan(); new ExceptionMessageErrorParser().parseErrorTags(span, e); - then(span).hasATag("error", "foo"); + span.finish(); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).tags()).contains(new AbstractMap.SimpleEntry<>("error", "foo")); } @Test public void should_not_throw_an_exception_when_span_is_null() throws Exception { new ExceptionMessageErrorParser().parseErrorTags(null, null); + + then(this.reporter.getSpans()).isEmpty(); } @Test public void should_not_append_tag_for_non_exportable_span() throws Exception { - Throwable e = new RuntimeException("foo"); - Span span = new Span.SpanBuilder().exportable(false).build(); + Span span = this.tracer.nextSpan(); - new ExceptionMessageErrorParser().parseErrorTags(span, e); + new ExceptionMessageErrorParser().parseErrorTags(span, null); - then(span.tags()).isEmpty(); + span.finish(); + then(this.reporter.getSpans()).isEmpty(); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/InternalApiTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/InternalApiTests.java deleted file mode 100644 index 154f707272..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/InternalApiTests.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.springframework.cloud.sleuth; - -import org.assertj.core.api.BDDAssertions; -import org.junit.Test; - -/** - * @author Marcin Grzejszczak - */ -public class InternalApiTests { - - @Test - public void should_rename_a_span() { - Span span = Span.builder().name("foo").build(); - - InternalApi.renameSpan(span, "bar"); - - BDDAssertions.then(span.getName()).isEqualTo("bar"); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java deleted file mode 100644 index 28b3ad40ae..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/LogTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Adrian Cole - */ -public class LogTests { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - ObjectMapper objectMapper = new ObjectMapper(); - - @Test public void ctor_missing_event() throws IOException { - this.thrown.expect(NullPointerException.class); - this.thrown.expectMessage("event"); - - new Log(1234L, null); - } - - @Test public void serialization_round_trip() throws IOException { - Log log = new Log(1234L, "cs"); - - String serialized = this.objectMapper.writeValueAsString(log); - Log deserialized = this.objectMapper.readValue(serialized, Log.class); - - then(deserialized).isEqualTo(log); - } - - @Test public void deserialize_missing_event() throws IOException { - this.thrown.expect(JsonMappingException.class); - - this.objectMapper.readValue("{\"timestamp\": 1234}", Log.class); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/NoOpSpanAdjusterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/NoOpSpanAdjusterTests.java deleted file mode 100644 index e029854587..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/NoOpSpanAdjusterTests.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import org.junit.Test; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class NoOpSpanAdjusterTests { - @Test - public void should_return_same_span() { - Span span = Span.builder().spanId(1).build(); - - then(new NoOpSpanAdjuster().adjust(span)).isSameAs(span); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanAdjusterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanAdjusterTests.java new file mode 100644 index 0000000000..bcb556358e --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanAdjusterTests.java @@ -0,0 +1,60 @@ +package org.springframework.cloud.sleuth; + +import brave.Span; +import brave.Tracer; +import brave.sampler.Sampler; +import org.assertj.core.api.BDDAssertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import zipkin2.reporter.Reporter; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SpanAdjusterTests.SpanAdjusterAspectTestsConfig.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) +public class SpanAdjusterTests { + + @Autowired ArrayListSpanReporter reporter; + @Autowired Tracer tracer; + + @Test + public void should_adjust_span_twice_before_reporting() { + Span hello = this.tracer.nextSpan().name("hello").start(); + + hello.finish(); + + BDDAssertions.then(this.reporter.getSpans()).hasSize(1); + BDDAssertions.then(this.reporter.getSpans().get(0).name()).isEqualTo("foo bar"); + } + + @Configuration + @EnableAutoConfiguration + static class SpanAdjusterAspectTestsConfig { + @Bean Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean Reporter reporter() { + return new ArrayListSpanReporter(); + } + + // tag::adjuster[] + @Bean SpanAdjuster adjusterOne() { + return span -> span.toBuilder().name("foo").build(); + } + + @Bean SpanAdjuster adjusterTwo() { + return span -> span.toBuilder().name(span.name() + " bar").build(); + } + // end::adjuster[] + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java deleted file mode 100644 index 0484e603ca..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicLong; - -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * @author Marcin Grzejszczak - * @author Rob Winch - * @author Spencer Gibb - */ -public class SpanTests { - Span span = Span.builder().begin(1).end(2).name("http:name").traceId(1L).spanId(2L) - .remote(true).exportable(true).processId("process").build(); - - @Test - public void should_consider_trace_and_span_id_on_equals_and_hashCode() throws Exception { - Span span = Span.builder().traceId(1L).spanId(2L).build(); - Span differentSpan = Span.builder().traceId(1L).spanId(3L).build(); - Span withParent =Span.builder().traceId(1L).spanId(2L).parent(3L).build(); - - then(span).isEqualTo(withParent); - then(span).isNotEqualTo(differentSpan); - then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); - } - - @Test - public void should_have_toString_with_identifiers_and_export() throws Exception { - span = Span.builder().traceId(1L).spanId(2L).parent(3L).name("foo").build(); - - then(span).hasToString( - "[Trace: 0000000000000001, Span: 0000000000000002, Parent: 0000000000000003, exportable:true]"); - } - - @Test - public void should_have_toString_with_128bit_trace_id() throws Exception { - span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); - - then(span.toString()).startsWith("[Trace: 00000000000000010000000000000002,"); - } - - @Test - public void should_consider_128bit_trace_and_span_id_on_equals_and_hashCode() throws Exception { - Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); - Span differentSpan = Span.builder().traceIdHigh(2L).traceId(2L).spanId(3L).build(); - Span withParent = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).parent(4L).build(); - - then(span).isEqualTo(withParent); - then(span).isNotEqualTo(differentSpan); - then(span.hashCode()).isNotEqualTo(differentSpan.hashCode()); - } - - @Test - public void should_convert_long_to_16_character_hex_string() throws Exception { - long someLong = 123123L; - - String hexString = Span.idToHex(someLong); - - then(hexString).isEqualTo("000000000001e0f3"); - } - - @Test - public void should_convert_hex_string_to_long() throws Exception { - String hexString = "1e0f3"; - - long someLong = Span.hexToId(hexString); - - then(someLong).isEqualTo(123123L); - } - - @Test - public void should_convert_lower_64bits_of_hex_string_to_long() throws Exception { - String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; - String lower64Bits = "48485a3953bb6124"; - - long someLong = Span.hexToId(hex128Bits); - - then(someLong).isEqualTo(Span.hexToId(lower64Bits)); - } - - @Test - public void should_convert_offset_64bits_of_hex_string_to_long() throws Exception { - String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; - String high64Bits = "463ac35c9f6413ad"; - - long someLong = Span.hexToId(hex128Bits, 0); - - then(someLong).isEqualTo(Span.hexToId(high64Bits)); - } - - @Test - public void should_writeFixedLength64BitTraceId() throws Exception { - String traceId = span.traceIdString(); - - then(traceId).isEqualTo("0000000000000001"); - } - - @Test - public void should_writeFixedLength128BitTraceId() throws Exception { - String high128Bits = "463ac35c9f6413ad"; - String low64Bits = "48485a3953bb6124"; - - span = Span.builder().traceIdHigh(Span.hexToId(high128Bits)).traceId(Span.hexToId(low64Bits)) - .spanId(1L).name("foo").build(); - - String traceId = span.traceIdString(); - - then(traceId).isEqualTo(high128Bits + low64Bits); - } - - @Test(expected = IllegalArgumentException.class) - public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception { - Span.hexToId(null); - } - - @Test(expected = UnsupportedOperationException.class) - public void getAnnotationsReadOnly() { - span.tags().put("a", "b"); - } - - @Test(expected = UnsupportedOperationException.class) - public void getTimelineAnnotationsReadOnly() { - span.logs().add(new Log(1, "1")); - } - - @Test public void should_properly_serialize_object() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - - String serializedName = objectMapper.writeValueAsString(span); - - then(serializedName).isNotEmpty(); - } - - @Test public void should_properly_serialize_logs() throws IOException { - span.logEvent("cs"); - - ObjectMapper objectMapper = new ObjectMapper(); - - String serialized = objectMapper.writeValueAsString(span); - Span deserialized = objectMapper.readValue(serialized, Span.class); - - then(deserialized.logs()) - .isEqualTo(span.logs()); - } - - @Test public void can_log_with_generated_timestamp() throws IOException { - long beforeLog = System.currentTimeMillis(); - - span.logEvent("event1"); - - assertThat(span.logs().get(0).getTimestamp()).isGreaterThanOrEqualTo(beforeLog); - } - - @Test public void can_log_with_specified_timestamp() throws IOException { - span.logEvent(1L, "event1"); - - then(span.logs().get(0).getTimestamp()).isEqualTo(1L); - } - - @Test public void should_properly_serialize_tags() throws IOException { - span.tag("calculatedTax", "100"); - - ObjectMapper objectMapper = new ObjectMapper(); - - String serialized = objectMapper.writeValueAsString(span); - Span deserialized = objectMapper.readValue(serialized, Span.class); - - then(deserialized.tags()) - .isEqualTo(span.tags()); - } - - @Test(expected = IllegalArgumentException.class) - public void should_throw_exception_when_converting_invalid_hex_value() { - Span.hexToId("invalid"); - } - - /** When going over a transport like spring-cloud-stream, we must retain the precise duration. */ - @Test public void shouldSerializeDurationMicros() throws IOException { - Span span = Span.builder().traceId(1L).name("http:parent").build(); - span.stop(); - - assertThat(span.getAccumulatedMicros()) - .isGreaterThan(0L); // sanity check - - ObjectMapper objectMapper = new ObjectMapper(); - - String serialized = objectMapper.writeValueAsString(span); - assertThat(serialized) - .contains("\"durationMicros\""); - - Span deserialized = objectMapper.readValue(serialized, Span.class); - - assertThat(deserialized.getAccumulatedMicros()) - .isEqualTo(span.getAccumulatedMicros()); - } - - // Duration of 0 is confusing to plot and can be misinterpreted as null - @Test public void getAccumulatedMicros_roundsUpToOneWhenRunning() throws IOException { - AtomicLong nanoTime = new AtomicLong(); - - // starts the span, recording its initial tick as zero - Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { - @Override long nanoTime() { - return nanoTime.get(); - } - }; - - // When only 100 nanoseconds passed - nanoTime.set(100L); - - // We round so that we don't confuse "not started" with a short span. - assertThat(span.getAccumulatedMicros()).isEqualTo(1L); - } - - // Duration of 0 is confusing to plot and can be misinterpreted as null - @Test public void getAccumulatedMicros_roundsUpToOneWhenStopped() throws IOException { - AtomicLong nanoTime = new AtomicLong(); - - // starts the span, recording its initial tick as zero - Span span = new Span(Span.builder().name("http:name").traceId(1L).spanId(2L)) { - @Override long nanoTime() { - return nanoTime.get(); - } - }; - - // When only 100 nanoseconds passed - nanoTime.set(100L); - span.stop(); - - // We round so that we don't confuse "not started" with a short span. - assertThat(span.getAccumulatedMicros()).isEqualTo(1L); - } - - @Test - public void should_build_a_span_from_provided_span() throws IOException { - Span span = builder().build(); - - Span builtSpan = Span.builder().from(span).build(); - - assertThat(builtSpan).isEqualTo(span); - } - - @Test - public void should_build_a_continued_span_from_provided_span() throws IOException { - Span span = builder().tag("foo", "bar").build(); - Span savedSpan = builder().tag("foo2", "bar2").build(); - Span builtSpan = new Span(span, savedSpan); - - span.tag("foo2", "bar2"); - - assertThat(builtSpan).isEqualTo(span); - } - - @Test - public void should_convert_a_span_to_builder() throws IOException { - Span.SpanBuilder spanBuilder = builder(); - Span span = spanBuilder.build(); - - Span span2 = span.toBuilder().build(); - - assertThat(span).isEqualTo(span2); - } - - private Span.SpanBuilder builder() { - return Span.builder().name("http:name").traceId(1L).spanId(2L).parent(3L) - .begin(1L).end(2L).traceId(3L).exportable(true).parent(4L) - .baggage("foo", "bar") - .remote(true).shared(true).tag("tag", "tag").log(new Log(System.currentTimeMillis(), "log")); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolverTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolverTests.java index c3d91556cc..687070d3de 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolverTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/NoOpTagValueResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationDisableTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationDisableTests.java index 4c3ba2c2f9..6e92969434 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationDisableTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationDisableTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationNoSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationNoSleuthTests.java index 59a8e7482c..4954491d9e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationNoSleuthTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAnnotationNoSleuthTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ package org.springframework.cloud.sleuth.annotation; +import brave.Tracing; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -31,11 +31,11 @@ public class SleuthSpanCreatorAnnotationNoSleuthTests { @Autowired(required = false) SpanCreator spanCreator; - @Autowired(required = false) Tracer tracer; + @Autowired(required = false) Tracing tracing; @Test public void shouldNotAutowireBecauseConfigIsDisabled() { assertThat(this.spanCreator).isNull(); - assertThat(this.tracer).isNull(); + assertThat(this.tracing).isNull(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectNegativeTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectNegativeTests.java index e99fb29f29..8d66919c9b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectNegativeTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectNegativeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,56 +16,51 @@ package org.springframework.cloud.sleuth.annotation; -import java.util.ArrayList; import java.util.List; +import brave.sampler.Sampler; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorAspectNegativeTests.TestConfiguration; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) -@SpringBootTest(classes = TestConfiguration.class) +@SpringBootTest(classes = SleuthSpanCreatorAspectNegativeTests.TestConfiguration.class) public class SleuthSpanCreatorAspectNegativeTests { @Autowired NotAnnotatedTestBeanInterface testBean; @Autowired TestBeanInterface annotatedTestBean; - @Autowired ArrayListSpanAccumulator accumulator; + @Autowired ArrayListSpanReporter reporter; @Before public void setup() { - ExceptionUtils.setFail(true); - this.accumulator.clear(); + this.reporter.clear(); } @Test public void shouldNotCallAdviceForNotAnnotatedBean() { this.testBean.testMethod(); - then(this.accumulator.getSpans()).isEmpty(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.reporter.getSpans()).isEmpty(); } @Test public void shouldCallAdviceForAnnotatedBean() throws Throwable { this.annotatedTestBean.testMethod(); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("test-method"); } protected interface NotAnnotatedTestBeanInterface { @@ -139,8 +134,8 @@ public void testMethod7() { @Configuration @EnableAutoConfiguration protected static class TestConfiguration { - @Bean SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); + @Bean Reporter spanReporter() { + return new ArrayListSpanReporter(); } @Bean @@ -152,5 +147,10 @@ public NotAnnotatedTestBeanInterface testBean() { public TestBeanInterface annotatedTestBean() { return new TestBean(); } + + @Bean + public Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java index 5d43e35838..08aa63c3f9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorAspectTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,75 +18,74 @@ import java.util.ArrayList; import java.util.List; - +import java.util.stream.Collectors; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import zipkin2.Annotation; +import zipkin2.reporter.Reporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorAspectTests.TestConfiguration; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; -@SpringBootTest(classes = TestConfiguration.class) +@SpringBootTest(classes = SleuthSpanCreatorAspectTests.TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class SleuthSpanCreatorAspectTests { @Autowired TestBeanInterface testBean; @Autowired Tracer tracer; - @Autowired ArrayListSpanAccumulator accumulator; + @Autowired ArrayListSpanReporter reporter; @Before public void setup() { - ExceptionUtils.setFail(true); - this.accumulator.clear(); + this.reporter.clear(); } @Test public void shouldCreateSpanWhenAnnotationOnInterfaceMethod() { this.testBean.testMethod(); - - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method"); - then(ExceptionUtils.getLastException()).isNull(); + + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("test-method"); } @Test public void shouldCreateSpanWhenAnnotationOnClassMethod() { this.testBean.testMethod2(); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("test-method2"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("test-method2"); } @Test public void shouldCreateSpanWithCustomNameWhenAnnotationOnClassMethod() { this.testBean.testMethod3(); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("custom-name-on-test-method3"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method3"); } @Test public void shouldCreateSpanWithCustomNameWhenAnnotationOnInterfaceMethod() { this.testBean.testMethod4(); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1).hasASpanWithName("custom-name-on-test-method4"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method4"); } @Test @@ -95,96 +94,105 @@ public void shouldCreateSpanWithTagWhenAnnotationOnInterfaceMethod() { this.testBean.testMethod5("test"); // end::execution[] - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("custom-name-on-test-method5") - .hasASpanWithTagEqualTo("testTag", "test"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method5"); + then(spans.get(0).tags()).containsEntry("testTag", "test"); } @Test public void shouldCreateSpanWithTagWhenAnnotationOnClassMethod() { this.testBean.testMethod6("test"); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("custom-name-on-test-method6") - .hasASpanWithTagEqualTo("testTag6", "test"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method6"); + then(spans.get(0).tags()).containsEntry("testTag6", "test"); } @Test public void shouldCreateSpanWithLogWhenAnnotationOnInterfaceMethod() { this.testBean.testMethod8("test"); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("custom-name-on-test-method8"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method8"); } @Test public void shouldCreateSpanWithLogWhenAnnotationOnClassMethod() { this.testBean.testMethod9("test"); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("custom-name-on-test-method9") - .hasASpanWithTagEqualTo("class", "TestBean") - .hasASpanWithTagEqualTo("method", "testMethod9"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("custom-name-on-test-method9"); + then(spans.get(0).tags()) + .containsEntry("class", "TestBean") + .containsEntry("method", "testMethod9"); } @Test public void shouldContinueSpanWithLogWhenAnnotationOnInterfaceMethod() { - Span span = this.tracer.createSpan("foo"); - - this.testBean.testMethod10("test"); - - this.tracer.close(span); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("foo") - .hasASpanWithTagEqualTo("customTestTag10", "test") - .hasASpanWithLogEqualTo("customTest.before") - .hasASpanWithLogEqualTo("customTest.after"); - then(ExceptionUtils.getLastException()).isNull(); + Span span = this.tracer.nextSpan().name("foo"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + this.testBean.testMethod10("test"); + } finally { + span.finish(); + } + + List spans = new ArrayList<>(this.reporter.getSpans()); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("foo"); + then(spans.get(0).tags()) + .containsEntry("customTestTag10", "test"); + then(spans.get(0).annotations() + .stream().map(Annotation::value).collect(Collectors.toList())) + .contains("customTest.before", "customTest.after"); } @Test public void shouldContinueSpanWhenKeyIsUsedOnSpanTagWhenAnnotationOnInterfaceMethod() { - Span span = this.tracer.createSpan("foo"); - - this.testBean.testMethod10_v2("test"); - - this.tracer.close(span); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("foo") - .hasASpanWithTagEqualTo("customTestTag10", "test") - .hasASpanWithLogEqualTo("customTest.before") - .hasASpanWithLogEqualTo("customTest.after"); - then(ExceptionUtils.getLastException()).isNull(); + Span span = this.tracer.nextSpan().name("foo"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + this.testBean.testMethod10_v2("test"); + } finally { + span.finish(); + } + + List spans = new ArrayList<>(this.reporter.getSpans()); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("foo"); + then(spans.get(0).tags()) + .containsEntry("customTestTag10", "test"); + then(spans.get(0).annotations() + .stream().map(Annotation::value).collect(Collectors.toList())) + .contains("customTest.before", "customTest.after"); } @Test public void shouldContinueSpanWithLogWhenAnnotationOnClassMethod() { - Span span = this.tracer.createSpan("foo"); - - // tag::continue_span_execution[] - this.testBean.testMethod11("test"); - // end::continue_span_execution[] - - this.tracer.close(span); - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("foo") - .hasASpanWithTagEqualTo("customTestTag11", "test") - .hasASpanWithTagEqualTo("class", "TestBean") - .hasASpanWithTagEqualTo("method", "testMethod11") - .hasASpanWithLogEqualTo("customTest.before") - .hasASpanWithLogEqualTo("customTest.after"); - then(ExceptionUtils.getLastException()).isNull(); + Span span = this.tracer.nextSpan().name("foo"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + // tag::continue_span_execution[] + this.testBean.testMethod11("test"); + // end::continue_span_execution[] + } finally { + span.finish(); + } + + List spans = new ArrayList<>(this.reporter.getSpans()); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("foo"); + then(spans.get(0).tags()) + .containsEntry("class", "TestBean") + .containsEntry("method", "testMethod11") + .containsEntry("customTestTag11", "test"); + then(spans.get(0).annotations() + .stream().map(Annotation::value).collect(Collectors.toList())) + .contains("customTest.before", "customTest.after"); } @Test @@ -194,40 +202,43 @@ public void shouldAddErrorTagWhenExceptionOccurredInNewSpan() { } catch (RuntimeException ignored) { } - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("test-method12") - .hasASpanWithTagEqualTo("testTag12", "test") - .hasASpanWithTagEqualTo("error", "test exception 12"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = new ArrayList<>(this.reporter.getSpans()); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("test-method12"); + then(spans.get(0).tags()) + .containsEntry("testTag12", "test") + .containsEntry("error", "test exception 12"); } @Test public void shouldAddErrorTagWhenExceptionOccurredInContinueSpan() { - Span span = this.tracer.createSpan("foo"); - try { + Span span = this.tracer.nextSpan().name("foo"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + // tag::continue_span_execution[] this.testBean.testMethod13(); + // end::continue_span_execution[] } catch (RuntimeException ignored) { - } - finally { - this.tracer.close(span); + } finally { + span.finish(); } - List spans = new ArrayList<>(this.accumulator.getSpans()); - then(new ListOfSpans(spans)).hasSize(1) - .hasASpanWithName("foo") - .hasASpanWithTagEqualTo("error", "test exception 13") - .hasASpanWithLogEqualTo("testMethod13.before") - .hasASpanWithLogEqualTo("testMethod13.afterFailure") - .hasASpanWithLogEqualTo("testMethod13.after"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = new ArrayList<>(this.reporter.getSpans()); + then(spans).hasSize(1); + then(spans.get(0).name()).isEqualTo("foo"); + then(spans.get(0).tags()) + .containsEntry("error", "test exception 13"); + then(spans.get(0).annotations() + .stream().map(Annotation::value).collect(Collectors.toList())) + .contains("testMethod13.before", "testMethod13.afterFailure", + "testMethod13.after"); } @Test public void shouldNotCreateSpanWhenNotAnnotated() { this.testBean.testMethod7(); - List spans = new ArrayList<>(this.accumulator.getSpans()); + List spans = new ArrayList<>(this.reporter.getSpans()); then(spans).isEmpty(); } @@ -364,12 +375,12 @@ public TestBeanInterface testBean() { return new TestBean(); } - @Bean SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); + @Bean Reporter spanReporter() { + return new ArrayListSpanReporter(); } - @Bean AlwaysSampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorCircularDependencyTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorCircularDependencyTests.java index 0c4f515d76..29fd247ca0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorCircularDependencyTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SleuthSpanCreatorCircularDependencyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,19 @@ */ package org.springframework.cloud.sleuth.annotation; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.annotation.SleuthSpanCreatorCircularDependencyTests.TestConfiguration; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; -@SpringBootTest(classes = TestConfiguration.class) +@SpringBootTest(classes = SleuthSpanCreatorCircularDependencyTests.TestConfiguration.class) @RunWith(SpringRunner.class) public class SleuthSpanCreatorCircularDependencyTests { @Test public void contextLoads() throws Exception { @@ -49,8 +49,8 @@ private static class Service2 { @Configuration @EnableAutoConfiguration protected static class TestConfiguration { - @Bean SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); + @Bean Reporter spanReporter() { + return new ArrayListSpanReporter(); } @Bean public Service1 service1() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java index 20a64e1e57..9ece90d3df 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpanTagAnnotationHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import brave.sampler.Sampler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,9 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.annotation.SpanTagAnnotationHandlerTests.TestConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -36,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -@SpringBootTest(classes = TestConfiguration.class) +@SpringBootTest(classes = SpanTagAnnotationHandlerTests.TestConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class SpanTagAnnotationHandlerTests { @@ -46,7 +44,6 @@ public class SpanTagAnnotationHandlerTests { @Before public void setup() { - ExceptionUtils.setFail(true); this.handler = new SpanTagAnnotationHandler(this.beanFactory); } @@ -119,8 +116,8 @@ public TagValueResolver tagValueResolver() { } // end::custom_resolver[] - @Bean AlwaysSampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolverTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolverTests.java index e178846e73..c43e551967 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolverTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/annotation/SpelTagValueExpressionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java deleted file mode 100644 index bd79dfc110..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpans.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.sleuth.assertions; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.cloud.sleuth.Span; - -/** - * @author Marcin Grzejszczak - */ -public class ListOfSpans { - - public final List spans; - - public ListOfSpans(List spans) { - this.spans = new ArrayList<>(spans); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java deleted file mode 100644 index 66866242d7..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/ListOfSpansAssert.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.assertions; - -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.assertj.core.api.AbstractAssert; -import org.springframework.cloud.sleuth.Span; - -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; - -public class ListOfSpansAssert extends AbstractAssert { - - private static final Log log = LogFactory.getLog(ListOfSpansAssert.class); - - public ListOfSpansAssert(ListOfSpans actual) { - super(actual, ListOfSpansAssert.class); - } - - public static ListOfSpansAssert then(ListOfSpans actual) { - return new ListOfSpansAssert(actual); - } - - public ListOfSpansAssert everyParentIdHasItsCorrespondingSpan() { - isNotNull(); - printSpans(); - List parentSpanIds = this.actual.spans.stream().flatMap(span -> span.getParents().stream()) - .distinct().collect(toList()); - List spanIds = this.actual.spans.stream() - .map(Span::getSpanId).distinct() - .collect(toList()); - List difference = new ArrayList<>(parentSpanIds); - difference.removeAll(spanIds); - log.info("Difference between parent ids and span ids " + - difference.stream().map(span -> "id as long [" + span + "] and as hex [" + Span.idToHex(span) + "]").collect( - joining("\n"))); - assertThat(spanIds).containsAll(parentSpanIds); - return this; - } - - public ListOfSpansAssert clientSideSpanWithNameHasTags(String name, Map tags) { - isNotNull(); - printSpans(); - List matchingSpans = this.actual.spans.stream() - .filter(span -> span.getName().equals(name) && span.logs().stream().anyMatch(entry -> - entry.getEvent().equals(Span.CLIENT_SEND))).collect(toList()); - assertThat(matchingSpans).isNotEmpty(); - List> matchingSpansTags = matchingSpans.stream().map(Span::tags).collect( - toList()); - Map spanTags = new HashMap<>(); - matchingSpansTags.forEach(spanTags::putAll); - assertThat(spanTags.entrySet()).containsAll(tags.entrySet()); - return this; - } - - public ListOfSpansAssert hasASpanWithTagKeyEqualTo(String tagKey) { - isNotNull(); - printSpans(); - if (!spanWithKeyTagExists(tagKey)) { - failWithMessage("Expected spans \n <%s> \nto contain at least one span with tag key " - + "equal to <%s>", spansToString(), tagKey); - } - return this; - } - - public ListOfSpansAssert everySpanHasABaggage(String baggageKey, String baggageValue) { - isNotNull(); - printSpans(); - if (!everySpanHasBaggage(baggageKey, baggageValue)) { - failWithMessage("Expected spans \n <%s> \nto ALL contain baggage with key " - + "equal to <%s>, and value equal to <%s>", spansToString(), baggageKey, baggageValue); - } - return this; - } - - public ListOfSpansAssert anySpanHasABaggage(String baggageKey, String baggageValue) { - isNotNull(); - printSpans(); - if (!hasBaggage(baggageKey, baggageValue)) { - failWithMessage("Expected spans \n <%s> \nto contain at least one span with baggage key " - + "equal to <%s>, and value equal to <%s>", spansToString(), baggageKey, baggageValue); - } - return this; - } - - public ListOfSpansAssert allSpansAreExportable() { - isNotNull(); - printSpans(); - if (!everySpanIsExportable()) { - failWithMessage("Expected spans \n <%s> \nto be exportable but there's at least " - + "one which is not", spansToString()); - } - return this; - } - - public ListOfSpansAssert allSpansHaveTraceId(long traceId) { - isNotNull(); - printSpans(); - if (!everySpanHasTraceId(traceId)) { - failWithMessage("Expected spans \n <%s> \nto have trace id <%s> but there's at least " - + "one which doesn't have it", spansToString(), traceId); - } - return this; - } - - private boolean spanWithKeyTagExists(String tagKey) { - for (Span span : this.actual.spans) { - if (span.tags().containsKey(tagKey)) { - return true; - } - } - return false; - } - - private boolean everySpanHasBaggage(String baggageKey, String baggageValue) { - boolean exists = false; - for (Span span : this.actual.spans) { - for (Map.Entry baggage : span.baggageItems()) { - if (baggage.getKey().equals(baggageKey)) { - if (baggage.getValue().equals(baggageValue)) { - exists = true; - break; - } - } - } - if (!exists) { - return false; - } - } - return exists; - } - - private boolean everySpanIsExportable() { - for (Span span : this.actual.spans) { - if (!span.isExportable()) { - return false; - } - } - return true; - } - - private boolean everySpanHasTraceId(long traceId) { - for (Span span : this.actual.spans) { - if (span.getTraceId() != traceId) { - return false; - } - } - return true; - } - - private boolean hasBaggage(String baggageKey, String baggageValue) { - for (Span span : this.actual.spans) { - for (Map.Entry baggage : span.baggageItems()) { - if (baggage.getKey().equals(baggageKey)) { - if (baggage.getValue().equals(baggageValue)) { - return true; - } - } - } - } - return false; - } - - public ListOfSpansAssert hasASpanWithTagEqualTo(String tagKey, String tagValue) { - isNotNull(); - printSpans(); - List matchingSpans = this.actual.spans.stream() - .filter(span -> tagValue.equals(span.tags().get(tagKey))) - .collect(toList()); - if (matchingSpans.isEmpty()) { - failWithMessage("Expected spans \n <%s> \nto contain at least one span with tag key " - + "equal to <%s> and value equal to <%s>.\n\n", spansToString(), tagKey, tagValue); - } - return this; - } - - public ListOfSpansAssert hasASpanWithLogEqualTo(String logName) { - isNotNull(); - printSpans(); - boolean found = false; - for (Span span : this.actual.spans) { - try { - SleuthAssertions.assertThat(span).hasLoggedAnEvent(logName); - found = true; - break; - } catch (AssertionError e) {} - } - - if (!found) { - failWithMessage("Expected spans \n <%s> \nto contain at least one span with log name " - + "equal to <%s>.\n\n", spansToString(), logName); - } - return this; - } - - private String spansToString() { - return this.actual.spans.stream().map(span -> "\nSPAN: " + span.toString() + " with name [" + span.getName() + "] " + - "\nwith tags " + span.tags() + "\nwith logs " + span.logs() + - "\nwith baggage " + span.getBaggage()).collect(joining("\n")); - } - - public ListOfSpansAssert doesNotHaveASpanWithName(String name) { - isNotNull(); - printSpans(); - List matchingSpans = findSpansWithName(name); - if (!matchingSpans.isEmpty()) { - failWithMessage("Expected spans \n <%s> \nnot to contain a span with name <%s>", spansToString(), name); - } - return this; - } - - private List findSpansWithName(String name) { - return this.actual.spans.stream() - .filter(span -> span.getName().equals(name)) - .collect(toList()); - } - - private List findSpansWithSpanId(long spanId) { - return this.actual.spans.stream() - .filter(span -> spanId == span.getSpanId()) - .collect(toList()); - } - - public ListOfSpansAssert hasASpanWithName(String name) { - isNotNull(); - printSpans(); - List matchingSpans = findSpansWithName(name); - if (matchingSpans.isEmpty()) { - failWithMessage("Expected spans <%s> to contain a span with name <%s>", spansToString(), name); - } - return this; - } - - public ListOfSpansAssert hasASpanWithSpanId(Long spanId) { - isNotNull(); - printSpans(); - List matchingSpans = findSpansWithSpanId(spanId); - if (matchingSpans.isEmpty()) { - failWithMessage("Expected spans <%s> to contain a span with id <%s>", spansToString(), spanId); - } - return this; - } - - public ListOfSpansAssert hasSize(int size) { - isNotNull(); - printSpans(); - if (size != this.actual.spans.size()) { - failWithMessage("Expected spans <%s> to be of size <%s> but was <%s>", spansToString(), size, actual.spans.size()); - } - return this; - } - - public ListOfSpansAssert hasRpcLogsInProperOrder() { - isNotNull(); - printSpans(); - RpcLogKeeper rpcLogKeeper = findRpcLogs(); - log.info("Rpc logs [" + rpcLogKeeper.toString() + "]"); - rpcLogKeeper.assertThatAllBelongToSameTraceAndSpan(); - rpcLogKeeper.assertThatFullRpcCycleTookPlace(); - rpcLogKeeper.assertThatRpcLogsTookPlaceInOrder(); - return this; - } - - public ListOfSpansAssert hasServerSideSpansInProperOrder() { - isNotNull(); - printSpans(); - RpcLogKeeper rpcLogKeeper = findRpcLogs(); - log.info("Rpc logs [" + rpcLogKeeper.toString() + "]"); - rpcLogKeeper.assertThatServerSideEventsBelongToSameTraceAndSpan(); - rpcLogKeeper.assertThatServerSideEventsTookPlace(); - rpcLogKeeper.assertThatServerLogsTookPlaceInOrder(); - return this; - } - - public ListOfSpansAssert hasRpcWithoutSeverSideDueToException() { - isNotNull(); - printSpans(); - RpcLogKeeper rpcLogKeeper = findRpcLogs(); - log.info("Rpc logs [" + rpcLogKeeper.toString() + "]"); - rpcLogKeeper.assertThatClientSideEventsBelongToSameTraceAndSpan(); - rpcLogKeeper.assertThatClientSideEventsTookPlace(); - rpcLogKeeper.assertThatClientLogsTookPlaceInOrder(); - return this; - } - - private void printSpans() { - log.info("Stored spans " + spansToString()); - } - - @Override - protected void failWithMessage(String errorMessage, Object... arguments) { - log.error(String.format(errorMessage, arguments)); - super.failWithMessage(errorMessage, arguments); - } - - RpcLogKeeper findRpcLogs() { - final RpcLogKeeper rpcLogKeeper = new RpcLogKeeper(); - this.actual.spans.forEach(span -> span.logs().forEach(log -> { - switch (log.getEvent()) { - case Span.CLIENT_SEND: - rpcLogKeeper.cs = log; - rpcLogKeeper.csSpanId = span.getSpanId(); - rpcLogKeeper.csTraceId = span.getTraceId(); - break; - case Span.SERVER_RECV: - rpcLogKeeper.sr = log; - rpcLogKeeper.srSpanId = span.getSpanId(); - rpcLogKeeper.srTraceId = span.getTraceId(); - break; - case Span.SERVER_SEND: - rpcLogKeeper.ss = log; - rpcLogKeeper.ssSpanId = span.getSpanId(); - rpcLogKeeper.ssTraceId = span.getTraceId(); - break; - case Span.CLIENT_RECV: - rpcLogKeeper.cr = log; - rpcLogKeeper.crSpanId = span.getSpanId(); - rpcLogKeeper.crTraceId = span.getTraceId(); - break; - default: - break; - } - })); - return rpcLogKeeper; - } -} - -class RpcLogKeeper { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - org.springframework.cloud.sleuth.Log cs; - long csSpanId; - long csTraceId; - org.springframework.cloud.sleuth.Log sr; - long srSpanId; - long srTraceId; - org.springframework.cloud.sleuth.Log ss; - long ssSpanId; - long ssTraceId; - org.springframework.cloud.sleuth.Log cr; - long crSpanId; - long crTraceId; - - void assertThatFullRpcCycleTookPlace() { - assertThatServerSideEventsTookPlace(); - assertThatClientSideEventsTookPlace(); - } - void assertThatServerSideEventsTookPlace() { - log.info("Checking if Server Received took place"); - assertThat(this.sr).describedAs("Server Received log").isNotNull(); - log.info("Checking if Server Send took place"); - assertThat(this.ss).describedAs("Server Send log").isNotNull(); - log.info("Checking if Client Received took place"); - } - - void assertThatClientSideEventsTookPlace() { - log.info("Checking if Client Send took place"); - assertThat(this.cs).describedAs("Client Send log").isNotNull(); - log.info("Checking if Client Received took place"); - assertThat(this.cr).describedAs("Client Received log").isNotNull(); - } - - void assertThatAllBelongToSameTraceAndSpan() { - log.info("Checking if RPC spans are coming from the same span"); - assertThat(this.csSpanId).describedAs("All logs should come from the same span") - .isEqualTo(this.srSpanId).isEqualTo(this.ssSpanId).isEqualTo(this.crSpanId); - log.info("Checking if RPC spans have the same trace id"); - assertThat(this.csTraceId).describedAs("All logs should come from the same trace") - .isEqualTo(this.srTraceId).isEqualTo(this.ssTraceId).isEqualTo(this.crTraceId); - } - - void assertThatClientSideEventsBelongToSameTraceAndSpan() { - log.info("Checking if CR/CS logs are coming from the same span"); - assertThat(this.csSpanId).describedAs("All logs should come from the same span").isEqualTo(this.crSpanId); - log.info("Checking if CR/CS logs have the same trace id"); - assertThat(this.csTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.crTraceId); - } - - void assertThatServerSideEventsBelongToSameTraceAndSpan() { - log.info("Checking if SS/SR logs are coming from the same span"); - assertThat(this.ssSpanId).describedAs("All logs should come from the same span").isEqualTo(this.srSpanId); - log.info("Checking if SS/SR logs have the same trace id"); - assertThat(this.ssTraceId).describedAs("All logs should come from the same trace").isEqualTo(this.srTraceId); - } - - void assertThatRpcLogsTookPlaceInOrder() { - long csTimestamp = this.cs.getTimestamp(); - long srTimestamp = this.sr.getTimestamp(); - long ssTimestamp = this.ss.getTimestamp(); - long crTimestamp = this.cr.getTimestamp(); - log.info("Checking if CR is before SR"); - assertThat(csTimestamp).as("CS timestamp should be before SR timestamp").isLessThanOrEqualTo(srTimestamp); - log.info("Checking if SR is before SS"); - assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); - log.info("Checking if SS is before CR"); - assertThat(ssTimestamp).as("SS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); - } - - void assertThatClientLogsTookPlaceInOrder() { - long csTimestamp = this.cs.getTimestamp(); - long crTimestamp = this.cr.getTimestamp(); - log.info("Checking if CS is before CR"); - assertThat(csTimestamp).as("CS timestamp should be before CR timestamp").isLessThanOrEqualTo(crTimestamp); - } - - void assertThatServerLogsTookPlaceInOrder() { - long srTimestamp = this.sr.getTimestamp(); - long ssTimestamp = this.ss.getTimestamp(); - log.info("Checking if CS is before CR"); - assertThat(srTimestamp).as("SR timestamp should be before SS timestamp").isLessThanOrEqualTo(ssTimestamp); - } - - @Override public String toString() { - return "RpcLogKeeper{" + "cs=" + cs + ", csSpanId=" + csSpanId + ", csTraceId=" - + csTraceId + ", sr=" + sr + ", srSpanId=" + srSpanId + ", srTraceId=" - + srTraceId + ", ss=" + ss + ", ssSpanId=" + ssSpanId + ", ssTraceId=" - + ssTraceId + ", cr=" + cr + ", crSpanId=" + crSpanId + ", crTraceId=" - + crTraceId + '}'; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SleuthAssertions.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SleuthAssertions.java deleted file mode 100644 index 8dc5504479..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SleuthAssertions.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.springframework.cloud.sleuth.assertions; - -import org.assertj.core.api.BDDAssertions; -import org.springframework.cloud.sleuth.Span; - -public class SleuthAssertions extends BDDAssertions { - - public static SpanAssert then(Span actual) { - return assertThat(actual); - } - - public static SpanAssert assertThat(Span actual) { - return new SpanAssert(actual); - } - - - public static ListOfSpansAssert then(ListOfSpans actual) { - return assertThat(actual); - } - - public static ListOfSpansAssert assertThat(ListOfSpans actual) { - return new ListOfSpansAssert(actual); - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java deleted file mode 100644 index 846987bb19..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/assertions/SpanAssert.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.assertions; - -import java.util.Objects; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.assertj.core.api.AbstractAssert; -import org.springframework.cloud.sleuth.Span; - -public class SpanAssert extends AbstractAssert { - - private static final Log log = LogFactory.getLog(SpanAssert.class); - - public SpanAssert(Span actual) { - super(actual, SpanAssert.class); - } - - public static SpanAssert then(Span actual) { - return new SpanAssert(actual); - } - - public SpanAssert hasTraceIdEqualTo(Long traceId) { - isNotNull(); - if (!Objects.equals(this.actual.getTraceId(), traceId)) { - String message = String.format("Expected span's traceId to be <%s> but was <%s>", traceId, this.actual.getTraceId()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasNameEqualTo(String name) { - isNotNull(); - if (!Objects.equals(this.actual.getName(), name)) { - String message = String.format("Expected span's name to be <%s> but it was <%s>", name, this.actual.getName()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert nameStartsWith(String string) { - isNotNull(); - if (!this.actual.getName().startsWith(string)) { - String message = String.format("Expected span's name to start with <%s> but it was equal to <%s>", string, this.actual.getName()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasNameNotEqualTo(String name) { - isNotNull(); - if (Objects.equals(this.actual.getName(), name)) { - String message = String.format("Expected span's name NOT to be <%s> but it was <%s>", name, this.actual.getName()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert isALocalComponentSpan() { - isNotNull(); - if (!this.actual.tags().containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME)) { - String message = String.format("Expected span to be a local component. " - + "LC tag is missing. Found tags are <%s>", this.actual.tags()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasATag(String tagKey, String tagValue) { - isNotNull(); - assertThatTagIsPresent(tagKey); - String foundTagValue = this.actual.tags().get(tagKey); - if (!foundTagValue.equals(tagValue)) { - String message = String.format("Expected span to have the tag with key <%s> and value <%s>. " - + "Found value for that tag is <%s>", tagKey, tagValue, foundTagValue); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasBaggageItem(String baggageKey, String baggageValue) { - isNotNull(); - assertThatBaggageContainsKey(baggageKey); - String foundValue = this.actual.getBaggageItem(baggageKey); - if (!foundValue.equals(baggageValue)) { - String message = String.format("Expected span to have the baggage with key <%s> and value <%s>. " - + "Found value for that baggage is <%s>", baggageKey, baggageValue, foundValue); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasATagWithKey(String tagKey) { - isNotNull(); - assertThatTagIsPresent(tagKey); - boolean foundTagValue = this.actual.tags().containsKey(tagKey); - if (!foundTagValue) { - String message = String.format("Expected span to have the tag with key <%s>. " - + "Found tags are <%s>", tagKey, this.actual.tags()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert matchesATag(String tagKey, String tagRegex) { - isNotNull(); - assertThatTagIsPresent(tagKey); - String foundTagValue = this.actual.tags().get(tagKey); - if (!foundTagValue.matches(tagRegex)) { - String message = String.format("Expected span to have the tag with key <%s> and match a regex <%s>. " - + "Found value for that tag is <%s>", tagKey, tagRegex, foundTagValue); - log.error(message); - failWithMessage(message); - } - return this; - } - - private void assertThatTagIsPresent(String tagKey) { - if (!this.actual.tags().containsKey(tagKey)) { - String message = String.format("Expected span to have the tag with key <%s>. " - + "Found tags are <%s>", tagKey, this.actual.tags()); - log.error(message); - failWithMessage(message); - } - } - - private void assertThatBaggageContainsKey(String baggageKey) { - if (!this.actual.getBaggage().containsKey(baggageKey)) { - String message = String.format("Expected span to have the baggage with key <%s>. " - + "Found baggage are <%s>", baggageKey, this.actual.getBaggage()); - log.error(message); - failWithMessage(message); - } - } - - public SpanAssert hasLoggedAnEvent(String event) { - isNotNull(); - if (!this.actual.logs().stream().map(org.springframework.cloud.sleuth.Log::getEvent) - .anyMatch(s -> s.equals(event))) { - String message = String.format("Expected span to have the event with event value <%s>. " - + "Found logs are <%s>", event, this.actual.logs()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert hasNotLoggedAnEvent(String event) { - isNotNull(); - if (this.actual.logs().stream().map(org.springframework.cloud.sleuth.Log::getEvent) - .anyMatch(s -> s.equals(event))) { - String message = String.format("Expected span NOT to have the event with event value <%s>. " - + "Found logs are <%s>", event, this.actual.logs()); - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert isExportable() { - isNotNull(); - if (!this.actual.isExportable()) { - String message = "The span is supposed to be exportable but it's not!"; - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert isShared() { - isNotNull(); - if (!this.actual.isShared()) { - String message = "The span is supposed to be shared but it's not!"; - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert isNotShared() { - isNotNull(); - if (this.actual.isShared()) { - String message = "The span is NOT supposed to be shared but it is!"; - log.error(message); - failWithMessage(message); - } - return this; - } - - public SpanAssert isNotExportable() { - isNotNull(); - if (this.actual.isExportable()) { - String message = "The span is NOT supposed to be exportable but it is!"; - log.error(message); - failWithMessage(message); - } - return this; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTest.java deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java deleted file mode 100644 index e065876f48..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2015-2016 The OpenZipkin Authors - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package org.springframework.cloud.sleuth.autoconfig; - -import org.junit.After; -import org.junit.Test; -import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.test.util.EnvironmentTestUtils; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TraceAutoConfigurationTests { - - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - - @After - public void close() { - context.close(); - } - - @Test - public void defaultsTo64BitTraceId() { - context = new AnnotationConfigApplicationContext(); - context.register(PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } - finally { - if (span != null) { - tracer.close(span); - } - } - } - - @Test - public void optInto128BitTraceId() { - EnvironmentTestUtils.addEnvironment(context, "spring.sleuth.traceId128:true"); - context.register(PropertyPlaceholderAutoConfiguration.class, - SleuthLogAutoConfiguration.class, TraceAutoConfiguration.class); - context.refresh(); - Tracer tracer = context.getBean(Tracer.class); - - Span span = null; - try { - span = tracer.createSpan("foo", NeverSampler.INSTANCE); - assertThat(span.getTraceIdHigh()).isNotEqualTo(0L); - assertThat(span.getTraceId()).isNotEqualTo(0L); - } - finally { - if (span != null) { - tracer.close(span); - } - } - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java index fe24d438fa..bc41851da1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/autoconfig/TraceAutoConfigurationWithDisabledSleuthTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,10 @@ import java.security.SecureRandom; +import brave.Tracing; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.assertj.core.api.BDDAssertions; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,8 +29,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.rule.OutputCapture; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ActiveProfiles; @@ -41,21 +41,22 @@ @ActiveProfiles("disabled") public class TraceAutoConfigurationWithDisabledSleuthTests { - private static final Log log = LogFactory.getLog(TraceAutoConfigurationWithDisabledSleuthTests.class); + private static final Log log = LogFactory.getLog( + TraceAutoConfigurationWithDisabledSleuthTests.class); @Rule public OutputCapture capture = new OutputCapture(); - @Autowired(required = false) Tracer tracer; + @Autowired(required = false) Tracing tracing; @Test public void shouldStartContext() { - SleuthAssertions.then(this.tracer).isNull(); + BDDAssertions.then(this.tracing).isNull(); } @Test public void shouldNotContainAnyTracingInfoInTheLogs() { log.info("hello"); - SleuthAssertions.then(this.capture.toString()).doesNotContain("[foo"); + BDDAssertions.then(this.capture.toString()).doesNotContain("[foo"); } @EnableAutoConfiguration diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java index f253726d1a..5da8b7c393 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/documentation/SpringCloudSleuthDocTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,34 @@ package org.springframework.cloud.sleuth.documentation; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; +import org.assertj.core.api.BDDAssertions; +import org.junit.Before; import org.junit.Test; -import org.mockito.BDDMockito; -import org.mockito.Mockito; -import org.springframework.cloud.sleuth.*; -import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceCallable; -import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceRunnable; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.SpanName; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.instrument.async.TraceCallable; +import org.springframework.cloud.sleuth.instrument.async.TraceRunnable; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.Random; -import java.util.concurrent.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * Test class to be embedded in the @@ -42,13 +53,25 @@ */ public class SpringCloudSleuthDocTests { + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .sampler(Sampler.ALWAYS_SAMPLE) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + + @Before + public void setup() { + this.reporter.clear(); + } @Configuration public class SamplingConfiguration { // tag::always_sampler[] @Bean public Sampler defaultSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } // end::always_sampler[] } @@ -68,16 +91,20 @@ public void should_set_runnable_name_to_annotated_value() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); SpanNamer spanNamer = new DefaultSpanNamer(); - Tracer tracer = Mockito.mock(Tracer.class); + ErrorParser errorParser = new ExceptionMessageErrorParser(); // tag::span_name_annotated_runnable_execution[] - Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable()); + Runnable runnable = new TraceRunnable(tracer, spanNamer, errorParser, + new TaxCountingRunnable()); Future future = executorService.submit(runnable); // ... some additional logic ... future.get(); // end::span_name_annotated_runnable_execution[] - BDDMockito.then(tracer).should().createSpan(BDDMockito.eq("calculateTax"), (Span) BDDMockito.any()); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()) + .isEqualTo("calculatetax"); } @Test @@ -85,10 +112,10 @@ public void should_set_runnable_name_to_to_string_value() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); SpanNamer spanNamer = new DefaultSpanNamer(); - Tracer tracer = Mockito.mock(Tracer.class); + ErrorParser errorParser = new ExceptionMessageErrorParser(); // tag::span_name_to_string_runnable_execution[] - Runnable runnable = new TraceRunnable(tracer, spanNamer, new Runnable() { + Runnable runnable = new TraceRunnable(tracer, spanNamer, errorParser, new Runnable() { @Override public void run() { // perform logic } @@ -102,13 +129,13 @@ public void should_set_runnable_name_to_to_string_value() future.get(); // end::span_name_to_string_runnable_execution[] - BDDMockito.then(tracer).should().createSpan(BDDMockito.eq("calculateTax"), (Span) BDDMockito.any()); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()) + .isEqualTo("calculatetax"); executorService.shutdown(); } - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - @Test public void should_create_a_span_with_tracer() { String taxValue = "10"; @@ -116,59 +143,67 @@ public void should_create_a_span_with_tracer() { // tag::manual_span_creation[] // Start a span. If there was a span present in this thread it will become // the `newSpan`'s parent. - Span newSpan = this.tracer.createSpan("calculateTax"); - try { + Span newSpan = this.tracer.nextSpan().name("calculateTax"); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(newSpan.start())) { // ... // You can tag a span - this.tracer.addTag("taxValue", taxValue); + newSpan.tag("taxValue", taxValue); // ... // You can log an event on a span - newSpan.logEvent("taxCalculated"); + newSpan.annotate("taxCalculated"); } finally { - // Once done remember to close the span. This will allow collecting + // Once done remember to finish the span. This will allow collecting // the span to send it to Zipkin - this.tracer.close(newSpan); + newSpan.finish(); } // end::manual_span_creation[] - then(this.tracer.getCurrentSpan()).isNull(); - then(newSpan).isNotNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).name()) + .isEqualTo("calculatetax"); + then(spans.get(0).tags()) + .containsEntry("taxValue", "10"); + then(spans.get(0).annotations()).hasSize(1); } @Test public void should_continue_a_span_with_tracer() throws Exception { ExecutorService executorService = Executors.newSingleThreadExecutor(); String taxValue = "10"; - Span initialSpan = this.tracer.createSpan("calculateTax"); - assertThat(initialSpan.tags()).doesNotContainKeys("taxValue"); - assertThat(initialSpan.logs()).extracting("event").doesNotContain("taxCalculated"); - - executorService.submit(() -> { - // tag::manual_span_continuation[] - // let's assume that we're in a thread Y and we've received - // the `initialSpan` from thread X - Span continuedSpan = this.tracer.continueSpan(initialSpan); - try { - // ... - // You can tag a span - this.tracer.addTag("taxValue", taxValue); - // ... - // You can log an event on a span - continuedSpan.logEvent("taxCalculated"); - } finally { - // Once done remember to detach the span. That way you'll - // safely remove it from the current thread without closing it - this.tracer.detach(continuedSpan); + Span newSpan = this.tracer.nextSpan().name("calculateTax"); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(newSpan.start())) { + executorService.submit(() -> { + // tag::manual_span_continuation[] + // let's assume that we're in a thread Y and we've received + // the `initialSpan` from thread X + Span continuedSpan = this.tracer.joinSpan(newSpan.context()); + try { + // ... + // You can tag a span + continuedSpan.tag("taxValue", taxValue); + // ... + // You can log an event on a span + continuedSpan.annotate("taxCalculated"); + } finally { + // Once done remember to flush the span. That means that + // it will get reported but the span itself is not yet finished + continuedSpan.flush(); + } + // end::manual_span_continuation[] } - // end::manual_span_continuation[] - } - ).get(); + ).get(); + } finally { + newSpan.finish(); + } - this.tracer.close(initialSpan); - then(this.tracer.getCurrentSpan()).isNull(); - then(initialSpan) - .hasATag("taxValue", taxValue) - .hasLoggedAnEvent("taxCalculated"); + List spans = this.reporter.getSpans(); + BDDAssertions.then(spans).hasSize(1); + BDDAssertions.then(spans.get(0).name()) + .isEqualTo("calculatetax"); + BDDAssertions.then(spans.get(0).tags()) + .containsEntry("taxValue", "10"); + BDDAssertions.then(spans.get(0).annotations()).hasSize(1); executorService.shutdown(); } @@ -176,46 +211,48 @@ public void should_continue_a_span_with_tracer() throws Exception { public void should_start_a_span_with_explicit_parent() throws Exception { ExecutorService executorService = Executors.newSingleThreadExecutor(); String commissionValue = "10"; - Span initialSpan = this.tracer.createSpan("calculateTax"); - assertThat(initialSpan.tags()).doesNotContainKeys("commissionValue"); - assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated"); + Span initialSpan = this.tracer.nextSpan().name("calculateTax").start(); executorService.submit(() -> { // tag::manual_span_joining[] // let's assume that we're in a thread Y and we've received // the `initialSpan` from thread X. `initialSpan` will be the parent // of the `newSpan` - Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan); - try { + Span newSpan = null; + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) { + newSpan = this.tracer.nextSpan().name("calculateCommission"); // ... // You can tag a span - this.tracer.addTag("commissionValue", commissionValue); + newSpan.tag("commissionValue", commissionValue); // ... // You can log an event on a span - newSpan.logEvent("commissionCalculated"); + newSpan.annotate("commissionCalculated"); } finally { - // Once done remember to close the span. This will allow collecting + // Once done remember to finish the span. This will allow collecting // the span to send it to Zipkin. The tags and events set on the // newSpan will not be present on the parent - this.tracer.close(newSpan); + if (newSpan != null) { + newSpan.finish(); + } } // end::manual_span_joining[] } ).get(); - this.tracer.close(initialSpan); - then(this.tracer.getCurrentSpan()).isNull(); - assertThat(initialSpan.tags()).doesNotContainKeys("commissionValue"); - assertThat(initialSpan.logs()).extracting("event").doesNotContain("commissionCalculated"); + List spans = this.reporter.getSpans(); + Optional calculateTax = spans.stream() + .filter(span -> span.name().equals("calculatecommission")).findFirst(); + BDDAssertions.then(calculateTax).isPresent(); + BDDAssertions.then(calculateTax.get().tags()) + .containsEntry("commissionValue", "10"); + BDDAssertions.then(calculateTax.get().annotations()).hasSize(1); executorService.shutdown(); } @Test public void should_wrap_runnable_in_its_sleuth_representative() { SpanNamer spanNamer = new DefaultSpanNamer(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), spanNamer, - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - Span initialSpan = tracer.createSpan("initialSpan"); + ErrorParser errorParser = new ExceptionMessageErrorParser(); // tag::trace_runnable[] Runnable runnable = new Runnable() { @Override @@ -229,24 +266,20 @@ public String toString() { } }; // Manual `TraceRunnable` creation with explicit "calculateTax" Span name - Runnable traceRunnable = new TraceRunnable(tracer, spanNamer, runnable, "calculateTax"); - // Wrapping `Runnable` with `Tracer`. The Span name will be taken either from the - // `@SpanName` annotation or from `toString` method - Runnable traceRunnableFromTracer = tracer.wrap(runnable); + Runnable traceRunnable = new TraceRunnable(tracer, spanNamer, errorParser, + runnable, "calculateTax"); + // Wrapping `Runnable` with `Tracing`. That way the current span will be available + // in the thread of `Runnable` + Runnable traceRunnableFromTracer = tracing.currentTraceContext().wrap(runnable); // end::trace_runnable[] then(traceRunnable).isExactlyInstanceOf(TraceRunnable.class); - then(traceRunnableFromTracer).isExactlyInstanceOf(SpanContinuingTraceRunnable.class); - tracer.close(initialSpan); - then(this.tracer.getCurrentSpan()).isNull(); } @Test public void should_wrap_callable_in_its_sleuth_representative() { SpanNamer spanNamer = new DefaultSpanNamer(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), spanNamer, - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - Span initialSpan = tracer.createSpan("initialSpan"); + ErrorParser errorParser = new ExceptionMessageErrorParser(); // tag::trace_callable[] Callable callable = new Callable() { @Override @@ -260,15 +293,12 @@ public String toString() { } }; // Manual `TraceCallable` creation with explicit "calculateTax" Span name - Callable traceCallable = new TraceCallable<>(tracer, spanNamer, callable, "calculateTax"); - // Wrapping `Callable` with `Tracer`. The Span name will be taken either from the - // `@SpanName` annotation or from `toString` method - Callable traceCallableFromTracer = tracer.wrap(callable); + Callable traceCallable = new TraceCallable<>(tracer, spanNamer, errorParser, + callable, "calculateTax"); + // Wrapping `Callable` with `Tracing`. That way the current span will be available + // in the thread of `Callable` + Callable traceCallableFromTracer = tracing.currentTraceContext().wrap(callable); // end::trace_callable[] - - then(traceCallable).isExactlyInstanceOf(TraceCallable.class); - then(traceCallableFromTracer).isExactlyInstanceOf(SpanContinuingTraceCallable.class); - tracer.close(initialSpan); } private String someLogic() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java index 91c0eca1ae..978146e764 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/DefaultTestAutoConfiguration.java @@ -8,15 +8,14 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration; import org.springframework.context.annotation.Configuration; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @EnableAutoConfiguration(exclude = { LoadBalancerAutoConfiguration.class, - JmxAutoConfiguration.class, TraceSpringIntegrationAutoConfiguration.class, - TraceWebSocketAutoConfiguration.class }) + JmxAutoConfiguration.class}) +// ,TraceSpringIntegrationAutoConfiguration.class, +// TraceWebSocketAutoConfiguration.class }) @Configuration public @interface DefaultTestAutoConfiguration { } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java index 99de29cdfa..4af65dda68 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/AsyncCustomAutoConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java index e93edf0a99..0552ecb704 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LazyTraceAsyncCustomizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java deleted file mode 100644 index 374dc68104..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/LocalComponentTraceCallableTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.async; - -import java.util.Random; - -import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -@SuppressWarnings("unchecked") -public class LocalComponentTraceCallableTest { - - Span closedSpan; - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()) { - @Override public Span close(Span span) { - LocalComponentTraceCallableTest.this.closedSpan = span; - return super.close(span); - } - }; - - @Test - public void should_delegate_to_callable_wrapped_in_a_local_component() throws Exception { - SpanContinuingTraceCallable callable = new SpanContinuingTraceCallable<>(this.tracer, new TraceKeys(), new DefaultSpanNamer(), - () -> "hello"); - - String response = callable.call(); - - then(response).isEqualTo("hello"); - then(this.closedSpan).isALocalComponentSpan(); - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncIntegrationTests.java new file mode 100644 index 0000000000..bd2ea783b4 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncIntegrationTests.java @@ -0,0 +1,212 @@ + +package org.springframework.cloud.sleuth.instrument.async; + +import java.util.AbstractMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import org.awaitility.Awaitility; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.SpanName; +import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.test.context.junit4.SpringRunner; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { + TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }) +public class TraceAsyncIntegrationTests { + + @Autowired + ClassPerformingAsyncLogic classPerformingAsyncLogic; + @Autowired + Tracing tracer; + @Autowired + ArrayListSpanReporter reporter; + + @Before + public void cleanup() { + this.classPerformingAsyncLogic.clear(); + this.reporter.clear(); + } + + @Test + public void should_set_span_on_an_async_annotated_method() { + whenAsyncProcessingTakesPlace(); + + thenANewAsyncSpanGetsCreated(); + } + + @Test + public void should_set_span_with_custom_method_on_an_async_annotated_method() { + whenAsyncProcessingTakesPlaceWithCustomSpanName(); + + thenAsyncSpanHasCustomName(); + } + + @Test + public void should_continue_a_span_on_an_async_annotated_method() { + Span span = givenASpanInCurrentThread(); + + try (Tracer.SpanInScope ws = this.tracer.tracer().withSpanInScope(span.start())) { + whenAsyncProcessingTakesPlace(); + + thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(span); + } finally { + span.finish(); + } + } + + @Test + public void should_continue_a_span_with_custom_method_on_an_async_annotated_method() { + Span span = givenASpanInCurrentThread(); + + try (Tracer.SpanInScope ws = this.tracer.tracer().withSpanInScope(span.start())) { + whenAsyncProcessingTakesPlaceWithCustomSpanName(); + + thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(span); + } finally { + span.finish(); + } + } + + private Span givenASpanInCurrentThread() { + return this.tracer.tracer().nextSpan().name("http:existing"); + } + + private void whenAsyncProcessingTakesPlace() { + this.classPerformingAsyncLogic.invokeAsynchronousLogic(); + } + + private void whenAsyncProcessingTakesPlaceWithCustomSpanName() { + this.classPerformingAsyncLogic.customNameInvokeAsynchronousLogic(); + } + + private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(final Span span) { + Awaitility.await().atMost(5, SECONDS).untilAsserted( + () -> { + Span asyncSpan = TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan(); + then(asyncSpan.context().traceId()).isEqualTo(span.context().traceId()); + List spans = TraceAsyncIntegrationTests.this.reporter + .getSpans(); + then(spans).hasSize(1); + zipkin2.Span reportedAsyncSpan = spans.get(0); + then(reportedAsyncSpan.traceId()).isEqualTo(span.context().traceIdString()); + then(reportedAsyncSpan.name()).isEqualTo("invoke-asynchronous-logic"); + then(reportedAsyncSpan.tags()) + .contains(new AbstractMap.SimpleEntry<>("class", "ClassPerformingAsyncLogic")) + .contains(new AbstractMap.SimpleEntry<>("method", "invokeAsynchronousLogic")); + }); + } + + private void thenANewAsyncSpanGetsCreated() { + Awaitility.await().atMost(5, SECONDS).untilAsserted( + () -> { + List spans = TraceAsyncIntegrationTests.this.reporter + .getSpans(); + then(spans).hasSize(1); + zipkin2.Span reportedAsyncSpan = spans.get(0); + then(reportedAsyncSpan.name()).isEqualTo("invoke-asynchronous-logic"); + then(reportedAsyncSpan.tags()) + .contains(new AbstractMap.SimpleEntry<>("class", "ClassPerformingAsyncLogic")) + .contains(new AbstractMap.SimpleEntry<>("method", "invokeAsynchronousLogic")); + }); + } + + private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(final Span span) { + Awaitility.await().atMost(5, SECONDS).untilAsserted( + () -> { + Span asyncSpan = TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan(); + then(asyncSpan.context().traceId()).isEqualTo(span.context().traceId()); + List spans = TraceAsyncIntegrationTests.this.reporter + .getSpans(); + then(spans).hasSize(1); + zipkin2.Span reportedAsyncSpan = spans.get(0); + then(reportedAsyncSpan.traceId()).isEqualTo(span.context().traceIdString()); + then(reportedAsyncSpan.name()).isEqualTo("foo"); + then(reportedAsyncSpan.tags()) + .contains(new AbstractMap.SimpleEntry<>("class", "ClassPerformingAsyncLogic")) + .contains(new AbstractMap.SimpleEntry<>("method", "customNameInvokeAsynchronousLogic")); + }); + } + + private void thenAsyncSpanHasCustomName() { + Awaitility.await().atMost(5, SECONDS).untilAsserted( + () -> { + List spans = TraceAsyncIntegrationTests.this.reporter + .getSpans(); + then(spans).hasSize(1); + zipkin2.Span reportedAsyncSpan = spans.get(0); + then(reportedAsyncSpan.name()).isEqualTo("foo"); + then(reportedAsyncSpan.tags()) + .contains(new AbstractMap.SimpleEntry<>("class", "ClassPerformingAsyncLogic")) + .contains(new AbstractMap.SimpleEntry<>("method", "customNameInvokeAsynchronousLogic")); + }); + } + + @DefaultTestAutoConfiguration + @EnableAsync + @Configuration + static class TraceAsyncITestConfiguration { + + @Bean + ClassPerformingAsyncLogic asyncClass(Tracer tracer) { + return new ClassPerformingAsyncLogic(tracer); + } + + @Bean Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + } + + static class ClassPerformingAsyncLogic { + + AtomicReference span = new AtomicReference<>(); + + private final Tracer tracer; + + ClassPerformingAsyncLogic(Tracer tracer) { + this.tracer = tracer; + } + + @Async + public void invokeAsynchronousLogic() { + this.span.set(this.tracer.currentSpan()); + } + + @Async + @SpanName("foo") + public void customNameInvokeAsynchronousLogic() { + this.span.set(this.tracer.currentSpan()); + } + + public Span getSpan() { + return this.span.get(); + } + + public void clear() { + this.span.set(null); + } + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutorTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutorTest.java new file mode 100644 index 0000000000..742ec18f42 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceAsyncListenableTaskExecutorTest.java @@ -0,0 +1,151 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.async; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; +import org.junit.Test; +import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; + +/** + * @author Marcin Grzejszczak + */ +public class TraceAsyncListenableTaskExecutorTest { + + AsyncListenableTaskExecutor delegate = new SimpleAsyncTaskExecutor(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .build(); + Tracer tracer = this.tracing.tracer(); + TraceAsyncListenableTaskExecutor traceAsyncListenableTaskExecutor = new TraceAsyncListenableTaskExecutor( + this.delegate, this.tracing); + + @Test + public void should_submit_listenable_trace_runnable() throws Exception { + AtomicBoolean executed = new AtomicBoolean(); + Span span = this.tracer.nextSpan().name("foo"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceAsyncListenableTaskExecutor.submitListenable(aRunnable(this.tracing, executed)).get(); + } finally { + span.finish(); + } + + BDDAssertions.then(executed.get()).isTrue(); + } + + @Test + public void should_submit_listenable_trace_callable() throws Exception { + Span span = this.tracer.nextSpan().name("foo"); + Span spanFromListenable; + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + spanFromListenable = this.traceAsyncListenableTaskExecutor + .submitListenable(aCallable(this.tracing)).get(); + } finally { + span.finish(); + } + + BDDAssertions.then(spanFromListenable).isNotNull(); + } + + @Test + public void should_execute_a_trace_runnable() throws Exception { + AtomicBoolean executed = new AtomicBoolean(); + Span span = this.tracer.nextSpan().name("foo"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceAsyncListenableTaskExecutor.execute(aRunnable(this.tracing, executed)); + } finally { + span.finish(); + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> { + BDDAssertions.then(executed.get()).isTrue(); + }); + } + + @Test + public void should_execute_with_timeout_a_trace_runnable() throws Exception { + AtomicBoolean executed = new AtomicBoolean(); + Span span = this.tracer.nextSpan().name("foo"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceAsyncListenableTaskExecutor.execute(aRunnable(this.tracing, executed), 1L); + } finally { + span.finish(); + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> { + BDDAssertions.then(executed.get()).isTrue(); + }); + } + + @Test + public void should_submit_trace_callable() throws Exception { + Span span = this.tracer.nextSpan().name("foo"); + Span spanFromListenable; + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + spanFromListenable = this.traceAsyncListenableTaskExecutor + .submit(aCallable(this.tracing)).get(); + } finally { + span.finish(); + } + + BDDAssertions.then(spanFromListenable).isNotNull(); + } + + @Test + public void should_submit_trace_runnable() throws Exception { + AtomicBoolean executed = new AtomicBoolean(); + Span span = this.tracer.nextSpan().name("foo"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceAsyncListenableTaskExecutor.submit(aRunnable(this.tracing, executed)).get(); + } finally { + span.finish(); + } + + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .untilAsserted(() -> { + BDDAssertions.then(executed.get()).isTrue(); + }); + } + + Runnable aRunnable(Tracing tracing, AtomicBoolean executed) { + return () -> { + BDDAssertions.then(tracing.tracer().currentSpan()).isNotNull(); + executed.set(true); + }; + } + + Callable aCallable(Tracing tracing) { + return () -> tracing.tracer().currentSpan(); + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceCallableTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceCallableTests.java index e30eec21de..940a074315 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceCallableTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceCallableTests.java @@ -1,39 +1,39 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanName; -import org.springframework.cloud.sleuth.TraceCallable; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(MockitoJUnitRunner.class) public class TraceCallableTests { ExecutorService executor = Executors.newSingleThreadExecutor(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), - new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); @After public void clean() { - TestSpanContextHolder.removeCurrentSpan(); + this.tracing.close(); + this.reporter.clear(); } @Test @@ -45,9 +45,8 @@ public void should_not_see_same_trace_id_in_successive_tasks() Span secondSpan = whenCallableGetsSubmitted( thatRetrievesTraceFromThreadLocal()); - then(secondSpan.getTraceId()) - .isNotEqualTo(firstSpan.getTraceId()); - then(secondSpan.getSavedSpan()).isNull(); + then(secondSpan.context().traceId()) + .isNotEqualTo(firstSpan.context().traceId()); } @Test @@ -64,10 +63,13 @@ public void should_remove_span_from_thread_local_after_finishing_work() @Test public void should_remove_parent_span_from_thread_local_after_finishing_work() throws Exception { - Span parent = givenSpanIsAlreadyActive(); - Span child = givenCallableGetsSubmitted(thatRetrievesTraceFromThreadLocal()); - then(parent).as("parent").isNotNull(); - then(child.getSavedSpan()).isEqualTo(parent); + Span parent = this.tracer.nextSpan().name("http:parent"); + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(parent)){ + Span child = givenCallableGetsSubmitted(thatRetrievesTraceFromThreadLocal()); + then(parent).as("parent").isNotNull(); + then(child.context().parentId()).isEqualTo(parent.context().spanId()); + } + then(this.tracer.currentSpan()).isNull(); Span secondSpan = whenNonTraceableCallableGetsSubmitted( thatRetrievesTraceFromThreadLocal()); @@ -78,29 +80,27 @@ public void should_remove_parent_span_from_thread_local_after_finishing_work() @Test public void should_take_name_of_span_from_span_name_annotation() throws Exception { - Span span = whenATraceKeepingCallableGetsSubmitted(); + whenATraceKeepingCallableGetsSubmitted(); - then(span).hasNameEqualTo("some-callable-name-from-annotation"); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).name()).isEqualTo("some-callable-name-from-annotation"); } @Test public void should_take_name_of_span_from_to_string_if_span_name_annotation_is_missing() throws Exception { - Span span = whenCallableGetsSubmitted( + whenCallableGetsSubmitted( thatRetrievesTraceFromThreadLocal()); - then(span).hasNameEqualTo("some-callable-name-from-to-string"); - } - - private Span givenSpanIsAlreadyActive() { - return this.tracer.createSpan("http:parent"); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).name()).isEqualTo("some-callable-name-from-to-string"); } private Callable thatRetrievesTraceFromThreadLocal() { return new Callable() { @Override public Span call() throws Exception { - return TestSpanContextHolder.getCurrentSpan(); + return Tracing.currentTracer().currentSpan(); } @Override @@ -117,13 +117,13 @@ private Span givenCallableGetsSubmitted(Callable callable) private Span whenCallableGetsSubmitted(Callable callable) throws InterruptedException, java.util.concurrent.ExecutionException { - return this.executor.submit(new TraceCallable<>(this.tracer, new DefaultSpanNamer(), callable)) - .get(); + return this.executor.submit(new TraceCallable<>(this.tracing.tracer(), new DefaultSpanNamer(), + new ExceptionMessageErrorParser(), callable)).get(); } private Span whenATraceKeepingCallableGetsSubmitted() throws InterruptedException, java.util.concurrent.ExecutionException { - return this.executor.submit(new TraceCallable<>(this.tracer, new DefaultSpanNamer(), - new TraceKeepingCallable())).get(); + return this.executor.submit(new TraceCallable<>(this.tracing.tracer(), new DefaultSpanNamer(), + new ExceptionMessageErrorParser(), new TraceKeepingCallable())).get(); } private Span whenNonTraceableCallableGetsSubmitted(Callable callable) @@ -137,7 +137,7 @@ static class TraceKeepingCallable implements Callable { @Override public Span call() throws Exception { - this.span = TestSpanContextHolder.getCurrentSpan(); + this.span = Tracing.currentTracer().currentSpan(); return this.span; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnableTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnableTests.java index e576c71385..404d8e2726 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnableTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceRunnableTests.java @@ -1,39 +1,37 @@ package org.springframework.cloud.sleuth.instrument.async; -import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; +import brave.Span; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanName; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.TraceRunnable; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(MockitoJUnitRunner.class) public class TraceRunnableTests { ExecutorService executor = Executors.newSingleThreadExecutor(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), - new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); @After - public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); + public void clean() { + this.tracing.close(); + this.reporter.clear(); } @Test @@ -50,11 +48,11 @@ public void should_remove_span_from_thread_local_after_finishing_work() // then Span secondSpan = traceKeepingRunnable.span; - then(secondSpan.getTraceId()).as("second span id") - .isNotEqualTo(firstSpan.getTraceId()).as("first span id"); + then(secondSpan.context().traceId()).as("second span id") + .isNotEqualTo(firstSpan.context().traceId()).as("first span id"); // and - then(secondSpan.getSavedSpan()).as("saved span as remnant of first span") + then(secondSpan.context().parentId()).as("saved span as remnant of first span") .isNull(); } @@ -82,7 +80,8 @@ public void should_take_name_of_span_from_span_name_annotation() whenRunnableGetsSubmitted(traceKeepingRunnable); - then(traceKeepingRunnable.span).hasNameEqualTo("some-runnable-name-from-annotation"); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).name()).isEqualTo("some-runnable-name-from-annotation"); } @Test @@ -93,7 +92,8 @@ public void should_take_name_of_span_from_to_string_if_span_name_annotation_is_m whenRunnableGetsSubmitted(runnable); - then(span.get()).hasNameEqualTo("some-runnable-name-from-to-string"); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).name()).isEqualTo("some-runnable-name-from-to-string"); } private TraceKeepingRunnable runnableThatRetrievesTraceFromThreadLocal() { @@ -105,7 +105,8 @@ private void givenRunnableGetsSubmitted(Runnable runnable) throws Exception { } private void whenRunnableGetsSubmitted(Runnable runnable) throws Exception { - this.executor.submit(new TraceRunnable(this.tracer, new DefaultSpanNamer(), runnable)).get(); + this.executor.submit(new TraceRunnable(this.tracing.tracer(), new DefaultSpanNamer(), + new ExceptionMessageErrorParser(), runnable)).get(); } private void whenNonTraceableRunnableGetsSubmitted(Runnable runnable) @@ -117,7 +118,7 @@ private Runnable runnableWithCustomToString(final AtomicReference span) { return new Runnable() { @Override public void run() { - span.set(TestSpanContextHolder.getCurrentSpan()); + span.set(Tracing.currentTracer().currentSpan()); } @Override public String toString() { @@ -132,7 +133,7 @@ static class TraceKeepingRunnable implements Runnable { @Override public void run() { - this.span = TestSpanContextHolder.getCurrentSpan(); + this.span = Tracing.currentTracer().currentSpan(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java index e515eb866c..8a06fa3ede 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableExecutorServiceTests.java @@ -5,7 +5,6 @@ import java.util.Collections; import java.util.List; import java.util.Queue; -import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -13,6 +12,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.assertj.core.api.BDDAssertions; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -22,17 +26,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.BDDAssertions.then; @@ -41,46 +40,54 @@ public class TraceableExecutorServiceTests { private static int TOTAL_THREADS = 10; - @Mock SpanNamer spanNamer; - Tracer tracer; + @Mock BeanFactory beanFactory; ExecutorService executorService = Executors.newFixedThreadPool(3); ExecutorService traceManagerableExecutorService; + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); SpanVerifyingRunnable spanVerifyingRunnable = new SpanVerifyingRunnable(); @Before public void setup() { - this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - this.traceManagerableExecutorService = new TraceableExecutorService(this.executorService, - this.tracer, new TraceKeys(), this.spanNamer); - TestSpanContextHolder.removeCurrentSpan(); + this.traceManagerableExecutorService = new TraceableExecutorService(beanFactory(), this.executorService); + this.reporter.clear(); + this.spanVerifyingRunnable.clear(); } @After public void tearDown() throws Exception { - this.tracer = null; this.traceManagerableExecutorService.shutdown(); this.executorService.shutdown(); - TestSpanContextHolder.removeCurrentSpan(); + if (Tracing.current() != null) { + Tracing.current().close(); + } } @Test public void should_propagate_trace_id_and_set_new_span_when_traceable_executor_service_is_executed() throws Exception { - Span span = this.tracer.createSpan("http:PARENT"); - CompletableFuture.allOf(runnablesExecutedViaTraceManagerableExecutorService()).get(); - this.tracer.close(span); + Span span = this.tracer.nextSpan().name("http:PARENT"); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + CompletableFuture.allOf(runnablesExecutedViaTraceManagerableExecutorService()).get(); + } finally { + span.finish(); + } - then(this.spanVerifyingRunnable.traceIds.stream().distinct().collect(toList())).containsOnly(span.getTraceId()); - then(this.spanVerifyingRunnable.spanIds.stream().distinct().collect(toList())).hasSize(TOTAL_THREADS); + then(this.spanVerifyingRunnable.traceIds.stream().distinct() + .collect(toList())).containsOnly(span.context().traceId()); + then(this.spanVerifyingRunnable.spanIds.stream().distinct() + .collect(toList())).hasSize(TOTAL_THREADS); } @Test @SuppressWarnings("unchecked") public void should_wrap_methods_in_trace_representation_only_for_non_tracing_callables() throws Exception { ExecutorService executorService = Mockito.mock(ExecutorService.class); - TraceableExecutorService traceExecutorService = new TraceableExecutorService( - executorService, this.tracer, new TraceKeys(), this.spanNamer); + TraceableExecutorService traceExecutorService = new TraceableExecutorService(beanFactory(), executorService); traceExecutorService.invokeAll(callables()); BDDMockito.then(executorService).should().invokeAll(BDDMockito.argThat( @@ -104,9 +111,9 @@ public void should_wrap_methods_in_trace_representation_only_for_non_tracing_cal private ArgumentMatcher>> withSpanContinuingTraceCallablesOnly() { return argument -> { try { - SleuthAssertions.then(argument) + BDDAssertions.then(argument) .flatExtracting(Object::getClass) - .containsOnlyElementsOf(Collections.singletonList(SpanContinuingTraceCallable.class)); + .containsOnlyElementsOf(Collections.singletonList(TraceCallable.class)); } catch (AssertionError e) { return false; } @@ -116,29 +123,27 @@ private ArgumentMatcher>> withSpanContinui private List callables() { List list = new ArrayList<>(); - list.add(new LocalComponentTraceCallable(this.tracer, new TraceKeys(), this.spanNamer, () -> "foo")); + list.add(new TraceCallable<>(this.tracing.tracer(), new DefaultSpanNamer(), + new ExceptionMessageErrorParser(), () -> "foo")); list.add((Callable) () -> "bar"); return list; } @Test public void should_propagate_trace_info_when_compleable_future_is_used() throws Exception { - Tracer tracer = this.tracer; - TraceKeys traceKeys = new TraceKeys(); - SpanNamer spanNamer = new DefaultSpanNamer(); ExecutorService executorService = this.executorService; - + BeanFactory beanFactory = beanFactory(); // tag::completablefuture[] CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> { // perform some logic return 1_000_000L; - }, new TraceableExecutorService(executorService, + }, new TraceableExecutorService(beanFactory, executorService, // 'calculateTax' explicitly names the span - this param is optional - tracer, traceKeys, spanNamer, "calculateTax")); + "calculateTax")); // end::completablefuture[] then(completableFuture.get()).isEqualTo(1_000_000L); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.currentSpan()).isNull(); } private CompletableFuture[] runnablesExecutedViaTraceManagerableExecutorService() { @@ -148,6 +153,13 @@ private CompletableFuture[] runnablesExecutedViaTraceManagerableExecutorServi } return futures.toArray(new CompletableFuture[futures.size()]); } + + BeanFactory beanFactory() { + BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); + BDDMockito.given(this.beanFactory.getBean(SpanNamer.class)).willReturn(new DefaultSpanNamer()); + BDDMockito.given(this.beanFactory.getBean(ErrorParser.class)).willReturn(new ExceptionMessageErrorParser()); + return this.beanFactory; + } class SpanVerifyingRunnable implements Runnable { @@ -156,11 +168,15 @@ class SpanVerifyingRunnable implements Runnable { @Override public void run() { - Span span = TestSpanContextHolder.getCurrentSpan(); - this.traceIds.add(span.getTraceId()); - this.spanIds.add(span.getSpanId()); + Span span = Tracing.currentTracer().currentSpan(); + this.traceIds.add(span.context().traceId()); + this.spanIds.add(span.context().spanId()); } + void clear() { + this.traceIds.clear(); + this.spanIds.clear(); + } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java index e82b5086d8..a047663bd9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/TraceableScheduledExecutorServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,15 @@ package org.springframework.cloud.sleuth.instrument.async; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; @@ -23,14 +32,11 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -42,24 +48,28 @@ @RunWith(MockitoJUnitRunner.class) public class TraceableScheduledExecutorServiceTest { + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .build(); @Mock - Tracer tracer; - @Mock - TraceKeys traceKeys; - @Mock - SpanNamer spanNamer; + BeanFactory beanFactory; @Mock ScheduledExecutorService scheduledExecutorService; @InjectMocks TraceableScheduledExecutorService traceableScheduledExecutorService; + @Before + public void setup() { + beanFactory(); + } + @Test public void should_schedule_a_trace_runnable() throws Exception { this.traceableScheduledExecutorService.schedule(aRunnable(), 1L, TimeUnit.DAYS); then(this.scheduledExecutorService).should().schedule( BDDMockito.argThat( - matcher(Runnable.class, instanceOf(SpanContinuingTraceRunnable.class))), + matcher(Runnable.class, instanceOf(TraceRunnable.class))), anyLong(), any(TimeUnit.class)); } @@ -69,7 +79,7 @@ public void should_schedule_a_trace_callable() throws Exception { then(this.scheduledExecutorService).should().schedule( BDDMockito.argThat(matcher(Callable.class, - instanceOf(SpanContinuingTraceCallable.class))), + instanceOf(TraceCallable.class))), anyLong(), any(TimeUnit.class)); } @@ -80,7 +90,7 @@ public void should_schedule_at_fixed_rate_a_trace_runnable() TimeUnit.DAYS); then(this.scheduledExecutorService).should().scheduleAtFixedRate( - BDDMockito.argThat(matcher(Runnable.class, instanceOf(SpanContinuingTraceRunnable.class))), + BDDMockito.argThat(matcher(Runnable.class, instanceOf(TraceRunnable.class))), anyLong(), anyLong(), any(TimeUnit.class)); } @@ -91,7 +101,7 @@ public void should_schedule_with_fixed_delay_a_trace_runnable() TimeUnit.DAYS); then(this.scheduledExecutorService).should().scheduleWithFixedDelay( - BDDMockito.argThat(matcher(Runnable.class, instanceOf(SpanContinuingTraceRunnable.class))), + BDDMockito.argThat(matcher(Runnable.class, instanceOf(TraceRunnable.class))), anyLong(), anyLong(), any(TimeUnit.class)); } @@ -111,4 +121,11 @@ Runnable aRunnable() { Callable aCallable() { return () -> null; } + + BeanFactory beanFactory() { + BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracing.tracer()); + BDDMockito.given(this.beanFactory.getBean(SpanNamer.class)).willReturn(new DefaultSpanNamer()); + BDDMockito.given(this.beanFactory.getBean(ErrorParser.class)).willReturn(new ExceptionMessageErrorParser()); + return this.beanFactory; + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java index 49ebff6a52..a321b56b61 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue410/Issue410Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,19 @@ package org.springframework.cloud.sleuth.instrument.async.issues.issue410; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.awaitility.Awaitility; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanFactory; @@ -35,11 +38,7 @@ import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -52,7 +51,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import org.awaitility.Awaitility; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak @@ -65,14 +64,10 @@ public class Issue410Tests { private static final Log log = LogFactory .getLog(MethodHandles.lookup().lookupClass()); - @Autowired - Environment environment; - @Autowired - Tracer tracer; - @Autowired - AsyncTask asyncTask; - @Autowired - RestTemplate restTemplate; + @Autowired Environment environment; + @Autowired Tracer tracer; + @Autowired AsyncTask asyncTask; + @Autowired RestTemplate restTemplate; /** * Related to issue #445 */ @@ -81,42 +76,46 @@ public class Issue410Tests { @Test public void should_pass_tracing_info_for_tasks_running_without_a_pool() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo"); log.info("Starting test"); - try { + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { String response = this.restTemplate.getForObject( "http://localhost:" + port() + "/without_pool", String.class); - then(response).isEqualTo(span.traceIdString()); + then(response).isEqualTo(span.context().traceIdString()); Awaitility.await().untilAsserted(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()) - .isEqualTo(span.getTraceId()); + then(this.asyncTask.getSpan().get().context().traceId()) + .isEqualTo(span.context().traceId()); }); } finally { - this.tracer.close(span); + span.finish(); } + + then(this.tracer.currentSpan()).isNull(); } @Test public void should_pass_tracing_info_for_tasks_running_with_a_pool() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo"); log.info("Starting test"); - try { + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { String response = this.restTemplate.getForObject( "http://localhost:" + port() + "/with_pool", String.class); - then(response).isEqualTo(span.traceIdString()); + then(response).isEqualTo(span.context().traceIdString()); Awaitility.await().untilAsserted(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()) - .isEqualTo(span.getTraceId()); + then(this.asyncTask.getSpan().get().context().traceId()) + .isEqualTo(span.context().traceId()); }); } finally { - this.tracer.close(span); + span.finish(); } + + then(this.tracer.currentSpan()).isNull(); } /** @@ -124,22 +123,24 @@ public void should_pass_tracing_info_for_tasks_running_with_a_pool() { */ @Test public void should_pass_tracing_info_for_completable_futures_with_executor() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo"); log.info("Starting test"); - try { + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { String response = this.restTemplate.getForObject( "http://localhost:" + port() + "/completable", String.class); - then(response).isEqualTo(span.traceIdString()); + then(response).isEqualTo(span.context().traceIdString()); Awaitility.await().untilAsserted(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()) - .isEqualTo(span.getTraceId()); + then(this.asyncTask.getSpan().get().context().traceId()) + .isEqualTo(span.context().traceId()); }); } finally { - this.tracer.close(span); + span.finish(); } + + then(this.tracer.currentSpan()).isNull(); } /** @@ -147,22 +148,24 @@ public void should_pass_tracing_info_for_completable_futures_with_executor() { */ @Test public void should_pass_tracing_info_for_completable_futures_with_task_scheduler() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo"); log.info("Starting test"); - try { + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { String response = this.restTemplate.getForObject( "http://localhost:" + port() + "/taskScheduler", String.class); - then(response).isEqualTo(span.traceIdString()); + then(response).isEqualTo(span.context().traceIdString()); Awaitility.await().untilAsserted(() -> { then(this.asyncTask.getSpan().get()).isNotNull(); - then(this.asyncTask.getSpan().get().getTraceId()) - .isEqualTo(span.getTraceId()); + then(this.asyncTask.getSpan().get().context().traceId()) + .isEqualTo(span.context().traceId()); }); } finally { - this.tracer.close(span); + span.finish(); } + + then(this.tracer.currentSpan()).isNull(); } private int port() { @@ -176,7 +179,7 @@ class AppConfig { @Bean public Sampler testSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } @Bean @@ -196,11 +199,12 @@ public Executor poolTaskExecutor() { @Component class AsyncTask { - private static final Log log = LogFactory.getLog(AsyncTask.class); + private static final Log log = LogFactory.getLog( + AsyncTask.class); private AtomicReference span = new AtomicReference<>(); - @Autowired + @Autowired Tracer tracer; @Autowired @Qualifier("poolTaskExecutor") @@ -214,24 +218,24 @@ class AsyncTask { @Async("poolTaskExecutor") public void runWithPool() { log.info("This task is running with a pool."); - this.span.set(this.tracer.getCurrentSpan()); + this.span.set(this.tracer.currentSpan()); } @Async public void runWithoutPool() { log.info("This task is running without a pool."); - this.span.set(this.tracer.getCurrentSpan()); + this.span.set(this.tracer.currentSpan()); } public Span completableFutures() throws ExecutionException, InterruptedException { log.info("This task is running with completable future"); CompletableFuture span1 = CompletableFuture.supplyAsync(() -> { AsyncTask.log.info("First completable future"); - return AsyncTask.this.tracer.getCurrentSpan(); + return AsyncTask.this.tracer.currentSpan(); }, AsyncTask.this.executor); CompletableFuture span2 = CompletableFuture.supplyAsync(() -> { AsyncTask.log.info("Second completable future"); - return AsyncTask.this.tracer.getCurrentSpan(); + return AsyncTask.this.tracer.currentSpan(); }, AsyncTask.this.executor); CompletableFuture response = CompletableFuture.allOf(span1, span2) .thenApply(ignoredVoid -> { @@ -239,7 +243,7 @@ public Span completableFutures() throws ExecutionException, InterruptedException Span joinedSpan1 = span1.join(); Span joinedSpan2 = span2.join(); then(joinedSpan2).isNotNull(); - then(joinedSpan1).hasTraceIdEqualTo(joinedSpan2.getTraceId()); + then(joinedSpan1.context().traceId()).isEqualTo(joinedSpan2.context().traceId()); AsyncTask.log.info("TraceIds are correct"); return joinedSpan2; }); @@ -251,13 +255,15 @@ public Span taskScheduler() throws ExecutionException, InterruptedException { log.info("This task is running with completable future"); CompletableFuture span1 = CompletableFuture.supplyAsync(() -> { AsyncTask.log.info("First completable future"); - return AsyncTask.this.tracer.getCurrentSpan(); - }, new LazyTraceExecutor(AsyncTask.this.beanFactory, + return AsyncTask.this.tracer.currentSpan(); + }, new LazyTraceExecutor( + AsyncTask.this.beanFactory, AsyncTask.this.taskScheduler)); CompletableFuture span2 = CompletableFuture.supplyAsync(() -> { AsyncTask.log.info("Second completable future"); - return AsyncTask.this.tracer.getCurrentSpan(); - }, new LazyTraceExecutor(AsyncTask.this.beanFactory, + return AsyncTask.this.tracer.currentSpan(); + }, new LazyTraceExecutor( + AsyncTask.this.beanFactory, AsyncTask.this.taskScheduler)); CompletableFuture response = CompletableFuture.allOf(span1, span2) .thenApply(ignoredVoid -> { @@ -265,7 +271,7 @@ public Span taskScheduler() throws ExecutionException, InterruptedException { Span joinedSpan1 = span1.join(); Span joinedSpan2 = span2.join(); then(joinedSpan2).isNotNull(); - then(joinedSpan1).hasTraceIdEqualTo(joinedSpan2.getTraceId()); + then(joinedSpan1.context().traceId()).isEqualTo(joinedSpan2.context().traceId()); AsyncTask.log.info("TraceIds are correct"); return joinedSpan2; }); @@ -282,18 +288,17 @@ public AtomicReference getSpan() { @RestController class Application { - private static final Log log = LogFactory.getLog(Application.class); + private static final Log log = LogFactory.getLog( + Application.class); - @Autowired - AsyncTask asyncTask; - @Autowired - Tracer tracer; + @Autowired AsyncTask asyncTask; + @Autowired Tracer tracer; @RequestMapping("/with_pool") public String withPool() { log.info("Executing with pool."); this.asyncTask.runWithPool(); - return this.tracer.getCurrentSpan().traceIdString(); + return this.tracer.currentSpan().context().traceIdString(); } @@ -301,19 +306,19 @@ public String withPool() { public String withoutPool() { log.info("Executing without pool."); this.asyncTask.runWithoutPool(); - return this.tracer.getCurrentSpan().traceIdString(); + return this.tracer.currentSpan().context().traceIdString(); } @RequestMapping("/completable") public String completable() throws ExecutionException, InterruptedException { log.info("Executing completable"); - return this.asyncTask.completableFutures().traceIdString(); + return this.asyncTask.completableFutures().context().traceIdString(); } @RequestMapping("/taskScheduler") public String taskScheduler() throws ExecutionException, InterruptedException { log.info("Executing completable via task scheduler"); - return this.asyncTask.taskScheduler().traceIdString(); + return this.asyncTask.taskScheduler().context().traceIdString(); } /** diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java index 4b7b562a5a..f74aba1ad4 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue546/Issue546Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.lang.invoke.MethodHandles; +import brave.Tracing; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; @@ -26,7 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit4.SpringRunner; @@ -37,7 +38,7 @@ import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak @@ -65,6 +66,11 @@ private int port() { @SpringBootApplication class Issue546TestsApp { + @Bean + AsyncRestTemplate asyncRestTemplate() { + return new AsyncRestTemplate(); + } + } @RestController @@ -72,9 +78,9 @@ class Controller { private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); private final AsyncRestTemplate traceAsyncRestTemplate; - private final Tracer tracer; + private final Tracing tracer; - public Controller(AsyncRestTemplate traceAsyncRestTemplate, Tracer tracer) { + public Controller(AsyncRestTemplate traceAsyncRestTemplate, Tracing tracer) { this.traceAsyncRestTemplate = traceAsyncRestTemplate; this.tracer = tracer; } @@ -90,20 +96,24 @@ public Controller(AsyncRestTemplate traceAsyncRestTemplate, Tracer tracer) { public void asyncTest(@RequestParam(required = false) boolean isSleep) throws InterruptedException { log.info("(/trace-async-rest-template) I got a request!"); - final long traceId = tracer.getCurrentSpan().getTraceId(); + final long traceId = tracer.tracer().currentSpan().context().traceId(); ListenableFuture> res = traceAsyncRestTemplate .getForEntity("http://localhost:" + port + "/bean", HogeBean.class); if (isSleep) { Thread.sleep(1000); } res.addCallback(success -> { - then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + then(Controller.this.tracer.tracer().currentSpan().context().traceId()) + .isEqualTo(traceId); log.info("(/trace-async-rest-template) success"); - then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + then(Controller.this.tracer.tracer().currentSpan().context().traceId()) + .isEqualTo(traceId); }, failure -> { - then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + then(Controller.this.tracer.tracer().currentSpan().context().traceId()) + .isEqualTo(traceId); log.error("(/trace-async-rest-template) failure", failure); - then(Controller.this.tracer.getCurrentSpan()).hasTraceIdEqualTo(traceId); + then(Controller.this.tracer.tracer().currentSpan().context().traceId()) + .isEqualTo(traceId); }); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java index 967559dc54..f29bc85a60 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/HystrixAnnotationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,34 +16,31 @@ package org.springframework.cloud.sleuth.instrument.hystrix; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.util.concurrent.atomic.AtomicReference; -import org.junit.After; +import brave.Span; +import brave.Tracing; +import brave.sampler.Sampler; +import org.awaitility.Awaitility; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.netflix.hystrix.EnableHystrix; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.awaitility.Awaitility; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.strategy.HystrixPlugins; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; + @RunWith(SpringRunner.class) @SpringBootTest(classes = { HystrixAnnotationsIntegrationTests.TestConfig.class }) @DirtiesContext @@ -52,7 +49,7 @@ public class HystrixAnnotationsIntegrationTests { @Autowired HystrixCommandInvocationSpanCatcher catcher; @Autowired - Tracer tracer; + Tracing tracer; @BeforeClass @AfterClass @@ -60,20 +57,6 @@ public static void reset() { HystrixPlugins.reset(); } - @After - public void cleanTrace() { - TestSpanContextHolder.removeCurrentSpan(); - } - - @Test - public void should_continue_current_span_when_executed_a_hystrix_command_annotated_method() { - Span span = givenASpanInCurrentThread(); - - whenHystrixCommandAnnotatedMethodGetsExecuted(); - - thenSpanInHystrixThreadIsContinued(span); - } - @Test public void should_create_new_span_with_thread_name_when_executed_a_hystrix_command_annotated_method() { whenHystrixCommandAnnotatedMethodGetsExecuted(); @@ -81,10 +64,6 @@ public void should_create_new_span_with_thread_name_when_executed_a_hystrix_comm thenSpanInHystrixThreadIsCreated(); } - private Span givenASpanInCurrentThread() { - return this.tracer.createSpan("http:existing"); - } - private void whenHystrixCommandAnnotatedMethodGetsExecuted() { this.catcher.invokeLogicWrappedInHystrixCommand(); } @@ -93,18 +72,14 @@ private void thenSpanInHystrixThreadIsContinued(final Span span) { then(span).isNotNull(); Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { then(HystrixAnnotationsIntegrationTests.this.catcher).isNotNull(); - then(span) - .hasTraceIdEqualTo(HystrixAnnotationsIntegrationTests.this.catcher - .getTraceId()) - .hasNameEqualTo(HystrixAnnotationsIntegrationTests.this.catcher - .getSpanName()); + then(span.context().traceId()) + .isEqualTo(HystrixAnnotationsIntegrationTests.this.catcher.getTraceId()); }); } private void thenSpanInHystrixThreadIsCreated() { Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { - then(HystrixAnnotationsIntegrationTests.this.catcher.getSpan()) - .nameStartsWith("hystrix").isALocalComponentSpan(); + then(HystrixAnnotationsIntegrationTests.this.catcher.getSpan()).isNotNull(); }); } @@ -114,13 +89,13 @@ private void thenSpanInHystrixThreadIsCreated() { static class TestConfig { @Bean - HystrixCommandInvocationSpanCatcher spanCatcher() { - return new HystrixCommandInvocationSpanCatcher(); + HystrixCommandInvocationSpanCatcher spanCatcher(Tracing tracing) { + return new HystrixCommandInvocationSpanCatcher(tracing); } @Bean Sampler sampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } @@ -128,11 +103,16 @@ Sampler sampler() { public static class HystrixCommandInvocationSpanCatcher { AtomicReference spanCaughtFromHystrixThread; + private final Tracing tracing; + + public HystrixCommandInvocationSpanCatcher(Tracing tracing) { + this.tracing = tracing; + } @HystrixCommand public void invokeLogicWrappedInHystrixCommand() { this.spanCaughtFromHystrixThread = new AtomicReference<>( - TestSpanContextHolder.getCurrentSpan()); + tracing.tracer().currentSpan()); } public Long getTraceId() { @@ -140,17 +120,7 @@ public Long getTraceId() { || this.spanCaughtFromHystrixThread.get() == null) { return null; } - return this.spanCaughtFromHystrixThread.get().getTraceId(); - } - - public String getSpanName() { - if (this.spanCaughtFromHystrixThread == null - || (this.spanCaughtFromHystrixThread.get() != null - && this.spanCaughtFromHystrixThread.get() - .getName() == null)) { - return null; - } - return this.spanCaughtFromHystrixThread.get().getName(); + return this.spanCaughtFromHystrixThread.get().context().traceId(); } public Span getSpan() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java index 429c19a978..96dc1a4f9f 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/SleuthHystrixConcurrencyStrategyTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,22 @@ package org.springframework.cloud.sleuth.instrument.hystrix; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.BDDMockito; +import org.mockito.Mockito; +import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.instrument.async.TraceCallable; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; + import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; @@ -26,44 +42,25 @@ import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.BDDMockito; -import org.mockito.Mockito; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; - -import java.util.Random; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; + /** * @author Marcin Grzejszczak */ public class SleuthHystrixConcurrencyStrategyTest { - ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanReporter, new TraceKeys()); - TraceKeys traceKeys = new TraceKeys(); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); @Before @After public void setup() { - ExceptionUtils.setFail(true); HystrixPlugins.reset(); - this.spanReporter.getSpans().clear(); + this.reporter.clear(); } @Test @@ -73,7 +70,7 @@ public void should_not_override_existing_custom_strategies() { HystrixPlugins.getInstance().registerMetricsPublisher(new MyHystrixMetricsPublisher()); HystrixPlugins.getInstance().registerPropertiesStrategy(new MyHystrixPropertiesStrategy()); - new SleuthHystrixConcurrencyStrategy(this.tracer, this.traceKeys); + new SleuthHystrixConcurrencyStrategy(this.tracing.tracer(), new DefaultSpanNamer(), new ExceptionMessageErrorParser()); then(HystrixPlugins .getInstance().getCommandExecutionHook()).isExactlyInstanceOf(MyHystrixCommandExecutionHook.class); @@ -90,11 +87,11 @@ public void should_wrap_delegates_callable_in_trace_callable_when_delegate_is_pr throws Exception { HystrixPlugins.getInstance().registerConcurrencyStrategy(new MyHystrixConcurrencyStrategy()); SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); + this.tracing.tracer(), new DefaultSpanNamer(), new ExceptionMessageErrorParser()); Callable callable = strategy.wrapCallable(() -> "hello"); - then(callable).isInstanceOf(SleuthHystrixConcurrencyStrategy.HystrixTraceCallable.class); + then(callable).isInstanceOf(TraceCallable.class); then(callable.call()).isEqualTo("executed_custom_callable"); } @@ -102,63 +99,24 @@ public void should_wrap_delegates_callable_in_trace_callable_when_delegate_is_pr public void should_wrap_callable_in_trace_callable_when_delegate_is_present() throws Exception { SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); + this.tracing.tracer(), new DefaultSpanNamer(), new ExceptionMessageErrorParser()); Callable callable = strategy.wrapCallable(() -> "hello"); - then(callable).isInstanceOf(SleuthHystrixConcurrencyStrategy.HystrixTraceCallable.class); + then(callable).isInstanceOf(TraceCallable.class); } @Test public void should_add_trace_keys_when_span_is_created() throws Exception { SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); - Callable callable = strategy.wrapCallable(() -> "hello"); - - callable.call(); - - String asyncKey = this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(); - then(new ListOfSpans(this.spanReporter.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "hystrix") - .hasASpanWithTagKeyEqualTo(asyncKey); - } - - @Test - public void should_add_trace_keys_when_span_is_continued() - throws Exception { - Span span = this.tracer.createSpan("new_span"); - SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); - Callable callable = strategy.wrapCallable(() -> "hello"); - - callable.call(); - - String asyncKey = this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(); - then(span) - .hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "hystrix") - .hasATagWithKey(asyncKey); - } - - @Test - public void should_not_override_trace_keys_when_span_is_continued() - throws Exception { - Span span = this.tracer.createSpan("new_span"); - String asyncKey = this.traceKeys.getAsync().getPrefix() - + this.traceKeys.getAsync().getThreadNameKey(); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "foo"); - this.tracer.addTag(asyncKey, "bar"); - SleuthHystrixConcurrencyStrategy strategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); + this.tracing.tracer(), new DefaultSpanNamer(), new ExceptionMessageErrorParser()); Callable callable = strategy.wrapCallable(() -> "hello"); callable.call(); - then(span) - .hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "foo") - .hasATag(asyncKey, "bar"); + then(callable).isInstanceOf(TraceCallable.class); + then(this.reporter.getSpans()).hasSize(1); } @Test @@ -167,7 +125,7 @@ public void should_delegate_work_to_custom_hystrix_concurrency_strategy() HystrixConcurrencyStrategy strategy = Mockito.mock(HystrixConcurrencyStrategy.class); HystrixPlugins.getInstance().registerConcurrencyStrategy(strategy); SleuthHystrixConcurrencyStrategy sleuthStrategy = new SleuthHystrixConcurrencyStrategy( - this.tracer, this.traceKeys); + this.tracing.tracer(), new DefaultSpanNamer(), new ExceptionMessageErrorParser()); sleuthStrategy.wrapCallable(() -> "foo"); sleuthStrategy.getThreadPool(HystrixThreadPoolKey.Factory.asKey(""), Mockito.mock( diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java index 603c2aea7f..4e0dcce6d1 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/hystrix/TraceCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,45 +16,40 @@ package org.springframework.cloud.sleuth.instrument.hystrix; +import java.util.List; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.junit.Before; +import org.junit.Test; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; + import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; - -import java.util.Random; import static com.netflix.hystrix.HystrixCommand.Setter.withGroupKey; import static com.netflix.hystrix.HystrixCommandGroupKey.Factory.asKey; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; public class TraceCommandTests { - static final long EXPECTED_TRACE_ID = 1L; - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); @Before public void setup() { HystrixPlugins.reset(); - TestSpanContextHolder.removeCurrentSpan(); - } - - @After - public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } @Test @@ -64,41 +59,42 @@ public void should_remove_span_from_thread_local_after_finishing_work() Span secondSpanFromHystrix = whenCommandIsExecuted(traceReturningCommand()); - then(secondSpanFromHystrix.getTraceId()).as("second trace id") - .isNotEqualTo(firstSpanFromHystrix.getTraceId()).as("first trace id"); - then(secondSpanFromHystrix.getSavedSpan()) - .as("saved span as remnant of first span").isNull(); + then(secondSpanFromHystrix.context().traceId()).as("second trace id") + .isNotEqualTo(firstSpanFromHystrix.context().traceId()).as("first trace id"); } @Test public void should_create_a_local_span_with_proper_tags_when_hystrix_command_gets_executed() throws Exception { - Span spanFromHystrix = whenCommandIsExecuted(traceReturningCommand()); + whenCommandIsExecuted(traceReturningCommand()); - then(spanFromHystrix) - .isALocalComponentSpan() - .hasNameEqualTo("traceCommandKey") - .hasATag("commandKey", "traceCommandKey"); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("commandKey", "traceCommandKey"); } @Test public void should_run_Hystrix_command_with_span_passed_from_parent_thread() { - givenATraceIsPresentInTheCurrentThread(); - TraceCommand command = traceReturningCommand(); - - Span spanFromCommand = whenCommandIsExecuted(command); - - then(spanFromCommand).as("Span from the Hystrix Thread") - .isNotNull() - .hasTraceIdEqualTo(EXPECTED_TRACE_ID) - .hasATag("commandKey", "traceCommandKey") - .hasATag("commandGroup", "group") - .hasATag("threadPoolKey", "group"); + Span span = this.tracer.nextSpan(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + TraceCommand command = traceReturningCommand(); + whenCommandIsExecuted(command); + } finally { + span.finish(); + } + + List spans = this.reporter.getSpans(); + then(spans).hasSize(2); + then(spans.get(0).traceId()).isEqualTo(span.context().traceIdString()); + then(spans.get(0).tags()) + .containsEntry("commandKey", "traceCommandKey") + .containsEntry("commandGroup", "group") + .containsEntry("threadPoolKey", "group"); } @Test public void should_pass_tracing_information_when_using_Hystrix_commands() { - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); + Tracer tracer = this.tracer; TraceKeys traceKeys = new TraceKeys(); HystrixCommand.Setter setter = withGroupKey(asKey("group")) .andCommandKey(HystrixCommandKey.Factory.asKey("command")); @@ -123,18 +119,12 @@ public String doRun() throws Exception { String resultFromTraceCommand = traceCommand.execute(); then(resultFromHystrixCommand).isEqualTo(resultFromTraceCommand); - then(tracer.getCurrentSpan()).isNull(); } private String someLogic(){ return "some logic"; } - private Span givenATraceIsPresentInTheCurrentThread() { - return this.tracer.createSpan("http:test", - Span.builder().traceId(EXPECTED_TRACE_ID).build()); - } - private TraceCommand traceReturningCommand() { return new TraceCommand(this.tracer, new TraceKeys(), withGroupKey(asKey("group")) @@ -145,7 +135,7 @@ private TraceCommand traceReturningCommand() { .andCommandKey(HystrixCommandKey.Factory.asKey("traceCommandKey"))) { @Override public Span doRun() throws Exception { - return TestSpanContextHolder.getCurrentSpan(); + return TraceCommandTests.this.tracer.currentSpan(); } }; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java deleted file mode 100644 index 572b9dfb40..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingExtractorTests.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.junit.Test; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * @author Marcin Grzejszczak - */ -public class HeaderBasedMessagingExtractorTests { - - @Test - public void overridesTheSampleFlagWithSpanFlagForSampledScenario() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L)); - spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "0"); - spanTextMap.put(TraceMessageHeaders.SPAN_FLAGS_NAME, "1"); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isShared(); - } - - @Test - public void doesNotOverrideTheSampleHeaderWithSpanFlagWhenTheSpanFlagIsNot1() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L)); - spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "1"); - spanTextMap.put(TraceMessageHeaders.SPAN_FLAGS_NAME, "0"); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isShared(); - } - - @Test - public void samplesASpanWhenSampledFlagIsSetTo1() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L)); - spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "1"); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isShared(); - } - - @Test - public void doesNotSampleASpanWhenSampledFlagIsSetTo0() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L)); - spanTextMap.put(TraceMessageHeaders.SAMPLED_NAME, "0"); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isNotExportable().isNotShared(); - } - - @Test - public void samplesWhenDebugFlagIsSetTo1RegardlessOfTraceAndSpanId() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_FLAGS_NAME, "1"); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isNotShared(); - then(span.traceIdString()).isNotEmpty(); - then(span.getSpanId()).isNotNull(); - } - - @Test - public void samplesWhenDebugFlagIsSetTo1AndOnlySpanIdIsSet() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_FLAGS_NAME, "1"); - spanTextMap.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isNotShared(); - then(span.traceIdString()).isNotEmpty(); - then(span.getSpanId()).isEqualTo(10L); - } - - @Test - public void samplesWhenDebugFlagIsSetTo1AndOnlyTraceIdIsSet() { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - SpanTextMap spanTextMap = spanTextMap(); - spanTextMap.put(TraceMessageHeaders.SPAN_FLAGS_NAME, "1"); - spanTextMap.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)); - - Span span = extractor.joinTrace(spanTextMap); - - then(span).isExportable().isNotShared(); - then(span.getTraceId()).isEqualTo(10L); - then(span.getSpanId()).isEqualTo(10L); - } - - private SpanTextMap spanTextMap() { - return new SpanTextMap() { - private final Map map = new HashMap<>(); - - @Override public Iterator> iterator() { - return this.map.entrySet().iterator(); - } - - @Override public void put(String key, String value) { - this.map.put(key, value); - } - }; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java deleted file mode 100644 index a40d5501e5..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/HeaderBasedMessagingInjectorTests.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.junit.Test; - -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.TraceKeys; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class HeaderBasedMessagingInjectorTests { - - HeaderBasedMessagingInjector injector = new HeaderBasedMessagingInjector(new TraceKeys()); - - @Test - public void should_not_override_already_existing_headers() throws Exception { - Span span = Span.builder() - .spanId(1L) - .traceId(2L) - .parent(3L) - .baggage("foo", "bar") - .name("span") - .exportable(true) - .build(); - Map holder = new HashMap<>(); - final SpanTextMap map = textMap(holder); - holder.put(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L)); - holder.put(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L)); - holder.put(TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(30L)); - holder.put(TraceMessageHeaders.SPAN_NAME_NAME, "anotherSpan"); - - injector.inject(span, map); - - then(map) - .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(10L))) - .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(20L))) - .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.PARENT_ID_NAME, Span.idToHex(30L))) - .contains(new AbstractMap.SimpleEntry(TraceMessageHeaders.SPAN_NAME_NAME, "anotherSpan")) - .contains(new AbstractMap.SimpleEntry("baggage_foo", "bar")); - } - - private SpanTextMap textMap(Map textMap) { - return new SpanTextMap() { - @Override public Iterator> iterator() { - return textMap.entrySet().iterator(); - } - - @Override public void put(String key, String value) { - textMap.put(key, value); - } - }; - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptor.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptor.java new file mode 100644 index 0000000000..98166330f9 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/ITTracingChannelInterceptor.java @@ -0,0 +1,364 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.StrictCurrentTraceContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.ExecutorChannel; +import org.springframework.integration.config.GlobalChannelInterceptor; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Ported from org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptorTest to + * allow sleuth to decommission its implementation. + */ +@SpringBootTest(classes = ITTracingChannelInterceptor.App.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE) +@RunWith(SpringRunner.class) +@DirtiesContext +public class ITTracingChannelInterceptor implements MessageHandler { + + @Autowired @Qualifier("directChannel") DirectChannel directChannel; + + @Autowired @Qualifier("executorChannel") ExecutorChannel executorChannel; + + @Autowired Tracer tracer; + + @Autowired List spans; + + @Autowired MessagingTemplate messagingTemplate; + + Message message; + Span currentSpan; + + @Override public void handleMessage(Message msg) { + message = msg; + currentSpan = tracer.currentSpan(); + if (message.getHeaders().containsKey("THROW_EXCEPTION")) { + throw new RuntimeException("A terrible exception has occurred"); + } + } + + @Before public void init() { + directChannel.subscribe(this); + executorChannel.subscribe(this); + } + + @After public void close() { + directChannel.unsubscribe(this); + executorChannel.unsubscribe(this); + } + + // formerly known as TraceChannelInterceptorTest.executableSpanCreation + @Test public void propagatesNoopSpan() { + directChannel.send(MessageBuilder.withPayload("hi").setHeader("X-B3-Sampled", "0") + .build()); + + assertThat(message.getHeaders()).containsEntry("X-B3-Sampled", "0"); + + assertThat(currentSpan.isNoop()).isTrue(); + } + + @Test public void messageHeadersStillMutable() { + directChannel.send(MessageBuilder.withPayload("hi").setHeader("X-B3-Sampled", "0") + .build()); + + assertThat( + MessageHeaderAccessor.getAccessor(message, MessageHeaderAccessor.class)) + .isNotNull(); + } + + //@Test + //public void parentSpanIncluded() { + // this.directChannel.send(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNotNull(); + // long traceId = Span + // .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + // then(traceId).isEqualTo(10L); + // then(spanId).isNotEqualTo(20L); + // then(this.accumulator.getSpans()).hasSize(1); + //} + // + //@Test + //public void spanCreation() { + // this.directChannel.send(MessageBuilder.withPayload("hi").build()); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNotNull(); + // + // String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); + // then(traceId).isNotNull(); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //@Test + //public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { + // this.directChannel.send(MessageBuilder.withPayload("hi").build()); + // + // then(this.accumulator.getSpans()).hasSize(1); + // then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.CLIENT_SEND, + // Span.CLIENT_RECV); + //} + // + //@Test + //public void shouldLogServerReceivedServerSentEventWhenTheMessageIsPropagatedToTheNextListener() { + // this.directChannel.send(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true).build()); + // + // then(this.accumulator.getSpans()).hasSize(1); + // then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.SERVER_RECV, + // Span.SERVER_SEND); + //} + // + //@Test + //public void headerCreation() { + // Span currentSpan = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); + // this.directChannel.send(MessageBuilder.withPayload("hi").build()); + // this.tracer.close(currentSpan); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNotNull(); + // + // String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); + // then(traceId).isNotNull(); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //// TODO: Refactor to parametrized test together with sending messages via channel + //@Test + //public void headerCreationViaMessagingTemplate() { + // Span currentSpan = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); + // this.messagingTemplate.send(MessageBuilder.withPayload("hi").build()); + // + // this.tracer.close(currentSpan); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNotNull(); + // + // String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); + // then(traceId).isNotNull(); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //@Test + //public void shouldCloseASpanWhenExceptionOccurred() { + // Span currentSpan = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); + // Map errorHeaders = new HashMap<>(); + // errorHeaders.put("THROW_EXCEPTION", "TRUE"); + // + // try { + // this.messagingTemplate.send( + // MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); + // SleuthAssertions.fail("Exception should occur"); + // } + // catch (RuntimeException e) { + // } + // + // then(this.message).isNotNull(); + // this.tracer.close(currentSpan); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + // then(new ListOfSpans(this.accumulator.getSpans())) + // .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, + // "A terrible exception has occurred"); + //} + // + //@Test + //public void shouldNotTraceIgnoredChannel() { + // this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNull(); + // + // String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); + // then(traceId).isNull(); + // + // then(this.accumulator.getSpans()).isEmpty(); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //@Test + //public void downgrades128bitIdsByDroppingHighBits() { + // String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + // String lower64Bits = "48485a3953bb6124"; + // this.directChannel.send(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, hex128Bits) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + // then(this.message).isNotNull(); + // + // long traceId = Span.hexToId(this.message.getHeaders() + // .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + // then(traceId).isEqualTo(Span.hexToId(lower64Bits)); + //} + // + //@Test + //public void shouldNotBreakWhenInvalidHeadersAreSent() { + // this.directChannel.send(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.PARENT_ID_NAME, "-") + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + // + // then(this.message).isNotNull(); + // then(this.accumulator.getSpans()).isNotEmpty(); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //@Test + //public void shouldShortenTheNameWhenItsTooLarge() { + // this.directChannel.send(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.SPAN_NAME_NAME, bigName()) + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); + // + // then(this.message).isNotNull(); + // + // then(this.accumulator.getSpans()).isNotEmpty(); + // this.accumulator.getSpans().forEach(span1 -> then(span1.getName().length()).isLessThanOrEqualTo(50)); + // then(TestSpanContextHolder.getCurrentSpan()).isNull(); + //} + // + //private String bigName() { + // StringBuilder sb = new StringBuilder(); + // for (int i = 0; i < 60; i++) { + // sb.append("a"); + // } + // return sb.toString(); + //} + // + //@Test + //public void serializeMutableHeaders() throws Exception { + // Map headers = new HashMap<>(); + // headers.put("foo", "bar"); + // Message message = new GenericMessage<>("test", headers); + // ChannelInterceptor immutableMessageInterceptor = new ChannelInterceptorAdapter() { + // @Override + // public Message preSend(Message message, MessageChannel channel) { + // MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); + // return new GenericMessage(message.getPayload(), headers.toMessageHeaders()); + // } + // }; + // this.directChannel.addInterceptor(immutableMessageInterceptor); + // + // this.directChannel.send(message); + // + // Message output = (Message) SerializationUtils.deserialize(SerializationUtils.serialize(this.message)); + // then(output.getPayload()).isEqualTo("test"); + // then(output.getHeaders().get("foo")).isEqualTo("bar"); + // this.directChannel.removeInterceptor(immutableMessageInterceptor); + //} + // + //@Test + //public void workWithMessagingException() throws Exception { + // Message message = new GenericMessage<>(new MessagingException( + // MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build() + // )); + // + // this.directChannel.send(message); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(message.getPayload()).isEqualTo(this.message.getPayload()); + // then(spanId).isNotNull(); + // long traceId = Span + // .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + // then(traceId).isEqualTo(10L); + // then(spanId).isNotEqualTo(20L); + // then(this.accumulator.getSpans()).hasSize(1); + //} + // + //@Test + //public void errorMessageHeadersRetained() { + // QueueChannel deadReplyChannel = new QueueChannel(); + // QueueChannel errorsReplyChannel = new QueueChannel(); + // Map errorChannelHeaders = new HashMap<>(); + // errorChannelHeaders.put(MessageHeaders.REPLY_CHANNEL, errorsReplyChannel); + // errorChannelHeaders.put(MessageHeaders.ERROR_CHANNEL, errorsReplyChannel); + // + // this.directChannel.send(new ErrorMessage( + // new MessagingException(MessageBuilder.withPayload("hi") + // .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) + // .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)) + // .setReplyChannel(deadReplyChannel) + // .setErrorChannel(deadReplyChannel) + // .build() ), + // errorChannelHeaders)); + // then(this.message).isNotNull(); + // + // String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); + // then(spanId).isNotNull(); + // long traceId = Span + // .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + // then(traceId).isEqualTo(10L); + // then(spanId).isNotEqualTo(20L); + // then(this.accumulator.getSpans()).hasSize(1); + // then(this.message.getHeaders().getReplyChannel()).isSameAs(errorsReplyChannel); + // then(this.message.getHeaders().getErrorChannel()).isSameAs(errorsReplyChannel); + //} + + @Configuration @EnableAutoConfiguration static class App { + + @Bean List spans() { + return new ArrayList<>(); + } + + @Bean Tracing tracing() { + return Tracing.newBuilder() + .currentTraceContext(new StrictCurrentTraceContext()) + .spanReporter(spans()::add).build(); + } + + @Bean Tracer tracer() { + return tracing().tracer(); + } + + @Bean ExecutorChannel executorChannel() { + return new ExecutorChannel(Executors.newSingleThreadExecutor()); + } + + @Bean DirectChannel directChannel() { + return new DirectChannel(); + } + + @Bean public MessagingTemplate messagingTemplate() { + return new MessagingTemplate(directChannel()); + } + + @Bean @GlobalChannelInterceptor + public ChannelInterceptor tracingChannelInterceptor(Tracing tracing) { + return TracingChannelInterceptor.create(tracing); + } + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java new file mode 100644 index 0000000000..93b19e2dc8 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagationTest.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.Collections; + +import brave.propagation.Propagation; +import org.springframework.messaging.support.MessageHeaderAccessor; + +public class MessageHeaderPropagationTest + extends PropagationSetterTest { + MessageHeaderAccessor carrier = new MessageHeaderAccessor(); + + @Override public Propagation.KeyFactory keyFactory() { + return Propagation.KeyFactory.STRING; + } + + @Override protected MessageHeaderAccessor carrier() { + return carrier; + } + + @Override protected Propagation.Setter setter() { + return MessageHeaderPropagation.INSTANCE; + } + + @Override protected Iterable read(MessageHeaderAccessor carrier, String key) { + Object result = carrier.getHeader(key); + return result != null ? + Collections.singleton(result.toString()) : + Collections.emptyList(); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation_NativeTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation_NativeTest.java new file mode 100644 index 0000000000..9bdd9b7549 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessageHeaderPropagation_NativeTest.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import brave.propagation.Propagation; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.messaging.support.NativeMessageHeaderAccessor; + +/** + * Tests that native headers are redundantly added + */ +public class MessageHeaderPropagation_NativeTest + extends PropagationSetterTest { + NativeMessageHeaderAccessor carrier = new NativeMessageHeaderAccessor() { + }; + + @Override public Propagation.KeyFactory keyFactory() { + return Propagation.KeyFactory.STRING; + } + + @Override protected MessageHeaderAccessor carrier() { + return carrier; + } + + @Override protected Propagation.Setter setter() { + return MessageHeaderPropagation.INSTANCE; + } + + @Override protected Iterable read(MessageHeaderAccessor carrier, String key) { + return ((NativeMessageHeaderAccessor) carrier).getNativeHeader(key); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java deleted file mode 100644 index 594cd3ca88..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanExtractorTests.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.junit.Test; -import org.springframework.cloud.sleuth.Span; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.StringUtils; - -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -public class MessagingSpanExtractorTests { - HeaderBasedMessagingExtractor extractor = new HeaderBasedMessagingExtractor(); - - @Test - public void should_return_null_if_trace_or_span_is_missing() { - then(this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("")))).isNull(); - - then(this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("").copyHeaders(headers("trace"))))).isNull(); - } - - @Test - public void should_set_random_traceid_if_header_value_is_invalid() { - try { - this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("") - .copyHeaders(headers("invalid", randomId())))); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } - } - - @Test - public void should_parse_128bit_trace_id() { - String traceId128 = "463ac35c9f6413ad48485a3953bb6124"; - - Span span = this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("") - .copyHeaders(headers(traceId128, randomId())))); - - then(span.traceIdString()).isEqualTo(traceId128); - } - - @Test - public void should_propagate_baggage_headers() { - String traceId128 = "463ac35c9f6413ad48485a3953bb6124"; - - Span span = this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("") - .copyHeaders(headers(traceId128, randomId())))); - - then(span) - .hasBaggageItem("foo", "foofoo") - .hasBaggageItem("bar", "barbar"); - then(span.getBaggageItem("Foo")).isEqualTo("foofoo"); - then(span.getBaggageItem("BAr")).isEqualTo("barbar"); - } - - @Test - public void should_set_random_spanid_if_header_value_is_invalid() { - try { - this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("") - .copyHeaders(headers(randomId(), "invalid")))); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } - } - - @Test - public void should_not_throw_exception_if_parent_id_is_invalid() { - try { - this.extractor.joinTrace( - new MessagingTextMap(MessageBuilder.withPayload("") - .copyHeaders(headers(randomId(), randomId(), "invalid")))); - fail("should throw an exception"); - } catch (IllegalArgumentException e) { - then(e).hasMessageContaining("Malformed id"); - } - } - - private MessageHeaders headers(String traceId) { - return headers(traceId, null, null); - } - - private MessageHeaders headers(String traceId, String spanId) { - return headers(traceId, spanId, null); - } - - private MessageHeaders headers(String traceId, String spanId, String parentId) { - Map map = new HashMap<>(); - if (StringUtils.hasText(traceId)) { - map.put(TraceMessageHeaders.TRACE_ID_NAME, traceId); - } - if (StringUtils.hasText(spanId)) { - map.put(TraceMessageHeaders.SPAN_ID_NAME, spanId); - } - if (StringUtils.hasText(parentId)) { - map.put(TraceMessageHeaders.PARENT_ID_NAME, parentId); - } - map.put("baggage_foo", "foofoo"); - map.put("BAGGAGE_BAR", "barbar"); - return new MessageHeaders(map); - } - - private String randomId() { - return String.valueOf(new Random().nextLong()); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java deleted file mode 100644 index f0aead8143..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingSpanInjectorTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import org.junit.Test; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.messaging.Message; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.messaging.support.NativeMessageHeaderAccessor; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class MessagingSpanInjectorTests { - - private HeaderBasedMessagingInjector spanInjector = new HeaderBasedMessagingInjector(new TraceKeys()); - - @Test - public void spanHeadersAdded() { - Span span = Span.builder().name("http:foo").spanId(1L).traceId(2L).build(); - Message message = new GenericMessage<>("Hello World"); - MessageBuilder messageBuilder = MessageBuilder.fromMessage(message); - - this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); - - assertThat(messageBuilder.build().getHeaders()).containsKey(TraceMessageHeaders.SPAN_ID_NAME); - } - - @Test - public void shouldNotOverrideSpanTags() { - Span span = spanWithStringPayloadType(); - MessageBuilder messageBuilder = messageWithIntegerPayloadType(); - - this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); - - assertThat(messageBuilder.build().getHeaders()).containsKeys(TraceMessageHeaders.SPAN_ID_NAME, - "message/payload-type"); - assertThat(span).hasATag("message/payload-type", "java.lang.String"); - } - - private Span spanWithStringPayloadType() { - Span span = Span.builder().name("http:foo").spanId(1L).traceId(2L).build(); - span.tag("message/payload-type", "java.lang.String"); - return span; - } - - private MessageBuilder messageWithIntegerPayloadType() { - MessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(); - accessor.setHeader("message/payload-type", "java.lang.Integer"); - return MessageBuilder.withPayload("Hello World").setHeaders(accessor); - } - - @Test - public void nativeSpanHeadersAdded() { - Span span = Span.builder().name("http:foo").spanId(1L).traceId(2L).build(); - MessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(); - Message messageToBuild = MessageBuilder.createMessage("Hello World", - accessor.getMessageHeaders()); - MessageBuilder messageBuilder = MessageBuilder - .fromMessage(messageToBuild); - - this.spanInjector.inject(span, new MessagingTextMap(messageBuilder)); - - Message message = messageBuilder.build(); - assertThat(message.getHeaders()) - .containsKey(NativeMessageHeaderAccessor.NATIVE_HEADERS); - MessageHeaderAccessor natives = NativeMessageHeaderAccessor - .getMutableAccessor(message); - assertThat(natives.getMessageHeaders()).containsKey(TraceMessageHeaders.SPAN_ID_NAME); - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMapTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMapTests.java deleted file mode 100644 index 83f1c730a9..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/MessagingTextMapTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.HashSet; -import java.util.Set; - -import org.junit.Test; - -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.messaging.support.NativeMessageHeaderAccessor; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dave Syer - * - */ -public class MessagingTextMapTests { - - @Test - public void vanilla() { - MessageBuilder builder = MessageBuilder.withPayload("foo"); - MessagingTextMap map = new MessagingTextMap(builder); - map.put("foo", "bar"); - Set keys = new HashSet<>(); - map.forEach(entry -> keys.add(entry.getKey())); - assertThat(keys).contains("foo"); - @SuppressWarnings("unchecked") - MultiValueMap natives = (MultiValueMap) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS); - assertThat(natives).containsKey("foo"); - assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS); - } - - @Test - public void nativeHeadersAlreadyExist() { - MessageBuilder builder = MessageBuilder.withPayload("foo").setHeader( - NativeMessageHeaderAccessor.NATIVE_HEADERS, new LinkedMultiValueMap<>()); - MessagingTextMap map = new MessagingTextMap(builder); - map.put("foo", "bar"); - Set keys = new HashSet<>(); - map.forEach(entry -> keys.add(entry.getKey())); - assertThat(keys).contains("foo"); - @SuppressWarnings("unchecked") - MultiValueMap natives = (MultiValueMap) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS); - assertThat(natives).containsKey("foo"); - assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS); - } - - @Test - public void nativeHeaders() { - Message message = MessageBuilder.withPayload("foo").build(); - MessageBuilder builder = MessageBuilder.fromMessage(message) - .setHeaders(NativeMessageHeaderAccessor.getMutableAccessor(message)); - MessagingTextMap map = new MessagingTextMap(builder); - map.put("foo", "bar"); - Set keys = new HashSet<>(); - map.forEach(entry -> keys.add(entry.getKey())); - assertThat(keys).contains("foo"); - @SuppressWarnings("unchecked") - MultiValueMap natives = (MultiValueMap) builder.build().getHeaders().get(NativeMessageHeaderAccessor.NATIVE_HEADERS); - assertThat(natives).containsKey("foo"); - assertThat(keys).doesNotContain(NativeMessageHeaderAccessor.NATIVE_HEADERS); - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java new file mode 100644 index 0000000000..256e553e29 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/PropagationSetterTest.java @@ -0,0 +1,54 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import brave.propagation.Propagation; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Taken from Brave + */ +public abstract class PropagationSetterTest { + protected abstract Propagation.KeyFactory keyFactory(); + + protected abstract C carrier(); + + protected abstract Propagation.Setter setter(); + + protected abstract Iterable read(C carrier, K key); + + @Test public void set() throws Exception { + K key = keyFactory().create("X-B3-TraceId"); + setter().put(carrier(), key, "48485a3953bb6124"); + + assertThat(read(carrier(), key)).containsExactly("48485a3953bb6124"); + } + + @Test public void set128() throws Exception { + K key = keyFactory().create("X-B3-TraceId"); + setter().put(carrier(), key, "463ac35c9f6413ad48485a3953bb6124"); + + assertThat(read(carrier(), key)) + .containsExactly("463ac35c9f6413ad48485a3953bb6124"); + } + + @Test public void setTwoKeys() throws Exception { + K key1 = keyFactory().create("X-B3-TraceId"); + K key2 = keyFactory().create("X-B3-SpanId"); + setter().put(carrier(), key1, "463ac35c9f6413ad48485a3953bb6124"); + setter().put(carrier(), key2, "48485a3953bb6124"); + + assertThat(read(carrier(), key1)) + .containsExactly("463ac35c9f6413ad48485a3953bb6124"); + assertThat(read(carrier(), key2)).containsExactly("48485a3953bb6124"); + } + + @Test public void reset() throws Exception { + K key = keyFactory().create("X-B3-TraceId"); + setter().put(carrier(), key, "48485a3953bb6124"); + setter().put(carrier(), key, "463ac35c9f6413ad"); + + assertThat(read(carrier(), key)).containsExactly("463ac35c9f6413ad"); + } +} + diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java deleted file mode 100644 index 25aec363f4..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.messaging; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.assertj.core.api.BDDAssertions; -import org.awaitility.Awaitility; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; -import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptorTests.App; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.ExecutorChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.core.MessagingTemplate; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.ChannelInterceptorAdapter; -import org.springframework.messaging.support.ErrorMessage; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.messaging.support.MessageHeaderAccessor; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.util.SerializationUtils; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.Assert.assertNotNull; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Dave Syer - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = App.class, - properties = "spring.sleuth.integration.patterns=traced*", - webEnvironment = SpringBootTest.WebEnvironment.NONE) -@DirtiesContext -public class TraceChannelInterceptorTests implements MessageHandler { - - @Autowired - @Qualifier("tracedChannel") - private DirectChannel tracedChannel; - - @Autowired - @Qualifier("tracedExecutorChannel") - private ExecutorChannel executorChannel; - - @Autowired - @Qualifier("ignoredChannel") - private DirectChannel ignoredChannel; - - @Autowired - private Tracer tracer; - - @Autowired - private MessagingTemplate messagingTemplate; - - @Autowired - private ArrayListSpanAccumulator accumulator; - - private Message message; - - private Span span; - - @Override - public void handleMessage(Message message) throws MessagingException { - this.message = message; - this.span = TestSpanContextHolder.getCurrentSpan(); - if (message.getHeaders().containsKey("THROW_EXCEPTION")) { - throw new RuntimeException("A terrible exception has occurred"); - } - } - - @Before - public void init() { - this.tracedChannel.subscribe(this); - this.executorChannel.subscribe(this); - this.ignoredChannel.subscribe(this); - this.accumulator.getSpans().clear(); - } - - @After - public void close() { - then(ExceptionUtils.getLastException()).isNull(); - TestSpanContextHolder.removeCurrentSpan(); - this.tracedChannel.unsubscribe(this); - this.executorChannel.unsubscribe(this); - this.ignoredChannel.unsubscribe(this); - this.accumulator.getSpans().clear(); - } - - @Test - public void nonExportableSpanCreation() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); - assertNotNull("message was null", this.message); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(this.span.isExportable()).isFalse(); - } - - @Test - public void executableSpanCreation() throws Exception { - this.executorChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); - Awaitility.await() - .untilAsserted(() -> BDDAssertions.assertThat(this.message).isNotNull()); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(this.span.isExportable()).isFalse(); - } - - @Test - public void messageHeadersStillMutable() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.SAMPLED_NAME, Span.SPAN_NOT_SAMPLED).build()); - assertNotNull("message was null", this.message); - MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(this.message, MessageHeaderAccessor.class); - assertNotNull("Message header accessor should be still available", accessor); - } - - @Test - public void parentSpanIncluded() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - long traceId = Span - .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); - then(traceId).isEqualTo(10L); - then(spanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - } - - @Test - public void spanCreation() { - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); - then(traceId).isNotNull(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - @Test - public void shouldLogClientReceivedClientSentEventWhenTheMessageIsSentAndReceived() { - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - - then(this.accumulator.getSpans()).hasSize(1); - then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.CLIENT_SEND, - Span.CLIENT_RECV); - } - - @Test - public void shouldLogServerReceivedServerSentEventWhenTheMessageIsPropagatedToTheNextListener() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.MESSAGE_SENT_FROM_CLIENT, true).build()); - - then(this.accumulator.getSpans()).hasSize(1); - then(this.accumulator.getSpans().get(0).logs()).extracting("event").contains(Span.SERVER_RECV, - Span.SERVER_SEND); - } - - @Test - public void headerCreation() { - Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.tracedChannel.send(MessageBuilder.withPayload("hi").build()); - this.tracer.close(span); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); - then(traceId).isNotNull(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - // TODO: Refactor to parametrized test together with sending messages via channel - @Test - public void headerCreationViaMessagingTemplate() { - Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.messagingTemplate.send(MessageBuilder.withPayload("hi").build()); - - this.tracer.close(span); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); - then(traceId).isNotNull(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - @Test - public void shouldCloseASpanWhenExceptionOccurred() { - Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - Map errorHeaders = new HashMap<>(); - errorHeaders.put("THROW_EXCEPTION", "TRUE"); - - try { - this.messagingTemplate.send( - MessageBuilder.withPayload("hi").copyHeaders(errorHeaders).build()); - SleuthAssertions.fail("Exception should occur"); - } - catch (RuntimeException e) { - } - - then(this.message).isNotNull(); - this.tracer.close(span); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.accumulator.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "A terrible exception has occurred"); - } - - @Test - public void shouldNotTraceIgnoredChannel() { - this.ignoredChannel.send(MessageBuilder.withPayload("hi").build()); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNull(); - - String traceId = this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class); - then(traceId).isNull(); - - then(this.accumulator.getSpans()).isEmpty(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - @Test - public void downgrades128bitIdsByDroppingHighBits() { - String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; - String lower64Bits = "48485a3953bb6124"; - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, hex128Bits) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); - then(this.message).isNotNull(); - - long traceId = Span.hexToId(this.message.getHeaders() - .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); - then(traceId).isEqualTo(Span.hexToId(lower64Bits)); - } - - @Test - public void shouldNotBreakWhenInvalidHeadersAreSent() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.PARENT_ID_NAME, "-") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); - - then(this.message).isNotNull(); - then(this.accumulator.getSpans()).isNotEmpty(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - @Test - public void shouldShortenTheNameWhenItsTooLarge() { - this.tracedChannel.send(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.SPAN_NAME_NAME, bigName()) - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build()); - - then(this.message).isNotNull(); - - then(this.accumulator.getSpans()).isNotEmpty(); - this.accumulator.getSpans().forEach(span1 -> then(span1.getName().length()).isLessThanOrEqualTo(50)); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - private String bigName() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 60; i++) { - sb.append("a"); - } - return sb.toString(); - } - - @Test - public void serializeMutableHeaders() throws Exception { - Map headers = new HashMap<>(); - headers.put("foo", "bar"); - Message message = new GenericMessage<>("test", headers); - ChannelInterceptor immutableMessageInterceptor = new ChannelInterceptorAdapter() { - @Override - public Message preSend(Message message, MessageChannel channel) { - MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message); - return new GenericMessage(message.getPayload(), headers.toMessageHeaders()); - } - }; - this.tracedChannel.addInterceptor(immutableMessageInterceptor); - - this.tracedChannel.send(message); - - Message output = (Message) SerializationUtils.deserialize(SerializationUtils.serialize(this.message)); - then(output.getPayload()).isEqualTo("test"); - then(output.getHeaders().get("foo")).isEqualTo("bar"); - this.tracedChannel.removeInterceptor(immutableMessageInterceptor); - } - - @Test - public void workWithMessagingException() throws Exception { - Message message = new GenericMessage<>(new MessagingException( - MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)).build() - )); - - this.tracedChannel.send(message); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(message.getPayload()).isEqualTo(this.message.getPayload()); - then(spanId).isNotNull(); - long traceId = Span - .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); - then(traceId).isEqualTo(10L); - then(spanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - } - - @Test - public void errorMessageHeadersRetained() { - QueueChannel deadReplyChannel = new QueueChannel(); - QueueChannel errorsReplyChannel = new QueueChannel(); - Map errorChannelHeaders = new HashMap<>(); - errorChannelHeaders.put(MessageHeaders.REPLY_CHANNEL, errorsReplyChannel); - errorChannelHeaders.put(MessageHeaders.ERROR_CHANNEL, errorsReplyChannel); - - this.tracedChannel.send(new ErrorMessage( - new MessagingException(MessageBuilder.withPayload("hi") - .setHeader(TraceMessageHeaders.TRACE_ID_NAME, Span.idToHex(10L)) - .setHeader(TraceMessageHeaders.SPAN_ID_NAME, Span.idToHex(20L)) - .setReplyChannel(deadReplyChannel) - .setErrorChannel(deadReplyChannel) - .build() ), - errorChannelHeaders)); - then(this.message).isNotNull(); - - String spanId = this.message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); - then(spanId).isNotNull(); - long traceId = Span - .hexToId(this.message.getHeaders().get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); - then(traceId).isEqualTo(10L); - then(spanId).isNotEqualTo(20L); - then(this.accumulator.getSpans()).hasSize(1); - then(this.message.getHeaders().getReplyChannel()).isSameAs(errorsReplyChannel); - then(this.message.getHeaders().getErrorChannel()).isSameAs(errorsReplyChannel); - } - - @Configuration - @EnableAutoConfiguration - static class App { - - @Bean - ArrayListSpanAccumulator arrayListSpanAccumulator() { - return new ArrayListSpanAccumulator(); - } - - @Bean - public ExecutorChannel tracedExecutorChannel() { - return new ExecutorChannel(Executors.newSingleThreadExecutor()); - } - - @Bean - public DirectChannel tracedChannel() { - return new DirectChannel(); - } - - @Bean - public DirectChannel ignoredChannel() { - return new DirectChannel(); - } - - @Bean - public MessagingTemplate messagingTemplate() { - return new MessagingTemplate(tracedChannel()); - } - - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); - } - - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java index da0946f805..a703d1dd6d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceContextPropagationChannelInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.springframework.cloud.sleuth.instrument.messaging; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; - +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,12 +27,8 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.messaging.TraceContextPropagationChannelInterceptorTests.App; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.channel.QueueChannel; @@ -42,49 +38,51 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + /** * @author Spencer Gibb */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = App.class) +@SpringBootTest(classes = TraceContextPropagationChannelInterceptorTests.App.class) @DirtiesContext public class TraceContextPropagationChannelInterceptorTests { - @Autowired - @Qualifier("channel") - private PollableChannel channel; + @Autowired @Qualifier("channel") private PollableChannel channel; - @Autowired - private Tracer tracer; + @Autowired private Tracing tracing; + @Autowired private ArrayListSpanReporter reporter; - @After - public void close() { - TestSpanContextHolder.removeCurrentSpan(); + @After public void close() { + this.reporter.clear(); } - @Test - public void testSpanPropagation() { - - Span span = this.tracer.createSpan("http:testSendMessage", new AlwaysSampler()); - this.channel.send(MessageBuilder.withPayload("hi").build()); - Long expectedSpanId = span.getSpanId(); - this.tracer.close(span); + @Test public void testSpanPropagation() { + Span span = this.tracing.tracer().nextSpan().name("http:testSendMessage").start(); + String expectedSpanId = SpanUtil.idToHex(span.context().spanId()); + try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) { + this.channel.send(MessageBuilder.withPayload("hi").build()); + } finally { + span.finish(); + } Message message = this.channel.receive(0); - assertNotNull("message was null", message); - Long spanId = Span.hexToId( - message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class)); + String spanId = + message.getHeaders().get(TraceMessageHeaders.SPAN_ID_NAME, String.class); assertNotEquals("spanId was equal to parent's id", expectedSpanId, spanId); - long traceId = Span.hexToId(message.getHeaders() - .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + String traceId = message.getHeaders() + .get(TraceMessageHeaders.TRACE_ID_NAME, String.class); assertNotNull("traceId was null", traceId); - Long parentId = Span.hexToId(message.getHeaders() - .get(TraceMessageHeaders.PARENT_ID_NAME, String.class)); - assertEquals("parentId was not equal to parent's id", expectedSpanId, parentId); + String parentId = message.getHeaders() + .get(TraceMessageHeaders.PARENT_ID_NAME, String.class); + assertEquals("parentId was not equal to parent's id", + this.reporter.getSpans().get(0).id(), parentId); } @@ -92,14 +90,16 @@ public void testSpanPropagation() { @EnableAutoConfiguration static class App { - @Bean - public QueueChannel channel() { + @Bean public QueueChannel channel() { return new QueueChannel(); } - @Bean - Sampler testSampler() { - return new AlwaysSampler(); + @Bean Sampler testSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorAutowireTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorAutowireTest.java new file mode 100644 index 0000000000..b8775c7b52 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorAutowireTest.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import brave.Tracing; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.support.ChannelInterceptor; + +public class TracingChannelInterceptorAutowireTest { + + @Configuration static class TracingConfiguration { + @Bean Tracing tracing() { + return Tracing.newBuilder().build(); + } + } + + @Test public void autowiredWithBeanConfig() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(TracingConfiguration.class); + ctx.register(TracingChannelInterceptor.class); + ctx.refresh(); + + ctx.getBean(ChannelInterceptor.class); + } + + @After public void close() { + Tracing.current().close(); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java new file mode 100644 index 0000000000..f0c537d2d2 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptorTest.java @@ -0,0 +1,237 @@ +package org.springframework.cloud.sleuth.instrument.messaging; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import brave.Tracing; +import brave.propagation.StrictCurrentTraceContext; +import zipkin2.Span; +import org.junit.After; +import org.junit.Test; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.ChannelInterceptorAdapter; +import org.springframework.messaging.support.ExecutorChannelInterceptor; +import org.springframework.messaging.support.ExecutorSubscribableChannel; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.messaging.support.NativeMessageHeaderAccessor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.messaging.support.NativeMessageHeaderAccessor.NATIVE_HEADERS; + +public class TracingChannelInterceptorTest { + + List spans = new ArrayList<>(); + ChannelInterceptor interceptor = TracingChannelInterceptor.create(Tracing.newBuilder() + .currentTraceContext(new StrictCurrentTraceContext()).spanReporter(spans::add) + .build()); + + QueueChannel channel = new QueueChannel(); + + @Test public void injectsProducerSpan() { + channel.addInterceptor(producerSideOnly(interceptor)); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat(channel.receive().getHeaders()) + .containsKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", + "nativeHeaders"); + assertThat(spans).hasSize(1).flatExtracting(Span::kind) + .containsExactly(Span.Kind.PRODUCER); + } + + @Test public void injectsProducerSpan_nativeHeaders() { + channel.addInterceptor(producerSideOnly(interceptor)); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat((Map) channel.receive().getHeaders().get(NATIVE_HEADERS)) + .containsOnlyKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", + "spanTraceId", "spanId", "spanSampled"); + } + + /** + * If the producer is acting on an un-processed message (ex via a polling consumer), it should + * look at trace headers when there is no span in scope, and use that as the parent context. + */ + @Test public void producerConsidersOldSpanIds() { + channel.addInterceptor(producerSideOnly(interceptor)); + + channel.send(MessageBuilder.withPayload("foo") + .setHeader("X-B3-TraceId", "000000000000000a") + .setHeader("X-B3-ParentSpanId", "000000000000000a") + .setHeader("X-B3-SpanId", "000000000000000b").build()); + + assertThat(channel.receive().getHeaders()) + .containsEntry("X-B3-ParentSpanId", "000000000000000b"); + } + + @Test public void producerConsidersOldSpanIds_nativeHeaders() { + channel.addInterceptor(producerSideOnly(interceptor)); + + NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor() { + }; + + accessor.setNativeHeader("X-B3-TraceId", "000000000000000a"); + accessor.setNativeHeader("X-B3-ParentSpanId", "000000000000000a"); + accessor.setNativeHeader("X-B3-SpanId", "000000000000000b"); + + channel.send( + MessageBuilder.withPayload("foo").copyHeaders(accessor.toMessageHeaders()) + .build()); + + assertThat((Map) channel.receive().getHeaders().get(NATIVE_HEADERS)) + .containsEntry("X-B3-ParentSpanId", + Collections.singletonList("000000000000000b")); + } + + /** + * We have to inject headers on a polling receive as any future processor will come later + */ + @Test public void pollingReceive_injectsConsumerSpan() { + channel.addInterceptor(consumerSideOnly(interceptor)); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat(channel.receive().getHeaders()) + .containsKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", + "nativeHeaders"); + assertThat(spans).hasSize(1).flatExtracting(Span::kind) + .containsExactly(Span.Kind.CONSUMER); + } + + @Test public void pollingReceive_injectsConsumerSpan_nativeHeaders() { + channel.addInterceptor(consumerSideOnly(interceptor)); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat((Map) channel.receive().getHeaders().get(NATIVE_HEADERS)) + .containsOnlyKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", + "spanTraceId", "spanId", "spanSampled"); + } + + @Test public void subscriber_startsAndStopsConsumerAndProcessingSpan() { + ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); + channel.addInterceptor(executorSideOnly(interceptor)); + List> messages = new ArrayList<>(); + channel.subscribe(messages::add); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat(messages.get(0).getHeaders()) + .doesNotContainKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled", + "nativeHeaders"); + assertThat(spans).flatExtracting(Span::kind) + .containsExactly(Span.Kind.CONSUMER, null); + } + + /** + * The subscriber consumes a message then synchronously processes it. Since we only inject trace + * IDs on unprocessed messages, we remove IDs to prevent accidental re-use of the same span. + */ + @Test public void subscriber_removesTraceIdsFromMessage() { + ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); + channel.addInterceptor(interceptor); + List> messages = new ArrayList<>(); + channel.subscribe(messages::add); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat(messages.get(0).getHeaders()) + .doesNotContainKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled"); + } + + @Test public void subscriber_removesTraceIdsFromMessage_nativeHeaders() { + ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); + channel.addInterceptor(interceptor); + List> messages = new ArrayList<>(); + channel.subscribe(messages::add); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat((Map) messages.get(0).getHeaders().get(NATIVE_HEADERS)) + .doesNotContainKeys("X-B3-TraceId", "X-B3-SpanId", "X-B3-Sampled"); + } + + @Test public void integrated_sendAndPoll() { + channel.addInterceptor(interceptor); + + channel.send(MessageBuilder.withPayload("foo").build()); + channel.receive(); + + assertThat(spans).flatExtracting(Span::kind) + .containsExactlyInAnyOrder(Span.Kind.CONSUMER, Span.Kind.PRODUCER); + } + + @Test public void integrated_sendAndSubscriber() { + ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); + channel.addInterceptor(interceptor); + List> messages = new ArrayList<>(); + channel.subscribe(messages::add); + + channel.send(MessageBuilder.withPayload("foo").build()); + + assertThat(spans).flatExtracting(Span::kind) + .containsExactly(Span.Kind.CONSUMER, null, Span.Kind.PRODUCER); + } + + ChannelInterceptor producerSideOnly(ChannelInterceptor delegate) { + return new ChannelInterceptorAdapter() { + @Override + public Message preSend(Message message, MessageChannel channel) { + return delegate.preSend(message, channel); + } + + @Override + public void afterSendCompletion(Message message, MessageChannel channel, + boolean sent, Exception ex) { + delegate.afterSendCompletion(message, channel, sent, ex); + } + }; + } + + ChannelInterceptor consumerSideOnly(ChannelInterceptor delegate) { + return new ChannelInterceptorAdapter() { + @Override + public Message postReceive(Message message, MessageChannel channel) { + return delegate.postReceive(message, channel); + } + + @Override + public void afterReceiveCompletion(Message message, MessageChannel channel, + Exception ex) { + delegate.afterReceiveCompletion(message, channel, ex); + } + }; + } + + ExecutorChannelInterceptor executorSideOnly(ChannelInterceptor delegate) { + class ExecutorSideOnly extends ChannelInterceptorAdapter + implements ExecutorChannelInterceptor { + @Override + public Message beforeHandle(Message message, MessageChannel channel, + MessageHandler handler) { + return ((ExecutorChannelInterceptor) delegate) + .beforeHandle(message, channel, handler); + } + + @Override + public void afterMessageHandled(Message message, MessageChannel channel, + MessageHandler handler, Exception ex) { + ((ExecutorChannelInterceptor) delegate) + .afterMessageHandled(message, channel, handler, ex); + } + } + return new ExecutorSideOnly(); + } + + @After public void close() { + assertThat(Tracing.current().currentTraceContext().get()).isNull(); + Tracing.current().close(); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTests.java index d42d2587f2..7203f0658d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/websocket/TraceWebSocketAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,13 @@ package org.springframework.cloud.sleuth.instrument.messaging.websocket; -import static org.assertj.core.api.BDDAssertions.then; - +import brave.sampler.Sampler; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.instrument.messaging.TraceChannelInterceptor; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.cloud.sleuth.instrument.messaging.TracingChannelInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; @@ -35,6 +32,8 @@ import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import static org.assertj.core.api.BDDAssertions.then; + /** * @author Marcin Grzejszczak */ @@ -45,38 +44,32 @@ public class TraceWebSocketAutoConfigurationTests { @Autowired DelegatingWebSocketMessageBrokerConfiguration delegatingWebSocketMessageBrokerConfiguration; - @Test - public void should_register_interceptors_for_all_channels() { + @Test public void should_register_interceptors_for_all_channels() { then(this.delegatingWebSocketMessageBrokerConfiguration.clientInboundChannel() .getInterceptors()) - .hasAtLeastOneElementOfType(TraceChannelInterceptor.class); + .hasAtLeastOneElementOfType(TracingChannelInterceptor.class); then(this.delegatingWebSocketMessageBrokerConfiguration.clientOutboundChannel() .getInterceptors()) - .hasAtLeastOneElementOfType(TraceChannelInterceptor.class); + .hasAtLeastOneElementOfType(TracingChannelInterceptor.class); then(this.delegatingWebSocketMessageBrokerConfiguration.brokerChannel() .getInterceptors()) - .hasAtLeastOneElementOfType(TraceChannelInterceptor.class); + .hasAtLeastOneElementOfType(TracingChannelInterceptor.class); } - @EnableAutoConfiguration - @Configuration - @EnableWebSocketMessageBroker + @EnableAutoConfiguration @Configuration @EnableWebSocketMessageBroker public static class Config extends AbstractWebSocketMessageBrokerConfigurer { - @Override - public void configureMessageBroker(MessageBrokerRegistry config) { + @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { + @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/hello").withSockJS(); } - @Bean - Sampler testSampler() { - return new AlwaysSampler(); + @Bean Sampler testSampler() { + return Sampler.ALWAYS_SAMPLE; } } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriberTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriberTests.java index 33aaf4122d..c2253b52f5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriberTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/reactor/SpanSubscriberTests.java @@ -2,14 +2,19 @@ import java.util.concurrent.atomic.AtomicReference; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import reactor.core.publisher.BaseSubscriber; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.awaitility.Awaitility; -import org.junit.After; import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.reactivestreams.Publisher; @@ -17,20 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; -import reactor.core.publisher.BaseSubscriber; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; import static org.assertj.core.api.BDDAssertions.then; @@ -43,143 +37,138 @@ public class SpanSubscriberTests { @Autowired Tracer tracer; - @Before - public void setup() { - ExceptionUtils.setFail(true); - } - @Test public void should_pass_tracing_info_when_using_reactor() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo").start(); final AtomicReference spanInOperation = new AtomicReference<>(); Publisher traced = Flux.just(1, 2, 3); log.info("Hello"); - Flux.from(traced) - .map( d -> d + 1) - .map( d -> d + 1) - .map( (d) -> { - spanInOperation.set(SpanSubscriberTests.this.tracer.getCurrentSpan()); - return d + 1; - }) - .map( d -> d + 1) - .subscribe(System.out::println); - - then(this.tracer.getCurrentSpan()).isNull(); - then(spanInOperation.get().getTraceId()).isEqualTo(span.getTraceId()); - then(ExceptionUtils.getLastException()).isNull(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + Flux.from(traced) + .map( d -> d + 1) + .map( d -> d + 1) + .map( (d) -> { + spanInOperation.set( + SpanSubscriberTests.this.tracer.currentSpan()); + return d + 1; + }) + .map( d -> d + 1) + .subscribe(System.out::println); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + then(spanInOperation.get().context().traceId()) + .isEqualTo(span.context().traceId()); } @Test public void should_support_reactor_fusion_optimization() { - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo").start(); final AtomicReference spanInOperation = new AtomicReference<>(); log.info("Hello"); - Mono.just(1) - .flatMap( d -> Flux.just(d + 1).collectList().map(p -> p.get(0))) - .map( d -> d + 1) - .map( (d) -> { - spanInOperation.set(SpanSubscriberTests.this.tracer.getCurrentSpan()); - return d + 1; - }) - .map( d -> d + 1) - .subscribe(System.out::println); - - then(this.tracer.getCurrentSpan()).isNull(); - then(spanInOperation.get().getTraceId()).isEqualTo(span.getTraceId()); - then(ExceptionUtils.getLastException()).isNull(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + Mono.just(1).flatMap(d -> Flux.just(d + 1).collectList().map(p -> p.get(0))) + .map(d -> d + 1).map((d) -> { + spanInOperation.set(SpanSubscriberTests.this.tracer.currentSpan()); + return d + 1; + }).map(d -> d + 1).subscribe(System.out::println); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + then(spanInOperation.get().context().traceId()).isEqualTo(span.context().traceId()); } @Test public void should_not_trace_scalar_flows() { - this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo").start(); final AtomicReference spanInOperation = new AtomicReference<>(); log.info("Hello"); - Mono.just(1) - .subscribe(new BaseSubscriber() { - @Override - protected void hookOnSubscribe(Subscription subscription) { - spanInOperation.set(subscription); - } - }); - - then(this.tracer.getCurrentSpan()).isNotNull(); - then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); - - Mono.error(new Exception()) - .subscribe(new BaseSubscriber() { - @Override - protected void hookOnSubscribe(Subscription subscription) { - spanInOperation.set(subscription); - } - - @Override - protected void hookOnError(Throwable throwable) { - } - }); - - then(this.tracer.getCurrentSpan()).isNotNull(); - then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); - - Mono.empty() - .subscribe(new BaseSubscriber() { - @Override - protected void hookOnSubscribe(Subscription subscription) { - spanInOperation.set(subscription); - } - }); - - then(this.tracer.getCurrentSpan()).isNotNull(); - then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); - then(ExceptionUtils.getLastException()).isNull(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + Mono.just(1).subscribe(new BaseSubscriber() { + @Override protected void hookOnSubscribe(Subscription subscription) { + spanInOperation.set(subscription); + } + }); + + then(this.tracer.currentSpan()).isNotNull(); + then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); + + Mono.error(new Exception()) + .subscribe(new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) { + spanInOperation.set(subscription); + } + + @Override + protected void hookOnError(Throwable throwable) { + } + }); + + then(this.tracer.currentSpan()).isNotNull(); + then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); + + Mono.empty() + .subscribe(new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) { + spanInOperation.set(subscription); + } + }); + + then(this.tracer.currentSpan()).isNotNull(); + then(spanInOperation.get()).isNotInstanceOf(SpanSubscriber.class); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); } @Test public void should_pass_tracing_info_when_using_reactor_async() { - - Span span = this.tracer.createSpan("foo"); + Span span = this.tracer.nextSpan().name("foo").start(); final AtomicReference spanInOperation = new AtomicReference<>(); log.info("Hello"); - Flux.just(1, 2, 3) - .publishOn(Schedulers.single()) - .log("reactor.1") - .map( d -> d + 1) - .map( d -> d + 1) - .publishOn(Schedulers.newSingle("secondThread")) - .log("reactor.2") - .map( (d) -> { - spanInOperation.set(SpanSubscriberTests.this.tracer.getCurrentSpan()); - return d + 1; - }) - .map( d -> d + 1) - .blockLast(); - - Awaitility.await().untilAsserted(() -> { - then(spanInOperation.get().getTraceId()).isEqualTo(span.getTraceId()); - then(ExceptionUtils.getLastException()).isNull(); - }); - then(this.tracer.getCurrentSpan()).isEqualTo(span); - this.tracer.close(span); - - Span foo2 = this.tracer.createSpan("foo2"); - - Flux.just(1, 2, 3) - .publishOn(Schedulers.single()) - .log("reactor.") - .map( d -> d + 1) - .map( d -> d + 1) - .map( (d) -> { - spanInOperation.set(SpanSubscriberTests.this.tracer.getCurrentSpan()); - return d + 1; - }) - .map( d -> d + 1) - .blockLast(); - - then(this.tracer.getCurrentSpan()).isEqualTo(foo2); - then(ExceptionUtils.getLastException()).isNull(); - // parent cause there's an async span in the meantime - then(spanInOperation.get().getTraceId()).isEqualTo(foo2.getTraceId()); - tracer.close(foo2); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + Flux.just(1, 2, 3).publishOn(Schedulers.single()).log("reactor.1") + .map(d -> d + 1).map(d -> d + 1).publishOn(Schedulers.newSingle("secondThread")).log("reactor.2") + .map((d) -> { + spanInOperation.set(SpanSubscriberTests.this.tracer.currentSpan()); + return d + 1; + }).map(d -> d + 1).blockLast(); + + Awaitility.await().untilAsserted(() -> { + then(spanInOperation.get().context().traceId()).isEqualTo(span.context().traceId()); + }); + then(this.tracer.currentSpan()).isEqualTo(span); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + Span foo2 = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(foo2)) { + Flux.just(1, 2, 3).publishOn(Schedulers.single()).log("reactor.").map(d -> d + 1).map(d -> d + 1).map((d) -> { + spanInOperation.set(SpanSubscriberTests.this.tracer.currentSpan()); + return d + 1; + }).map(d -> d + 1).blockLast(); + + then(this.tracer.currentSpan()).isEqualTo(foo2); + // parent cause there's an async span in the meantime + then(spanInOperation.get().context().traceId()).isEqualTo(foo2.context().traceId()); + } finally { + foo2.finish(); + } + + then(this.tracer.currentSpan()).isNull(); } @AfterClass @@ -192,7 +181,7 @@ public static void cleanup() { @Configuration static class Config { @Bean Sampler sampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHookTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHookTests.java index a610a43073..e65f4ca449 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHookTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaSchedulersHookTests.java @@ -10,38 +10,42 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; - +import brave.Tracer; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; import rx.functions.Action0; import rx.plugins.RxJavaErrorHandler; import rx.plugins.RxJavaObservableExecutionHook; import rx.plugins.RxJavaPlugins; import rx.plugins.RxJavaSchedulersHook; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.never; /** * * @author Shivang Shah */ -@RunWith(MockitoJUnitRunner.class) public class SleuthRxJavaSchedulersHookTests { List threadsToIgnore = new ArrayList<>(); - @Mock Tracer tracer; TraceKeys traceKeys = new TraceKeys(); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + @After + public void clean() { + this.tracing.close(); + this.reporter.clear(); + } private static StringBuilder caller; @Before @@ -55,7 +59,9 @@ public void setup() { public void should_not_override_existing_custom_hooks() { RxJavaPlugins.getInstance().registerErrorHandler(new MyRxJavaErrorHandler()); RxJavaPlugins.getInstance().registerObservableExecutionHook(new MyRxJavaObservableExecutionHook()); + new SleuthRxJavaSchedulersHook(this.tracer, this.traceKeys, threadsToIgnore); + then(RxJavaPlugins.getInstance().getErrorHandler()).isExactlyInstanceOf(MyRxJavaErrorHandler.class); then(RxJavaPlugins.getInstance().getObservableExecutionHook()).isExactlyInstanceOf(MyRxJavaObservableExecutionHook.class); } @@ -68,9 +74,13 @@ public void should_wrap_delegates_action_in_wrapped_action_when_delegate_is_pres Action0 action = schedulersHook.onSchedule(() -> { caller = new StringBuilder("hello"); }); + action.call(); + then(action).isInstanceOf(SleuthRxJavaSchedulersHook.TraceAction.class); then(caller.toString()).isEqualTo("called_from_schedulers_hook"); + then(this.reporter.getSpans()).isNotEmpty(); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -90,8 +100,8 @@ public void should_not_create_a_span_when_current_thread_should_be_ignored() hello.get(); - BDDMockito.then(this.tracer).should(never()).createSpan(anyString()); - BDDMockito.then(this.tracer).should(never()).continueSpan(any()); + then(this.reporter.getSpans()).isEmpty(); + then(this.tracer.currentSpan()).isNull(); } private ExecutorService executorService() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java index fed36665c6..83190451b5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/rxjava/SleuthRxJavaTests.java @@ -1,13 +1,8 @@ package org.springframework.cloud.sleuth.instrument.rxjava; -import static org.awaitility.Awaitility.await; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.After; +import brave.Span; +import brave.Tracer; +import brave.sampler.Sampler; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -16,41 +11,34 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; - import rx.Observable; import rx.functions.Action0; import rx.plugins.RxJavaPlugins; import rx.schedulers.Schedulers; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; +import static org.awaitility.Awaitility.await; + @RunWith(SpringRunner.class) @SpringBootTest(classes = { SleuthRxJavaTests.TestConfig.class }) @DirtiesContext public class SleuthRxJavaTests { @Autowired - Listener listener; + ArrayListSpanReporter reporter; @Autowired Tracer tracer; StringBuffer caller = new StringBuffer(); @Before public void clean() { - this.listener.getEvents().clear(); - } - - @After - public void clearTrace() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } @BeforeClass @@ -68,50 +56,34 @@ public void should_create_new_span_when_rx_java_action_is_executed_and_there_was .subscribe(Action0::call); then(this.caller.toString()).isEqualTo("actual_action"); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.currentSpan()).isNull(); await().atMost(5, SECONDS) - .untilAsserted(() -> then(this.listener.getEvents()).hasSize(1)); - then(this.listener.getEvents().get(0)).hasNameEqualTo("rxjava"); - then(this.listener.getEvents().get(0)).isExportable(); - then(this.listener.getEvents().get(0)).hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, - "rxjava"); - then(this.listener.getEvents().get(0)).isALocalComponentSpan(); + .untilAsserted(() -> then(this.reporter.getSpans()).hasSize(1)); + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span span = this.reporter.getSpans().get(0); + then(span.name()).isEqualTo("rxjava"); } @Test public void should_continue_current_span_when_rx_java_action_is_executed() { - Span spanInCurrentThread = this.tracer.createSpan("current_span"); - this.tracer.addTag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "current_span"); - - Observable - .defer(() -> Observable.just( - (Action0) () -> this.caller = new StringBuffer("actual_action"))) - .subscribeOn(Schedulers.newThread()).toBlocking() - .subscribe(Action0::call); + Span spanInCurrentThread = this.tracer.nextSpan().name("current_span"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(spanInCurrentThread)) { + Observable + .defer(() -> Observable.just( + (Action0) () -> this.caller = new StringBuffer("actual_action"))) + .subscribeOn(Schedulers.newThread()).toBlocking() + .subscribe(Action0::call); + } finally { + spanInCurrentThread.finish(); + } then(this.caller.toString()).isEqualTo("actual_action"); - then(this.tracer.getCurrentSpan()).isNotNull(); + then(this.tracer.currentSpan()).isNull(); // making sure here that no new spans were created or reported as closed - then(this.listener.getEvents()).isEmpty(); - then(spanInCurrentThread).hasNameEqualTo(spanInCurrentThread.getName()); - then(spanInCurrentThread).isExportable(); - then(spanInCurrentThread).hasATag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, - "current_span"); - then(spanInCurrentThread).isALocalComponentSpan(); - } - - static class Listener implements SpanReporter { - - private List events = new ArrayList<>(); - - public List getEvents() { - return this.events; - } - - @Override - public void report(Span span) { - this.events.add(span); - } + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span span = this.reporter.getSpans().get(0); + then(span.name()).isEqualTo("current_span"); } @Configuration @@ -120,12 +92,12 @@ public static class TestConfig { @Bean Sampler alwaysSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } @Bean - SpanReporter spanReporter() { - return new Listener(); + ArrayListSpanReporter spanReporter() { + return new ArrayListSpanReporter(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java index d8a4d32864..73516e45a9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/scheduling/TracingOnScheduledTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,35 +16,41 @@ package org.springframework.cloud.sleuth.instrument.scheduling; +import java.util.AbstractMap; import java.util.concurrent.atomic.AtomicBoolean; +import brave.Span; +import brave.Tracing; +import brave.sampler.Sampler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import zipkin2.reporter.Reporter; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; @RunWith(SpringRunner.class) @SpringBootTest(classes = { ScheduledTestConfiguration.class }) +@DirtiesContext public class TracingOnScheduledTests { - @Autowired - TestBeanWithScheduledMethod beanWithScheduledMethod; - @Autowired - TestBeanWithScheduledMethodToBeIgnored beanWithScheduledMethodToBeIgnored; + @Autowired TestBeanWithScheduledMethod beanWithScheduledMethod; + @Autowired TestBeanWithScheduledMethodToBeIgnored beanWithScheduledMethodToBeIgnored; + @Autowired ArrayListSpanReporter reporter; @Before public void setup() { @@ -82,9 +88,10 @@ private void spanIsSetOnAScheduledMethod() { Span storedSpan = TracingOnScheduledTests.this.beanWithScheduledMethod .getSpan(); then(storedSpan).isNotNull(); - then(storedSpan.getTraceId()).isNotNull(); - then(storedSpan).hasATag("class", "TestBeanWithScheduledMethod"); - then(storedSpan).hasATag("method", "scheduledMethod"); + then(storedSpan.context().traceId()).isNotNull(); + then(this.reporter.getSpans().get(0).tags()) + .contains(new AbstractMap.SimpleEntry<>("class", "TestBeanWithScheduledMethod"), + new AbstractMap.SimpleEntry<>("method", "scheduledMethod")); } private void differentSpanHasBeenSetThan(final Span spanToCompare) { @@ -99,32 +106,43 @@ private void differentSpanHasBeenSetThan(final Span spanToCompare) { @EnableScheduling class ScheduledTestConfiguration { - @Bean - TestBeanWithScheduledMethod testBeanWithScheduledMethod() { - return new TestBeanWithScheduledMethod(); + @Bean Reporter testRepoter() { + return new ArrayListSpanReporter(); } - @Bean - TestBeanWithScheduledMethodToBeIgnored testBeanWithScheduledMethodToBeIgnored() { - return new TestBeanWithScheduledMethodToBeIgnored(); + @Bean TestBeanWithScheduledMethod testBeanWithScheduledMethod(Tracing tracing) { + return new TestBeanWithScheduledMethod(tracing); } - @Bean - AlwaysSampler alwaysSampler() { - return new AlwaysSampler(); + @Bean TestBeanWithScheduledMethodToBeIgnored testBeanWithScheduledMethodToBeIgnored(Tracing tracing) { + return new TestBeanWithScheduledMethodToBeIgnored(tracing); + } + + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; } } class TestBeanWithScheduledMethod { + private static final Log log = LogFactory.getLog(TestBeanWithScheduledMethod.class); + + private final Tracing tracing; + Span span; AtomicBoolean executed = new AtomicBoolean(false); + TestBeanWithScheduledMethod(Tracing tracing) { + this.tracing = tracing; + } + @Scheduled(fixedDelay = 1L) public void scheduledMethod() { - this.span = TestSpanContextHolder.getCurrentSpan(); + log.info("Running the scheduled method"); + this.span = this.tracing.tracer().currentSpan(); + log.info("Stored the span " + this.span + " as current span"); this.executed.set(true); } @@ -137,18 +155,25 @@ public AtomicBoolean isExecuted() { } public void clear() { + this.span = null; this.executed.set(false); } } class TestBeanWithScheduledMethodToBeIgnored { + private final Tracing tracing; + Span span; AtomicBoolean executed = new AtomicBoolean(false); + TestBeanWithScheduledMethodToBeIgnored(Tracing tracing) { + this.tracing = tracing; + } + @Scheduled(fixedDelay = 1L) public void scheduledMethodToIgnore() { - this.span = TestSpanContextHolder.getCurrentSpan(); + this.span = this.tracing.tracer().currentSpan(); this.executed.set(true); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/AbstractMvcIntegrationTest.java similarity index 72% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/AbstractMvcIntegrationTest.java index e1c8b3edf2..1d40e00bbe 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/common/AbstractMvcIntegrationTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/AbstractMvcIntegrationTest.java @@ -1,14 +1,10 @@ -package org.springframework.cloud.sleuth.instrument.web.common; +package org.springframework.cloud.sleuth.instrument.web; +import brave.Tracing; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanExtractor; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; import org.springframework.context.ApplicationContext; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -32,15 +28,11 @@ public abstract class AbstractMvcIntegrationTest { @Autowired protected WebApplicationContext webApplicationContext; protected MockMvc mockMvc; @Autowired protected SleuthProperties properties; - @Autowired protected Tracer tracer; + @Autowired protected Tracing tracing; @Autowired protected TraceKeys traceKeys; - @Autowired protected HttpSpanExtractor spanExtractor; - @Autowired protected HttpTraceKeysInjector httpTraceKeysInjector; @Before public void setup() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); DefaultMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext); configureMockMvcBuilder(mockMvcBuilder); this.mockMvc = mockMvcBuilder.build(); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java deleted file mode 100644 index 6451f9f291..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.Span; - -import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; -import java.util.Collections; -import java.util.Random; -import java.util.Vector; -import java.util.regex.Pattern; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -@RunWith(MockitoJUnitRunner.class) -public class HttpServletRequestExtractorTests { - - @Mock HttpServletRequest request; - ZipkinHttpSpanExtractor extractor = new ZipkinHttpSpanExtractor( - Pattern.compile("")); - - @Before - public void setup() { - BDDMockito.given(this.request.getRequestURI()).willReturn("http://foo.com"); - BDDMockito.given(this.request.getContextPath()).willReturn("/"); - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Arrays.asList("invalid", Span.TRACE_ID_NAME, - Span.SPAN_ID_NAME, Span.PARENT_ID_NAME)).elements()); - } - - @Test - public void should_return_null_if_there_is_no_trace_id() { - then(extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); - } - - @Test - public void should_set_random_traceid_if_header_value_is_invalid() { - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Collections.singletonList(Span.TRACE_ID_NAME)).elements()); - BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) - .willReturn("invalid"); - - then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); - } - - @Test - public void should_set_random_spanid_if_header_value_is_invalid() { - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, - Span.SPAN_ID_NAME)).elements()); - BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) - .willReturn(String.valueOf(new Random().nextLong())); - BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) - .willReturn("invalid"); - - then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); - } - - @Test - public void should_not_throw_exception_if_parent_id_is_invalid() { - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, - Span.SPAN_ID_NAME, Span.PARENT_ID_NAME)).elements()); - BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) - .willReturn(String.valueOf(new Random().nextLong())); - BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) - .willReturn(String.valueOf(new Random().nextLong())); - BDDMockito.given(this.request.getHeader(Span.PARENT_ID_NAME)) - .willReturn("invalid"); - - then(this.extractor.joinTrace(new HttpServletRequestTextMap(this.request))).isNull(); - } - - @Test - public void should_accept_128bit_trace_id() { - String hex128Bits = spanInHeaders(); - - - Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); - - then(span.traceIdString()).isEqualTo(hex128Bits); - } - - @Test - public void should_set_shared_flag_for_sampled_span_in_headers() { - spanInHeaders(); - - Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); - - then(span.isShared()).isTrue(); - } - - @Test - public void should_not_set_shared_flag_for_non_sampled_span_in_headers() { - spanInHeaders(); - BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME)) - .willReturn(Span.SPAN_NOT_SAMPLED); - - Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); - - then(span.isShared()).isFalse(); - } - - @Test - public void should_not_set_shared_flag_for_sampled_span_in_headers_without_span_trace_id() { - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Arrays.asList(Span.SPAN_FLAGS, Span.SPAN_ID_NAME)).elements()); - BDDMockito.given(this.request.getHeader(Span.SPAN_FLAGS)) - .willReturn("1"); - BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) - .willReturn("48485a3953bb6124"); - - Span span = this.extractor.joinTrace(new HttpServletRequestTextMap(this.request)); - - then(span.isShared()).isFalse(); - } - - private String spanInHeaders() { - String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; - String lower64Bits = "48485a3953bb6124"; - - BDDMockito.given(this.request.getHeaderNames()) - .willReturn(new Vector<>(Arrays.asList(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME, Span.SAMPLED_NAME)).elements()); - BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) - .willReturn(hex128Bits); - BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) - .willReturn(lower64Bits); - BDDMockito.given(this.request.getHeader(Span.SAMPLED_NAME)) - .willReturn(Span.SPAN_SAMPLED); - return hex128Bits; - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjectorUnitTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjectorUnitTests.java deleted file mode 100644 index d4bb4dc034..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpTraceKeysInjectorUnitTests.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.net.URL; -import java.util.Arrays; -import java.util.Random; - -import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * Test case for HttpTraceKeysInjector - * - * @author Sven Zethelius - */ -public class HttpTraceKeysInjectorUnitTests { - private TraceKeys traceKeys = new TraceKeys(); - private Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), new NoOpSpanReporter(), traceKeys); - private HttpTraceKeysInjector injector = new HttpTraceKeysInjector(tracer, traceKeys); - - @Test - public void should_set_tags_on_span_with_proper_header_values() throws Exception { - Span span = tracer.createSpan("TestSpan"); - URL url = new URL("http://localhost:8080/"); - HttpHeaders headers = new HttpHeaders(); - headers.add("User-Agent", "Test"); - headers.put("Accept", Arrays.asList(MediaType.TEXT_PLAIN_VALUE, MediaType.TEXT_XML_VALUE)); - headers.add("Content-Length", "0"); - headers.add(Span.TRACE_ID_NAME,"3bb9a1fa9e70fdbd763261a53162f330"); - headers.add(Span.SPAN_ID_NAME, "763261a53162f330"); - headers.add(Span.SAMPLED_NAME,"1"); - headers.add(Span.SPAN_NAME_NAME, "http:/"); - this.traceKeys.getHttp().setHeaders(Arrays.asList("Accept", "User-Agent", "Content-Type")); - - this.injector.addRequestTags(url.toString(), url.getHost(), url.getPath(), HttpMethod.GET.name(), headers ); - - tracer.close(span); - then(span.tags()) - .containsEntry("http.user-agent", "Test") - .containsEntry("http.accept", "'text/plain','text/xml'") - .doesNotContainKey("http.content-type"); - then(tracer.getCurrentSpan()).isNull(); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 85b3324385..8df05463db 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,12 +63,11 @@ public void should_pick_management_context_when_skip_patterns_is_empty_and_conte public void should_pick_default_pattern_when_both_management_context_and_skip_patterns_are_empty() throws Exception { SleuthWebProperties sleuthWebProperties = new SleuthWebProperties(); sleuthWebProperties.setSkipPattern(""); - - ManagementServerProperties properties = new ManagementServerProperties(); - properties.getServlet().setContextPath(""); + ManagementServerProperties managementServerProperties = new ManagementServerProperties(); + managementServerProperties.getServlet().setContextPath(""); Pattern pattern = TraceWebAutoConfiguration.SkipPatternProviderConfig.getPatternForManagementServerProperties( - properties, sleuthWebProperties); + managementServerProperties, sleuthWebProperties); then(pattern.pattern()).isEqualTo(SleuthWebProperties.DEFAULT_SKIP_PATTERN); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParserTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParserTests.java new file mode 100644 index 0000000000..0210774a46 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpClientParserTests.java @@ -0,0 +1,82 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import static org.assertj.core.api.BDDAssertions.then; + +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.springframework.cloud.sleuth.TraceKeys; + +import brave.SpanCustomizer; +import brave.http.HttpClientAdapter; + +/** + * Test case for HttpTraceKeysInjector + * + * @author Sven Zethelius + */ +public class SleuthHttpClientParserTests { + private TraceKeys traceKeys = new TraceKeys(); + private TestSpanCustomizer customizer = new TestSpanCustomizer(); + private SleuthHttpClientParser parser = new SleuthHttpClientParser(this.traceKeys); + + @Test + public void should_set_tags_on_span_with_proper_header_values() throws Exception { + this.traceKeys.getHttp().setHeaders(Arrays.asList("Accept", "User-Agent", "Content-Type")); + + this.parser.request(new HttpClientAdapter() { + private final URL url = new URL("http://localhost:8080/"); + + @Override public String method(Object request) { + return "GET"; + } + + @Override public String url(Object request) { + return url.toString(); + } + + @Override public String requestHeader(Object request, String name) { + if (name.equals("Accept")) { + return "'text/plain','text/xml'"; + } else if (name.equals("User-Agent")) { + return "Test"; + } + return null; + } + + @Override public Integer statusCode(Object response) { + return 200; + } + }, null, this.customizer); + + then(this.customizer.tags) + .containsEntry("http.user-agent", "Test") + .containsEntry("http.accept", "'text/plain','text/xml'") + .doesNotContainKey("http.content-type"); + } +} + +class TestSpanCustomizer implements SpanCustomizer { + + Map tags = new HashMap<>(); + + @Override public SpanCustomizer name(String name) { + return this; + } + + @Override public SpanCustomizer tag(String key, String value) { + this.tags.put(key, value); + return this; + } + + @Override public SpanCustomizer annotate(String value) { + return this; + } + + @Override public SpanCustomizer annotate(long timestamp, String value) { + return this; + } +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpParserAccessor.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpParserAccessor.java new file mode 100644 index 0000000000..66331eb380 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SleuthHttpParserAccessor.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.sleuth.instrument.web; + +import brave.http.HttpClientParser; +import brave.http.HttpServerParser; +import org.springframework.cloud.sleuth.ErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; + +/** + * @author Marcin Grzejszczak + * @since + */ +public class SleuthHttpParserAccessor { + public static HttpClientParser getClient(TraceKeys traceKeys) { + return new SleuthHttpClientParser(traceKeys); + } + + public static HttpServerParser getServer(TraceKeys traceKeys, ErrorParser errorParser) { + return new SleuthHttpServerParser(traceKeys, errorParser); + } +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java index 71d51a8aeb..03fc9d334a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SpringDataInstrumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,18 @@ package org.springframework.cloud.sleuth.instrument.web; +import javax.annotation.PostConstruct; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import java.net.URI; +import java.util.stream.Stream; + +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import org.awaitility.Awaitility; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -25,13 +35,7 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -44,22 +48,15 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.client.RestTemplate; -import org.awaitility.Awaitility; -import javax.annotation.PostConstruct; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import java.net.URI; -import java.util.stream.Stream; - import static org.assertj.core.api.BDDAssertions.then; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = ReservationServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = ReservationServiceApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.sleuth.http.legacy.enabled=true") @DirtiesContext @ActiveProfiles("data") public class SpringDataInstrumentationTests { @@ -71,11 +68,11 @@ public class SpringDataInstrumentationTests { @Autowired Tracer tracer; @Autowired - ArrayListSpanAccumulator arrayListSpanAccumulator; + ArrayListSpanReporter reporter; @Before public void setup() { - TestSpanContextHolder.removeCurrentSpan(); + reporter.clear(); } @Test @@ -83,15 +80,14 @@ public void should_create_span_instrumented_by_a_handler_interceptor() { long noOfNames = namesCount(); then(noOfNames).isEqualTo(8); - then(this.arrayListSpanAccumulator.getSpans()).isNotEmpty(); + then(this.reporter.getSpans()).isNotEmpty(); Awaitility.await().untilAsserted(() -> { - then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())) - .hasASpanWithName("http:/reservations") - .hasASpanWithTagKeyEqualTo("mvc.controller.class"); + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span storedSpan = this.reporter.getSpans().get(0); + then(storedSpan.name()).isEqualTo("http:/reservations"); + then(storedSpan.tags()).containsKey("mvc.controller.class"); }); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())).hasRpcLogsInProperOrder(); + then(this.tracer.currentSpan()).isNull(); } long namesCount() { @@ -116,19 +112,19 @@ RestTemplate restTemplate() { return new RestTemplate(); } - @Bean - SampleRecords sampleRecords(ReservationRepository reservationRepository) { + @Bean SampleRecords sampleRecords( + ReservationRepository reservationRepository) { return new SampleRecords(reservationRepository); } @Bean - ArrayListSpanAccumulator arrayListSpanAccumulator() { - return new ArrayListSpanAccumulator(); + ArrayListSpanReporter arrayListSpanAccumulator() { + return new ArrayListSpanReporter(); } @Bean Sampler alwaysSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } @@ -137,7 +133,8 @@ class SampleRecords { private final ReservationRepository reservationRepository; - public SampleRecords(ReservationRepository reservationRepository) { + public SampleRecords( + ReservationRepository reservationRepository) { this.reservationRepository = reservationRepository; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java index 054c9c0b0f..062f9d5e50 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceAsyncIntegrationTests.java @@ -1,41 +1,43 @@ package org.springframework.cloud.sleuth.instrument.web; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.util.concurrent.atomic.AtomicReference; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.SpanName; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.test.context.junit4.SpringRunner; -import org.awaitility.Awaitility; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) @SpringBootTest(classes = { - TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }) + TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }, + properties = "spring.sleuth.http.legacy.enabled=true") public class TraceAsyncIntegrationTests { @Autowired ClassPerformingAsyncLogic classPerformingAsyncLogic; @Autowired Tracer tracer; + @Autowired + ArrayListSpanReporter reporter; @Before public void cleanup() { @@ -60,24 +62,30 @@ public void should_set_span_with_custom_method_on_an_async_annotated_method() { public void should_continue_a_span_on_an_async_annotated_method() { Span span = givenASpanInCurrentThread(); - whenAsyncProcessingTakesPlace(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + whenAsyncProcessingTakesPlace(); + } finally { + span.finish(); + } thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(span); - this.tracer.close(span); } @Test public void should_continue_a_span_with_custom_method_on_an_async_annotated_method() { Span span = givenASpanInCurrentThread(); - whenAsyncProcessingTakesPlaceWithCustomSpanName(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + whenAsyncProcessingTakesPlaceWithCustomSpanName(); + } finally { + span.finish(); + } thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(span); - this.tracer.close(span); } private Span givenASpanInCurrentThread() { - return this.tracer.createSpan("http:existing"); + return this.tracer.nextSpan().name("http:existing"); } private void whenAsyncProcessingTakesPlace() { @@ -90,45 +98,61 @@ private void whenAsyncProcessingTakesPlaceWithCustomSpanName() { private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(final Span span) { Awaitility.await().atMost(5, SECONDS).untilAsserted( - () -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan()) - .hasTraceIdEqualTo(span.getTraceId()) - .hasNameEqualTo("http:existing") - .isALocalComponentSpan() - .hasATag("class", "ClassPerformingAsyncLogic") - .hasATag("method", "invokeAsynchronousLogic")); + () -> { + then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic + .getSpan().context().traceId()).isEqualTo(span.context().traceId()); + then(this.reporter.getSpans()).hasSize(2); + // HTTP + then(this.reporter.getSpans().get(0).name()).isEqualTo("http:existing"); + // ASYNC + then(this.reporter.getSpans().get(1).tags()) + .containsEntry("class", "ClassPerformingAsyncLogic") + .containsEntry("method", "invokeAsynchronousLogic"); + }); } private void thenANewAsyncSpanGetsCreated() { Awaitility.await().atMost(5, SECONDS).untilAsserted( - () -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan()) - .hasNameEqualTo("invoke-asynchronous-logic") - .isALocalComponentSpan() - .hasATag("class", "ClassPerformingAsyncLogic") - .hasATag("method", "invokeAsynchronousLogic")); + () -> { + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span storedSpan = this.reporter.getSpans().get(0); + then(storedSpan.name()).isEqualTo("invoke-asynchronous-logic"); + then(storedSpan.tags()) + .containsEntry("class", "ClassPerformingAsyncLogic") + .containsEntry("method", "invokeAsynchronousLogic"); + }); } private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(final Span span) { Awaitility.await().atMost(5, SECONDS).untilAsserted( - () -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan()) - .hasTraceIdEqualTo(span.getTraceId()) - .hasNameEqualTo("http:existing") - .isALocalComponentSpan() - .hasATag("class", "ClassPerformingAsyncLogic") - .hasATag("method", "customNameInvokeAsynchronousLogic")); + () -> { + then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic + .getSpan().context().traceId()).isEqualTo(span.context().traceId()); + then(this.reporter.getSpans()).hasSize(2); + // HTTP + then(this.reporter.getSpans().get(0).name()).isEqualTo("http:existing"); + // ASYNC + then(this.reporter.getSpans().get(1).tags()) + .containsEntry("class", "ClassPerformingAsyncLogic") + .containsEntry("method", "customNameInvokeAsynchronousLogic"); + }); } private void thenAsyncSpanHasCustomName() { Awaitility.await().atMost(5, SECONDS).untilAsserted( - () -> then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan()) - .hasNameEqualTo("foo") - .isALocalComponentSpan() - .hasATag("class", "ClassPerformingAsyncLogic") - .hasATag("method", "customNameInvokeAsynchronousLogic")); + () -> { + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span storedSpan = this.reporter.getSpans().get(0); + then(storedSpan.name()).isEqualTo("foo"); + then(storedSpan.tags()) + .containsEntry("class", "ClassPerformingAsyncLogic") + .containsEntry("method", "customNameInvokeAsynchronousLogic"); + }); } @After - public void cleanTrace() { - TestSpanContextHolder.removeCurrentSpan(); + public void cleanTrace(){ + this.reporter.clear(); } @DefaultTestAutoConfiguration @@ -137,13 +161,18 @@ public void cleanTrace() { static class TraceAsyncITestConfiguration { @Bean - ClassPerformingAsyncLogic asyncClass() { - return new ClassPerformingAsyncLogic(); + ClassPerformingAsyncLogic asyncClass(Tracer tracer) { + return new ClassPerformingAsyncLogic(tracer); } @Bean Sampler defaultSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); } } @@ -152,15 +181,21 @@ static class ClassPerformingAsyncLogic { AtomicReference span = new AtomicReference<>(); + private final Tracer tracer; + + ClassPerformingAsyncLogic(Tracer tracer) { + this.tracer = tracer; + } + @Async public void invokeAsynchronousLogic() { - this.span.set(TestSpanContextHolder.getCurrentSpan()); + this.span.set(this.tracer.currentSpan()); } @Async @SpanName("foo") public void customNameInvokeAsynchronousLogic() { - this.span.set(TestSpanContextHolder.getCurrentSpan()); + this.span.set(this.tracer.currentSpan()); } public Span getSpan() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java index da06c9b676..412d95e020 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceCustomFilterResponseInjectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,25 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.io.IOException; -import java.net.URI; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import brave.Span; +import brave.http.HttpTracing; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -57,11 +56,13 @@ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class TraceCustomFilterResponseInjectorTests { + static final String TRACE_ID_NAME = "X-B3-TraceId"; + static final String SPAN_ID_NAME = "X-B3-SpanId"; + @Autowired RestTemplate restTemplate; @Autowired Config config; @Autowired CustomRestController customRestController; - - + @Test @SuppressWarnings("unchecked") public void should_inject_trace_and_span_ids_in_response_headers() { @@ -73,7 +74,7 @@ public void should_inject_trace_and_span_ids_in_response_headers() { ResponseEntity responseEntity = this.restTemplate.exchange(requestEntity, Map.class); then(responseEntity.getHeaders()) - .containsKeys(Span.TRACE_ID_NAME, Span.SPAN_ID_NAME) + .containsKeys(TRACE_ID_NAME, SPAN_ID_NAME) .as("Trace headers must be present in response headers"); } @@ -84,13 +85,9 @@ static class Config int port; // tag::configuration[] - @Bean HttpSpanInjector customHttpServletResponseSpanInjector() { - return new CustomHttpServletResponseSpanInjector(); - } - @Bean - HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) { - return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector()); + HttpResponseInjectingTraceFilter responseInjectingTraceFilter(HttpTracing httpTracing) { + return new HttpResponseInjectingTraceFilter(httpTracing); } // end::configuration[] @@ -113,56 +110,24 @@ CustomRestController customRestController() { } // tag::injector[] - static class CustomHttpServletResponseSpanInjector extends ZipkinHttpSpanInjector { - - @Override - public void inject(Span span, SpanTextMap carrier) { - super.inject(span, carrier); - carrier.put(Span.TRACE_ID_NAME, span.traceIdString()); - carrier.put(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId())); - } - } - static class HttpResponseInjectingTraceFilter extends GenericFilterBean { - private final Tracer tracer; - private final HttpSpanInjector spanInjector; + private final HttpTracing httpTracing; - public HttpResponseInjectingTraceFilter(Tracer tracer, HttpSpanInjector spanInjector) { - this.tracer = tracer; - this.spanInjector = spanInjector; + public HttpResponseInjectingTraceFilter(HttpTracing httpTracing) { + this.httpTracing = httpTracing; } @Override public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; - Span currentSpan = this.tracer.getCurrentSpan(); - this.spanInjector.inject(currentSpan, new HttpServletResponseTextMap(response)); + Span currentSpan = this.httpTracing.tracing().tracer().currentSpan(); + response.addHeader("X-B3-TraceId", + currentSpan.context().traceIdString()); + response.addHeader("X-B3-SpanId", + SpanUtil.idToHex(currentSpan.context().spanId())); filterChain.doFilter(request, response); } - - class HttpServletResponseTextMap implements SpanTextMap { - - private final HttpServletResponse delegate; - - HttpServletResponseTextMap(HttpServletResponse delegate) { - this.delegate = delegate; - } - - @Override - public Iterator> iterator() { - Map map = new HashMap<>(); - for (String header : this.delegate.getHeaderNames()) { - map.put(header, this.delegate.getHeader(header)); - } - return map.entrySet().iterator(); - } - - @Override - public void put(String key, String value) { - this.delegate.addHeader(key, value); - } - } } // end::injector[] diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java deleted file mode 100644 index 7dc01b6c82..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterAlwaysSamplerIntegrationTests.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.Random; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mockito; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; -import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.BDDAssertions.then; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes = TraceFilterAlwaysSamplerIntegrationTests.Config.class) -public class TraceFilterAlwaysSamplerIntegrationTests extends AbstractMvcIntegrationTest { - - private static Log logger = LogFactory - .getLog(TraceFilterAlwaysSamplerIntegrationTests.class); - - static Span span; - - @Test - public void when_always_sampler_is_used_span_is_exportable() throws Exception { - Long expectedTraceId = new Random().nextLong(); - - MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); - - then(span.isExportable()); - } - - @Test - public void when_not_sampling_header_present_span_is_not_exportable() - throws Exception { - Long expectedTraceId = new Random().nextLong(); - - MvcResult mvcResult = whenSentPingWithTraceIdAndNotSampling(expectedTraceId); - - then(span.isExportable()).isFalse(); - } - - @Override - protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { - BeanFactory beanFactory = beanFactory(); - mockMvcBuilder.addFilters(new TraceFilter(beanFactory)); - } - - private BeanFactory beanFactory() { - BeanFactory beanFactory = Mockito.mock(BeanFactory.class); - BDDMockito.given(beanFactory.getBean(SkipPatternProvider.class)) - .willThrow(new NoSuchBeanDefinitionException("foo")); - BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); - BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); - BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); - BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)).willReturn(this.spanExtractor); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(new NoOpSpanReporter()); - BDDMockito.given(beanFactory.getBean(HttpTraceKeysInjector.class)).willReturn(this.httpTraceKeysInjector); - return beanFactory; - } - - private MvcResult whenSentPingWithTraceIdAndNotSampling(Long traceId) - throws Exception { - return sendPingWithTraceId(Span.TRACE_ID_NAME, traceId, false); - } - - private MvcResult whenSentPingWithTraceId(Long traceId) throws Exception { - return sendPingWithTraceId(Span.TRACE_ID_NAME, traceId); - } - - private MvcResult sendPingWithTraceId(String headerName, Long correlationId) - throws Exception { - return sendPingWithTraceId(headerName, correlationId, true); - } - - private MvcResult sendPingWithTraceId(String headerName, Long correlationId, - boolean sampling) throws Exception { - MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/ping") - .accept(MediaType.TEXT_PLAIN) - .header(headerName, Span.idToHex(correlationId)) - .header(Span.SPAN_ID_NAME, Span.idToHex(new Random().nextLong())); - request.header(Span.SAMPLED_NAME, - sampling ? Span.SPAN_SAMPLED : Span.SPAN_NOT_SAMPLED); - return this.mockMvc.perform(request).andReturn(); - } - - @DefaultTestAutoConfiguration - @RestController - @Configuration - @Import(AlwaysSampler.class) - static class Config { - - @Autowired - private Tracer tracer; - - @RequestMapping("/ping") - public String ping() { - logger.info("ping"); - span = this.tracer.getCurrentSpan(); - return "ping"; - } - - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java deleted file mode 100644 index 7133bf0fa0..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterCustomExtractorTests.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.net.URI; -import java.util.HashMap; -import java.util.Map; - -import org.assertj.core.api.BDDAssertions; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.SpanTextMap; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.TextMapUtil; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import static org.awaitility.Awaitility.await; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = TraceFilterCustomExtractorTests.Config.class, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DirtiesContext -public class TraceFilterCustomExtractorTests { - @Autowired RestTemplate restTemplate; - @Autowired Config config; - @Autowired CustomRestController customRestController; - @Autowired ArrayListSpanAccumulator accumulator; - @Autowired Tracer tracer; - - @Before - public void setup() { - this.accumulator.getSpans().clear(); - } - - @Test - @SuppressWarnings("unchecked") - public void should_create_a_valid_span_from_custom_headers() { - final Span newSpan = this.tracer.createSpan("new_span"); - ResponseEntity responseEntity = null; - try { - RequestEntity requestEntity = RequestEntity - .get(URI.create("http://localhost:" + this.config.port + "/headers")) - .build(); - responseEntity = this.restTemplate.exchange(requestEntity, Map.class); - - } finally { - this.tracer.close(newSpan); - } - await().atMost(5, SECONDS).untilAsserted(() -> { - then(this.accumulator.getSpans().stream().filter( - span -> span.getSpanId() == newSpan.getSpanId()).findFirst().get()) - .hasTraceIdEqualTo(newSpan.getTraceId()); - }); - BDDAssertions.then(responseEntity.getBody()) - .containsEntry("correlationid", Span.idToHex(newSpan.getTraceId())) - .containsKey("myspanid") - .as("input request headers"); - } - - @Configuration - @EnableAutoConfiguration - static class Config - implements ApplicationListener { - int port; - - // tag::configuration[] - @Bean - HttpSpanInjector customHttpSpanInjector() { - return new CustomHttpSpanInjector(); - } - - @Bean - HttpSpanExtractor customHttpSpanExtractor() { - return new CustomHttpSpanExtractor(); - } - // end::configuration[] - - @Override - public void onApplicationEvent(ServletWebServerInitializedEvent event) { - this.port = event.getSource().getPort(); - } - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - CustomRestController customRestController() { - return new CustomRestController(); - } - - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); - } - - @Bean - SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); - } - } - - // tag::extractor[] - static class CustomHttpSpanExtractor implements HttpSpanExtractor { - - @Override public Span joinTrace(SpanTextMap carrier) { - Map map = TextMapUtil.asMap(carrier); - long traceId = Span.hexToId(map.get("correlationid")); - long spanId = Span.hexToId(map.get("myspanid")); - // extract all necessary headers - Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId); - // build rest of the Span - return builder.build(); - } - } - - static class CustomHttpSpanInjector implements HttpSpanInjector { - - @Override - public void inject(Span span, SpanTextMap carrier) { - carrier.put("correlationId", span.traceIdString()); - carrier.put("mySpanId", Span.idToHex(span.getSpanId())); - } - } - // end::extractor[] - - @RestController - static class CustomRestController { - - @Autowired Tracer tracer; - - @RequestMapping("/headers") - public Map headers(@RequestHeader HttpHeaders headers) { - Map map = new HashMap<>(); - for (String key : headers.keySet()) { - map.put(key, headers.getFirst(key)); - } - return map; - } - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java index d868d3811a..61725ac949 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterIntegrationTests.java @@ -1,10 +1,19 @@ package org.springframework.cloud.sleuth.instrument.web; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.util.Optional; import java.util.Random; import java.util.concurrent.CompletableFuture; -import javax.servlet.http.HttpServletResponse; +import brave.Span; +import brave.Tracer; +import brave.sampler.Sampler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; @@ -12,26 +21,20 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.MDC; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.common.AbstractMvcIntegrationTest; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -39,40 +42,45 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.filter.GenericFilterBean; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @SpringBootTest(classes = TraceFilterIntegrationTests.Config.class) public class TraceFilterIntegrationTests extends AbstractMvcIntegrationTest { + static final String TRACE_ID_NAME = "X-B3-TraceId"; + static final String SPAN_ID_NAME = "X-B3-SpanId"; + static final String SAMPLED_NAME = "X-B3-Sampled"; - private static Log logger = LogFactory.getLog(TraceFilterIntegrationTests.class); + private static Log logger = LogFactory.getLog( + TraceFilterIntegrationTests.class); @Autowired TraceFilter traceFilter; - @Autowired ArrayListSpanAccumulator spanAccumulator; + @Autowired MyFilter myFilter; + @Autowired ArrayListSpanReporter reporter; + @Autowired Tracer tracer; private static Span span; @Before @After public void clearSpans() { - this.spanAccumulator.getSpans().clear(); + this.reporter.clear(); } @Test public void should_create_a_trace() throws Exception { whenSentPingWithoutTracingData(); - then(this.spanAccumulator.getSpans()).hasSize(1); - Span span = this.spanAccumulator.getSpans().get(0); - then(span).hasLoggedAnEvent(Span.SERVER_RECV) - .hasATagWithKey(new TraceKeys().getMvc().getControllerClass()) - .hasATagWithKey(new TraceKeys().getMvc().getControllerMethod()) - .hasLoggedAnEvent(Span.SERVER_SEND); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span span = this.reporter.getSpans().get(0); + then(span.tags()) + .containsKey(new TraceKeys().getMvc().getControllerClass()) + .containsKey(new TraceKeys().getMvc().getControllerMethod()); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -83,7 +91,8 @@ public void should_ignore_sampling_the_span_if_uri_matches_management_properties // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 // we don't want to respond with any tracing data then(notSampledHeaderIsPresent(mvcResult)).isEqualTo(false); - then(ExceptionUtils.getLastException()).isNull(); + then(this.reporter.getSpans()).isEmpty(); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -91,9 +100,11 @@ public void when_traceId_is_sent_should_not_create_a_new_one_but_return_the_exis throws Exception { Long expectedTraceId = new Random().nextLong(); - MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); + whenSentPingWithTraceId(expectedTraceId); + + then(this.reporter.getSpans()).hasSize(1); + then(this.tracer.currentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -103,7 +114,8 @@ public void when_message_is_sent_should_eventually_clear_mdc() throws Exception whenSentPingWithTraceId(expectedTraceId); then(MDC.getCopyOfContextMap()).isEmpty(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.reporter.getSpans()).hasSize(1); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -114,8 +126,7 @@ public void when_traceId_is_sent_to_async_endpoint_span_is_joined() throws Excep this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -126,24 +137,24 @@ public void should_add_a_custom_tag_to_the_span_created_in_controller() throws E this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()).andReturn(); - Optional taggedSpan = this.spanAccumulator.getSpans().stream() + Optional taggedSpan = this.reporter.getSpans().stream() .filter(span -> span.tags().containsKey("tag")).findFirst(); then(taggedSpan.isPresent()).isTrue(); - then(taggedSpan.get()).hasATag("tag", "value"); - then(taggedSpan.get()).hasATag("mvc.controller.method", "deferredMethod"); - then(taggedSpan.get()).hasATag("mvc.controller.class", "TestController"); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); + then(taggedSpan.get().tags()) + .containsEntry("tag", "value") + .containsEntry("mvc.controller.method", "deferredMethod") + .containsEntry("mvc.controller.class", "TestController"); + then(this.tracer.currentSpan()).isNull(); } @Test public void should_log_tracing_information_when_exception_was_thrown() throws Exception { Long expectedTraceId = new Random().nextLong(); - MvcResult mvcResult = whenSentToNonExistentEndpointWithTraceId(expectedTraceId); + whenSentToNonExistentEndpointWithTraceId(expectedTraceId); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.reporter.getSpans()).hasSize(1); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -153,11 +164,10 @@ public void should_assume_that_a_request_without_span_and_with_trace_is_a_root_s whenSentRequestWithTraceIdAndNoSpanId(expectedTraceId); whenSentRequestWithTraceIdAndNoSpanId(expectedTraceId); - then(this.spanAccumulator.getSpans().stream().filter(span -> - span.getSpanId() == span.getTraceId()).findAny().isPresent()).as("a root span exists").isTrue(); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.spanAccumulator.getSpans())).hasServerSideSpansInProperOrder(); + then(this.reporter.getSpans().stream().filter(span -> + span.id().equals(span.traceId())) + .findAny().isPresent()).as("a root span exists").isTrue(); + then(this.tracer.currentSpan()).isNull(); } @Test @@ -166,14 +176,15 @@ public void should_return_custom_response_headers_when_custom_trace_filter_gets_ MvcResult mvcResult = whenSentPingWithTraceId(expectedTraceId); - then(ExceptionUtils.getLastException()).isNull(); - then(mvcResult.getResponse().getHeader("ZIPKIN-TRACE-ID")).isEqualTo(Span.idToHex(expectedTraceId)); - then(new ListOfSpans(this.spanAccumulator.getSpans())).hasASpanWithTagEqualTo("custom", "tag"); + then(mvcResult.getResponse().getHeader("ZIPKIN-TRACE-ID")) + .isEqualTo(SpanUtil.idToHex(expectedTraceId)); + then(this.reporter.getSpans()).hasSize(1); + then(this.reporter.getSpans().get(0).tags()).containsEntry("custom", "tag"); } @Override protected void configureMockMvcBuilder(DefaultMockMvcBuilder mockMvcBuilder) { - mockMvcBuilder.addFilters(this.traceFilter); + mockMvcBuilder.addFilters(this.traceFilter, this.myFilter); } private MvcResult whenSentPingWithoutTracingData() throws Exception { @@ -183,24 +194,24 @@ private MvcResult whenSentPingWithoutTracingData() throws Exception { } private MvcResult whenSentPingWithTraceId(Long passedTraceId) throws Exception { - return sendPingWithTraceId(Span.TRACE_ID_NAME, passedTraceId); + return sendPingWithTraceId(TRACE_ID_NAME, passedTraceId); } private MvcResult whenSentInfoWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/additionalContextPath/info", Span.TRACE_ID_NAME, + return sendRequestWithTraceId("/additionalContextPath/info", TRACE_ID_NAME, passedTraceId); } private MvcResult whenSentFutureWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/future", Span.TRACE_ID_NAME, passedTraceId); + return sendRequestWithTraceId("/future", TRACE_ID_NAME, passedTraceId); } private MvcResult whenSentDeferredWithTraceId(Long passedTraceId) throws Exception { - return sendDeferredWithTraceId(Span.TRACE_ID_NAME, passedTraceId); + return sendDeferredWithTraceId(TRACE_ID_NAME, passedTraceId); } private MvcResult whenSentToNonExistentEndpointWithTraceId(Long passedTraceId) throws Exception { - return sendRequestWithTraceId("/exception/nonExistent", Span.TRACE_ID_NAME, passedTraceId, HttpStatus.NOT_FOUND); + return sendRequestWithTraceId("/exception/nonExistent", TRACE_ID_NAME, passedTraceId, HttpStatus.NOT_FOUND); } private MvcResult sendPingWithTraceId(String headerName, Long traceId) @@ -217,8 +228,8 @@ private MvcResult sendRequestWithTraceId(String path, String headerName, Long tr throws Exception { return this.mockMvc .perform(MockMvcRequestBuilders.get(path).accept(MediaType.TEXT_PLAIN) - .header(headerName, Span.idToHex(traceId)) - .header(Span.SPAN_ID_NAME, Span.idToHex(new Random().nextLong()))) + .header(headerName, SpanUtil.idToHex(traceId)) + .header(SPAN_ID_NAME, SpanUtil.idToHex(new Random().nextLong()))) .andReturn(); } @@ -226,7 +237,7 @@ private MvcResult whenSentRequestWithTraceIdAndNoSpanId(Long traceId) throws Exception { return this.mockMvc .perform(MockMvcRequestBuilders.get("/ping").accept(MediaType.TEXT_PLAIN) - .header(Span.TRACE_ID_NAME, Span.idToHex(traceId))) + .header(TRACE_ID_NAME, SpanUtil.idToHex(traceId))) .andReturn(); } @@ -234,15 +245,14 @@ private MvcResult sendRequestWithTraceId(String path, String headerName, Long tr throws Exception { return this.mockMvc .perform(MockMvcRequestBuilders.get(path).accept(MediaType.TEXT_PLAIN) - .header(headerName, Span.idToHex(traceId)) - .header(Span.SPAN_ID_NAME, Span.idToHex(new Random().nextLong()))) + .header(headerName, SpanUtil.idToHex(traceId)) + .header(SPAN_ID_NAME, SpanUtil.idToHex(new Random().nextLong()))) .andExpect(status().is(status.value())) .andReturn(); } private boolean notSampledHeaderIsPresent(MvcResult mvcResult) { - return Span.SPAN_NOT_SAMPLED - .equals(mvcResult.getResponse().getHeader(Span.SAMPLED_NAME)); + return "0".equals(mvcResult.getResponse().getHeader(SAMPLED_NAME)); } @DefaultTestAutoConfiguration @@ -257,7 +267,7 @@ public static class TestController { @RequestMapping("/ping") public String ping() { logger.info("ping"); - span = this.tracer.getCurrentSpan(); + span = this.tracer.currentSpan(); return "ping"; } @@ -269,8 +279,8 @@ public void throwsException() { @RequestMapping("/deferred") public DeferredResult deferredMethod() { logger.info("deferred"); - this.tracer.addTag("tag", "value"); - span = this.tracer.getCurrentSpan(); + span = this.tracer.currentSpan(); + span.tag("tag", "value"); DeferredResult result = new DeferredResult<>(); result.setResult("deferred"); return result; @@ -295,31 +305,44 @@ ManagementServerProperties managementServerProperties() { } @Bean - public SpanReporter testSpanReporter() { - return new ArrayListSpanAccumulator(); + public ArrayListSpanReporter testSpanReporter() { + return new ArrayListSpanReporter(); } - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; } - //tag::response_headers[] @Bean - TraceFilter myTraceFilter(BeanFactory beanFactory, final Tracer tracer) { - return new TraceFilter(beanFactory) { - @Override protected void addResponseTags(HttpServletResponse response, - Throwable e) { - // execute the default behaviour - super.addResponseTags(response, e); - // for readability we're returning trace id in a hex form - response.addHeader("ZIPKIN-TRACE-ID", - Span.idToHex(tracer.getCurrentSpan().getTraceId())); - // we can also add some custom tags - tracer.addTag("custom", "tag"); - } - }; + @Order(TraceFilter.ORDER + 1) + Filter myTraceFilter(Tracer tracer) { + return new MyFilter(tracer); } - //end::response_headers[] } } + +//tag::response_headers[] +@Component +@Order(TraceFilter.ORDER + 1) +class MyFilter extends GenericFilterBean { + + private final Tracer tracer; + + MyFilter(Tracer tracer) { + this.tracer = tracer; + } + + @Override public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + Span currentSpan = this.tracer.currentSpan(); + then(currentSpan).isNotNull(); + // for readability we're returning trace id in a hex form + ((HttpServletResponse) response) + .addHeader("ZIPKIN-TRACE-ID", + currentSpan.context().traceIdString()); + // we can also add some custom tags + currentSpan.tag("custom", "tag"); + chain.doFilter(request, response); + } +} +//end::response_headers[] \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java deleted file mode 100644 index 072cce7684..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterMockChainIntegrationTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.Random; -import java.util.regex.Pattern; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.BDDMockito; -import org.mockito.Mockito; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.junit.Assert.assertNull; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * @author Spencer Gibb - * @author Dave Syer - */ -public class TraceFilterMockChainIntegrationTests { - - private Tracer tracer = new DefaultTracer(new AlwaysSampler(), - new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - private TraceKeys traceKeys = new TraceKeys(); - private SleuthProperties properties = new SleuthProperties(); - private HttpTraceKeysInjector keysInjector = new HttpTraceKeysInjector(this.tracer, this.traceKeys); - - private MockHttpServletRequest request; - private MockHttpServletResponse response; - private MockFilterChain filterChain; - - @Before - public void init() { - TestSpanContextHolder.removeCurrentSpan(); - this.request = builder().buildRequest(new MockServletContext()); - this.response = new MockHttpServletResponse(); - this.response.setContentType(MediaType.APPLICATION_JSON_VALUE); - this.filterChain = new MockFilterChain(); - } - - public MockHttpServletRequestBuilder builder() { - return get("/").accept(MediaType.APPLICATION_JSON) - .header("User-Agent", "MockMvc"); - } - - @Test - public void startsNewTrace() throws Exception { - TraceFilter filter = new TraceFilter(beanFactory()); - filter.doFilter(this.request, this.response, this.filterChain); - assertNull(TestSpanContextHolder.getCurrentSpan()); - } - - @Test - public void continuesSpanFromHeaders() throws Exception { - Random generator = new Random(); - this.request = builder().header(Span.SPAN_ID_NAME, generator.nextLong()) - .header(Span.TRACE_ID_NAME, generator.nextLong()).buildRequest(new MockServletContext()); - BeanFactory beanFactory = beanFactory(); - TraceFilter filter = new TraceFilter(beanFactory); - filter.doFilter(this.request, this.response, this.filterChain); - assertNull(TestSpanContextHolder.getCurrentSpan()); - } - - private BeanFactory beanFactory() { - BeanFactory beanFactory = Mockito.mock(BeanFactory.class); - BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); - BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); - BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); - BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)) - .willReturn(new ZipkinHttpSpanExtractor( - Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN))); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(new NoOpSpanReporter()); - BDDMockito.given(beanFactory.getBean(HttpTraceKeysInjector.class)).willReturn(this.keysInjector); - return beanFactory; - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java index 4b716c92a3..58251c27f0 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,36 +16,25 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Random; -import java.util.regex.Pattern; - +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.BDDMockito; -import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; import org.springframework.cloud.sleuth.autoconfig.SleuthProperties; -import org.springframework.cloud.sleuth.log.SpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -55,11 +44,8 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import static org.assertj.core.api.BDDAssertions.then; import static org.junit.Assert.assertEquals; -import static org.mockito.MockitoAnnotations.initMocks; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.assertThat; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.entry; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** @@ -67,42 +53,37 @@ */ public class TraceFilterTests { - public static final long PARENT_ID = 10L; - - @Mock SpanLogger spanLogger; - ArrayListSpanAccumulator spanReporter = new ArrayListSpanAccumulator(); - HttpSpanExtractor spanExtractor = new ZipkinHttpSpanExtractor(Pattern - .compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN)); - - private Tracer tracer; - private TraceKeys traceKeys = new TraceKeys(); - private SleuthProperties properties = new SleuthProperties(); - private HttpTraceKeysInjector httpTraceKeysInjector; - - private Span span; - - private MockHttpServletRequest request; - private MockHttpServletResponse response; - private MockFilterChain filterChain; - private Sampler sampler = new AlwaysSampler(); + static final String PARENT_ID = SpanUtil.idToHex(10L); + static final String TRACE_ID_NAME = "X-B3-TraceId"; + static final String SPAN_ID_NAME = "X-B3-SpanId"; + static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; + static final String SPAN_FLAGS = "X-B3-Flags"; + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(new SleuthHttpClientParser(this.traceKeys)) + .serverParser(new SleuthHttpServerParser(this.traceKeys, + new ExceptionMessageErrorParser())) + .build(); + SleuthProperties properties = new SleuthProperties(); + + MockHttpServletRequest request; + MockHttpServletResponse response; + MockFilterChain filterChain; BeanFactory beanFactory = Mockito.mock(BeanFactory.class); @Before public void init() { - initMocks(this); - this.tracer = new DefaultTracer(new DelegateSampler(), new Random(), - new DefaultSpanNamer(), this.spanLogger, this.spanReporter, new TraceKeys()) { - @Override - public Span continueSpan(Span span) { - TraceFilterTests.this.span = super.continueSpan(span); - return TraceFilterTests.this.span; - } - }; this.request = builder().buildRequest(new MockServletContext()); this.response = new MockHttpServletResponse(); this.response.setContentType(MediaType.APPLICATION_JSON_VALUE); this.filterChain = new MockFilterChain(); - this.httpTraceKeysInjector = new HttpTraceKeysInjector(this.tracer, this.traceKeys); } public MockHttpServletRequestBuilder builder() { @@ -112,39 +93,54 @@ public MockHttpServletRequestBuilder builder() { @After public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); + Tracing.current().close(); } @Test public void notTraced() throws Exception { - this.sampler = NeverSampler.INSTANCE; - TraceFilter filter = new TraceFilter(beanFactory()); + BeanFactory beanFactory = neverSampleTracing(); + TraceFilter filter = new TraceFilter(beanFactory); this.request = get("/favicon.ico").accept(MediaType.ALL) .buildRequest(new MockServletContext()); filter.doFilter(this.request, this.response, this.filterChain); - then(this.span.isExportable()).isFalse(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isEmpty(); + } + + private BeanFactory neverSampleTracing() { + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .sampler(Sampler.NEVER_SAMPLE) + .supportsJoin(false) + .build(); + HttpTracing httpTracing = HttpTracing.newBuilder(tracing) + .clientParser(new SleuthHttpClientParser(this.traceKeys)) + .serverParser(new SleuthHttpServerParser(this.traceKeys, + new ExceptionMessageErrorParser())) + .build(); + BeanFactory beanFactory = beanFactory(); + BDDMockito.given(beanFactory.getBean(HttpTracing.class)).willReturn(httpTracing); + return beanFactory; } @Test public void startsNewTrace() throws Exception { TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - - assertThat(this.span.tags()).containsEntry("http.status_code", HttpStatus.OK.toString()); - - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.spanReporter.getSpans())) - .hasSize(1) - .hasASpanWithTagEqualTo("http.url", "http://localhost/?foo=bar") - .hasASpanWithTagEqualTo("http.host", "localhost") - .hasASpanWithTagEqualTo("http.path", "/") - .hasASpanWithTagEqualTo("http.method", HttpMethod.GET.toString()) - .hasASpanWithTagEqualTo("http.status_code", HttpStatus.OK.toString()) - .allSpansAreExportable(); + + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.url", "http://localhost/?foo=bar") + .containsEntry("http.host", "localhost") + .containsEntry("http.path", "/") + .containsEntry("http.method", HttpMethod.GET.toString()); + // we don't check for status_code anymore cause Brave doesn't support it oob + //.containsEntry("http.status_code", "200") } @Test @@ -154,18 +150,20 @@ public void startsNewTraceWithTraceHandlerInterceptor() throws Exception { filter.doFilter(this.request, this.response, (req, resp) -> { this.filterChain.doFilter(req, resp); // Simulate execution of the TraceHandlerInterceptor - request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, tracer.getCurrentSpan()); + request.setAttribute(TraceRequestAttributes.HANDLED_SPAN_REQUEST_ATTR, + tracing.tracer().currentSpan()); }); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.spanReporter.getSpans())) - .hasSize(1) - .hasASpanWithTagEqualTo("http.url", "http://localhost/?foo=bar") - .hasASpanWithTagEqualTo("http.host", "localhost") - .hasASpanWithTagEqualTo("http.path", "/") - .hasASpanWithTagEqualTo("http.method", HttpMethod.GET.toString()) - .hasASpanWithTagEqualTo("http.status_code", HttpStatus.OK.toString()) - .allSpansAreExportable(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.url", "http://localhost/?foo=bar") + .containsEntry("http.host", "localhost") + .containsEntry("http.path", "/") + .containsEntry("http.method", HttpMethod.GET.toString()); + // we don't check for status_code anymore cause Brave doesn't support it oob + //.containsEntry("http.status_code", "200") } @Test @@ -174,163 +172,160 @@ public void shouldNotStoreHttpStatusCodeWhenResponseCodeHasNotYetBeenSet() throw this.response.setStatus(0); filter.doFilter(this.request, this.response, this.filterChain); - assertThat(this.span.tags()).doesNotContainKey("http.status_code"); - - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .doesNotContainKey("http.status_code"); } @Test public void startsNewTraceWithParentIdInHeaders() throws Exception { this.request = builder() - .header(Span.SPAN_ID_NAME, Span.idToHex(PARENT_ID)) - .header(Span.TRACE_ID_NAME, Span.idToHex(2L)) - .header(Span.PARENT_ID_NAME, Span.idToHex(3L)) - .buildRequest(new MockServletContext());BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); + .header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(2L)) + .header(PARENT_SPAN_ID_NAME, SpanUtil.idToHex(3L)) + .buildRequest(new MockServletContext()); + BeanFactory beanFactory = beanFactory(); TraceFilter filter = new TraceFilter(beanFactory); filter.doFilter(this.request, this.response, this.filterChain); - assertThat(this.span.getSpanId()).isEqualTo(PARENT_ID); - assertThat(this.span) - .hasATag("http.url", "http://localhost/?foo=bar") - .hasATag("http.host", "localhost") - .hasATag("http.path", "/") - .hasATag("http.method", "GET"); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).id()).isEqualTo(PARENT_ID); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.url", "http://localhost/?foo=bar") + .containsEntry("http.host", "localhost") + .containsEntry("http.path", "/") + .containsEntry("http.method", HttpMethod.GET.toString()); } @Test public void continuesSpanInRequestAttr() throws Exception { - Span span = this.tracer.createSpan("http:foo"); + Span span = this.tracer.nextSpan().name("http:foo"); this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); - // It should have been removed from the thread local context so simulate that - TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); then(this.request.getAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR)).isNull(); } @Test public void closesSpanInRequestAttrIfStatusCodeNotSuccessful() throws Exception { - Span span = this.tracer.createSpan("http:foo"); + Span span = this.tracer.nextSpan().name("http:foo"); this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); this.response.setStatus(404); - // It should have been removed from the thread local context so simulate that - TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); then(this.request.getAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR)).isNotNull(); + then(this.reporter.getSpans()) + .hasSize(1); } @Test public void doesntDetachASpanIfStatusCodeNotSuccessfulAndRequestWasProcessed() throws Exception { - Span span = this.tracer.createSpan("http:foo"); + Span span = this.tracer.nextSpan().name("http:foo"); this.request.setAttribute(TraceFilter.TRACE_REQUEST_ATTR, span); this.request.setAttribute(TraceFilter.TRACE_ERROR_HANDLED_REQUEST_ATTR, true); this.response.setStatus(404); - // It should have been removed from the thread local context so simulate that - TestSpanContextHolder.removeCurrentSpan(); TraceFilter filter = new TraceFilter(beanFactory()); - filter.doFilter(this.request, this.response, this.filterChain); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + filter.doFilter(this.request, this.response, this.filterChain); } @Test public void continuesSpanFromHeaders() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - TraceFilter filter = new TraceFilter(beanFactory); + filter.doFilter(this.request, this.response, this.filterChain); + then(Tracing.current().tracer().currentSpan()).isNull(); verifyParentSpanHttpTags(); - - then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @Test public void createsChildFromHeadersWhenJoinUnsupported() throws Exception { - this.properties.setSupportsJoin(false); - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .supportsJoin(false) + .build(); + HttpTracing httpTracing = HttpTracing.create(tracing); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - + BDDMockito.given(beanFactory.getBean(HttpTracing.class)).willReturn(httpTracing); TraceFilter filter = new TraceFilter(beanFactory); + filter.doFilter(this.request, this.response, this.filterChain); - assertThat(this.spanReporter.getSpans().get(0).getParents().get(0)) - .isEqualTo(16); // test data is in hex! + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).parentId()) + .isEqualTo(PARENT_ID); } @Test public void addsAdditionalHeaders() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); this.traceKeys.getHttp().getHeaders().add("x-foo"); BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - TraceFilter filter = new TraceFilter(beanFactory); this.request.addHeader("X-Foo", "bar"); - filter.doFilter(this.request, this.response, this.filterChain); - - assertThat(this.span.tags()).contains(entry("http.x-foo", "bar")); - assertThat(this.span.tags()).contains(entry("http.x-foo", "bar")); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - } - - @Test - public void ensuresThatParentSpanIsStoppedWhenReported() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); - TraceFilter filter = new TraceFilter(beanFactory()); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(spanIsStoppedVeryfingReporter()); filter.doFilter(this.request, this.response, this.filterChain); - } - SpanReporter spanIsStoppedVeryfingReporter() { - return (span) -> assertThat(span.getEnd()).as("Span has to be stopped before reporting").isNotZero(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.x-foo", "bar"); } @Test public void additionalMultiValuedHeader() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); this.traceKeys.getHttp().getHeaders().add("x-foo");BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - TraceFilter filter = new TraceFilter(beanFactory); this.request.addHeader("X-Foo", "bar"); this.request.addHeader("X-Foo", "spam"); filter.doFilter(this.request, this.response, this.filterChain); - assertThat(this.span.tags()).contains(entry("http.x-foo", "'bar','spam'")); - - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + // We no longer support multi value headers + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.x-foo", "bar"); } @Test public void shouldAnnotateSpanWithErrorWhenExceptionIsThrown() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - TraceFilter filter = new TraceFilter(beanFactory); + this.filterChain = new MockFilterChain() { @Override public void doFilter(javax.servlet.ServletRequest request, @@ -345,142 +340,147 @@ public void doFilter(javax.servlet.ServletRequest request, catch (RuntimeException e) { assertEquals("Planned", e.getMessage()); } - verifyParentSpanHttpTags(HttpStatus.INTERNAL_SERVER_ERROR); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.spanReporter.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Planned"); + then(Tracing.current().tracer().currentSpan()).isNull(); + verifyParentSpanHttpTags(HttpStatus.INTERNAL_SERVER_ERROR); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("error", "Planned"); } @Test public void detachesSpanWhenResponseStatusIsNot2xx() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(beanFactory()); + this.response.setStatus(404); + then(Tracing.current().tracer().currentSpan()).isNull(); filter.doFilter(this.request, this.response, this.filterChain); - - then(TestSpanContextHolder.getCurrentSpan()).isNull(); } @Test public void closesSpanWhenResponseStatusIs2xx() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(200); filter.doFilter(this.request, this.response, this.filterChain); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); } @Test public void closesSpanWhenResponseStatusIs3xx() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(beanFactory()); this.response.setStatus(302); filter.doFilter(this.request, this.response, this.filterChain); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); } @Test public void returns400IfSpanIsMalformedAndCreatesANewSpan() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, "asd") - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, "asd") + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ArrayList<>(this.spanReporter.getSpans())).isNotEmpty(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); then(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); } @Test public void returns200IfSpanParentIsMalformedAndCreatesANewSpan() throws Exception { - this.request = builder().header(Span.SPAN_ID_NAME, PARENT_ID) - .header(Span.PARENT_ID_NAME, "-") - .header(Span.TRACE_ID_NAME, 20L).buildRequest(new MockServletContext()); + this.request = builder().header(SPAN_ID_NAME, PARENT_ID) + .header(PARENT_SPAN_ID_NAME, "-") + .header(TRACE_ID_NAME, SpanUtil.idToHex(20L)) + .buildRequest(new MockServletContext()); TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ArrayList<>(this.spanReporter.getSpans())).isNotEmpty(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); then(this.response.getStatus()).isEqualTo(HttpStatus.OK.value()); } @Test public void samplesASpanRegardlessOfTheSamplerWhenXB3FlagsIsPresentAndSetTo1() throws Exception { this.request = builder() - .header(Span.SPAN_FLAGS, 1) + .header(SPAN_FLAGS, 1) .buildRequest(new MockServletContext()); - this.sampler = new NeverSampler(); - TraceFilter filter = new TraceFilter(beanFactory()); + TraceFilter filter = new TraceFilter(neverSampleTracing()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())).allSpansAreExportable(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } @Test public void doesNotOverrideTheSampledFlagWhenXB3FlagIsSetToOtherValueThan1() throws Exception { this.request = builder() - .header(Span.SPAN_FLAGS, 0) + .header(SPAN_FLAGS, 0) .buildRequest(new MockServletContext()); - this.sampler = new AlwaysSampler(); TraceFilter filter = new TraceFilter(beanFactory()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())).allSpansAreExportable(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } + @SuppressWarnings("Duplicates") @Test public void samplesWhenDebugFlagIsSetTo1AndOnlySpanIdIsSet() throws Exception { this.request = builder() - .header(Span.SPAN_FLAGS, 1) - .header(Span.SPAN_ID_NAME, 10L) + .header(SPAN_FLAGS, 1) + .header(SPAN_ID_NAME, SpanUtil.idToHex(10L)) .buildRequest(new MockServletContext()); - this.sampler = new NeverSampler(); - BeanFactory beanFactory = beanFactory(); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - TraceFilter filter = new TraceFilter(beanFactory); + TraceFilter filter = new TraceFilter(neverSampleTracing()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())) - .allSpansAreExportable().hasSize(1).hasASpanWithSpanId(Span.hexToId("10")); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + // Brave doesn't work like Sleuth. No trace will be created for an invalid span + // where invalid means that there is no trace id + then(this.reporter.getSpans()).isEmpty(); } + @SuppressWarnings("Duplicates") @Test public void samplesWhenDebugFlagIsSetTo1AndTraceIdIsAlsoSet() throws Exception { this.request = builder() - .header(Span.SPAN_FLAGS, 1) - .header(Span.TRACE_ID_NAME, 10L) + .header(SPAN_FLAGS, 1) + .header(TRACE_ID_NAME, SpanUtil.idToHex(10L)) .buildRequest(new MockServletContext()); - this.sampler = new NeverSampler(); - TraceFilter filter = new TraceFilter(beanFactory()); + TraceFilter filter = new TraceFilter(neverSampleTracing()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())) - .allSpansAreExportable().allSpansHaveTraceId(Span.hexToId("10")); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + // Brave creates a new trace if there was no span id + then(this.reporter.getSpans().get(0).traceId()) + .isNotEqualTo(SpanUtil.idToHex(10L)); } // #668 @@ -494,35 +494,31 @@ public void shouldSetTraceKeysForAnUntracedRequest() throws Exception { filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())) - .hasASpanWithName("http:/") - .hasASpanWithTagEqualTo("http.url", "http://localhost/?foo=bar") - .hasASpanWithTagEqualTo("http.host", "localhost") - .hasASpanWithTagEqualTo("http.path", "/") - .hasASpanWithTagEqualTo("http.method", "GET") - .hasASpanWithTagEqualTo("http.status_code", "295") - .allSpansAreExportable(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.url", "http://localhost/?foo=bar") + .containsEntry("http.host", "localhost") + .containsEntry("http.path", "/") + .containsEntry("http.method", HttpMethod.GET.toString()); + // we don't check for status_code anymore cause Brave doesn't support it oob + //.containsEntry("http.status_code", "295") } @Test public void samplesASpanDebugFlagWithInterceptor() throws Exception { this.request = builder() - .header(Span.SPAN_FLAGS, 1) + .header(SPAN_FLAGS, 1) .buildRequest(new MockServletContext()); - this.sampler = new NeverSampler(); - TraceFilter filter = new TraceFilter(beanFactory()); + TraceFilter filter = new TraceFilter(neverSampleTracing()); filter.doFilter(this.request, this.response, this.filterChain); - then(new ListOfSpans(this.spanReporter.getSpans())) - .doesNotHaveASpanWithName("http:/parent/") - .hasASpanWithName("http:/") - .hasSize(1) - .allSpansAreExportable(); - then(TestSpanContextHolder.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).name()).isEqualTo("http:/"); } public void verifyParentSpanHttpTags() { @@ -534,9 +530,12 @@ public void verifyParentSpanHttpTags() { * org.springframework.cloud.sleuth.instrument.TraceKeys}. */ public void verifyParentSpanHttpTags(HttpStatus status) { - assertThat(this.span.tags()).contains(entry("http.host", "localhost"), - entry("http.url", "http://localhost/?foo=bar"), entry("http.path", "/"), - entry("http.method", "GET")); + then(this.reporter.getSpans().size()).isGreaterThan(0); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.url", "http://localhost/?foo=bar") + .containsEntry("http.host", "localhost") + .containsEntry("http.path", "/") + .containsEntry("http.method", HttpMethod.GET.toString()); verifyCurrentSpanStatusCodeForAContinuedSpan(status); } @@ -545,31 +544,30 @@ private void verifyCurrentSpanStatusCodeForAContinuedSpan(HttpStatus status) { // Status is only interesting in non-success case. Omitting it saves at least // 20bytes per span. if (status.is2xxSuccessful()) { - assertThat(this.span.tags()).doesNotContainKey("http.status_code"); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .doesNotContainKey("http.status_code"); } else { - assertThat(this.span.tags()).containsEntry("http.status_code", - status.toString()); - } - } - - private class DelegateSampler implements Sampler { - @Override - public boolean isSampled(Span span) { - return TraceFilterTests.this.sampler.isSampled(span); + then(this.reporter.getSpans()) + .hasSize(1); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("http.status_code", status.toString()); } } private BeanFactory beanFactory() { BDDMockito.given(beanFactory.getBean(SkipPatternProvider.class)) .willThrow(new NoSuchBeanDefinitionException("foo")); - BDDMockito.given(beanFactory.getBean(SleuthProperties.class)).willReturn(this.properties); - BDDMockito.given(beanFactory.getBean(Tracer.class)).willReturn(this.tracer); - BDDMockito.given(beanFactory.getBean(TraceKeys.class)).willReturn(this.traceKeys); - BDDMockito.given(beanFactory.getBean(HttpSpanExtractor.class)).willReturn(this.spanExtractor); - BDDMockito.given(beanFactory.getBean(SpanReporter.class)).willReturn(this.spanReporter); - BDDMockito.given(beanFactory.getBean(HttpTraceKeysInjector.class)).willReturn(this.httpTraceKeysInjector); - BDDMockito.given(beanFactory.getBean(ErrorParser.class)).willReturn(new ExceptionMessageErrorParser()); + BDDMockito.given(beanFactory.getBean(SleuthProperties.class)) + .willReturn(this.properties); + BDDMockito.given(beanFactory.getBean(HttpTracing.class)) + .willReturn(this.httpTracing); + BDDMockito.given(beanFactory.getBean(TraceKeys.class)) + .willReturn(this.traceKeys); + BDDMockito.given(beanFactory.getBean(ErrorParser.class)) + .willReturn(new ExceptionMessageErrorParser()); return beanFactory; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java index 6abfa389af..251243da36 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationMultipleFiltersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,18 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.io.IOException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicReference; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import java.io.IOException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; -import org.junit.After; -import org.junit.Before; +import brave.Span; +import brave.Tracing; +import brave.sampler.Sampler; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -34,12 +35,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -49,32 +45,27 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.filter.GenericFilterBean; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { TraceFilterWebIntegrationMultipleFiltersTests.Config.class }, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.sleuth.http.legacy.enabled=true") public class TraceFilterWebIntegrationMultipleFiltersTests { - @Autowired Tracer tracer; + @Autowired Tracing tracer; @Autowired RestTemplate restTemplate; @Autowired Environment environment; @Autowired MyFilter myFilter; + @Autowired ArrayListSpanReporter reporter; // issue #550 @Autowired @Qualifier("myExecutor") Executor myExecutor; @Autowired @Qualifier("finalExecutor") Executor finalExecutor; @Autowired MyExecutor cglibExecutor; - @Before - @After - public void cleanup() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); - } - @Test public void should_register_trace_filter_before_the_custom_filter() { this.myExecutor.execute(() -> System.out.println("foo")); @@ -83,9 +74,9 @@ public void should_register_trace_filter_before_the_custom_filter() { this.restTemplate.getForObject("http://localhost:" + port() + "/", String.class); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.tracer().currentSpan()).isNull(); then(this.myFilter.getSpan().get()).isNotNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } private int port() { @@ -112,7 +103,7 @@ public static class Config { } @Bean Sampler alwaysSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } @Bean RestTemplate restTemplate() { @@ -125,7 +116,7 @@ public static class Config { return restTemplate; } - @Bean MyFilter myFilter(Tracer tracer) { + @Bean MyFilter myFilter(Tracing tracer) { return new MyFilter(tracer); } @@ -135,21 +126,25 @@ public static class Config { bean.setOrder(0); return bean; } + + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } } static class MyFilter extends GenericFilterBean { AtomicReference span = new AtomicReference<>(); - private final Tracer tracer; + private final Tracing tracer; - MyFilter(Tracer tracer) { + MyFilter(Tracing tracer) { this.tracer = tracer; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - Span currentSpan = tracer.getCurrentSpan(); + Span currentSpan = tracer.tracer().currentSpan(); this.span.set(currentSpan); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java index 8d5a006907..1231fd8974 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceFilterWebIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,15 @@ package org.springframework.cloud.sleuth.instrument.web; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import brave.Tracing; +import brave.sampler.Sampler; +import zipkin2.Span; +import org.assertj.core.api.BDDAssertions; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -33,14 +34,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.rule.OutputCapture; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -54,44 +48,45 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.BDDAssertions.then; + /** * @author Marcin Grzejszczak */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = TraceFilterWebIntegrationTests.Config.class, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "spring.sleuth.http.legacy.enabled=true") public class TraceFilterWebIntegrationTests { - @Autowired Tracer tracer; - @Autowired ArrayListSpanAccumulator accumulator; - @Autowired RestTemplate restTemplate; + @Autowired Tracing tracer; + @Autowired ArrayListSpanReporter accumulator; @Autowired Environment environment; - @Rule public OutputCapture capture = new OutputCapture(); + @Rule public OutputCapture capture = new OutputCapture(); @Before @After public void cleanup() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); this.accumulator.clear(); } @Test public void should_not_create_a_span_for_error_controller() { - this.restTemplate.getForObject("http://localhost:" + port() + "/", String.class); - - then(this.tracer.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.accumulator.getSpans())) - .doesNotHaveASpanWithName("error") - .hasASpanWithTagEqualTo("http.status_code", "500"); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.accumulator.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception") - .hasRpcLogsInProperOrder(); + try { + new RestTemplate().getForObject("http://localhost:" + port() + "/", String.class); + BDDAssertions.fail("should fail due to runtime exception"); + } catch (Exception e) { + } + + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.accumulator.getSpans()).hasSize(1); + Span reportedSpan = this.accumulator.getSpans().get(0); + then(reportedSpan.tags()) + .containsEntry("http.status_code", "500") + .containsEntry("error", "Request processing failed; nested exception is java.lang.RuntimeException: Throwing exception"); // issue#714 - Span span = this.accumulator.getSpans().get(0); - String hex = Span.idToHex(span.getTraceId()); + String hex = reportedSpan.traceId(); String[] split = capture.toString().split("\n"); List list = Arrays.stream(split).filter(s -> s.contains( "Uncaught exception thrown")) @@ -108,10 +103,10 @@ public void should_create_spans_for_endpoint_returning_unsuccessful_result() { } catch (HttpClientErrorException e) { } - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.accumulator.getSpans())) - .hasServerSideSpansInProperOrder(); + //TODO: Check if it should be 1 or 2 spans + then(Tracing.current().tracer().currentSpan()).isNull(); + then(this.accumulator.getSpans()).hasSize(1); + then(this.accumulator.getSpans().get(0).kind().ordinal()).isEqualTo(Span.Kind.SERVER.ordinal()); } private int port() { @@ -126,12 +121,12 @@ public static class Config { return new ExceptionThrowingController(); } - @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { - return new ArrayListSpanAccumulator(); + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); } @Bean Sampler alwaysSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java index 3a80ea19a6..ad03401f96 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceHandlerInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,8 +43,8 @@ public class TraceHandlerInterceptorTests { public void should_cache_the_retrieved_bean_when_exception_took_place() throws Exception { given(this.beanFactory.getBean(ErrorController.class)).willThrow(new NoSuchBeanDefinitionException("errorController")); - then(this.traceHandlerInterceptor.getErrorController()).isNull(); - then(this.traceHandlerInterceptor.getErrorController()).isNull(); + then(this.traceHandlerInterceptor.errorController()).isNull(); + then(this.traceHandlerInterceptor.errorController()).isNull(); BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); } @@ -52,8 +52,8 @@ public void should_cache_the_retrieved_bean_when_exception_took_place() throws E public void should_cache_the_retrieved_bean_when_no_exception_took_place() throws Exception { given(this.beanFactory.getBean(ErrorController.class)).willReturn(() -> null); - then(this.traceHandlerInterceptor.getErrorController()).isNotNull(); - then(this.traceHandlerInterceptor.getErrorController()).isNotNull(); + then(this.traceHandlerInterceptor.errorController()).isNotNull(); + then(this.traceHandlerInterceptor.errorController()).isNotNull(); BDDMockito.then(this.beanFactory).should(only()).getBean(ErrorController.class); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java index 51058a6df7..725d6b710d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceNoWebEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java new file mode 100644 index 0000000000..b4f80ab935 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceRestTemplateInterceptorTests.java @@ -0,0 +1,268 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; +import brave.spring.web.TracingClientHttpRequestInterceptor; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.test.web.client.MockMvcClientHttpRequestFactory; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Dave Syer + * + */ +public class TraceRestTemplateInterceptorTests { + + private TestController testController = new TestController(); + private MockMvc mockMvc = MockMvcBuilders.standaloneSetup(this.testController) + .build(); + private RestTemplate template = new RestTemplate( + new MockMvcClientHttpRequestFactory(this.mockMvc)); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + TraceKeys traceKeys = new TraceKeys(); + + @Before + public void setup() { + setInterceptors(HttpTracing.create(this.tracing)); + } + + private void setInterceptors(HttpTracing httpTracing) { + this.template.setInterceptors(Arrays.asList( + TracingClientHttpRequestInterceptor.create(httpTracing))); + } + + @After + public void clean() { + Tracing.current().close(); + } + + @Test + public void headersAddedWhenNoTracingWasPresent() { + @SuppressWarnings("unchecked") + Map headers = this.template.getForEntity("/", Map.class) + .getBody(); + + then(headers.get("X-B3-TraceId")).isNotNull(); + then(headers.get("X-B3-SpanId")).isNotNull(); + } + + @Test + public void headersAddedWhenTracing() { + Span span = this.tracer.nextSpan().name("new trace"); + Map headers; + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + headers = this.template.getForEntity("/", Map.class) + .getBody(); + } finally { + span.finish(); + } + + then(headers.get("X-B3-TraceId")).isEqualTo( + SpanUtil.idToHex(span.context().traceId())); + then(headers.get("X-B3-SpanId")).isNotEqualTo( + SpanUtil.idToHex(span.context().spanId())); + then(headers.get("X-B3-ParentSpanId")).isEqualTo( + SpanUtil.idToHex(span.context().spanId())); + } + + // Issue #290 + @Test + public void requestHeadersAddedWhenTracing() { + setInterceptors(HttpTracing.newBuilder(this.tracing) + .clientParser(new SleuthHttpClientParser(this.traceKeys)) + .build()); + Span span = this.tracer.nextSpan().name("new trace"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.template.getForEntity("/foo?a=b", Map.class); + } finally { + span.finish(); + } + + List spans = reporter.getSpans(); + then(spans).isNotEmpty(); + then(spans.get(0).tags()) + .containsEntry("http.url", "/foo?a=b") + .containsEntry("http.path", "/foo") + .containsEntry("http.method", "GET"); + } + + @Test + public void notSampledHeaderAddedWhenNotExportable() { + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .sampler(Sampler.NEVER_SAMPLE) + .build(); + this.template.setInterceptors(Arrays.asList( + TracingClientHttpRequestInterceptor.create(HttpTracing.create(tracing)))); + + Span span = tracing.tracer().nextSpan().name("new trace"); + Map headers; + + try(Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(span.start())) { + headers = this.template.getForEntity("/", Map.class) + .getBody(); + } finally { + span.finish(); + } + + then(reporter.getSpans()).isEmpty(); + } + + // issue #198 + @Test + public void spanRemovedFromThreadUponException() { + Span span = this.tracer.nextSpan().name("new trace"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.template.getForEntity("/exception", Map.class).getBody(); + Assert.fail("should throw an exception"); + } catch (RuntimeException e) { + then(e).hasMessage("500 Internal Server Error"); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + } + + @Test + public void createdSpanNameHasOnlyPrintableAsciiCharactersForNonEncodedURIWithNonAsciiChars() { + setInterceptors(HttpTracing.newBuilder(this.tracing) + .clientParser(new SleuthHttpClientParser(this.traceKeys)) + .build()); + Span span = this.tracer.nextSpan().name("new trace"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.template.getForEntity("/cas~fs~划", Map.class).getBody(); + } catch (Exception e) { + + } finally { + span.finish(); + } + + List spans = reporter.getSpans(); + then(spans).hasSize(2); + String spanName = spans.get(0).name(); + then(spanName) + .isEqualTo("http:/cas~fs~%c3%a5%cb%86%e2%80%99"); + then(StringUtils.isAsciiPrintable(spanName)); + } + + @Test + public void willShortenTheNameOfTheSpan() { + setInterceptors(HttpTracing.newBuilder(this.tracing) + .clientParser(new SleuthHttpClientParser(this.traceKeys)) + .build()); + Span span = this.tracer.nextSpan().name("new trace"); + + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.template.getForEntity("/" + bigName(), Map.class).getBody(); + } catch (Exception e) { + + } finally { + span.finish(); + } + + List spans = reporter.getSpans(); + then(spans).isNotEmpty(); + String spanName = spans.get(0).name(); + then(spanName) + .hasSize(50); + then(StringUtils.isAsciiPrintable(spanName)); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + + @RestController + public class TestController { + + Span span; + + @RequestMapping("/") + public Map home(@RequestHeader HttpHeaders headers) { + this.span = TraceRestTemplateInterceptorTests.this.tracer.currentSpan(); + Map map = new HashMap(); + addHeaders(map, headers, "X-B3-SpanId", "X-B3-TraceId", + "X-B3-ParentSpanId"); + return map; + } + + @RequestMapping("/foo") + public void foo() { + } + + @RequestMapping("/exception") + public Map exception() { + throw new RuntimeException("foo"); + } + + private void addHeaders(Map map, HttpHeaders headers, + String... names) { + if (headers != null) { + for (String name : names) { + String value = headers.getFirst(name); + if (value != null) { + map.put(name, value); + } + } + } + } + } + +} + diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java index 50b560c17c..942cbcdf8a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/TraceWebFluxTests.java @@ -1,35 +1,29 @@ package org.springframework.cloud.sleuth.instrument.web; +import brave.sampler.Sampler; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Hooks; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import org.assertj.core.api.BDDAssertions; import org.awaitility.Awaitility; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.instrument.web.client.TraceWebClientAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; public class TraceWebFluxTests { @@ -39,40 +33,32 @@ public static void setup() { Schedulers.resetFactory(); } - @Ignore("Ignored until fixed in Reactor") @Test public void should_instrument_web_filter() throws Exception { - ConfigurableApplicationContext context = new SpringApplicationBuilder(TraceWebFluxTests.Config.class) - .web(WebApplicationType.REACTIVE).properties("server.port=0", "spring.jmx.enabled=false", - "spring.application.name=TraceWebFluxTests").run(); - ExceptionUtils.setFail(true); - Span span = null; - try { - span = context.getBean(Tracer.class).createSpan("foo"); - int port = context.getBean(Environment.class).getProperty("local.server.port", Integer.class); - ArrayListSpanAccumulator accumulator = context.getBean(ArrayListSpanAccumulator.class); + ConfigurableApplicationContext context = new SpringApplicationBuilder( + TraceWebFluxTests.Config.class).web(WebApplicationType.REACTIVE) + .properties("server.port=0", "spring.jmx.enabled=false", + "spring.application.name=TraceWebFluxTests", "security.basic.enabled=false", + "management.security.enabled=false").run(); + ArrayListSpanReporter accumulator = context.getBean(ArrayListSpanReporter.class); + int port = context.getBean(Environment.class).getProperty("local.server.port", Integer.class); - Mono exchange = context.getBean(WebClient.class).get().uri("http://localhost:" + port + "/api/c2/10").exchange(); - - Awaitility.await().untilAsserted(() -> { - ClientResponse response = exchange.block(); - SleuthAssertions.then(response.statusCode().value()).isEqualTo(200); - SleuthAssertions.then(ExceptionUtils.getLastException()).isNull(); - SleuthAssertions.then(new ListOfSpans(accumulator.getSpans())) - .hasASpanWithLogEqualTo(Span.CLIENT_SEND) - .hasASpanWithLogEqualTo(Span.SERVER_RECV) - .hasASpanWithLogEqualTo(Span.SERVER_SEND) - .hasASpanWithLogEqualTo(Span.CLIENT_RECV) - .hasASpanWithTagEqualTo("mvc.controller.method", "successful") - .hasASpanWithTagEqualTo("mvc.controller.class", "Controller2"); - }); - } finally { - context.getBean(Tracer.class).close(span); - } + Mono exchange = WebClient.create().get() + .uri("http://localhost:" + port + "/api/c2/10").exchange(); + Awaitility.await().untilAsserted(() -> { + ClientResponse response = exchange.block(); + BDDAssertions.then(response.statusCode().value()).isEqualTo(200); + }); + BDDAssertions.then(accumulator.getSpans()).hasSize(1); + BDDAssertions.then(accumulator.getSpans().get(0).tags()) + .containsEntry("mvc.controller.method", "successful") + .containsEntry("mvc.controller.class", "Controller2"); } @Configuration - @EnableAutoConfiguration + @EnableAutoConfiguration( + exclude = { TraceWebClientAutoConfiguration.class, + ReactiveSecurityAutoConfiguration.class }) static class Config { @Bean WebClient webClient() { @@ -80,23 +66,21 @@ static class Config { } @Bean Sampler sampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } - @Bean SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); + @Bean ArrayListSpanReporter spanReporter() { + return new ArrayListSpanReporter(); } - @Bean - Controller2 controller2() { + @Bean Controller2 controller2() { return new Controller2(); } } @RestController - @RequestMapping("/api/c2") static class Controller2 { - @GetMapping("/{id}") + @GetMapping("/api/c2/{id}") public Flux successful(@PathVariable Long id) { return Flux.just(id.toString()); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java deleted file mode 100644 index 4110eaeb19..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanInjectorTests.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web; - -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.junit.Test; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class ZipkinHttpSpanInjectorTests { - - ZipkinHttpSpanInjector injector = new ZipkinHttpSpanInjector(); - - @SuppressWarnings("unchecked") - @Test - public void should_not_override_already_existing_headers() throws Exception { - Span span = Span.builder() - .spanId(1L) - .traceId(2L) - .parent(3L) - .baggage("foo", "bar") - .name("span") - .build(); - Map holder = new HashMap<>(); - final SpanTextMap map = textMap(holder); - holder.put(Span.SPAN_ID_NAME, Span.idToHex(10L)); - holder.put(Span.TRACE_ID_NAME, Span.idToHex(20L)); - holder.put(Span.PARENT_ID_NAME, Span.idToHex(30L)); - holder.put(Span.SPAN_NAME_NAME, "anotherSpan"); - - injector.inject(span, map); - - then(map) - .contains(new AbstractMap.SimpleEntry(Span.SPAN_ID_NAME, Span.idToHex(10L))) - .contains(new AbstractMap.SimpleEntry(Span.TRACE_ID_NAME, Span.idToHex(20L))) - .contains(new AbstractMap.SimpleEntry(Span.PARENT_ID_NAME, Span.idToHex(30L))) - .contains(new AbstractMap.SimpleEntry(Span.SPAN_NAME_NAME, "anotherSpan")) - .contains(new AbstractMap.SimpleEntry("baggage-foo", "bar")); - } - - private SpanTextMap textMap(Map textMap) { - return new SpanTextMap() { - @Override public Iterator> iterator() { - return textMap.entrySet().iterator(); - } - - @Override public void put(String key, String value) { - textMap.put(key, value); - } - }; - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapperTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapperTest.java deleted file mode 100644 index 86598ee354..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/ZipkinHttpSpanMapperTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; -import static org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanMapper.URI_HEADER; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.UUID; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanTextMap; - -/** - * @author Anton Kislitsyn - */ -@RunWith(Parameterized.class) -public class ZipkinHttpSpanMapperTest { - - private static final ZipkinHttpSpanMapper MAPPER = new ZipkinHttpSpanMapper(); - - private final String headerName; - private final String value; - private final boolean suitable; - - @Parameterized.Parameters - public static Collection parameters() { - return Arrays.asList(new Object[] { Span.TRACE_ID_NAME, "traceId", true }, - new Object[] { Span.SPAN_ID_NAME, "spanId", true }, - new Object[] { Span.SPAN_FLAGS, "flags", true }, - new Object[] { Span.PROCESS_ID_NAME, "process", true }, - new Object[] { Span.SPAN_NAME_NAME, "name", true }, - new Object[] { Span.PARENT_ID_NAME, "parent", true }, - new Object[] { Span.SAMPLED_NAME, "sampled", true }, - new Object[] { URI_HEADER, "uri", true }, - new Object[] { UUID.randomUUID().toString(), UUID.randomUUID().toString(), - false }, - new Object[] { Span.SPAN_ID_NAME, null, false }, - new Object[] { UUID.randomUUID().toString(), null, false }); - } - - public ZipkinHttpSpanMapperTest(String headerName, String value, boolean suitable) { - this.headerName = headerName; - this.value = value; - this.suitable = suitable; - } - - @Test - public void should_map_zipkin_suitable_fields() throws Exception { - Map map = MAPPER - .convert(textMap(Collections.singletonMap(headerName, value))); - assertThat(map.get(headerName), suitable ? equalTo(value) : is(nullValue())); - } - - private SpanTextMap textMap(Map textMap) { - return new SpanTextMap() { - @Override - public Iterator> iterator() { - return textMap.entrySet().iterator(); - } - - @Override - public void put(String key, String value) { - textMap.put(key, value); - } - }; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java similarity index 50% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java index 61a8a43a01..8966dc8a9a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/MultipleAsyncRestTemplateTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/MultipleAsyncRestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,22 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.async; +package org.springframework.cloud.sleuth.instrument.web.client; import java.io.IOException; import java.net.URI; import java.util.concurrent.Executor; - +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.awaitility.Awaitility; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanFactory; @@ -27,24 +37,25 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.client.TraceAsyncClientHttpRequestFactoryWrapper; -import org.springframework.cloud.sleuth.instrument.web.client.TraceAsyncRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.http.HttpMethod; import org.springframework.http.client.AsyncClientHttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurerSupport; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; import static org.assertj.core.api.BDDAssertions.then; @@ -52,47 +63,90 @@ /** * @author Marcin Grzejszczak */ -@RunWith(SpringRunner.class) @SpringBootTest( +@RunWith(SpringRunner.class) +@SpringBootTest( classes = { MultipleAsyncRestTemplateTests.Config.class, - MultipleAsyncRestTemplateTests.CustomExecutorConfig.class }, + MultipleAsyncRestTemplateTests.CustomExecutorConfig.class, + MultipleAsyncRestTemplateTests.ControllerConfig.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext public class MultipleAsyncRestTemplateTests { + private static final Log log = LogFactory.getLog(MultipleAsyncRestTemplateTests.class); + @Autowired @Qualifier("customAsyncRestTemplate") AsyncRestTemplate asyncRestTemplate; @Autowired AsyncConfigurer executor; + Executor wrappedExecutor; + @Autowired Tracer tracer; + @LocalServerPort int port; + + @Before + public void setup() { + this.wrappedExecutor = this.executor.getAsyncExecutor(); + } @Test public void should_start_context_with_custom_async_client() throws Exception { then(this.asyncRestTemplate).isNotNull(); } + @Test + public void should_pass_tracing_context_with_custom_async_client() throws Exception { + Span span = this.tracer.nextSpan().name("foo"); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + String result = this.asyncRestTemplate.getForEntity("http://localhost:" + + port + "/foo", String.class).get().getBody(); + then(span.context().traceIdString()).isEqualTo(result); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + } + @Test public void should_start_context_with_custom_executor() throws Exception { then(this.executor).isNotNull(); - then(this.executor.getAsyncExecutor()).isInstanceOf(LazyTraceExecutor.class); + then(this.wrappedExecutor).isInstanceOf(LazyTraceExecutor.class); + + then(this.tracer.currentSpan()).isNull(); + } + + @Test + public void should_inject_traced_executor_that_passes_tracing_context() throws Exception { + Span span = this.tracer.nextSpan().name("foo"); + AtomicBoolean executed = new AtomicBoolean(false); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.wrappedExecutor.execute(() -> { + Span currentSpan = this.tracer.currentSpan(); + log.info("Current span " + currentSpan); + then(currentSpan).isNotNull(); + long currentTraceId = currentSpan.context().traceId(); + long initialTraceId = span.context().traceId(); + log.info("Hello from runnable before trace id check. Initial [" + initialTraceId + "] current [" + currentTraceId + "]"); + then(currentTraceId).isEqualTo(initialTraceId); + executed.set(true); + log.info("Hello from runnable"); + }); + } finally { + span.finish(); + } + + Awaitility.await().atMost(10L, TimeUnit.SECONDS) + .untilAsserted(() -> { + then(executed.get()).isTrue(); + }); + then(this.tracer.currentSpan()).isNull(); } //tag::custom_async_rest_template[] @Configuration @EnableAutoConfiguration static class Config { - @Autowired Tracer tracer; - @Autowired HttpTraceKeysInjector httpTraceKeysInjector; - @Autowired HttpSpanInjector spanInjector; @Bean(name = "customAsyncRestTemplate") - public AsyncRestTemplate traceAsyncRestTemplate(@Qualifier("customHttpRequestFactoryWrapper") - TraceAsyncClientHttpRequestFactoryWrapper wrapper, ErrorParser errorParser) { - return new TraceAsyncRestTemplate(wrapper, this.tracer, errorParser); - } - - @Bean(name = "customHttpRequestFactoryWrapper") - public TraceAsyncClientHttpRequestFactoryWrapper traceAsyncClientHttpRequestFactory() { - return new TraceAsyncClientHttpRequestFactoryWrapper(this.tracer, - this.spanInjector, - asyncClientFactory(), - clientHttpRequestFactory(), - this.httpTraceKeysInjector); + public AsyncRestTemplate traceAsyncRestTemplate() { + return new AsyncRestTemplate(asyncClientFactory(), clientHttpRequestFactory()); } private ClientHttpRequestFactory clientHttpRequestFactory() { @@ -130,21 +184,56 @@ static class CustomExecutorConfig extends AsyncConfigurerSupport { } } //end::custom_executor[] + + @Configuration + static class ControllerConfig { + @Bean + MyRestController myRestController(Tracer tracer) { + return new MyRestController(tracer); + } + + @Bean Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + } } class CustomClientHttpRequestFactory implements ClientHttpRequestFactory { + private final SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { - return null; + return this.factory.createRequest(uri, httpMethod); } } class CustomAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { + private final SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + + public CustomAsyncClientHttpRequestFactory() { + this.factory.setTaskExecutor(new SimpleAsyncTaskExecutor()); + } + @Override public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { - return null; + return this.factory.createAsyncRequest(uri, httpMethod); + } +} + +@RestController +class MyRestController { + + private final Tracer tracer; + + MyRestController(Tracer tracer) { + this.tracer = tracer; + } + + @RequestMapping("/foo") + String foo() { + return this.tracer.currentSpan().context().traceIdString(); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/RestTemplateTraceAspectIntegrationTests.java similarity index 69% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/RestTemplateTraceAspectIntegrationTests.java index 6868910bbf..9301eb8ccf 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/RestTemplateTraceAspectIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/RestTemplateTraceAspectIntegrationTests.java @@ -1,20 +1,22 @@ -package org.springframework.cloud.sleuth.instrument.web; +package org.springframework.cloud.sleuth.instrument.web.client; +import java.util.Collections; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import brave.Tracing; +import brave.sampler.Sampler; +import brave.spring.web.TracingAsyncClientHttpRequestInterceptor; +import zipkin2.Span; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; import org.springframework.cloud.sleuth.instrument.DefaultTestAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; @@ -44,37 +46,31 @@ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = RestTemplateTraceAspectIntegrationTests.Config.class, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DirtiesContext + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext public class RestTemplateTraceAspectIntegrationTests { @Autowired WebApplicationContext context; @Autowired AspectTestingController controller; - @Autowired Tracer tracer; + @Autowired Tracing tracer; + @Autowired ArrayListSpanReporter reporter; private MockMvc mockMvc; - @Before - public void init() { + @Before public void init() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); this.controller.reset(); - ExceptionUtils.setFail(true); } - @Before - @After - public void verify() { - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + @Before @After public void verify() { + then(this.tracer.tracer().currentSpan()).isNull(); } - @Test - public void should_set_span_data_on_headers_via_aspect_in_synchronous_call() + @Test public void should_set_span_data_on_headers_via_aspect_in_synchronous_call() throws Exception { whenARequestIsSentToASyncEndpoint(); thenTraceIdHasBeenSetOnARequestHeader(); - then(ExceptionUtils.getLastException()).isNull(); + thenClientKindIsReported(); } @Test @@ -83,23 +79,25 @@ public void should_set_span_data_on_headers_when_sending_a_request_via_async_res whenARequestIsSentToAnAsyncRestTemplateEndpoint(); thenTraceIdHasBeenSetOnARequestHeader(); - then(ExceptionUtils.getLastException()).isNull(); + thenClientKindIsReported(); } @Test public void should_set_span_data_on_headers_via_aspect_in_asynchronous_callable() throws Exception { whenARequestIsSentToAnAsyncEndpoint("/callablePing"); + thenTraceIdHasBeenSetOnARequestHeader(); - then(ExceptionUtils.getLastException()).isNull(); + thenClientKindIsReported(); } @Test public void should_set_span_data_on_headers_via_aspect_in_asynchronous_web_async() throws Exception { whenARequestIsSentToAnAsyncEndpoint("/webAsyncTaskPing"); + thenTraceIdHasBeenSetOnARequestHeader(); - then(ExceptionUtils.getLastException()).isNull(); + thenClientKindIsReported(); } private void whenARequestIsSentToAnAsyncRestTemplateEndpoint() throws Exception { @@ -117,6 +115,14 @@ private void thenTraceIdHasBeenSetOnARequestHeader() { assertThat(this.controller.getTraceId()).matches("^(?!\\s*$).+"); } + // Brave was never designed to run tests of server and client in one test + // that's why we have to pick only CLIENT side + private void thenClientKindIsReported() { + assertThat(this.reporter.getSpans().stream().map(Span::kind) + .collect(Collectors.toList())) + .contains(Span.Kind.CLIENT); + } + private void whenARequestIsSentToAnAsyncEndpoint(String url) throws Exception { MvcResult mvcResult = this.mockMvc .perform(MockMvcRequestBuilders.get(url).accept(MediaType.TEXT_PLAIN)) @@ -129,78 +135,84 @@ private void whenARequestIsSentToAnAsyncEndpoint(String url) throws Exception { @DefaultTestAutoConfiguration @Import(AspectTestingController.class) public static class Config { - @Bean - public RestTemplate restTemplate() { + @Bean public RestTemplate restTemplate() { return new RestTemplate(); } - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean public AsyncRestTemplate asyncRestTemplate(Tracing tracing) { + AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(); + asyncRestTemplate.setInterceptors(Collections.singletonList( + TracingAsyncClientHttpRequestInterceptor.create(tracing))); + return asyncRestTemplate; + } + + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); } } - @RestController - public static class AspectTestingController { - - @Autowired - Tracer tracer; - @Autowired - RestTemplate restTemplate; - @Autowired - Environment environment; - @Autowired - AsyncRestTemplate asyncRestTemplate; + @RestController public static class AspectTestingController { + + @Autowired Tracing tracer; + @Autowired RestTemplate restTemplate; + @Autowired Environment environment; + @Autowired AsyncRestTemplate asyncRestTemplate; private String traceId; public void reset() { this.traceId = null; } - @RequestMapping(value = "/", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - public String home( - @RequestHeader(value = Span.TRACE_ID_NAME, required = false) String traceId) { + @RequestMapping(value = "/", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public String home( + @RequestHeader(value = "X-B3-SpanId", required = false) String traceId) { this.traceId = traceId == null ? "UNKNOWN" : traceId; return "trace=" + this.getTraceId(); } - @RequestMapping(value = "/customTag", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - public String customTag( - @RequestHeader(value = Span.TRACE_ID_NAME, required = false) String traceId) { + @RequestMapping(value = "/customTag", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public String customTag( + @RequestHeader(value = "X-B3-TraceId", required = false) String traceId) { this.traceId = traceId == null ? "UNKNOWN" : traceId; return "trace=" + this.getTraceId(); } - @RequestMapping(value = "/asyncRestTemplate", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - public String asyncRestTemplate() + @RequestMapping(value = "/asyncRestTemplate", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public String asyncRestTemplate() throws ExecutionException, InterruptedException { return callViaAsyncRestTemplateAndReturnOk(); } - @RequestMapping(value = "/syncPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) - public String syncPing() { + @RequestMapping(value = "/syncPing", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public String syncPing() { return callAndReturnOk(); } - @RequestMapping(value = "/callablePing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(value = "/callablePing", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public Callable asyncPing() { return new Callable() { - @Override - public String call() throws Exception { + @Override public String call() throws Exception { return callAndReturnOk(); } }; } - @RequestMapping(value = "/webAsyncTaskPing", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(value = "/webAsyncTaskPing", method = RequestMethod.GET, + produces = MediaType.TEXT_PLAIN_VALUE) public WebAsyncTask webAsyncTaskPing() { return new WebAsyncTask<>(new Callable() { - @Override - public String call() throws Exception { + @Override public String call() throws Exception { return callAndReturnOk(); } }); - }; + } + + ; private String callAndReturnOk() { this.restTemplate.getForObject("http://localhost:" + port(), String.class); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java deleted file mode 100644 index 2e7d1af716..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceAsyncListenableTaskExecutorTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import org.junit.Test; -import org.mockito.BDDMockito; -import org.springframework.cloud.sleuth.*; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.core.task.AsyncListenableTaskExecutor; - -import java.util.Random; -import java.util.concurrent.Callable; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; - -/** - * @author Marcin Grzejszczak - */ -public class TraceAsyncListenableTaskExecutorTest { - - AsyncListenableTaskExecutor delegate = mock(AsyncListenableTaskExecutor.class); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()) { - @Override - public boolean isTracing() { - return true; - } - }; - TraceAsyncListenableTaskExecutor traceAsyncListenableTaskExecutor = new TraceAsyncListenableTaskExecutor( - this.delegate, this.tracer); - - @Test - public void should_submit_listenable_trace_runnable() throws Exception { - this.traceAsyncListenableTaskExecutor.submitListenable(aRunnable()); - - BDDMockito.then(this.delegate).should().submitListenable(any(TraceRunnable.class)); - } - - @Test - public void should_submit_listenable_trace_callable() throws Exception { - this.traceAsyncListenableTaskExecutor.submitListenable(aCallable()); - - BDDMockito.then(this.delegate).should().submitListenable(any(TraceCallable.class)); - } - - @Test - public void should_execute_a_trace_runnable() throws Exception { - this.traceAsyncListenableTaskExecutor.execute(aRunnable()); - - BDDMockito.then(this.delegate).should().execute(any(TraceRunnable.class)); - } - - @Test - public void should_execute_with_timeout_a_trace_runnable() throws Exception { - this.traceAsyncListenableTaskExecutor.execute(aRunnable(), 1L); - - BDDMockito.then(this.delegate).should().execute(any(TraceRunnable.class), - BDDMockito.anyLong()); - } - - @Test - public void should_submit_trace_callable() throws Exception { - this.traceAsyncListenableTaskExecutor.submit(aCallable()); - - BDDMockito.then(this.delegate).should().submit(any(TraceCallable.class)); - } - - @Test - public void should_submit_trace_runnable() throws Exception { - this.traceAsyncListenableTaskExecutor.submit(aRunnable()); - - BDDMockito.then(this.delegate).should().submit(any(TraceRunnable.class)); - } - - Runnable aRunnable() { - return () -> { - - }; - } - - Callable aCallable() { - return () -> null; - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java index ab40e3cc02..20252862de 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,31 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.SocketPolicy; - import java.io.IOException; import java.util.Arrays; import java.util.Map; -import java.util.Random; +import org.assertj.core.api.BDDAssertions; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.BDDAssertions.then; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.spring.web.TracingClientHttpRequestInterceptor; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.SocketPolicy; /** * @author Marcin Grzejszczak @@ -60,47 +51,47 @@ public class TraceRestTemplateInterceptorIntegrationTests { private RestTemplate template = new RestTemplate(clientHttpRequestFactory()); - private DefaultTracer tracer; - - private ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); @Before public void setup() { - this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator, new TraceKeys()); this.template.setInterceptors(Arrays.asList( - new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(), - new HttpTraceKeysInjector(this.tracer, new TraceKeys()), - new ExceptionMessageErrorParser()))); - TestSpanContextHolder.removeCurrentSpan(); + TracingClientHttpRequestInterceptor.create(HttpTracing.create(this.tracing)))); } @After - public void clean() throws IOException { - TestSpanContextHolder.removeCurrentSpan(); + public void clean() { + Tracing.current().close(); } // Issue #198 @Test public void spanRemovedFromThreadUponException() throws IOException { this.mockWebServer.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); - Span span = this.tracer.createSpan("new trace"); + Span span = this.tracer.nextSpan().name("new trace"); - try { + try(Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { this.template.getForEntity( "http://localhost:" + this.mockWebServer.getPort() + "/exception", Map.class).getBody(); Assert.fail("should throw an exception"); } catch (RuntimeException e) { - SleuthAssertions.then(e).hasRootCauseInstanceOf(IOException.class); + BDDAssertions.then(e).hasRootCauseInstanceOf(IOException.class); + } finally { + span.finish(); } - SleuthAssertions.then(this.tracer.getCurrentSpan()).isEqualTo(span); - this.tracer.close(span); - SleuthAssertions.then(new ListOfSpans(this.spanAccumulator.getSpans())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, "Read timed out") - .hasRpcWithoutSeverSideDueToException(); - then(ExceptionUtils.getLastException()).isNull(); + // 1 span "new race", 1 span "rest template" + BDDAssertions.then(this.reporter.getSpans()).hasSize(2); + zipkin2.Span span1 = this.reporter.getSpans().get(0); + BDDAssertions.then(span1.tags()) + .containsEntry("error", "Read timed out"); + BDDAssertions.then(span1.kind().ordinal()).isEqualTo(Span.Kind.CLIENT.ordinal()); } private ClientHttpRequestFactory clientHttpRequestFactory() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java deleted file mode 100644 index f207ebec5d..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceRestTemplateInterceptorTests.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import org.apache.commons.lang.StringUtils; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.test.web.client.MockMvcClientHttpRequestFactory; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Dave Syer - * - */ -public class TraceRestTemplateInterceptorTests { - - private TestController testController = new TestController(); - - private MockMvc mockMvc = MockMvcBuilders.standaloneSetup(this.testController) - .build(); - - private RestTemplate template = new RestTemplate( - new MockMvcClientHttpRequestFactory(this.mockMvc)); - - private DefaultTracer tracer; - - private ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); - - @Before - public void setup() { - this.tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), this.spanAccumulator, new TraceKeys()); - this.template.setInterceptors(Arrays.asList( - new TraceRestTemplateInterceptor(this.tracer, new ZipkinHttpSpanInjector(), - new HttpTraceKeysInjector(this.tracer, new TraceKeys()), - new ExceptionMessageErrorParser()))); - TestSpanContextHolder.removeCurrentSpan(); - } - - @After - public void clean() { - TestSpanContextHolder.removeCurrentSpan(); - } - - @Test - public void headersAddedWhenNoTracingWasPresent() { - @SuppressWarnings("unchecked") - Map headers = this.template.getForEntity("/", Map.class) - .getBody(); - - then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isNotNull(); - then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotNull(); - } - - @Test - public void headersAddedWhenTracing() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).parent(3L).build()); - @SuppressWarnings("unchecked") - Map headers = this.template.getForEntity("/", Map.class) - .getBody(); - then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); - then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotEqualTo(2L); - then(Span.hexToId(headers.get(Span.PARENT_ID_NAME))).isEqualTo(2L); - } - - // Issue #290 - @Test - public void requestHeadersAddedWhenTracing() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).parent(3L).build()); - - this.template.getForEntity("/foo?a=b", Map.class); - - List spans = spanAccumulator.getSpans(); - then(spans).isNotEmpty(); - then(spans.get(0)) - .hasATag("http.url", "/foo?a=b") - .hasATag("http.path", "/foo") - .hasATag("http.method", "GET"); - } - - @Test - public void notSampledHeaderAddedWhenNotExportable() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build()); - @SuppressWarnings("unchecked") - Map headers = this.template.getForEntity("/", Map.class) - .getBody(); - then(Span.hexToId(headers.get(Span.TRACE_ID_NAME))).isEqualTo(1L); - then(Span.hexToId(headers.get(Span.SPAN_ID_NAME))).isNotEqualTo(2L); - then(headers.get(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); - } - - // issue #198 - @Test - public void spanRemovedFromThreadUponException() { - Span span = this.tracer.createSpan("new trace"); - - try { - this.template.getForEntity("/exception", Map.class).getBody(); - Assert.fail("should throw an exception"); - } catch (RuntimeException e) { - then(e).hasMessage("500 Internal Server Error"); - } - - then(this.tracer.getCurrentSpan()).isEqualTo(span); - this.tracer.close(span); - } - - @Test - public void createdSpanNameDoesNotHaveNullInName() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build()); - - this.template.getForEntity("/", Map.class).getBody(); - - then(this.testController.span).hasNameEqualTo("http:/"); - } - - @Test - public void createdSpanNameHasOnlyPrintableAsciiCharactersForNonEncodedURIWithNonAsciiChars() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build()); - - try { - this.template.getForEntity("/cas~fs~划", Map.class).getBody(); - } - catch (Exception e) { - - } - - String spanName = this.spanAccumulator.getSpans().get(0).getName(); - then(this.spanAccumulator.getSpans().get(0).getName()).isEqualTo("http:/cas~fs~%C3%A5%CB%86%E2%80%99"); - then(StringUtils.isAsciiPrintable(spanName)); - then(ExceptionUtils.getLastException()).isNull(); - } - - @Test - public void willShortenTheNameOfTheSpan() { - this.tracer.continueSpan(Span.builder().traceId(1L).spanId(2L).exportable(false).build()); - - try { - this.template.getForEntity("/" + bigName(), Map.class).getBody(); - } catch (Exception e) { - - } - - then(this.spanAccumulator.getSpans().get(0).getName()).hasSize(50); - then(ExceptionUtils.getLastException()).isNull(); - } - - private String bigName() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 60; i++) { - sb.append("a"); - } - return sb.toString(); - } - - @RestController - public class TestController { - - Span span; - - @RequestMapping("/") - public Map home(@RequestHeader HttpHeaders headers) { - this.span = TraceRestTemplateInterceptorTests.this.tracer.getCurrentSpan(); - Map map = new HashMap(); - addHeaders(map, headers, Span.SPAN_ID_NAME, Span.TRACE_ID_NAME, - Span.PARENT_ID_NAME, Span.SAMPLED_NAME); - return map; - } - - @RequestMapping("/foo") - public void foo() { - } - - @RequestMapping("/exception") - public Map exception() { - throw new RuntimeException("foo"); - } - - private void addHeaders(Map map, HttpHeaders headers, - String... names) { - if (headers != null) { - for (String name : names) { - String value = headers.getFirst(name); - if (value != null) { - map.put(name, value); - } - } - } - } - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java index 9f1162b142..4f3381409a 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,245 +16,143 @@ package org.springframework.cloud.sleuth.instrument.web.client; -import java.io.IOException; -import java.net.URI; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import zipkin2.Span; import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; import org.junit.Before; import org.junit.Test; -import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.AsyncClientHttpRequest; -import org.springframework.http.client.AsyncClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.AsyncRestTemplate; -import org.awaitility.Awaitility; - +import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(Enclosed.class) +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = { + TraceWebAsyncClientAutoConfigurationTests.TestConfiguration.class }, + webEnvironment = RANDOM_PORT) public class TraceWebAsyncClientAutoConfigurationTests { - - @RunWith(SpringJUnit4ClassRunner.class) - @SpringBootTest(classes = { - CustomSyncAndAsyncClientFactory.TestConfiguration.class }, webEnvironment = RANDOM_PORT) - public static class CustomSyncAndAsyncClientFactory { - @Autowired AsyncRestTemplate asyncRestTemplate; - - @Test - public void should_inject_to_async_rest_template_custom_client_http_request_factory() { - then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); - TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); - then(wrapper.syncDelegate).isInstanceOf(MySyncClientHttpRequestFactory.class); - then(wrapper.asyncDelegate).isInstanceOf(MyAsyncClientHttpRequestFactory.class); - then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); - } - - // tag::async_template_factories[] - @EnableAutoConfiguration - @Configuration - public static class TestConfiguration { - - @Bean - ClientHttpRequestFactory mySyncClientFactory() { - return new MySyncClientHttpRequestFactory(); - } - - @Bean - AsyncClientHttpRequestFactory myAsyncClientFactory() { - return new MyAsyncClientHttpRequestFactory(); - } - } - // end::async_template_factories[] - + @Autowired AsyncRestTemplate asyncRestTemplate; + @Autowired Environment environment; + @Autowired ArrayListSpanReporter accumulator; + @Autowired Tracing tracer; + + @Before + public void setup() { + this.accumulator.clear(); } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringBootTest(classes = { - CustomSyncClientFactory.TestConfiguration.class }, webEnvironment = RANDOM_PORT) - public static class CustomSyncClientFactory { - @Autowired AsyncRestTemplate asyncRestTemplate; - - @Test - public void should_inject_to_async_rest_template_custom_client_http_request_factory() { - then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); - TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); - then(wrapper.syncDelegate).isInstanceOf(MySyncClientHttpRequestFactory.class); - then(wrapper.asyncDelegate).isInstanceOf(SimpleClientHttpRequestFactory.class); - then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); - } + @Test + public void should_close_span_upon_success_callback() + throws ExecutionException, InterruptedException { + brave.Span initialSpan = this.tracer.tracer().nextSpan().name("foo"); - @EnableAutoConfiguration - @Configuration - public static class TestConfiguration { + try (Tracer.SpanInScope ws = this.tracer.tracer().withSpanInScope(initialSpan.start())) { + ListenableFuture> future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/foo", String.class); + String result = future.get().getBody(); - @Bean - ClientHttpRequestFactory mySyncClientFactory() { - return new MySyncClientHttpRequestFactory(); - } + then(result).isEqualTo("foo"); + } finally { + initialSpan.finish(); } + then(this.accumulator.getSpans().stream() + .filter(span -> Span.Kind.CLIENT == span.kind()).findFirst().get()) + .matches(span -> span.duration() >= TimeUnit.MILLISECONDS.toMicros(100)); + then(this.tracer.tracer().currentSpan()).isNull(); } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringBootTest(classes = { - CustomASyncClientFactory.TestConfiguration.class }, webEnvironment = RANDOM_PORT) - public static class CustomASyncClientFactory { - @Autowired AsyncRestTemplate asyncRestTemplate; - - @Test - public void should_inject_to_async_rest_template_custom_client_http_request_factory() { - then(this.asyncRestTemplate.getAsyncRequestFactory()).isInstanceOf(TraceAsyncClientHttpRequestFactoryWrapper.class); - TraceAsyncClientHttpRequestFactoryWrapper wrapper = (TraceAsyncClientHttpRequestFactoryWrapper) this.asyncRestTemplate.getAsyncRequestFactory(); - then(wrapper.syncDelegate).isInstanceOf(SimpleClientHttpRequestFactory.class); - then(wrapper.asyncDelegate).isInstanceOf(AsyncClientHttpRequestFactory.class); - then(this.asyncRestTemplate).isInstanceOf(TraceAsyncRestTemplate.class); - } - - @EnableAutoConfiguration - @Configuration - public static class TestConfiguration { - - @Bean - AsyncClientHttpRequestFactory myAsyncClientFactory() { - return new MyAsyncClientHttpRequestFactory(); - } + @Test + public void should_close_span_upon_failure_callback() + throws ExecutionException, InterruptedException { + ListenableFuture> future; + try { + future = this.asyncRestTemplate + .getForEntity("http://localhost:" + port() + "/blowsup", String.class); + future.get(); + BDDAssertions.fail("should throw an exception from the controller"); + } catch (Exception e) { } + Awaitility.await().untilAsserted(() -> { + Span reportedRpcSpan = new ArrayList<>(this.accumulator.getSpans()).stream() + .filter(span -> Span.Kind.CLIENT == span.kind()).findFirst().get(); + then(reportedRpcSpan).matches( + span -> span.duration() >= TimeUnit.MILLISECONDS.toMicros(100)); + then(reportedRpcSpan.tags()).containsKey("error"); + then(this.tracer.tracer().currentSpan()).isNull(); + }); } - private static class MySyncClientHttpRequestFactory implements ClientHttpRequestFactory { - @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) - throws IOException { - return null; - } - } - private static class MyAsyncClientHttpRequestFactory implements AsyncClientHttpRequestFactory { - @Override - public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) - throws IOException { - return null; - } + int port() { + return this.environment.getProperty("local.server.port", Integer.class); } - @RunWith(SpringJUnit4ClassRunner.class) - @SpringBootTest(classes = { - DurationChecking.TestConfiguration.class }, webEnvironment = RANDOM_PORT) - public static class DurationChecking { - @Autowired AsyncRestTemplate asyncRestTemplate; - @Autowired Environment environment; - @Autowired ArrayListSpanAccumulator accumulator; - @Autowired Tracer tracer; + @EnableAutoConfiguration( + // spring boot test will otherwise instrument the client and server with the same bean factory + // which isn't expected + exclude = TraceWebServletAutoConfiguration.class + ) + @Configuration + public static class TestConfiguration { - @Before - public void setup() { - ExceptionUtils.setFail(true); + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); } - @Test - public void should_close_span_upon_success_callback() - throws ExecutionException, InterruptedException { - ListenableFuture> future = this.asyncRestTemplate - .getForEntity("http://localhost:" + port() + "/foo", String.class); - String result = future.get().getBody(); - - then(result).isEqualTo("foo"); - then(new ArrayList<>(this.accumulator.getSpans()).stream().filter( - span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())).findFirst().isPresent() - ).findFirst().get()).matches(span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + @Bean + MyController myController() { + return new MyController(); } - @Test - public void should_close_span_upon_failure_callback() - throws ExecutionException, InterruptedException { - ListenableFuture> future; - try { - future = this.asyncRestTemplate - .getForEntity("http://localhost:" + port() + "/blowsup", String.class); - future.get(); - BDDAssertions.fail("should throw an exception from the controller"); - } catch (Exception e) { - - } - - Awaitility.await().untilAsserted(() -> { - then(new ArrayList<>(this.accumulator.getSpans()).stream() - .filter(span -> span.logs().stream().filter(log -> Span.CLIENT_RECV.equals(log.getEvent())) - .findFirst().isPresent()).findFirst().get()).matches( - span -> span.getAccumulatedMicros() >= TimeUnit.MILLISECONDS.toMicros(100)) - .hasATagWithKey(Span.SPAN_ERROR_TAG_NAME); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); - }); + @Bean Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; } - int port() { - return this.environment.getProperty("local.server.port", Integer.class); + @Bean + AsyncRestTemplate restTemplate() { + return new AsyncRestTemplate(); } + } - @EnableAutoConfiguration - @Configuration - public static class TestConfiguration { - - @Bean ArrayListSpanAccumulator accumulator() { - return new ArrayListSpanAccumulator(); - } - - @Bean - MyController myController() { - return new MyController(); - } + @RestController + public static class MyController { - @Bean AlwaysSampler sampler() { - return new AlwaysSampler(); - } + @RequestMapping("/foo") + String foo() throws Exception { + Thread.sleep(100); + return "foo"; } - @RestController - public static class MyController { - - @RequestMapping("/foo") - String foo() throws Exception { - Thread.sleep(100); - return "foo"; - } - - @RequestMapping("/blowsup") - String blowsup() throws Exception { - Thread.sleep(100); - throw new RuntimeException("boom"); - } + @RequestMapping("/blowsup") + String blowsup() throws Exception { + Thread.sleep(100); + throw new RuntimeException("boom"); } - } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java deleted file mode 100644 index 374d739163..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfigurationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.springframework.cloud.sleuth.instrument.web.client; - -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.web.TraceWebAutoConfiguration; -import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.context.junit4.SpringRunner; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = TraceWebClientAutoConfigurationTests.Config.class) -public abstract class TraceWebClientAutoConfigurationTests { - - @Autowired Config config; - //@Autowired UserInfoRestTemplateCustomizer customizer; - @Autowired TraceRestTemplateInterceptor interceptor; - -// @Test -// public void should_wrap_UserInfoRestTemplateCustomizer_in_a_trace_representation() { -// OAuth2ProtectedResourceDetails details = Mockito.mock(OAuth2ProtectedResourceDetails.class); -// OAuth2RestTemplate template = new OAuth2RestTemplate(details); -// -// this.customizer.customize(template); -// -// then(this.config.executed).isTrue(); -// then(template.getInterceptors()).contains(this.interceptor); -// } - - - @Configuration - @ImportAutoConfiguration(classes = { - TraceWebClientAutoConfiguration.class, SleuthLogAutoConfiguration.class, - TraceHttpAutoConfiguration.class, TraceWebAutoConfiguration.class, TraceAutoConfiguration.class }) - static class Config { - - boolean executed = false; - -// @Bean UserInfoRestTemplateCustomizer customizer() { -// return new UserInfoRestTemplateCustomizer() { -// @Override public void customize(OAuth2RestTemplate template) { -// Config.this.executed = true; -// } -// }; -// } - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java index bd56c698bf..be42e2dfa6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/discoveryexception/WebClientDiscoveryExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,30 +17,29 @@ package org.springframework.cloud.sleuth.instrument.web.client.discoveryexception; import java.io.IOException; +import java.util.List; import java.util.Map; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import zipkin2.reporter.Reporter; import org.assertj.core.api.Assertions; -import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; @@ -51,53 +50,50 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { WebClientDiscoveryExceptionTests.TestConfiguration.class }, webEnvironment = RANDOM_PORT) -@TestPropertySource(properties = "spring.application.name=exceptionservice") +@TestPropertySource(properties = { "spring.application.name=exceptionservice", + "spring.sleuth.http.legacy.enabled=true" }) @DirtiesContext public class WebClientDiscoveryExceptionTests { @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; @Autowired Tracer tracer; - @Rule public OutputCapture outputCapture = new OutputCapture(); + @Autowired ArrayListSpanReporter reporter; @Before - public void open() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - } - - @After public void close() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } // issue #240 private void shouldCloseSpanUponException(ResponseEntityProvider provider) throws IOException, InterruptedException { - Span span = this.tracer.createSpan("new trace"); + Span span = this.tracer.nextSpan().name("new trace"); - try { + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { provider.get(this); Assertions.fail("should throw an exception"); } catch (RuntimeException e) { } + finally { + span.finish(); + } - assertThat(ExceptionUtils.getLastException()).isNull(); - - then(this.tracer.getCurrentSpan()).isEqualTo(span); - this.tracer.close(span); - then(ExceptionUtils.getLastException()).isNull(); // hystrix commands should finish at this point Thread.sleep(200); - then(this.outputCapture.toString()).doesNotContain("Tried to detach trace span but it is not the current span"); + List spans = this.reporter.getSpans(); + then(spans).hasSize(2); + then(spans.stream() + .filter(span1 -> span1.kind() == zipkin2.Span.Kind.CLIENT) + .findFirst() + .get().tags()).containsKey("error"); } @Test @@ -120,7 +116,8 @@ public interface TestFeignInterfaceWithException { } @Configuration - @EnableAutoConfiguration(exclude = EurekaClientAutoConfiguration.class) + @EnableAutoConfiguration(exclude = {EurekaClientAutoConfiguration.class, + TraceWebServletAutoConfiguration.class}) @EnableDiscoveryClient @EnableFeignClients @RibbonClient("exceptionservice") @@ -132,14 +129,18 @@ public RestTemplate restTemplate() { return new RestTemplate(); } - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean Reporter mySpanReporter() { + return new ArrayListSpanReporter(); } } @FunctionalInterface interface ResponseEntityProvider { - ResponseEntity get(WebClientDiscoveryExceptionTests webClientTests); + ResponseEntity get( + WebClientDiscoveryExceptionTests webClientTests); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java index 2b3657e444..e09b3b8d52 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exception/WebClientExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,14 @@ import java.util.Collections; import java.util.Map; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; @@ -34,18 +39,11 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; @@ -60,11 +58,7 @@ import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - -import static junitparams.JUnitParamsRunner.$; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(JUnitParamsRunner.class) @SpringBootTest(classes = { @@ -84,18 +78,12 @@ public class WebClientExceptionTests { @Autowired TestFeignInterfaceWithException testFeignInterfaceWithException; @Autowired @LoadBalanced RestTemplate template; - @Autowired Tracer tracer; - @Autowired ArrayListSpanAccumulator accumulator; + @Autowired Tracing tracer; + @Autowired ArrayListSpanReporter reporter; @Before public void open() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - } - - @After - public void close() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } // issue #198 @@ -103,23 +91,23 @@ public void close() { @Parameters public void shouldCloseSpanUponException(ResponseEntityProvider provider) throws IOException { - Span span = this.tracer.createSpan("new trace"); - log.info("Started new span " + span); + Span span = this.tracer.tracer().nextSpan().name("new trace").start(); - try { + try (Tracer.SpanInScope ws = this.tracer.tracer().withSpanInScope(span)) { + log.info("Started new span " + span); provider.get(this); Assert.fail("should throw an exception"); } catch (RuntimeException e) { // SleuthAssertions.then(e).hasRootCauseInstanceOf(IOException.class); + } finally { + span.finish(); } - then(ExceptionUtils.getLastException()).isNull(); - then(this.tracer.getCurrentSpan()).isEqualTo(span); - this.tracer.close(span); - then(ExceptionUtils.getLastException()).isNull(); - then(this.capture.toString()).doesNotContain("Tried to detach trace span but it is not the current span"); - then(new ListOfSpans(this.accumulator.getSpans())).hasRpcWithoutSeverSideDueToException(); + then(this.tracer.tracer().currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); + then(this.reporter.getSpans().get(0).tags().get("error")) + .contains("invalid.host.to.break.tests"); } Object[] parametersForShouldCloseSpanUponException() { @@ -151,13 +139,12 @@ public RestTemplate restTemplate() { return new RestTemplate(clientHttpRequestFactory); } - @Bean - Sampler alwaysSampler() { - return new AlwaysSampler(); + @Bean Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; } - @Bean ArrayListSpanAccumulator accumulator() { - return new ArrayListSpanAccumulator(); + @Bean ArrayListSpanReporter accumulator() { + return new ArrayListSpanReporter(); } } @@ -176,6 +163,7 @@ public ILoadBalancer exceptionServiceRibbonLoadBalancer() { @FunctionalInterface interface ResponseEntityProvider { - ResponseEntity get(WebClientExceptionTests webClientTests); + ResponseEntity get( + WebClientExceptionTests webClientTests); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exceptionresolver/Issue585Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exceptionresolver/Issue585Tests.java index 037364ed13..ca461baaa7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exceptionresolver/Issue585Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/exceptionresolver/Issue585Tests.java @@ -1,10 +1,11 @@ package org.springframework.cloud.sleuth.instrument.web.client.exceptionresolver; -import com.fasterxml.jackson.annotation.JsonInclude; - -import java.time.Instant; import javax.servlet.http.HttpServletRequest; +import java.time.Instant; +import brave.Span; +import brave.Tracing; +import brave.sampler.Sampler; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; @@ -14,13 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -33,14 +28,16 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import com.fasterxml.jackson.annotation.JsonInclude; + +import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class Issue585Tests { TestRestTemplate testRestTemplate = new TestRestTemplate(); - @Autowired ArrayListSpanAccumulator accumulator; + @Autowired ArrayListSpanReporter reporter; @LocalServerPort int port; @Test @@ -49,29 +46,31 @@ public void should_report_span_when_using_custom_exception_resolver() { "http://localhost:" + this.port + "/sleuthtest?greeting=foo", String.class); + then(Tracing.current().tracer().currentSpan()).isNull(); then(entity.getStatusCode().value()).isEqualTo(500); - then(new ListOfSpans(this.accumulator.getSpans())) - .hasASpanWithTagEqualTo("custom", "tag") - .hasASpanWithTagKeyEqualTo("error"); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("custom", "tag") + .containsKeys("error"); } } @SpringBootApplication class TestConfig { - @Bean SpanReporter testSpanReporter() { - return new ArrayListSpanAccumulator(); + @Bean ArrayListSpanReporter testSpanReporter() { + return new ArrayListSpanReporter(); } @Bean Sampler testSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } @RestController class TestController { - private final static Logger logger = LoggerFactory.getLogger(TestController.class); + private final static Logger logger = LoggerFactory.getLogger( + TestController.class); @RequestMapping(value = "sleuthtest", method = RequestMethod.GET) public ResponseEntity testSleuth(@RequestParam String greeting) { @@ -87,9 +86,10 @@ public ResponseEntity testSleuth(@RequestParam String greeting) { class CustomExceptionHandler extends ResponseEntityExceptionHandler { private final static Logger logger = LoggerFactory - .getLogger(CustomExceptionHandler.class); + .getLogger( + CustomExceptionHandler.class); - @Autowired private Tracer tracer; + @Autowired private Tracing tracer; @ExceptionHandler(value = { Exception.class }) protected ResponseEntity handleDefaultError( @@ -102,9 +102,9 @@ protected ResponseEntity handleDefaultError( } private void reportErrorSpan(String message) { - Span span = tracer.getCurrentSpan(); - span.logEvent("ERROR: " + message); - tracer.addTag("custom", "tag"); + Span span = tracer.tracer().currentSpan(); + span.annotate("ERROR: " + message); + span.tag("custom", "tag"); logger.info("Foo"); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java index 330c5122ee..0e62ccb69b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/FeignRetriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,14 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; import feign.Client; import feign.Feign; import feign.FeignException; @@ -23,13 +31,7 @@ import feign.RequestLine; import feign.Response; import okhttp3.mockwebserver.MockWebServer; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; - +import zipkin2.Span; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -39,24 +41,13 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.DefaultSpanNamer; import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak @@ -69,20 +60,20 @@ public class FeignRetriesTests { @Mock BeanFactory beanFactory; - ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), this.spanAccumulator, new TraceKeys()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + org.springframework.cloud.sleuth.TraceKeys traceKeys = new org.springframework.cloud.sleuth.TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .build(); @Before @After public void setup() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); - BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) - .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); - BDDMockito.given(this.beanFactory.getBean(HttpSpanInjector.class)) - .willReturn(new ZipkinHttpSpanInjector()); - BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); + BDDMockito.given(this.beanFactory.getBean(HttpTracing.class)).willReturn(this.httpTracing); BDDMockito.given(this.beanFactory.getBean(ErrorParser.class)).willReturn(new ExceptionMessageErrorParser()); } @@ -95,16 +86,13 @@ public void testRetriedWhenExceededNumberOfRetries() throws Exception { TestInterface api = Feign.builder() - .client(new TraceFeignClient(beanFactory, client)) + .client(new TracingFeignClient(this.httpTracing, client)) .target(TestInterface.class, url); try { api.decodedPost(); failBecauseExceptionWasNotThrown(FeignException.class); } catch (FeignException e) { } - - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); } @Test @@ -112,7 +100,7 @@ public void testRetriedWhenRequestEventuallyIsSent() throws Exception { String url = "http://localhost:" + server.getPort(); final AtomicInteger atomicInteger = new AtomicInteger(); // Client to simulate a retry scenario - Client client = (request, options) -> { + final Client client = (request, options) -> { // we simulate an exception only for the first request if (atomicInteger.get() == 1) { throw new IOException(); @@ -128,24 +116,22 @@ public void testRetriedWhenRequestEventuallyIsSent() throws Exception { }; TestInterface api = Feign.builder() - .client(new TraceFeignClient(beanFactory, client) { + .client(new TracingFeignClient(this.httpTracing, new Client() { @Override public Response execute(Request request, Request.Options options) throws IOException { atomicInteger.incrementAndGet(); - return super.execute(request, options); + return client.execute(request, options); } - }) + })) .target(TestInterface.class, url); then(api.decodedPost()).isEqualTo("OK"); // request interception should take place only twice (1st request & 2nd retry) then(atomicInteger.get()).isEqualTo(2); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); - then(this.spanAccumulator.getSpans().get(0)) - .hasATag("error", "java.io.IOException"); - then(this.spanAccumulator.getSpans().get(1)) - .hasLoggedAnEvent(Span.CLIENT_RECV); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("error", "IOException"); + then(this.reporter.getSpans().get(1).kind().ordinal()) + .isEqualTo(Span.Kind.CLIENT.ordinal()); } interface TestInterface { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspectTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspectTests.java index 97f636480f..ea4fb8d6e6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspectTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignAspectTests.java @@ -1,18 +1,20 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; import java.io.IOException; -import java.nio.charset.Charset; -import java.util.HashMap; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; import feign.Client; -import feign.Request; import org.aspectj.lang.ProceedingJoinPoint; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; @@ -28,6 +30,13 @@ public class TraceFeignAspectTests { @Mock Client client; @Mock ProceedingJoinPoint pjp; @Mock TraceLoadBalancerFeignClient traceLoadBalancerFeignClient; + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .build(); TraceFeignAspect traceFeignAspect; @Before @@ -42,7 +51,7 @@ public void setup() { @Test public void should_wrap_feign_client_in_trace_representation() throws Throwable { given(this.pjp.getTarget()).willReturn(this.client); - + this.traceFeignAspect.feignClientWasCalled(this.pjp); verify(this.pjp, never()).proceed(); @@ -50,7 +59,7 @@ public void should_wrap_feign_client_in_trace_representation() throws Throwable @Test public void should_not_wrap_traced_feign_client_in_trace_representation() throws Throwable { - given(this.pjp.getTarget()).willReturn(new TraceFeignClient(this.beanFactory, this.client)); + given(this.pjp.getTarget()).willReturn(new TracingFeignClient(this.httpTracing, this.client)); this.traceFeignAspect.feignClientWasCalled(this.pjp); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java deleted file mode 100644 index 864641b2eb..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignClientTests.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.instrument.web.client.feign; - -import feign.Client; -import feign.Request; -import feign.Response; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Random; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.ErrorParser; -import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; -import org.springframework.cloud.sleuth.instrument.web.HttpSpanInjector; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(MockitoJUnitRunner.class) -public class TraceFeignClientTests { - - ArrayListSpanAccumulator spanAccumulator = new ArrayListSpanAccumulator(); - @Mock BeanFactory beanFactory; - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), new DefaultSpanNamer(), - new NoOpSpanLogger(), this.spanAccumulator, new TraceKeys()); - @Mock Client client; - @InjectMocks TraceFeignClient traceFeignClient; - - @Before - @After - public void setup() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - BDDMockito.given(this.beanFactory.getBean(HttpTraceKeysInjector.class)) - .willReturn(new HttpTraceKeysInjector(this.tracer, new TraceKeys())); - BDDMockito.given(this.beanFactory.getBean(HttpSpanInjector.class)) - .willReturn(new ZipkinHttpSpanInjector()); - BDDMockito.given(this.beanFactory.getBean(Tracer.class)).willReturn(this.tracer); - BDDMockito.given(this.beanFactory.getBean(ErrorParser.class)).willReturn(new ExceptionMessageErrorParser()); - } - - @Test - public void should_log_cr_when_response_successful() throws IOException { - Span span = this.tracer.createSpan("foo"); - Response response = this.traceFeignClient.execute( - Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), - Charset.defaultCharset()), new Request.Options()); - - then(this.tracer.getCurrentSpan()).isEqualTo(span); - then(this.spanAccumulator.getSpans().get(0)).hasLoggedAnEvent(Span.CLIENT_RECV); - } - - @Test - public void should_log_error_when_exception_thrown() throws IOException { - Span span = this.tracer.createSpan("foo"); - BDDMockito.given(this.client.execute(BDDMockito.any(), BDDMockito.any())) - .willThrow(new RuntimeException("exception has occurred")); - - try { - this.traceFeignClient.execute( - Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), - Charset.defaultCharset()), new Request.Options()); - SleuthAssertions.fail("Exception should have been thrown"); - } catch (Exception e) {} - - then(this.tracer.getCurrentSpan()).isEqualTo(span); - then(this.spanAccumulator.getSpans().get(0)) - .hasATag(Span.SPAN_ERROR_TAG_NAME, "exception has occurred"); - } - - @Test - public void should_shorten_the_span_name() throws IOException { - this.traceFeignClient.execute( - Request.create("GET", "http://foo/" + bigName(), new HashMap<>(), "".getBytes(), - Charset.defaultCharset()), new Request.Options()); - - then(this.spanAccumulator.getSpans().get(0).getName()).hasSize(50); - } - - private String bigName() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 60; i++) { - sb.append("a"); - } - return sb.toString(); - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java new file mode 100644 index 0000000000..4a29d2d355 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClientTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.web.client.feign; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import feign.Client; +import feign.Request; +import org.assertj.core.api.BDDAssertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +@RunWith(MockitoJUnitRunner.class) +public class TracingFeignClientTests { + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + @Mock BeanFactory beanFactory; + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .build(); + @Mock Client client; + Client traceFeignClient; + + @Before + public void setup() { + this.traceFeignClient = TracingFeignClient.create(this.httpTracing, this.client); + } + + @Test + public void should_log_cr_when_response_successful() throws IOException { + Span span = this.tracer.nextSpan().name("foo"); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceFeignClient.execute( + Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + } finally { + span.finish(); + } + + then(this.reporter.getSpans().get(0)).extracting("kind.ordinal") + .contains(Span.Kind.CLIENT.ordinal()); + } + + @Test + public void should_log_error_when_exception_thrown() throws IOException { + Span span = this.tracer.nextSpan().name("foo"); + BDDMockito.given(this.client.execute(BDDMockito.any(), BDDMockito.any())) + .willThrow(new RuntimeException("exception has occurred")); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) { + this.traceFeignClient.execute( + Request.create("GET", "http://foo", new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + BDDAssertions.fail("Exception should have been thrown"); + } catch (Exception e) { + } finally { + span.finish(); + } + + then(this.reporter.getSpans().get(0)).extracting("kind.ordinal") + .contains(Span.Kind.CLIENT.ordinal()); + then(this.reporter.getSpans().get(0).tags()) + .containsEntry("error", "exception has occurred"); + } + + @Test + public void should_shorten_the_span_name() throws IOException { + this.traceFeignClient.execute( + Request.create("GET", "http://foo/" + bigName(), new HashMap<>(), "".getBytes(), + Charset.defaultCharset()), new Request.Options()); + + then(this.reporter.getSpans().get(0).name()).hasSize(50); + } + + private String bigName() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 60; i++) { + sb.append("a"); + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignObjectWrapperTests.java similarity index 72% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignObjectWrapperTests.java index ebb87a09e5..7f686f60b7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TraceFeignObjectWrapperTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignObjectWrapperTests.java @@ -1,5 +1,7 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign; +import brave.Tracing; +import brave.http.HttpTracing; import feign.Client; import org.junit.Test; import org.junit.runner.RunWith; @@ -7,7 +9,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.sleuth.Tracer; import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.Mockito.mock; @@ -16,15 +17,16 @@ * @author Marcin Grzejszczak */ @RunWith(MockitoJUnitRunner.class) -public class TraceFeignObjectWrapperTests { +public class TracingFeignObjectWrapperTests { - @Mock Tracer tracer; + Tracing tracing = Tracing.newBuilder().build(); + HttpTracing httpTracing = HttpTracing.create(this.tracing); @Mock BeanFactory beanFactory; @InjectMocks TraceFeignObjectWrapper traceFeignObjectWrapper; @Test - public void should_wrap_a_client_into_trace_client() throws Exception { - then(this.traceFeignObjectWrapper.wrap(mock(Client.class))).isExactlyInstanceOf(TraceFeignClient.class); + public void should_wrap_a_client_into_lazy_trace_client() throws Exception { + then(this.traceFeignObjectWrapper.wrap(mock(Client.class))).isExactlyInstanceOf(LazyTracingFeignClient.class); } @Test diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java index cbda2dcaa4..249c2f9c32 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue307/Issue307Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import java.util.ArrayList; import java.util.List; -import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import org.junit.Before; +import brave.sampler.Sampler; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,9 +29,6 @@ import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @@ -44,32 +40,28 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.BDDAssertions.then; +import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; public class Issue307Tests { - @Before - public void setup() { - TestSpanContextHolder.removeCurrentSpan(); - } - @Test public void should_start_context() { try (ConfigurableApplicationContext applicationContext = SpringApplication .run(SleuthSampleApplication.class, "--spring.jmx.enabled=false", "--server.port=0")) { } - then(ExceptionUtils.getLastException()).isNull(); } } @EnableAutoConfiguration -@Import({ParticipantsBean.class, ParticipantsClient.class}) +@Import({ + ParticipantsBean.class, ParticipantsClient.class}) @RestController @EnableFeignClients @EnableCircuitBreaker class SleuthSampleApplication { - private static final Logger LOG = LoggerFactory.getLogger(SleuthSampleApplication.class.getName()); + private static final Logger LOG = LoggerFactory.getLogger( + SleuthSampleApplication.class.getName()); @Autowired private RestTemplate restTemplate; @@ -86,8 +78,8 @@ public RestTemplate getRestTemplate() { } @Bean - public AlwaysSampler defaultSampler() { - return new AlwaysSampler(); + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; } @RequestMapping("/") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java index f6c5e63b56..012eef422d 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue350/Issue350Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,24 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue350; +import java.util.List; import java.util.concurrent.ExecutionException; -import org.junit.After; -import org.junit.Before; +import brave.Tracing; +import brave.sampler.Sampler; +import feign.Logger; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @@ -41,8 +43,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import feign.Logger; - import static org.assertj.core.api.BDDAssertions.then; /** @@ -51,34 +51,28 @@ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9988"}) +@TestPropertySource(properties = {"ribbon.eureka.enabled=false", + "feign.hystrix.enabled=false", "server.port=9988"}) public class Issue350Tests { TestRestTemplate template = new TestRestTemplate(); - @Autowired Tracer tracer; - - @Before - public void setup() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); - } - - @After - public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); - } + @Autowired Tracing tracer; + @Autowired ArrayListSpanReporter reporter; @Test public void should_successfully_work_without_hystrix() { this.template.getForEntity("http://localhost:9988/sleuth/test-not-ok", String.class); - then(ExceptionUtils.getLastException()).isNull(); - then(this.tracer.getCurrentSpan()).isNull(); + + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).tags()).containsEntry("http.status_code", "406"); } } @Configuration -@EnableAutoConfiguration -@EnableFeignClients(basePackageClasses = {SleuthTestController.class}) +@EnableAutoConfiguration(exclude = TraceWebServletAutoConfiguration.class) +@EnableFeignClients(basePackageClasses = { + SleuthTestController.class}) class Application { @Bean @@ -93,12 +87,17 @@ public SleuthTestController sleuthTestController() { @Bean public Logger.Level feignLoggerLevel() { - return feign.Logger.Level.FULL; + return Logger.Level.FULL; + } + + @Bean + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; } @Bean - public AlwaysSampler defaultSampler() { - return new AlwaysSampler(); + public Reporter spanReporter() { + return new ArrayListSpanReporter(); } } @@ -128,7 +127,6 @@ interface MyFeignClient { String exp(); } - @RestController @RequestMapping(path = "/sleuth") class SleuthTestController { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java index 6a38d93d14..30bebeeef9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue362/Issue362Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,23 +18,33 @@ import java.io.IOException; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; -import org.junit.After; +import brave.Tracing; +import brave.sampler.Sampler; +import feign.Client; +import feign.Logger; +import feign.Request; +import feign.Response; +import feign.RetryableException; +import feign.Retryer; +import feign.codec.ErrorDecoder; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; @@ -46,14 +56,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import feign.Client; -import feign.Logger; -import feign.Request; -import feign.Response; -import feign.RetryableException; -import feign.Retryer; -import feign.codec.ErrorDecoder; - import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.BDDAssertions.then; @@ -63,23 +65,19 @@ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@TestPropertySource(properties = {"ribbon.eureka.enabled=false", "feign.hystrix.enabled=false", "server.port=9998"}) +@TestPropertySource(properties = {"ribbon.eureka.enabled=false", + "feign.hystrix.enabled=false", "server.port=9998"}) public class Issue362Tests { RestTemplate template = new RestTemplate(); @Autowired FeignComponentAsserter feignComponentAsserter; - @Autowired Tracer tracer; + @Autowired Tracing tracer; + @Autowired ArrayListSpanReporter reporter; @Before public void setup() { this.feignComponentAsserter.executedComponents.clear(); - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); - } - - @After - public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } @Test @@ -89,9 +87,10 @@ public void should_successfully_work_with_custom_error_decoder_when_sending_succ ResponseEntity response = this.template.getForEntity(securedURl, String.class); then(response.getBody()).isEqualTo("I'm OK"); - then(ExceptionUtils.getLastException()).isNull(); then(this.feignComponentAsserter.executedComponents).containsEntry(Client.class, true); - then(this.tracer.getCurrentSpan()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + then(spans.get(0).tags()).containsEntry("http.path", "/service/ok"); } @Test @@ -103,16 +102,19 @@ public void should_successfully_work_with_custom_error_decoder_when_sending_fail fail("should propagate an exception"); } catch (Exception e) { } - then(ExceptionUtils.getLastException()).isNull(); then(this.feignComponentAsserter.executedComponents) .containsEntry(ErrorDecoder.class, true) .containsEntry(Client.class, true); - then(this.tracer.getCurrentSpan()).isNull(); + List spans = this.reporter.getSpans(); + // retries + then(spans).hasSize(5); + then(spans.stream().map(span -> span.tags().get("http.status_code")).collect( + Collectors.toList())).containsOnly("409"); } } @Configuration -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = TraceWebServletAutoConfiguration.class) @EnableFeignClients(basePackageClasses = { SleuthTestController.class}) class Application { @@ -129,17 +131,22 @@ public SleuthTestController sleuthTestController() { @Bean public Logger.Level feignLoggerLevel() { - return feign.Logger.Level.FULL; + return Logger.Level.FULL; } @Bean - public AlwaysSampler defaultSampler() { - return new AlwaysSampler(); + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; } @Bean public FeignComponentAsserter testHolder() { return new FeignComponentAsserter(); } + @Bean + public Reporter spanReporter() { + return new ArrayListSpanReporter(); + } + } class FeignComponentAsserter { @@ -150,7 +157,8 @@ class FeignComponentAsserter { class CustomConfig { @Bean - public ErrorDecoder errorDecoder(FeignComponentAsserter feignComponentAsserter) { + public ErrorDecoder errorDecoder( + FeignComponentAsserter feignComponentAsserter) { return new CustomErrorDecoder(feignComponentAsserter); } @@ -163,7 +171,8 @@ public static class CustomErrorDecoder extends ErrorDecoder.Default { private final FeignComponentAsserter feignComponentAsserter; - public CustomErrorDecoder(FeignComponentAsserter feignComponentAsserter) { + public CustomErrorDecoder( + FeignComponentAsserter feignComponentAsserter) { this.feignComponentAsserter = feignComponentAsserter; } @@ -179,7 +188,8 @@ public Exception decode(String methodKey, Response response) { } @Bean - public Client client(FeignComponentAsserter feignComponentAsserter) { + public Client client( + FeignComponentAsserter feignComponentAsserter) { return new CustomClient(feignComponentAsserter); } @@ -187,7 +197,8 @@ public static class CustomClient extends Client.Default { private final FeignComponentAsserter feignComponentAsserter; - public CustomClient(FeignComponentAsserter feignComponentAsserter) { + public CustomClient( + FeignComponentAsserter feignComponentAsserter) { super(null, null); this.feignComponentAsserter = feignComponentAsserter; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java index ea8a6b8438..9b8ec80988 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue393/Issue393Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,25 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.issues.issue393; -import org.junit.After; +import java.util.List; +import java.util.stream.Collectors; + +import brave.Tracing; +import brave.sampler.Sampler; +import feign.okhttp.OkHttpClient; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; @@ -41,8 +46,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import feign.okhttp.OkHttpClient; - import static org.assertj.core.api.BDDAssertions.then; /** @@ -55,17 +58,12 @@ public class Issue393Tests { RestTemplate template = new RestTemplate(); - @Autowired Tracer tracer; + @Autowired ArrayListSpanReporter reporter; + @Autowired Tracing tracer; @Before public void open() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - } - - @After - public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } @Test @@ -75,19 +73,23 @@ public void should_successfully_work_when_service_discovery_is_on_classpath_and_ ResponseEntity response = this.template.getForEntity(url, String.class); then(response.getBody()).isEqualTo("mikesarver foo"); - then(ExceptionUtils.getLastException()).isNull(); - then(this.tracer.getCurrentSpan()).isNull(); + List spans = this.reporter.getSpans(); + // retries + then(spans).hasSize(2); + then(spans.stream().map(span -> span.tags().get("http.path")).collect( + Collectors.toList())).containsOnly("/name/mikesarver"); } } @Configuration -@EnableAutoConfiguration +@EnableAutoConfiguration(exclude = TraceWebServletAutoConfiguration.class) @EnableFeignClients @EnableDiscoveryClient class Application { @Bean - public DemoController demoController(MyNameRemote myNameRemote) { + public DemoController demoController( + MyNameRemote myNameRemote) { return new DemoController(myNameRemote); } @@ -103,8 +105,13 @@ public feign.Logger.Level feignLoggerLevel() { } @Bean - public AlwaysSampler defaultSampler() { - return new AlwaysSampler(); + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + public Reporter spanReporter() { + return new ArrayListSpanReporter(); } } @@ -122,7 +129,8 @@ class DemoController { private final MyNameRemote myNameRemote; - public DemoController(MyNameRemote myNameRemote) { + public DemoController( + MyNameRemote myNameRemote) { this.myNameRemote = myNameRemote; } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java index f282c43d4e..7006da7bc5 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/issues/issue502/Issue502Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,29 +19,30 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.HashMap; +import java.util.List; -import org.junit.After; +import brave.Tracing; +import brave.sampler.Sampler; +import feign.Client; +import feign.Request; +import feign.Response; +import zipkin2.Span; +import zipkin2.reporter.Reporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import feign.Client; -import feign.Request; -import feign.Response; - import static org.assertj.core.api.BDDAssertions.then; /** @@ -55,16 +56,12 @@ public class Issue502Tests { @Autowired MyClient myClient; @Autowired MyNameRemote myNameRemote; + @Autowired ArrayListSpanReporter reporter; + @Autowired Tracing tracer; @Before public void open() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - } - - @After - public void close() { - TestSpanContextHolder.removeCurrentSpan(); + this.reporter.clear(); } @Test @@ -73,7 +70,10 @@ public void should_reuse_custom_feign_client() { then(this.myClient.wasCalled()).isTrue(); then(response).isEqualTo("foo"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + // retries + then(spans).hasSize(1); + then(spans.get(0).tags().get("http.path")).isEqualTo("/"); } } @@ -88,8 +88,13 @@ public Client client() { } @Bean - public AlwaysSampler defaultSampler() { - return new AlwaysSampler(); + public Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + public Reporter spanReporter() { + return new ArrayListSpanReporter(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java index 24606c4740..08630ff6ea 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/feign/servererrors/FeignClientServerErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,39 +16,34 @@ package org.springframework.cloud.sleuth.instrument.web.client.feign.servererrors; -import com.netflix.hystrix.exception.HystrixRuntimeException; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import brave.Tracing; +import brave.sampler.Sampler; import feign.codec.Decoder; import feign.codec.ErrorDecoder; +import zipkin2.Span; import org.awaitility.Awaitility; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.web.bind.annotation.RequestHeader; @@ -57,11 +52,12 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import com.netflix.hystrix.exception.HystrixRuntimeException; +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * Related to https://github.com/spring-cloud/spring-cloud-sleuth/issues/257 @@ -69,20 +65,19 @@ * @author ryarabori */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = FeignClientServerErrorTests.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = FeignClientServerErrorTests.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = { "spring.application.name=fooservice" , -"feign.hystrix.enabled=true"}) +"feign.hystrix.enabled=true", "spring.sleuth.http.legacy.enabled=true"}) public class FeignClientServerErrorTests { @Autowired TestFeignInterface feignInterface; @Autowired TestFeignWithCustomConfInterface customConfFeignInterface; - @Autowired Listener listener; - @Rule public OutputCapture capture = new OutputCapture(); + @Autowired ArrayListSpanReporter reporter; @Before public void setup() { - this.listener.clear(); - ExceptionUtils.setFail(true); + this.reporter.clear(); } @Test @@ -93,12 +88,13 @@ public void shouldCloseSpanOnInternalServerError() throws InterruptedException { } Awaitility.await().untilAsserted(() -> { - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); - then(new ListOfSpans(this.listener.getEvents())) - .hasASpanWithTagEqualTo(Span.SPAN_ERROR_TAG_NAME, - "Request processing failed; nested exception is java.lang.RuntimeException: Internal Error"); + List spans = this.reporter.getSpans(); + Optional spanWithError = spans.stream() + .filter(span -> span.tags().containsKey("error")).findFirst(); + then(spanWithError.isPresent()).isTrue(); + then(spanWithError.get().tags()) + .containsEntry("error", "500") + .containsEntry("http.status_code", "500"); }); } @@ -110,9 +106,12 @@ public void shouldCloseSpanOnNotFound() throws InterruptedException { } Awaitility.await().untilAsserted(() -> { - then(this.capture.toString()) - .doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + Optional spanWithError = spans.stream() + .filter(span -> span.tags().containsKey("http.status_code")).findFirst(); + then(spanWithError.isPresent()).isTrue(); + then(spanWithError.get().tags()) + .containsEntry("http.status_code", "404"); }); } @@ -124,8 +123,13 @@ public void shouldCloseSpanOnOk() throws InterruptedException { } Awaitility.await().untilAsserted(() -> { - then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(2); + Optional spanWithError = spans.stream() + .filter(span -> span.tags().containsKey("http.method")).findFirst(); + then(spanWithError.isPresent()).isTrue(); + then(spanWithError.get().tags()) + .containsEntry("http.method", "GET"); }); } @@ -137,8 +141,13 @@ public void shouldCloseSpanOnOkWithCustomFeignConfiguration() throws Interrupted } Awaitility.await().untilAsserted(() -> { - then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(2); + Optional spanWithError = spans.stream() + .filter(span -> span.tags().containsKey("http.method")).findFirst(); + then(spanWithError.isPresent()).isTrue(); + then(spanWithError.get().tags()) + .containsEntry("http.method", "GET"); }); } @@ -150,13 +159,18 @@ public void shouldCloseSpanOnNotFoundWithCustomFeignConfiguration() throws Inter } Awaitility.await().untilAsserted(() -> { - then(this.capture.toString()).doesNotContain("Tried to close span but it is not the current span"); - then(ExceptionUtils.getLastException()).isNull(); + List spans = this.reporter.getSpans(); + Optional spanWithError = spans.stream() + .filter(span -> span.tags().containsKey("error")).findFirst(); + then(spanWithError.isPresent()).isTrue(); + then(spanWithError.get().tags()) + .containsEntry("error", "404") + .containsEntry("http.status_code", "404"); }); } @Configuration - @EnableAutoConfiguration + @EnableAutoConfiguration(exclude = TraceWebServletAutoConfiguration.class) @EnableFeignClients @RibbonClients({@RibbonClient(value = "fooservice", configuration = SimpleRibbonClientConfiguration.class), @@ -170,8 +184,8 @@ FooController fooController() { } @Bean - Listener listener() { - return new Listener(); + ArrayListSpanReporter listener() { + return new ArrayListSpanReporter(); } @LoadBalanced @@ -181,7 +195,7 @@ public RestTemplate restTemplate() { } @Bean Sampler testSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } @@ -223,51 +237,32 @@ ErrorDecoder errorDecoder() { } } - @Component - public static class Listener implements SpanReporter { - private List events = new ArrayList<>(); - - public List getEvents() { - return new ArrayList<>(this.events); - } - - public void clear() { - this.events.clear(); - } - - @Override - public void report(Span span) { - this.events.add(span); - } - } - @RestController public static class FooController { - @Autowired - Tracer tracer; + @Autowired Tracing tracer; @RequestMapping("/internalerror") public ResponseEntity internalError( - @RequestHeader(Span.TRACE_ID_NAME) String traceId, - @RequestHeader(Span.SPAN_ID_NAME) String spanId, - @RequestHeader(Span.PARENT_ID_NAME) String parentId) { + @RequestHeader("X-B3-TraceId") String traceId, + @RequestHeader("X-B3-SpanId") String spanId, + @RequestHeader("X-B3-ParentSpanId") String parentId) { throw new RuntimeException("Internal Error"); } @RequestMapping("/notfound") public ResponseEntity notFound( - @RequestHeader(Span.TRACE_ID_NAME) String traceId, - @RequestHeader(Span.SPAN_ID_NAME) String spanId, - @RequestHeader(Span.PARENT_ID_NAME) String parentId) { + @RequestHeader("X-B3-TraceId") String traceId, + @RequestHeader("X-B3-SpanId") String spanId, + @RequestHeader("X-B3-ParentSpanId") String parentId) { return new ResponseEntity<>("not found", HttpStatus.NOT_FOUND); } @RequestMapping("/ok") public ResponseEntity ok( - @RequestHeader(Span.TRACE_ID_NAME) String traceId, - @RequestHeader(Span.SPAN_ID_NAME) String spanId, - @RequestHeader(Span.PARENT_ID_NAME) String parentId) { + @RequestHeader("X-B3-TraceId") String traceId, + @RequestHeader("X-B3-SpanId") String spanId, + @RequestHeader("X-B3-ParentSpanId") String parentId) { return new ResponseEntity<>("ok", HttpStatus.OK); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java index bbc4da19fc..0643755667 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/integration/WebClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.cloud.sleuth.instrument.web.client.integration; +import javax.servlet.http.HttpServletRequest; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; @@ -23,21 +24,25 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import com.netflix.loadbalancer.BaseLoadBalancer; -import com.netflix.loadbalancer.ILoadBalancer; -import com.netflix.loadbalancer.Server; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.propagation.SamplingFlags; +import brave.propagation.TraceContextOrSamplingFlags; +import brave.sampler.Sampler; import junitparams.JUnitParamsRunner; import junitparams.Parameters; +import reactor.core.publisher.Hooks; +import reactor.core.scheduler.Schedulers; +import zipkin2.Annotation; +import zipkin2.reporter.Reporter; import org.apache.commons.logging.LogFactory; import org.assertj.core.api.BDDAssertions; import org.awaitility.Awaitility; import org.junit.After; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; @@ -52,21 +57,11 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; -import org.springframework.cloud.sleuth.Log; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; @@ -82,20 +77,27 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Hooks; -import reactor.core.scheduler.Schedulers; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; import static org.assertj.core.api.Assertions.fail; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(JUnitParamsRunner.class) @SpringBootTest(classes = WebClientTests.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = { + "spring.sleuth.http.legacy.enabled=true", "spring.application.name=fooservice", "feign.hystrix.enabled=false" }) @DirtiesContext public class WebClientTests { + static final String TRACE_ID_NAME = "X-B3-TraceId"; + static final String SPAN_ID_NAME = "X-B3-SpanId"; + static final String SAMPLED_NAME = "X-B3-Sampled"; + static final String PARENT_ID_NAME = "X-B3-ParentSpanId"; private static final org.apache.commons.logging.Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); @@ -106,7 +108,7 @@ public class WebClientTests { @Autowired @LoadBalanced RestTemplate template; @Autowired WebClient webClient; @Autowired WebClient.Builder webClientBuilder; - @Autowired ArrayListSpanAccumulator listener; + @Autowired ArrayListSpanReporter reporter; @Autowired Tracer tracer; @Autowired TestErrorController testErrorController; @Autowired RestTemplateBuilder restTemplateBuilder; @@ -115,9 +117,7 @@ public class WebClientTests { @After public void close() { - ExceptionUtils.setFail(true); - TestSpanContextHolder.removeCurrentSpan(); - this.listener.getSpans().clear(); + this.reporter.clear(); this.testErrorController.clear(); this.fooController.clear(); } @@ -136,19 +136,21 @@ public void shouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresen ResponseEntity response = provider.get(this); Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> { - then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); - then(getHeader(response, Span.SPAN_ID_NAME)).isNull(); - List spans = new ArrayList<>(this.listener.getSpans()); + then(getHeader(response, TRACE_ID_NAME)).isNull(); + then(getHeader(response, SPAN_ID_NAME)).isNull(); + List spans = this.reporter.getSpans(); then(spans).isNotEmpty(); - Optional noTraceSpan = new ArrayList<>(spans).stream() - .filter(span -> "http:/notrace".equals(span.getName()) && !span.tags() + Optional noTraceSpan = new ArrayList<>(spans).stream() + .filter(span -> "http:/notrace".equals(span.name()) && !span.tags() .isEmpty() && span.tags().containsKey("http.path")).findFirst(); then(noTraceSpan.isPresent()).isTrue(); + then(noTraceSpan.get().tags()) + .containsEntry("http.path", "/notrace") + .containsEntry("http.method", "GET"); // TODO: matches cause there is an issue with Feign not providing the full URL at the interceptor level - then(noTraceSpan.get()).matchesATag("http.url", ".*/notrace") - .hasATag("http.path", "/notrace").hasATag("http.method", "GET"); - then(new ListOfSpans(spans)).hasRpcLogsInProperOrder(); + then(noTraceSpan.get().tags().get("http.url")).matches(".*/notrace"); }); + then(Tracing.current().tracer().currentSpan()).isNull(); } Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { @@ -176,16 +178,21 @@ Object[] parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracin @Parameters @SuppressWarnings("unchecked") public void shouldPropagateNotSamplingHeader(ResponseEntityProvider provider) { - Long currentTraceId = 1L; - Long currentParentId = 2L; - this.tracer.continueSpan(Span.builder().traceId(currentTraceId) - .spanId(generatedId()).exportable(false).parent(currentParentId).build()); - - ResponseEntity> response = provider.get(this); - - then(response.getBody().get(Span.TRACE_ID_NAME)).isNotNull(); - then(response.getBody().get(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); - then(this.listener.getSpans()).isNotEmpty(); + Span span = this.tracer.nextSpan( + TraceContextOrSamplingFlags.create(SamplingFlags.NOT_SAMPLED)) + .name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + ResponseEntity> response = provider.get(this); + + then(response.getBody().get(TRACE_ID_NAME.toLowerCase())).isNotNull(); + then(response.getBody().get(SAMPLED_NAME.toLowerCase())).isEqualTo("0"); + } finally { + span.finish(); + } + + then(this.reporter.getSpans()).isEmpty(); + then(Tracing.current().tracer().currentSpan()).isNull(); } Object[] parametersForShouldPropagateNotSamplingHeader() { @@ -200,22 +207,29 @@ Object[] parametersForShouldPropagateNotSamplingHeader() { @SuppressWarnings("unchecked") public void shouldAttachTraceIdWhenCallingAnotherService( ResponseEntityProvider provider) { - spanContinued(); + Span span = this.tracer.nextSpan().name("foo").start(); - ResponseEntity response = provider.get(this); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + ResponseEntity response = provider.get(this); - // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 - // we don't want to respond with any tracing data - then(getHeader(response, Span.SAMPLED_NAME)).isNull(); - then(getHeader(response, Span.TRACE_ID_NAME)).isNull(); - thenRegisteredClientSentAndReceivedEvents(spanWithClientEvents()); + // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 + // we don't want to respond with any tracing data + then(getHeader(response, SAMPLED_NAME)).isNull(); + then(getHeader(response, TRACE_ID_NAME)).isNull(); + } finally { + span.finish(); + } + + then(this.tracer.currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } @Test @SuppressWarnings("unchecked") public void shouldAttachTraceIdWhenCallingAnotherServiceViaWebClient() { - Span span = this.tracer.createSpan("foo"); - try { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { this.webClient.get() .uri("http://localhost:" + this.port + "/traceid") .retrieve() @@ -224,32 +238,10 @@ public void shouldAttachTraceIdWhenCallingAnotherServiceViaWebClient() { assertThatSpanGotContinued(span); } finally { - this.tracer.close(span); - } - then(this.tracer.getCurrentSpan()).isNull(); - thenRegisteredClientSentAndReceivedEvents(spanWithClientEvents()); - } - - private void spanContinued() { - Long currentTraceId = 1L; - Long currentParentId = 2L; - Long currentSpanId = 100L; - this.tracer.continueSpan(Span.builder().traceId(currentTraceId) - .spanId(currentSpanId).parent(currentParentId).build()); - } - - private Span spanWithClientEvents() { - List spans = new ArrayList<>(this.listener.getSpans()); - for(Span span : spans) { - boolean present = span.logs().stream() - .filter(log -> log.getEvent().contains(Span.CLIENT_RECV) - || log.getEvent().contains(Span.CLIENT_SEND)) - .findFirst().isPresent(); - if (present) { - return span; - } + span.finish(); } - return null; + then(this.tracer.currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } Object[] parametersForShouldAttachTraceIdWhenCallingAnotherService() { @@ -263,17 +255,16 @@ Object[] parametersForShouldAttachTraceIdWhenCallingAnotherService() { @Parameters public void shouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody( ResponseEntityProvider provider) { - Long currentTraceId = 1L; - Long currentParentId = 2L; - Long currentSpanId = generatedId(); - Span span = Span.builder().traceId(currentTraceId).spanId(currentSpanId) - .parent(currentParentId).build(); - this.tracer.continueSpan(span); + Span span = this.tracer.nextSpan().name("foo").start(); - provider.get(this); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + provider.get(this); + } finally { + span.finish(); + } - then(this.tracer.getCurrentSpan()).isEqualTo(span); - thenRegisteredClientSentAndReceivedEvents(spanWithClientEvents()); + then(this.tracer.currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } Object[] parametersForShouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody() { @@ -292,16 +283,17 @@ public void shouldCloseSpanWhenErrorControllerGetsCalled() { fail("An exception should be thrown"); } catch (HttpClientErrorException e) { } - then(this.tracer.getCurrentSpan()).isNull(); - Optional storedSpan = this.listener.getSpans().stream() + then(this.tracer.currentSpan()).isNull(); + Optional storedSpan = this.reporter.getSpans().stream() .filter(span -> "404".equals(span.tags().get("http.status_code"))).findFirst(); then(storedSpan.isPresent()).isTrue(); - List spans = new ArrayList<>(this.listener.getSpans()); + List spans = this.reporter.getSpans(); spans.stream() .forEach(span -> { - int initialSize = span.logs().size(); - int distinctSize = span.logs().stream().map(Log::getEvent).distinct().collect(Collectors.toList()).size(); - log.info("logs " + span.logs()); + int initialSize = span.annotations().size(); + int distinctSize = span.annotations().stream().map(Annotation::value).distinct() + .collect(Collectors.toList()).size(); + log.info("logs " + span.annotations()); then(initialSize).as("there are no duplicate log entries").isEqualTo(distinctSize); }); then(this.testErrorController.getSpan()).isNotNull(); @@ -311,38 +303,30 @@ public void shouldCloseSpanWhenErrorControllerGetsCalled() { public void shouldNotExecuteErrorControllerWhenUrlIsFound() { this.template.getForEntity("http://fooservice/notrace", String.class); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.currentSpan()).isNull(); then(this.testErrorController.getSpan()).isNull(); } @Test public void should_wrap_rest_template_builders() { - Span span = this.tracer.createSpan("foo"); - try { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { RestTemplate template = this.restTemplateBuilder.build(); template.getForObject("http://localhost:" + this.port + "/traceid", String.class); assertThatSpanGotContinued(span); } finally { - this.tracer.close(span); + span.finish(); } - then(this.tracer.getCurrentSpan()).isNull(); + then(this.tracer.currentSpan()).isNull(); } private void assertThatSpanGotContinued(Span span) { Span spanInController = this.fooController.getSpan(); BDDAssertions.then(spanInController).isNotNull(); - then(spanInController.getTraceId()).isEqualTo(span.getTraceId()); - } - - private void thenRegisteredClientSentAndReceivedEvents(Span span) { - then(span).hasLoggedAnEvent(Span.CLIENT_RECV); - then(span).hasLoggedAnEvent(Span.CLIENT_SEND); - } - - private Long generatedId() { - return new Random().nextLong(); + then(spanInController.context().traceId()).isEqualTo(span.context().traceId()); } private String getHeader(ResponseEntity response, String name) { @@ -382,19 +366,17 @@ public RestTemplate restTemplate() { return new RestTemplate(); } - @Bean - Sampler testSampler() { - return new AlwaysSampler(); + @Bean Sampler testSampler() { + return Sampler.ALWAYS_SAMPLE; } @Bean - TestErrorController testErrorController(ErrorAttributes errorAttributes, Tracer tracer) { - return new TestErrorController(errorAttributes, tracer); + TestErrorController testErrorController(ErrorAttributes errorAttributes, Tracing tracer) { + return new TestErrorController(errorAttributes, tracer.tracer()); } - @Bean - SpanReporter spanReporter() { - return new ArrayListSpanAccumulator(); + @Bean Reporter spanReporter() { + return new ArrayListSpanReporter(); } @Bean @@ -421,7 +403,7 @@ public TestErrorController(ErrorAttributes errorAttributes, Tracer tracer) { @Override public ResponseEntity> error(HttpServletRequest request) { - this.span = this.tracer.getCurrentSpan(); + this.span = this.tracer.currentSpan(); return super.error(request); } @@ -444,39 +426,35 @@ public static class FooController { @RequestMapping(value = "/notrace", method = RequestMethod.GET) public String notrace( - @RequestHeader(name = Span.TRACE_ID_NAME, required = false) String traceId) { + @RequestHeader(name = TRACE_ID_NAME, required = false) String traceId) { then(traceId).isNotNull(); return "OK"; } @RequestMapping(value = "/traceid", method = RequestMethod.GET) - public String traceId(@RequestHeader(Span.TRACE_ID_NAME) String traceId, - @RequestHeader(Span.SPAN_ID_NAME) String spanId, - @RequestHeader(Span.PARENT_ID_NAME) String parentId) { + public String traceId(@RequestHeader(TRACE_ID_NAME) String traceId, + @RequestHeader(SPAN_ID_NAME) String spanId, + @RequestHeader(PARENT_ID_NAME) String parentId) { then(traceId).isNotEmpty(); then(parentId).isNotEmpty(); then(spanId).isNotEmpty(); - this.span = this.tracer.getCurrentSpan(); + this.span = this.tracer.currentSpan(); return traceId; } @RequestMapping("/") public Map home(@RequestHeader HttpHeaders headers) { - Map map = new HashMap(); + Map map = new HashMap<>(); for (String key : headers.keySet()) { - for (String spanKey : Span.SPAN_HEADERS) - if (key.equalsIgnoreCase(spanKey)) { - key = spanKey; - } map.put(key, headers.getFirst(key)); } return map; } @RequestMapping("/noresponse") - public void noResponse(@RequestHeader(Span.TRACE_ID_NAME) String traceId, - @RequestHeader(Span.SPAN_ID_NAME) String spanId, - @RequestHeader(Span.PARENT_ID_NAME) String parentId) { + public void noResponse(@RequestHeader(TRACE_ID_NAME) String traceId, + @RequestHeader(SPAN_ID_NAME) String spanId, + @RequestHeader(PARENT_ID_NAME) String parentId) { then(traceId).isNotEmpty(); then(parentId).isNotEmpty(); then(spanId).isNotEmpty(); @@ -509,6 +487,7 @@ public ILoadBalancer ribbonLoadBalancer() { @FunctionalInterface interface ResponseEntityProvider { @SuppressWarnings("rawtypes") - ResponseEntity get(WebClientTests webClientTests); + ResponseEntity get( + WebClientTests webClientTests); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/DemoApplication.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/DemoApplication.java index 49334ecd82..0e26b9405c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/DemoApplication.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/DemoApplication.java @@ -3,9 +3,12 @@ import java.util.Arrays; import java.util.List; +import brave.Span; +import brave.Tracer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.annotation.Gateway; import org.springframework.integration.annotation.IntegrationComponentScan; @@ -14,6 +17,7 @@ import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.annotation.Splitter; import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -23,32 +27,61 @@ @IntegrationComponentScan public class DemoApplication { - private static final Log log = LogFactory.getLog(DemoApplication.class); + private static final Log log = LogFactory.getLog( + DemoApplication.class); - @Autowired - Sender sender; + Span httpSpan; + Span splitterSpan; + Span aggregatorSpan; + Span serviceActivatorSpan; + + @Autowired Sender sender; + @Autowired Tracer tracer; @RequestMapping("/greeting") - public Greeting greeting(@RequestParam(defaultValue="Hello World!") String message) { + public Greeting greeting(@RequestParam(defaultValue="Hello World!") String message, @RequestHeader HttpHeaders headers) { this.sender.send(message); + this.httpSpan = this.tracer.currentSpan(); return new Greeting(message); } @Splitter(inputChannel="greetings", outputChannel="words") public List words(String greeting) { + this.splitterSpan = this.tracer.currentSpan(); return Arrays.asList(StringUtils.delimitedListToStringArray(greeting, " ")); } @Aggregator(inputChannel="words", outputChannel="counts") public int count(List greeting) { + this.aggregatorSpan = this.tracer.currentSpan(); return greeting.size(); } @ServiceActivator(inputChannel="counts") public void report(int count) { + this.serviceActivatorSpan = this.tracer.currentSpan(); log.info("Count: " + count); } + public Span getHttpSpan() { + return this.httpSpan; + } + + public Span getSplitterSpan() { + return this.splitterSpan; + } + + public Span getAggregatorSpan() { + return this.aggregatorSpan; + } + + public Span getServiceActivatorSpan() { + return this.serviceActivatorSpan; + } + + public List allSpans() { + return Arrays.asList(this.httpSpan, this.splitterSpan, this.aggregatorSpan, this.serviceActivatorSpan); + } } @MessagingGateway(name = "greeter") diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java index ec35425289..db506c0241 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/multiple/MultipleHopsIntegrationTests.java @@ -2,7 +2,13 @@ import java.net.URI; import java.util.Collections; +import java.util.Objects; +import java.util.stream.Collectors; +import brave.Span; +import brave.Tracer; +import brave.propagation.ExtraFieldPropagation; +import brave.sampler.Sampler; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -11,48 +17,45 @@ import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.instrument.web.TraceFilter; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.client.RestTemplate; -import static org.awaitility.Awaitility.await; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; +import static org.awaitility.Awaitility.await; @RunWith(SpringJUnit4ClassRunner.class) -@TestPropertySource(properties = "spring.application.name=multiplehopsintegrationtests") +@TestPropertySource(properties = { + "spring.application.name=multiplehopsintegrationtests", + "spring.sleuth.http.legacy.enabled=true" +}) @SpringBootTest(classes = MultipleHopsIntegrationTests.Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("baggage") public class MultipleHopsIntegrationTests { @Autowired Tracer tracer; @Autowired TraceKeys traceKeys; - @Autowired TraceFilter traceFilter; - @Autowired ArrayListSpanAccumulator arrayListSpanAccumulator; - @Autowired SpanReporter spanReporter; + @Autowired ArrayListSpanReporter reporter; @Autowired RestTemplate restTemplate; @Autowired Config config; + @Autowired DemoApplication application; @Before public void setup() { - this.arrayListSpanAccumulator.clear(); + this.reporter.clear(); } @Test @@ -60,40 +63,84 @@ public void should_prepare_spans_for_export() throws Exception { this.restTemplate.getForObject("http://localhost:" + this.config.port + "/greeting", String.class); await().atMost(5, SECONDS).untilAsserted(() -> { - then(this.arrayListSpanAccumulator.getSpans().stream().map(Span::getName) - .collect( - toList())).containsAll(asList("http:/greeting", "message:greetings", - "message:words", "message:counts")); + then(this.reporter.getSpans()).hasSize(5); }); + then(this.reporter.getSpans().stream().map(zipkin2.Span::name) + .collect(toList())).containsAll(asList("http:/greeting", "send")); + then(this.reporter.getSpans().stream() + .map(span -> span.tags().get("channel")) + .filter(Objects::nonNull) + .distinct() + .collect(toList())) + .hasSize(3) + .containsAll(asList("send:words", "send:counts", "send:greetings")); } // issue #237 - baggage @Test + // Notes: + // * path-prefix header propagation can't reliably support mixed case, due to http/2 downcasing + // * Since not all tokenizers are case insensitive, mixed case can break correlation + // * Brave's ExtraFieldPropagation downcases due to the above + // * This code should probably test the side-effect on http headers + // * the assumption all correlation fields (baggage) are saved to a span is an interesting one + // * should all correlation fields (baggage) be added to the MDC context? + // * Until below, a configuration item of a correlation field whitelist is needed + // * https://github.com/openzipkin/brave/pull/577 + // * probably needed anyway as an empty whitelist is a nice way to disable the feature public void should_propagate_the_baggage() throws Exception { //tag::baggage[] - Span initialSpan = this.tracer.createSpan("span"); - initialSpan.setBaggageItem("foo", "bar"); - initialSpan.setBaggageItem("UPPER_CASE", "someValue"); - //end::baggage[] + Span initialSpan = this.tracer.nextSpan().name("span").start(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) { + ExtraFieldPropagation.set("foo", "bar"); + ExtraFieldPropagation.set("UPPER_CASE", "someValue"); + //end::baggage[] + + //tag::baggage_tag[] + initialSpan.tag("foo", + ExtraFieldPropagation.get(initialSpan.context(), "foo")); + initialSpan.tag("UPPER_CASE", + ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE")); + //end::baggage_tag[] - try { HttpHeaders headers = new HttpHeaders(); headers.put("baggage-baz", Collections.singletonList("baz")); - headers.put("BAGGAGE-bizarreCASE", Collections.singletonList("value")); + headers.put("baggage-bizarreCASE", Collections.singletonList("value")); RequestEntity requestEntity = new RequestEntity(headers, HttpMethod.GET, URI.create("http://localhost:" + this.config.port + "/greeting")); this.restTemplate.exchange(requestEntity, String.class); - - await().atMost(5, SECONDS).untilAsserted(() -> { - then(new ListOfSpans(this.arrayListSpanAccumulator.getSpans())) - .everySpanHasABaggage("foo", "bar") - .everySpanHasABaggage("upper_case", "someValue") - .anySpanHasABaggage("baz", "baz") - .anySpanHasABaggage("bizarrecase", "value"); - }); } finally { - this.tracer.close(initialSpan); + initialSpan.finish(); } + await().atMost(5, SECONDS).untilAsserted(() -> { + then(this.reporter.getSpans()).isNotEmpty(); + }); + + then(this.application.allSpans()).as("All have foo") + .allMatch(span -> "bar".equals(baggage(span, "foo"))); + then(this.application.allSpans()).as("All have UPPER_CASE") + .allMatch(span -> "someValue".equals(baggage(span, "UPPER_CASE"))); + then(this.application.allSpans() + .stream() + .filter(span -> "baz".equals(baggage(span, "baz"))) + .collect(Collectors.toList())) + .as("Someone has baz") + .isNotEmpty(); + then(this.reporter.getSpans() + .stream() + .filter(span -> span.tags().containsKey("foo") && span.tags().containsKey("UPPER_CASE")) + .collect(Collectors.toList())) + .as("Someone has foo and UPPER_CASE tags") + .isNotEmpty(); + then(this.application.allSpans() + .stream() + .filter(span -> "value".equals(baggage(span, "bizarreCASE"))) + .collect(Collectors.toList())) + .isNotEmpty(); + } + + private String baggage(Span span, String name) { + return ExtraFieldPropagation.get(span.context(), name); } @Configuration @@ -112,12 +159,12 @@ RestTemplate restTemplate() { return new RestTemplate(); } - @Bean ArrayListSpanAccumulator arrayListSpanAccumulator() { - return new ArrayListSpanAccumulator(); + @Bean ArrayListSpanReporter arrayListSpanAccumulator() { + return new ArrayListSpanReporter(); } @Bean Sampler defaultTraceSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java index 85d6ea6316..45c5a6e829 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,27 @@ package org.springframework.cloud.sleuth.instrument.web.view; +import brave.sampler.Sampler; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @EnableAutoConfiguration +@Configuration public class Issue469 extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/welcome").setViewName("welcome"); } + + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + + @Bean Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java index 232f5b98a6..e40eb954e6 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/view/Issue469Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,14 @@ package org.springframework.cloud.sleuth.instrument.web.view; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.util.ExceptionUtils; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.core.env.Environment; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; - import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.BDDAssertions.then; @@ -38,15 +35,10 @@ "spring.mvc.view.suffix=.jsp"}) public class Issue469Tests { - @Autowired Tracer tracer; + @Autowired ArrayListSpanReporter reporter; @Autowired Environment environment; RestTemplate restTemplate = new RestTemplate(); - @Before - public void setup() { - ExceptionUtils.setFail(true); - } - @Test public void should_not_result_in_tracing_exceptions_when_using_view_controllers() throws Exception { try { @@ -57,8 +49,7 @@ public void should_not_result_in_tracing_exceptions_when_using_view_controllers( then(e).hasMessageContaining("404"); } - then(ExceptionUtils.getLastException()).isNull(); - then(this.tracer.getCurrentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } private int port() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java index 8154ced82b..1d081b9cb2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/ApacheHttpClientRibbonRequestCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,29 +16,48 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; import org.apache.http.Header; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class ApacheHttpClientRibbonRequestCustomizerTests { - @Mock Tracer tracer; - @InjectMocks ApacheHttpClientRibbonRequestCustomizer customizer; - Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) - .processId("processId").build(); + private static final String SAMPLED_NAME = "X-B3-Sampled"; + private static final String TRACE_ID_NAME = "X-B3-TraceId"; + private static final String SPAN_ID_NAME = "X-B3-SpanId"; + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); + ApacheHttpClientRibbonRequestCustomizer customizer = + new ApacheHttpClientRibbonRequestCustomizer(this.httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }; @Test public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { @@ -48,44 +67,55 @@ public void should_accept_customizer_when_apache_http_client_is_passed() throws @Test public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { - RequestBuilder requestBuilder = RequestBuilder.create("GET"); - - this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); + this.span = null; + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .sampler(Sampler.NEVER_SAMPLE) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(tracing) + .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) + .build(); + RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); + + new ApacheHttpClientRibbonRequestCustomizer(httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }.customize(requestBuilder); HttpUriRequest request = requestBuilder.build(); - Header header = request.getFirstHeader(Span.SAMPLED_NAME); - then(header.getName()).isEqualTo(Span.SAMPLED_NAME); - then(header.getValue()).isEqualTo(Span.SPAN_NOT_SAMPLED); + Header header = request.getFirstHeader(SAMPLED_NAME); + then(header.getName()).isEqualTo(SAMPLED_NAME); + then(header.getValue()).isEqualTo("0"); } @Test public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - RequestBuilder requestBuilder = RequestBuilder.create("GET"); + RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); HttpUriRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } @Test public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { - RequestBuilder requestBuilder = RequestBuilder.create("GET"); + RequestBuilder requestBuilder = RequestBuilder.create("GET").setUri("http://foo"); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); + this.customizer.customize(requestBuilder); HttpUriRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } - private void thenThereIsAHeaderWithNameAndValue(HttpUriRequest request, String name, String value) { + public void thenThereIsAHeaderWithNameAndValue(HttpUriRequest request, String name, String value) { then(request.getHeaders(name)).hasSize(1); Header header = request.getFirstHeader(name); then(header.getName()).isEqualTo(name); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java index fcc9e33419..99db830fb9 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/OkHttpClientRibbonRequestCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,28 +16,46 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; - +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; import okhttp3.Request; +import org.junit.Test; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class OkHttpClientRibbonRequestCustomizerTests { - @Mock Tracer tracer; - @InjectMocks OkHttpClientRibbonRequestCustomizer customizer; - Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) - .processId("processId").build(); + private static final String SAMPLED_NAME = "X-B3-Sampled"; + private static final String TRACE_ID_NAME = "X-B3-TraceId"; + private static final String SPAN_ID_NAME = "X-B3-SpanId"; + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); + OkHttpClientRibbonRequestCustomizer customizer = + new OkHttpClientRibbonRequestCustomizer(this.httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }; @Test public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { @@ -47,39 +65,53 @@ public void should_accept_customizer_when_apache_http_client_is_passed() throws @Test public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { + this.span = null; + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .sampler(Sampler.NEVER_SAMPLE) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(tracing) + .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) + .build(); Request.Builder requestBuilder = requestBuilder(); - this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); + new OkHttpClientRibbonRequestCustomizer(httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }.customize(requestBuilder); + + this.customizer.customize(requestBuilder); Request request = requestBuilder.build(); - then(request.header(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); + then(request.header(SAMPLED_NAME)).isEqualTo("0"); } @Test public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { Request.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); Request request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } @Test public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { Request.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); + this.customizer.customize(requestBuilder); Request request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } private void thenThereIsAHeaderWithNameAndValue(Request request, String name, String value) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java index 7daa7e5dae..9bd8a163fc 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/RestClientRibbonRequestCustomizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,28 +18,47 @@ import java.util.stream.Collectors; -import com.netflix.client.http.HttpRequest; - +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.sleuth.util.SpanUtil; + +import com.netflix.client.http.HttpRequest; import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak */ -@RunWith(MockitoJUnitRunner.class) public class RestClientRibbonRequestCustomizerTests { - @Mock Tracer tracer; - @InjectMocks RestClientRibbonRequestCustomizer customizer; - Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) - .processId("processId").build(); + private static final String SAMPLED_NAME = "X-B3-Sampled"; + private static final String TRACE_ID_NAME = "X-B3-TraceId"; + private static final String SPAN_ID_NAME = "X-B3-SpanId"; + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + brave.Span span = this.tracing.tracer().nextSpan().name("name").start(); + RestClientRibbonRequestCustomizer customizer = + new RestClientRibbonRequestCustomizer(this.httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }; @Test public void should_accept_customizer_when_apache_http_client_is_passed() throws Exception { @@ -49,39 +68,52 @@ public void should_accept_customizer_when_apache_http_client_is_passed() throws @Test public void should_set_not_sampled_on_the_context_when_there_is_no_span() throws Exception { + this.span = null; + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .sampler(Sampler.NEVER_SAMPLE) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(tracing) + .clientParser(SleuthHttpParserAccessor.getClient(traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(traceKeys, new ExceptionMessageErrorParser())) + .build(); HttpRequest.Builder requestBuilder = requestBuilder(); - this.customizer.inject(null, this.customizer.toSpanTextMap(requestBuilder)); + new RestClientRibbonRequestCustomizer(httpTracing) { + @Override brave.Span getCurrentSpan() { + return span; + } + }.customize(requestBuilder); + + this.customizer.customize(requestBuilder); HttpRequest request = requestBuilder.build(); - then(request.getHttpHeaders().getFirstValue(Span.SAMPLED_NAME)).isEqualTo(Span.SPAN_NOT_SAMPLED); + then(request.getHttpHeaders().getFirstValue(SAMPLED_NAME)).isEqualTo("0"); } @Test public void should_set_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { HttpRequest.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); HttpRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } @Test public void should_not_set_duplicate_tracing_headers_on_the_context_when_there_is_a_span() throws Exception { HttpRequest.Builder requestBuilder = requestBuilder(); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); - this.customizer.inject(this.span, this.customizer.toSpanTextMap(requestBuilder)); + this.customizer.customize(requestBuilder); + this.customizer.customize(requestBuilder); HttpRequest request = requestBuilder.build(); - thenThereIsAHeaderWithNameAndValue(request, Span.SPAN_ID_NAME, "0000000000000001"); - thenThereIsAHeaderWithNameAndValue(request, Span.TRACE_ID_NAME, "0000000000000002"); - thenThereIsAHeaderWithNameAndValue(request, Span.PARENT_ID_NAME, "0000000000000003"); - thenThereIsAHeaderWithNameAndValue(request, Span.PROCESS_ID_NAME, "processId"); + thenThereIsAHeaderWithNameAndValue(request, SPAN_ID_NAME, SpanUtil.idToHex(this.span.context().spanId())); + thenThereIsAHeaderWithNameAndValue(request, TRACE_ID_NAME, this.span.context().traceIdString()); } private void thenThereIsAHeaderWithNameAndValue(HttpRequest request, String name, String value) { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java index 10ba187448..89f00432b7 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilterTests.java @@ -16,9 +16,15 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import java.util.Random; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.List; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -26,18 +32,16 @@ import org.mockito.BDDMockito; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; +import org.springframework.cloud.netflix.zuul.metrics.EmptyTracerFactory; import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.monitoring.TracerFactory; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Dave Syer @@ -46,36 +50,57 @@ @RunWith(MockitoJUnitRunner.class) public class TracePostZuulFilterTests { + @Mock HttpServletRequest httpServletRequest; @Mock HttpServletResponse httpServletResponse; - private DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), - new Random(), new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - - private TracePostZuulFilter filter = new TracePostZuulFilter(this.tracer, new TraceKeys()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + private TracePostZuulFilter filter = new TracePostZuulFilter(this.httpTracing); + RequestContext requestContext = new RequestContext(); @After public void clean() { RequestContext.getCurrentContext().unset(); - TestSpanContextHolder.removeCurrentSpan(); + this.httpTracing.tracing().close(); RequestContext.testSetCurrentContext(null); } @Before public void setup() { - RequestContext requestContext = new RequestContext(); BDDMockito.given(this.httpServletResponse.getStatus()).willReturn(200); - requestContext.setResponse(this.httpServletResponse); - RequestContext.testSetCurrentContext(requestContext); + this.requestContext.setRequest(this.httpServletRequest); + this.requestContext.setResponse(this.httpServletResponse); + RequestContext.testSetCurrentContext(this.requestContext); + TracerFactory.initialize(new EmptyTracerFactory()); } @Test public void filterPublishesEventAndClosesSpan() throws Exception { - Span span = this.tracer.createSpan("http:start"); - this.filter.run(); + Span span = this.tracing.tracer().nextSpan().name("http:start").start(); + BDDMockito.given(this.httpServletRequest + .getAttribute(TracePostZuulFilter.ZUUL_CURRENT_SPAN)).willReturn(span); + BDDMockito.given(this.httpServletResponse.getStatus()).willReturn(456); + + try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) { + this.filter.runFilter(); + } finally { + span.finish(); + } - then(span) - .hasLoggedAnEvent(Span.CLIENT_RECV) - .hasATag("http.status_code", "200"); - then(this.tracer.getCurrentSpan()).isNull(); + List spans = this.reporter.getSpans(); + then(spans).hasSize(1); + // initial span + then(spans.get(0).tags()) + .containsEntry("http.status_code", "456"); + then(spans.get(0).name()).isEqualTo("http:start"); + then(this.tracing.tracer().currentSpan()).isNull(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java index a3b6052674..06211266a8 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TracePreZuulFilterTests.java @@ -16,6 +16,16 @@ package org.springframework.cloud.sleuth.instrument.zuul; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.http.HttpServletRequest; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; +import brave.sampler.Sampler; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.monitoring.MonitoringHelper; import org.junit.After; @@ -25,24 +35,13 @@ import org.mockito.BDDMockito; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.DefaultSpanNamer; +import org.springframework.cloud.sleuth.ErrorParser; import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.instrument.web.ZipkinHttpSpanInjector; -import org.springframework.cloud.sleuth.log.NoOpSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.sampler.NeverSampler; -import org.springframework.cloud.sleuth.trace.DefaultTracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; - -import javax.servlet.http.HttpServletRequest; -import java.util.Random; -import java.util.concurrent.atomic.AtomicReference; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import static org.assertj.core.api.BDDAssertions.then; /** * @author Dave Syer @@ -50,19 +49,30 @@ */ @RunWith(MockitoJUnitRunner.class) public class TracePreZuulFilterTests { + static final String TRACE_ID_NAME = "X-B3-TraceId"; + static final String SAMPLED_NAME = "X-B3-Sampled"; @Mock HttpServletRequest httpServletRequest; - private DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), new NoOpSpanLogger(), new NoOpSpanReporter(), new TraceKeys()); - - private TracePreZuulFilter filter = new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), - new HttpTraceKeysInjector(this.tracer, new TraceKeys()), new ExceptionMessageErrorParser()); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + Tracer tracer = this.tracing.tracer(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); + ErrorParser errorParser = new ExceptionMessageErrorParser(); + + private TracePreZuulFilter filter = new TracePreZuulFilter(this.httpTracing, this.errorParser); @After public void clean() { RequestContext.getCurrentContext().unset(); - TestSpanContextHolder.removeCurrentSpan(); + this.tracing.close(); RequestContext.testSetCurrentContext(null); } @@ -78,70 +88,100 @@ public void setup() { @Test public void filterAddsHeaders() throws Exception { - this.tracer.createSpan("http:start"); + Span span = this.tracer.nextSpan().name("http:start").start(); - this.filter.runFilter(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + this.filter.runFilter(); + } finally { + span.finish(); + } RequestContext ctx = RequestContext.getCurrentContext(); - then(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME)) + then(ctx.getZuulRequestHeaders().get(TRACE_ID_NAME)) .isNotNull(); - then(ctx.getZuulRequestHeaders().get(Span.SAMPLED_NAME)) - .isEqualTo(Span.SPAN_SAMPLED); + then(ctx.getZuulRequestHeaders().get(SAMPLED_NAME)) + .isEqualTo("1"); + then(this.tracer.currentSpan()).isNull(); } @Test public void notSampledIfNotExportable() throws Exception { - this.tracer.createSpan("http:start", NeverSampler.INSTANCE); - - this.filter.runFilter(); + Tracing tracing = Tracing.newBuilder() + .sampler(Sampler.NEVER_SAMPLE) + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + HttpTracing httpTracing = HttpTracing.create(tracing); + this.filter = new TracePreZuulFilter(httpTracing, this.errorParser); + + Span span = tracing.tracer().nextSpan().name("http:start").start(); + + try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(span)) { + this.filter.runFilter(); + } finally { + span.finish(); + } RequestContext ctx = RequestContext.getCurrentContext(); - then(ctx.getZuulRequestHeaders().get(Span.TRACE_ID_NAME)) + then(ctx.getZuulRequestHeaders().get(TRACE_ID_NAME)) .isNotNull(); - then(ctx.getZuulRequestHeaders().get(Span.SAMPLED_NAME)) - .isEqualTo(Span.SPAN_NOT_SAMPLED); + then(ctx.getZuulRequestHeaders().get(SAMPLED_NAME)) + .isEqualTo("0"); + then(this.tracer.currentSpan()).isNull(); } @Test public void shouldCloseSpanWhenExceptionIsThrown() throws Exception { - Span startedSpan = this.tracer.createSpan("http:start"); + Span startedSpan = this.tracer.nextSpan().name("http:start").start(); final AtomicReference span = new AtomicReference<>(); - new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), - new HttpTraceKeysInjector(this.tracer, new TraceKeys()), new ExceptionMessageErrorParser()) { - @Override - public Object run() { - super.run(); - span.set(TracePreZuulFilterTests.this.tracer.getCurrentSpan()); - throw new RuntimeException("foo"); - } - }.runFilter(); + try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(startedSpan)) { + new TracePreZuulFilter(this.httpTracing, this.errorParser) { + @Override + public Object run() { + super.run(); + span.set( + TracePreZuulFilterTests.this.tracer.currentSpan()); + throw new RuntimeException("foo"); + } + }.runFilter(); + } finally { + startedSpan.finish(); + } then(startedSpan).isNotEqualTo(span.get()); - then(span.get().logs()).extracting("event").contains(Span.CLIENT_SEND); - then(span.get()).hasATag("http.method", "GET"); - then(span.get()).hasATag("error", "foo"); - then(this.tracer.getCurrentSpan()).isEqualTo(startedSpan); + List spans = this.reporter.getSpans(); + then(spans).hasSize(2); + // initial span + then(spans.get(0).tags()) + .containsEntry("http.method", "GET") + .containsEntry("error", "foo"); + // span from zuul + then(spans.get(1).name()).isEqualTo("http:start"); + then(this.tracer.currentSpan()).isNull(); } @Test public void shouldNotCloseSpanWhenNoExceptionIsThrown() throws Exception { - Span startedSpan = this.tracer.createSpan("http:start"); + Span startedSpan = this.tracer.nextSpan().name("http:start").start(); final AtomicReference span = new AtomicReference<>(); - new TracePreZuulFilter(this.tracer, new ZipkinHttpSpanInjector(), - new HttpTraceKeysInjector(this.tracer, new TraceKeys()), new ExceptionMessageErrorParser()) { - @Override - public Object run() { - span.set(TracePreZuulFilterTests.this.tracer.getCurrentSpan()); - return super.run(); - } - }.runFilter(); + try (Tracer.SpanInScope ws = tracing.tracer().withSpanInScope(startedSpan)) { + new TracePreZuulFilter(this.httpTracing, this.errorParser) { + @Override + public Object run() { + span.set( + TracePreZuulFilterTests.this.tracer.currentSpan()); + return super.run(); + } + }.runFilter(); + } finally { + startedSpan.finish(); + } then(startedSpan).isNotEqualTo(span.get()); - then(span.get().logs()).extracting("event").contains(Span.CLIENT_SEND); - then(span.get().tags()).containsKey(Span.SPAN_LOCAL_COMPONENT_TAG_NAME); - then(this.tracer.getCurrentSpan()).isEqualTo(span.get()); + then(this.tracer.currentSpan()).isNull(); + then(this.reporter.getSpans()).isNotEmpty(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java index 5326570dee..0e11424d4b 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ public void should_return_a_bean_as_it_is_if_its_not_a_ribbon_command_Factory() @Test public void should_wrap_ribbon_command_factory_in_a_trace_representation() { - then(this.postProcessor.postProcessBeforeInitialization(ribbonCommandFactory, "name")).isInstanceOf(TraceRibbonCommandFactory.class); + then(this.postProcessor.postProcessBeforeInitialization(ribbonCommandFactory, "name")).isInstanceOf( + TraceRibbonCommandFactory.class); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java index 8fb6e853b1..32d176a815 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,29 @@ import java.util.ArrayList; -import com.netflix.zuul.context.RequestContext; +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.propagation.CurrentTraceContext; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.cloud.sleuth.ExceptionMessageErrorParser; +import org.springframework.cloud.sleuth.TraceKeys; +import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.instrument.web.HttpTraceKeysInjector; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; -import static org.mockito.BDDMockito.given; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; +import com.netflix.zuul.context.RequestContext; + +import static org.assertj.core.api.BDDAssertions.then; /** * @author Marcin Grzejszczak @@ -44,35 +48,47 @@ @RunWith(MockitoJUnitRunner.class) public class TraceRibbonCommandFactoryTest { - @Mock Tracer tracer; - HttpTraceKeysInjector httpTraceKeysInjector; + + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + TraceKeys traceKeys = new TraceKeys(); + HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) + .clientParser(SleuthHttpParserAccessor.getClient(this.traceKeys)) + .serverParser(SleuthHttpParserAccessor.getServer(this.traceKeys, new ExceptionMessageErrorParser())) + .build(); @Mock RibbonCommandFactory ribbonCommandFactory; TraceRibbonCommandFactory traceRibbonCommandFactory; - Span span = Span.builder().name("name").spanId(1L).traceId(2L).parent(3L) - .processId("processId").build(); + Span span = this.tracing.tracer().nextSpan().name("name"); @Before @SuppressWarnings({ "deprecation", "unchecked" }) public void setup() { - this.httpTraceKeysInjector = new HttpTraceKeysInjector(this.tracer, new TraceKeys()); this.traceRibbonCommandFactory = new TraceRibbonCommandFactory( - this.ribbonCommandFactory, this.tracer, - httpTraceKeysInjector); - given(this.tracer.getCurrentSpan()).willReturn(span); + this.ribbonCommandFactory, this.httpTracing); } @After public void cleanup() { RequestContext.getCurrentContext().unset(); - TestSpanContextHolder.removeCurrentSpan(); + this.tracing.close(); } @Test public void should_attach_trace_headers_to_the_span() throws Exception { - this.traceRibbonCommandFactory.create(ribbonCommandContext()); + try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(this.span)) { + this.traceRibbonCommandFactory.create(ribbonCommandContext()); + } finally { + this.span.finish(); + } - then(this.span).hasATag("http.method", "GET"); - then(this.span).hasATag("http.url", "http://localhost:1234/foo"); + then(this.reporter.getSpans()).hasSize(1); + zipkin2.Span span = this.reporter.getSpans().get(0); + then(span.tags()) + .containsEntry("http.method", "GET") + .containsEntry("http.url", "http://localhost:1234/foo"); } private RibbonCommandContext ribbonCommandContext() { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java index af3e8e3822..708c80f528 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulIntegrationTests.java @@ -1,13 +1,18 @@ package org.springframework.cloud.sleuth.instrument.zuul; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.util.HashMap; - +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import brave.Span; +import brave.Tracer; +import brave.Tracing; +import brave.sampler.Sampler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.assertj.core.api.BDDAssertions; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,6 +22,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.StaticServerList; @@ -24,15 +30,6 @@ import org.springframework.cloud.netflix.zuul.filters.RouteLocator; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanReporter; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.assertions.ListOfSpans; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ArrayListSpanAccumulator; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpEntity; @@ -50,7 +47,11 @@ import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; -import com.netflix.zuul.context.RequestContext; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) @SpringBootTest(classes = SampleZuulProxyApplication.class, properties = { @@ -58,79 +59,91 @@ @DirtiesContext public class TraceZuulIntegrationTests { - private static final Log log = LogFactory - .getLog(MethodHandles.lookup().lookupClass()); + private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); @Value("${local.server.port}") private int port; @Autowired - Tracer tracer; + Tracing tracing; @Autowired - ArrayListSpanAccumulator spanAccumulator; + ArrayListSpanReporter spanAccumulator; @Autowired RestTemplate restTemplate; @Before @After public void cleanup() { - TestSpanContextHolder.removeCurrentSpan(); - RequestContext.getCurrentContext().unset(); - this.spanAccumulator.getSpans().clear(); + this.spanAccumulator.clear(); } @Test public void should_close_span_when_routing_to_service_via_discovery() { - Span span = this.tracer.createSpan("new_span"); - log.info("Started span " + span); - ResponseEntity result = this.restTemplate.exchange( - "http://localhost:" + this.port + "/simple/foo", HttpMethod.GET, - new HttpEntity<>((Void) null), String.class); - - this.tracer.close(span); - - then(result.getStatusCode()).isEqualTo(HttpStatus.OK); - then(result.getBody()).isEqualTo("Hello world"); - then(this.tracer.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.spanAccumulator.getSpans())) - .everyParentIdHasItsCorrespondingSpan() - .clientSideSpanWithNameHasTags("http:/simple/foo", - TestTag.tag().tag("http.method", "GET") - .tag("http.status_code", "200") - .tag("http.path", "/simple/foo")); - then(ExceptionUtils.getLastException()).isNull(); + Span span = this.tracing.tracer().nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) { + ResponseEntity result = this.restTemplate.exchange( + "http://localhost:" + this.port + "/simple/foo", HttpMethod.GET, + new HttpEntity<>((Void) null), String.class); + + then(result.getStatusCode()).isEqualTo(HttpStatus.OK); + then(result.getBody()).isEqualTo("Hello world"); + } catch (Exception e) { + log.error(e); + throw e; + } finally { + span.finish(); + } + + then(this.tracing.tracer().currentSpan()).isNull(); + List spans = this.spanAccumulator.getSpans(); + then(spans).isNotEmpty(); + everySpanHasTheSameTraceId(spans); + everyParentIdHasItsCorrespondingSpan(spans); } @Test public void should_close_span_when_routing_to_service_via_discovery_to_a_non_existent_url() { - Span span = this.tracer.createSpan("new_span"); - log.info("Started span " + span); - ResponseEntity result = this.restTemplate.exchange( - "http://localhost:" + this.port + "/simple/nonExistentUrl", - HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - - this.tracer.close(span); - - then(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - then(this.tracer.getCurrentSpan()).isNull(); - then(new ListOfSpans(this.spanAccumulator.getSpans())) - .everyParentIdHasItsCorrespondingSpan() - .clientSideSpanWithNameHasTags("http:/simple/nonExistentUrl", - TestTag.tag().tag("http.method", "GET") - .tag("http.status_code", "404") - .tag("http.path", "/simple/nonExistentUrl")); - then(ExceptionUtils.getLastException()).isNull(); - } + Span span = this.tracing.tracer().nextSpan().name("foo").start(); - private static class TestTag extends HashMap { + try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(span)) { + ResponseEntity result = this.restTemplate.exchange( + "http://localhost:" + this.port + "/simple/nonExistentUrl", + HttpMethod.GET, new HttpEntity<>((Void) null), String.class); - public static TestTag tag() { - return new TestTag(); + then(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } finally { + span.finish(); } - public TestTag tag(String key, String value) { - put(key, value); - return this; - } + then(this.tracing.tracer().currentSpan()).isNull(); + List spans = this.spanAccumulator.getSpans(); + then(spans).isNotEmpty(); + everySpanHasTheSameTraceId(spans); + everyParentIdHasItsCorrespondingSpan(spans); + } + + void everySpanHasTheSameTraceId(List actual) { + BDDAssertions.assertThat(actual).isNotNull(); + List traceIds = actual.stream() + .map(zipkin2.Span::traceId).distinct() + .collect(toList()); + log.info("Stored traceids " + traceIds); + assertThat(traceIds).hasSize(1); + } + + void everyParentIdHasItsCorrespondingSpan(List actual) { + BDDAssertions.assertThat(actual).isNotNull(); + List parentSpanIds = actual.stream().map(zipkin2.Span::parentId) + .filter(Objects::nonNull).collect(toList()); + List spanIds = actual.stream() + .map(zipkin2.Span::id).distinct() + .collect(toList()); + List difference = new ArrayList<>(parentSpanIds); + difference.removeAll(spanIds); + log.info("Difference between parent ids and span ids " + + difference.stream().map(span -> "id as hex [" + span + "]").collect( + joining("\n"))); + assertThat(spanIds).containsAll(parentSpanIds); } } @@ -159,8 +172,8 @@ RouteLocator routeLocator(DiscoveryClient discoveryClient, } @Bean - SpanReporter testSpanReporter() { - return new ArrayListSpanAccumulator(); + ArrayListSpanReporter testSpanReporter() { + return new ArrayListSpanReporter(); } @Bean @@ -179,7 +192,7 @@ public void handleError(ClientHttpResponse response) throws IOException { @Bean Sampler alwaysSampler() { - return new AlwaysSampler(); + return Sampler.ALWAYS_SAMPLE; } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/issues/issue634/Issue634Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/issues/issue634/Issue634Tests.java index 2492e30842..557faf5e46 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/issues/issue634/Issue634Tests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/issues/issue634/Issue634Tests.java @@ -4,9 +4,9 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.netflix.zuul.ZuulFilter; -import org.junit.After; -import org.junit.Before; +import brave.Tracing; +import brave.http.HttpTracing; +import brave.sampler.Sampler; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -14,15 +14,15 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.trace.TestSpanContextHolder; -import org.springframework.cloud.sleuth.util.ExceptionUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; +import com.netflix.zuul.ZuulFilter; + import static org.assertj.core.api.BDDAssertions.then; @RunWith(SpringRunner.class) @@ -35,19 +35,9 @@ public class Issue634Tests { @LocalServerPort int port; - @Autowired Tracer tracer; + @Autowired HttpTracing tracer; @Autowired TraceCheckingSpanFilter filter; - - @Before - public void setup() { - TestSpanContextHolder.removeCurrentSpan(); - ExceptionUtils.setFail(true); - } - - @After - public void close() { - TestSpanContextHolder.removeCurrentSpan(); - } + @Autowired ArrayListSpanReporter reporter; @Test public void should_reuse_custom_feign_client() { @@ -56,11 +46,12 @@ public void should_reuse_custom_feign_client() { .getForEntity("http://localhost:" + this.port + "/display/ddd", String.class); - then(this.tracer.getCurrentSpan()).isNull(); - then(ExceptionUtils.getLastException()).isNull(); + then(this.tracer.tracing().tracer().currentSpan()).isNull(); } + then(new HashSet<>(this.filter.counter.values())) .describedAs("trace id should not be reused from thread").hasSize(1); + then(this.reporter.getSpans()).isNotEmpty(); } } @@ -69,19 +60,26 @@ public void should_reuse_custom_feign_client() { @Configuration class TestZuulApplication { - @Bean - TraceCheckingSpanFilter traceCheckingSpanFilter(Tracer tracer) { + @Bean TraceCheckingSpanFilter traceCheckingSpanFilter(Tracing tracer) { return new TraceCheckingSpanFilter(tracer); } + @Bean Sampler sampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean ArrayListSpanReporter reporter() { + return new ArrayListSpanReporter(); + } + } class TraceCheckingSpanFilter extends ZuulFilter { - private final Tracer tracer; + private final Tracing tracer; final Map counter = new ConcurrentHashMap<>(); - TraceCheckingSpanFilter(Tracer tracer) { + TraceCheckingSpanFilter(Tracing tracer) { this.tracer = tracer; } @@ -98,7 +96,7 @@ class TraceCheckingSpanFilter extends ZuulFilter { } @Override public Object run() { - long trace = this.tracer.getCurrentSpan().getTraceId(); + long trace = this.tracer.tracer().currentSpan().context().traceId(); Integer integer = this.counter.getOrDefault(trace, 0); counter.put(trace, integer + 1); return null; diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java index d3ac752cbe..fa542031de 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/log/Slf4JSpanLoggerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,164 +16,66 @@ package org.springframework.cloud.sleuth.log; +import brave.Span; +import brave.Tracing; +import brave.propagation.CurrentTraceContext; +import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; -import org.slf4j.Logger; import org.slf4j.MDC; -import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; /** * @author Marcin Grzejszczak */ public class Slf4JSpanLoggerTest { - Span spanWithNameToBeExcluded = Span.builder().name("Hystrix").build(); - Span spanWithNameNotToBeExcluded = Span.builder().name("Aspect").parent(3L).build(); - String nameExcludingPattern = "^.*Hystrix.*$"; - Logger log = Mockito.mock(Logger.class); - Slf4jSpanLogger slf4JSpanLogger = new Slf4jSpanLogger(this.nameExcludingPattern, this.log); + ArrayListSpanReporter reporter = new ArrayListSpanReporter(); + Tracing tracing = Tracing.newBuilder() + .currentTraceContext(CurrentTraceContext.Default.create()) + .spanReporter(this.reporter) + .build(); + + Span span = this.tracing.tracer().nextSpan().name("span").start(); + Slf4jCurrentTraceContext slf4jCurrentTraceContext = + new Slf4jCurrentTraceContext(CurrentTraceContext.Default.create()); @Before + @After public void setup() { MDC.clear(); - given(log.isTraceEnabled()).willReturn(true); - } - - @Test - public void when_start_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { - Span span = Span.builder().traceId(1L).spanId(2L).build(); - - this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); - - assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); - } - - @Test - public void when_start_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { - Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); - - this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, span); - - assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); - } - - @Test - public void when_continued_event_arrived_should_add_64bit_trace_id_to_MDC() throws Exception { - Span span = Span.builder().traceId(1L).spanId(2L).build(); - - this.slf4JSpanLogger.logContinuedSpan(span); - - assertThat(MDC.get("X-B3-TraceId")).isEqualTo("0000000000000001"); - } - - @Test - public void when_continued_event_arrived_should_add_128bit_trace_id_to_MDC() throws Exception { - Span span = Span.builder().traceIdHigh(1L).traceId(2L).spanId(3L).build(); - - this.slf4JSpanLogger.logContinuedSpan(span); - - assertThat(MDC.get("X-B3-TraceId")).isEqualTo("00000000000000010000000000000002"); - } - - @Test - public void should_log_when_start_event_arrived_and_pattern_doesnt_match_span_name() throws Exception { - this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, - this.spanWithNameNotToBeExcluded); - - then(this.log).should(times(2)).trace(anyString(), any(Span.class)); - } - - @Test - public void should_log_once_when_start_event_arrived_and_pattern_matches_only_parent_span_name() throws Exception { - this.slf4JSpanLogger.logStartedSpan(this.spanWithNameToBeExcluded, - this.spanWithNameNotToBeExcluded); - - then(this.log).should().trace(anyString(), any(Span.class)); - } - - @Test - public void should_log_when_continue_event_arrived_and_pattern_doesnt_match_span_name() throws Exception { - this.slf4JSpanLogger.logContinuedSpan( - this.spanWithNameNotToBeExcluded); - - then(this.log).should().trace(anyString(), any(Span.class)); - } - - @Test - public void should_not_log_when_continue_event_arrived_and_pattern_matches_name() throws Exception { - this.slf4JSpanLogger.logContinuedSpan(this.spanWithNameToBeExcluded); - - then(this.log).should(never()).trace(anyString(), any(Span.class)); - } - - @Test - public void should_log_when_close_event_arrived_and_pattern_doesnt_match_span_name() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(null, this.spanWithNameNotToBeExcluded); - - then(this.log).should().trace(anyString(), any(Span.class)); - } - - @Test - public void should_log_both_spans_when_their_names_dont_match_pattern() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameNotToBeExcluded, - this.spanWithNameNotToBeExcluded); - - then(this.log).should(times(2)).trace(anyString(), any(Span.class)); - } - - @Test - public void should_not_log_any_spans_if_both_match_pattern() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameToBeExcluded, - this.spanWithNameToBeExcluded); - - then(this.log).should(never()).trace(anyString(), any(Span.class)); } @Test - public void should_log_only_current_span_if_parent_span_name_matches_pattern() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameNotToBeExcluded, - this.spanWithNameToBeExcluded); + public void should_set_entries_to_mdc_from_span() throws Exception { + CurrentTraceContext.Scope scope = this.slf4jCurrentTraceContext + .newScope(this.span.context()); - then(this.log).should().trace(anyString(), any(Span.class)); - } + assertThat(MDC.get("X-B3-TraceId")).isEqualTo(span.context().traceIdString()); + assertThat(MDC.get("traceId")).isEqualTo(span.context().traceIdString()); - @Test - public void should_log_only_current_span_if_there_is_no_parent() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(null, this.spanWithNameNotToBeExcluded); + scope.close(); - then(this.log).should().trace(anyString(), any(Span.class)); + assertThat(MDC.get("X-B3-TraceId")).isNullOrEmpty(); + assertThat(MDC.get("traceId")).isNullOrEmpty(); } @Test - public void should_set_mdc_entry_for_parent_when_starting() throws Exception { - this.slf4JSpanLogger.logStartedSpan(this.spanWithNameNotToBeExcluded, - this.spanWithNameNotToBeExcluded); + public void should_remove_entries_from_mdc_from_null_span() throws Exception { + MDC.put("X-B3-TraceId", "A"); + MDC.put("traceId", "A"); - assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo( - Span.idToHex(this.spanWithNameNotToBeExcluded.getSpanId())); - } + CurrentTraceContext.Scope scope = this.slf4jCurrentTraceContext + .newScope(null); - @Test - public void should_set_mdc_entry_for_parent_when_continuing() throws Exception { - this.slf4JSpanLogger.logContinuedSpan(this.spanWithNameNotToBeExcluded); + assertThat(MDC.get("X-B3-TraceId")).isNullOrEmpty(); + assertThat(MDC.get("traceId")).isNullOrEmpty(); - assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); - } - - @Test - public void should_set_mdc_entry_for_parent_when_stopping() throws Exception { - this.slf4JSpanLogger.logStoppedSpan(this.spanWithNameNotToBeExcluded, - this.spanWithNameNotToBeExcluded); + scope.close(); - assertThat(MDC.get(Span.PARENT_ID_NAME)).isEqualTo(Span.idToHex(3L)); + assertThat(MDC.get("X-B3-TraceId")).isEqualTo("A"); + assertThat(MDC.get("traceId")).isEqualTo("A"); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java deleted file mode 100644 index ebb619e4a2..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/IsTracingSamplerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.sampler; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.SpanAccessor; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.BDDMockito.given; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(MockitoJUnitRunner.class) -public class IsTracingSamplerTest { - - @Mock SpanAccessor spanAccessor; - @Mock Span span; - @InjectMocks IsTracingSampler isTracingSampler; - - @Test - public void should_sample_when_tracing_is_on() throws Exception { - given(this.spanAccessor.isTracing()).willReturn(true); - - then(this.isTracingSampler.isSampled(this.span)).isTrue(); - } - - @Test - public void should_not_sample_when_tracing_is_off() throws Exception { - given(this.spanAccessor.isTracing()).willReturn(false); - - then(this.isTracingSampler.isSampled(this.span)).isFalse(); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSamplerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSamplerTests.java similarity index 64% rename from spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSamplerTests.java rename to spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSamplerTests.java index 91aba1ad58..37949f62c2 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/PercentageBasedSamplerTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/sampler/ProbabilityBasedSamplerTests.java @@ -2,25 +2,25 @@ import java.util.Random; -import org.junit.Before; +import brave.sampler.Sampler; import org.junit.Test; -import org.springframework.cloud.sleuth.Sampler; -import org.springframework.cloud.sleuth.Span; import static org.assertj.core.api.BDDAssertions.then; -public class PercentageBasedSamplerTests { - +/** + * @author Marcin Grzejszczak + */ +public class ProbabilityBasedSamplerTests { + SamplerProperties samplerConfiguration = new SamplerProperties(); - private Span span; private static Random RANDOM = new Random(); @Test public void should_pass_all_samples_when_config_has_1_percentage() throws Exception { - this.samplerConfiguration.setPercentage(1f); + this.samplerConfiguration.setProbability(1f); for (int i = 0; i < 10; i++) { - then(new PercentageBasedSampler(this.samplerConfiguration).isSampled(this.span)) + then(new ProbabilityBasedSampler(this.samplerConfiguration).isSampled(RANDOM.nextLong())) .isTrue(); } @@ -29,10 +29,10 @@ public void should_pass_all_samples_when_config_has_1_percentage() throws Except @Test public void should_reject_all_samples_when_config_has_0_percentage() throws Exception { - this.samplerConfiguration.setPercentage(0f); + this.samplerConfiguration.setProbability(0f); for (int i = 0; i < 10; i++) { - then(new PercentageBasedSampler(this.samplerConfiguration).isSampled(this.span)) + then(new ProbabilityBasedSampler(this.samplerConfiguration).isSampled(RANDOM.nextLong())) .isFalse(); } } @@ -41,7 +41,7 @@ public void should_reject_all_samples_when_config_has_0_percentage() public void should_pass_given_percent_of_samples() throws Exception { int numberOfIterations = 1000; float percentage = 1f; - this.samplerConfiguration.setPercentage(percentage); + this.samplerConfiguration.setProbability(percentage); int numberOfSampledElements = countNumberOfSampledElements(numberOfIterations); @@ -52,7 +52,7 @@ public void should_pass_given_percent_of_samples() throws Exception { public void should_pass_given_percent_of_samples_with_fractional_element() throws Exception { int numberOfIterations = 1000; float percentage = 0.35f; - this.samplerConfiguration.setPercentage(percentage); + this.samplerConfiguration.setProbability(percentage); int numberOfSampledElements = countNumberOfSampledElements(numberOfIterations); @@ -61,22 +61,12 @@ public void should_pass_given_percent_of_samples_with_fractional_element() throw } private int countNumberOfSampledElements(int numberOfIterations) { - Sampler sampler = new PercentageBasedSampler(this.samplerConfiguration); + Sampler sampler = new ProbabilityBasedSampler(this.samplerConfiguration); int passedCounter = 0; for (int i = 0; i < numberOfIterations; i++) { - boolean passed = sampler.isSampled(newSpan()); + boolean passed = sampler.isSampled(RANDOM.nextLong()); passedCounter = passedCounter + (passed ? 1 : 0); } return passedCounter; } - - @Before - public void setupSpan() { - this.span = newSpan(); - } - - Span newSpan() { - return Span.builder().traceId(RANDOM.nextLong()).build(); - } - } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/CustomLoggerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/CustomLoggerTests.java deleted file mode 100644 index 8a9246b271..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/CustomLoggerTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.trace; - -import java.lang.invoke.MethodHandles; -import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.Test; -import org.springframework.cloud.sleuth.DefaultSpanNamer; -import org.springframework.cloud.sleuth.NoOpSpanReporter; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.TraceKeys; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.log.Slf4jSpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; - -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class CustomLoggerTests { - - CustomSpanLogger spanLogger = new CustomSpanLogger(); - Tracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - new DefaultSpanNamer(), this.spanLogger, new NoOpSpanReporter(), new TraceKeys()); - - // https://github.com/spring-cloud/spring-cloud-sleuth/issues/547 - // https://github.com/spring-cloud/spring-cloud-sleuth/issues/605 - @Test - public void should_pass_baggage_to_custom_span_logger() { - Span parent = Span.builder().spanId(1).traceId(2).baggage("FOO", "bar").build(); - parent.setBaggageItem("bAz", "baz"); - - Span child = this.tracer.createSpan("child", parent); - - then(child).hasBaggageItem("foo", "bar"); - then(child.getBaggageItem("FoO")).isEqualTo("bar"); - then(this.spanLogger.called.get()).isTrue(); - TestSpanContextHolder.removeCurrentSpan(); - } -} - -class CustomSpanLogger extends Slf4jSpanLogger { - - private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass()); - - final AtomicBoolean called = new AtomicBoolean(); - - public CustomSpanLogger() { - super(""); - } - - @Override public void logStartedSpan(Span parent, Span span) { - super.logStartedSpan(parent, span); - called.set(true); - then(parent).hasBaggageItem("foo", "bar"); - then(span).hasBaggageItem("foo", "bar"); - then(span).hasBaggageItem("baz", "baz"); - log.info("Baggage item foo=>bar found"); - log.info("Parent's baggage: " + parent.getBaggage()); - log.info("Child's baggage: " + span.getBaggage()); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java deleted file mode 100644 index b5bbec69e8..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/DefaultTracerTests.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.trace; - -import java.util.Date; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.springframework.boot.test.rule.OutputCapture; -import org.springframework.cloud.sleuth.*; -import org.springframework.cloud.sleuth.log.SpanLogger; -import org.springframework.cloud.sleuth.sampler.AlwaysSampler; -import org.springframework.cloud.sleuth.sampler.NeverSampler; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -import static org.springframework.cloud.sleuth.assertions.SleuthAssertions.then; - -/** - * @author Spencer Gibb - */ -public class DefaultTracerTests { - - public static final String CREATE_SIMPLE_TRACE_SPAN_NAME = "createSimpleTrace"; - public static final String CREATE_SIMPLE_TRACE = "http" + ":" + - CREATE_SIMPLE_TRACE_SPAN_NAME; - public static final String IMPORTANT_WORK_1 = "http:important work 1"; - public static final String IMPORTANT_WORK_2 = "http:important work 2"; - public static final int NUM_SPANS = 3; - private SpanNamer spanNamer = new DefaultSpanNamer(); - private SpanLogger spanLogger = Mockito.mock(SpanLogger.class); - private SpanReporter spanReporter = Mockito.mock(SpanReporter.class); - @Rule public OutputCapture capture = new OutputCapture(); - - @Before - public void setup() { - TestSpanContextHolder.removeCurrentSpan(); - } - - @After - public void clean() { - TestSpanContextHolder.removeCurrentSpan(); - } - - @Test - public void tracingWorks() { - DefaultTracer tracer = new DefaultTracer(NeverSampler.INSTANCE, new Random(), - new DefaultSpanNamer(), this.spanLogger, this.spanReporter, new TraceKeys()); - - Span span = tracer.createSpan(CREATE_SIMPLE_TRACE, new AlwaysSampler()); - try { - importantWork1(tracer); - } - finally { - tracer.close(span); - } - - verify(this.spanLogger,atLeastOnce()).logStartedSpan(Mockito.any(Span.class), Mockito.any(Span.class)); - verify(this.spanReporter, times(NUM_SPANS)) - .report(Mockito.any(Span.class)); - - ArgumentCaptor captor = ArgumentCaptor - .forClass(Span.class); - verify(this.spanReporter, atLeast(NUM_SPANS)).report(captor.capture()); - - List spans = new ArrayList<>(captor.getAllValues()); - - assertThat(spans).hasSize(NUM_SPANS); - - Span root = assertSpan(spans, null, CREATE_SIMPLE_TRACE); - Span child = assertSpan(spans, root.getSpanId(), IMPORTANT_WORK_1); - Span grandChild = assertSpan(spans, child.getSpanId(), IMPORTANT_WORK_2); - - List gen4 = findSpans(spans, grandChild.getSpanId()); - assertThat(gen4).isEmpty(); - } - - @Test - public void nonExportable() { - DefaultTracer tracer = new DefaultTracer(NeverSampler.INSTANCE, new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = tracer.createSpan(CREATE_SIMPLE_TRACE); - assertThat(span.isExportable()).isFalse(); - } - - @Test - public void exportable() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = tracer.createSpan(CREATE_SIMPLE_TRACE); - assertThat(span.isExportable()).isTrue(); - } - - @Test - public void exportableInheritedFromParent() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = tracer.createSpan(CREATE_SIMPLE_TRACE, NeverSampler.INSTANCE); - assertThat(span.isExportable()).isFalse(); - Span child = tracer.createSpan(CREATE_SIMPLE_TRACE_SPAN_NAME + "/child", span); - assertThat(child.isExportable()).isFalse(); - } - - @Test - public void parentNotRemovedIfActiveOnJoin() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span parent = tracer.createSpan(CREATE_SIMPLE_TRACE); - Span span = tracer.createSpan(IMPORTANT_WORK_1, parent); - tracer.close(span); - assertThat(tracer.getCurrentSpan()).isEqualTo(parent); - } - - @Test - public void parentRemovedIfNotActiveOnJoin() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span parent = Span.builder().name(CREATE_SIMPLE_TRACE).traceId(1L).spanId(1L) - .build(); - Span span = tracer.createSpan(IMPORTANT_WORK_1, parent); - tracer.close(span); - assertThat(tracer.getCurrentSpan()).isNull(); - } - - @Test - public void grandParentRestoredAfterAutoClose() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span grandParent = tracer.createSpan(CREATE_SIMPLE_TRACE); - Span parent = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) - .build(); - Span span = tracer.createSpan(IMPORTANT_WORK_2, parent); - tracer.close(span); - assertThat(tracer.getCurrentSpan()).isEqualTo(grandParent); - } - - @Test - public void samplingIsRanAgainstChildSpanWhenThereIsNoParent() { - DefaultTracer tracer = new DefaultTracer(new NeverSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - - Span span = tracer.createChild(null, "childName"); - - assertThat(span.isExportable()).isFalse(); - } - - @Test - public void shouldUpdateLogsInSpanWhenItGetsContinued() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) - .build(); - Span continuedSpan = tracer.continueSpan(span); - - tracer.addTag("key", "value"); - continuedSpan.logEvent("event"); - - then(span).hasATag("key", "value").hasLoggedAnEvent("event"); - then(continuedSpan).hasATag("key", "value").hasLoggedAnEvent("event"); - then(span).isEqualTo(continuedSpan); - tracer.close(span); - } - - @Test - public void shouldPropagateBaggageFromParentToChild() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span parent = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) - .baggage("foo", "bar").build(); - Span child = tracer.createSpan("child", parent); - - then(parent).hasBaggageItem("foo", "bar"); - then(child).hasBaggageItem("foo", "bar"); - } - - @Test - public void shouldPropagateBaggageToContinuedSpan() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span parent = Span.builder().name(IMPORTANT_WORK_1).traceId(1L).spanId(1L) - .baggage("foo", "bar").build(); - Span continuedSpan = tracer.continueSpan(parent); - - parent.setBaggageItem("baz1", "baz1"); - continuedSpan.setBaggageItem("baz2", "baz2"); - - then(parent).hasBaggageItem("foo", "bar") - .hasBaggageItem("baz1", "baz1") - .hasBaggageItem("baz2", "baz2"); - then(continuedSpan).hasBaggageItem("foo", "bar") - .hasBaggageItem("baz1", "baz1") - .hasBaggageItem("baz2", "baz2"); - then(parent).isEqualTo(continuedSpan); - } - - @Test - public void shouldCreateNewSpanWithShortenedName() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = tracer.createSpan(bigName()); - - then(span.getName().length()).isEqualTo(50); - tracer.close(span); - } - - - /** - * To support conversion to Amazon trace IDs, the first 32 bits of the trace ID are epoch seconds. - */ - @Test - public void creates128bitTraceIdWithEncodedTimestamp() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, true, new TraceKeys()); - Span span = tracer.createSpan(bigName()); - String traceId = span.traceIdString(); - long epochSeconds = Long.parseLong(traceId.substring(0, 8), 16); - then(new Date(epochSeconds * 1000)).isToday(); - tracer.close(span); - } - - @Test - public void shouldCreateChildOfSpanWithShortenedName() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = Span.builder().name(bigName()).traceId(1L).spanId(1L).build(); - - Span child = tracer.createChild(span, bigName()); - - then(child.getName().length()).isEqualTo(50); - } - - @Test - public void shouldNotProduceAWarningMessageWhenThereIsNoSpanInContextAndWeDetachASpan() { - DefaultTracer tracer = new DefaultTracer(new AlwaysSampler(), new Random(), - this.spanNamer, this.spanLogger, this.spanReporter, new TraceKeys()); - Span span = Span.builder().name("foo").traceId(1L).spanId(1L).build(); - - Span child = tracer.detach(span); - - then(child).isNull(); - then(this.capture.toString()).doesNotContain("Tried to detach trace span"); - } - - private String bigName() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 60; i++) { - sb.append("a"); - } - return sb.toString(); - } - - private Span assertSpan(List spans, Long parentId, String name) { - List found = findSpans(spans, parentId); - assertThat(found).as("More than one span with parentId %s", parentId).hasSize(1); - Span span = found.get(0); - assertThat(span.getName()).as("Name should be %s", name).isEqualTo(name); - return span; - } - - private List findSpans(List spans, Long parentId) { - List found = new ArrayList<>(); - for (Span span : spans) { - if (parentId == null && span.getParents().isEmpty()) { - found.add(span); - } - else if (span.getParents().contains(parentId)) { - found.add(span); - } - } - return found; - } - - private void importantWork1(Tracer tracer) { - Span cur = tracer.createSpan(IMPORTANT_WORK_1); - try { - Thread.sleep((long) (50 * Math.random())); - importantWork2(tracer); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - finally { - tracer.close(cur); - } - } - - private void importantWork2(Tracer tracer) { - Span cur = tracer.createSpan(IMPORTANT_WORK_2); - try { - Thread.sleep((long) (50 * Math.random())); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - finally { - tracer.close(cur); - } - } - -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/TestSpanContextHolder.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/TestSpanContextHolder.java deleted file mode 100644 index 2b633421b4..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/trace/TestSpanContextHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2015 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.trace; - -import org.springframework.cloud.sleuth.Span; - -/** - * Test utility to access the thread context provided by the (private) - * {@link SpanContextHolder}. - * - * @author Dave Syer - */ -public class TestSpanContextHolder { - - public static Span getCurrentSpan() { - return SpanContextHolder.getCurrentSpan(); - } - - public static void removeCurrentSpan() { - SpanContextHolder.removeCurrentSpan(); - } - - public static boolean isTracing() { - return SpanContextHolder.isTracing(); - } -} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java deleted file mode 100644 index 83f96e7ac6..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/ExceptionUtilsTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.sleuth.util; - -import org.junit.After; -import org.junit.Test; - -import static org.assertj.core.api.BDDAssertions.then; -import static org.junit.Assert.fail; - -/** - * @author Marcin Grzejszczak - */ -public class ExceptionUtilsTest { - - @After - public void clean() { - ExceptionUtils.setFail(true); - } - - @Test - public void should_not_throw_exception_if_flag_is_disabled() throws Exception { - ExceptionUtils.setFail(false); - - ExceptionUtils.warn("warning message"); - } - - @Test - public void should_throw_exception_if_flag_is_disabled() throws Exception { - ExceptionUtils.setFail(true); - - try { - ExceptionUtils.warn("warning message"); - fail("should throw an exception"); - } catch (Exception e) { - then(e).isInstanceOf(IllegalStateException.class); - } - } - - @Test - public void should_print_error_message_when_there_is_one() throws Exception { - Throwable e = new RuntimeException("Foo"); - - String message = ExceptionUtils.getExceptionMessage(e); - - then(message).isEqualTo("Foo"); - } - - @Test - public void should_print_to_string_when_there_is_no_error() throws Exception { - Throwable e = new RuntimeException(); - - String message = ExceptionUtils.getExceptionMessage(e); - - then(message).isEqualTo("java.lang.RuntimeException"); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java index 6a30544231..649f130e3e 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanNameUtilTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,32 +16,32 @@ package org.springframework.cloud.sleuth.util; +import org.assertj.core.api.BDDAssertions; import org.junit.Test; -import org.springframework.cloud.sleuth.assertions.SleuthAssertions; public class SpanNameUtilTests { @Test public void should_convert_a_name_in_hyphen_based_notation() throws Exception { - SleuthAssertions.then(SpanNameUtil.toLowerHyphen("aMethodNameInCamelCaseNotation")) + BDDAssertions.then(SpanNameUtil.toLowerHyphen("aMethodNameInCamelCaseNotation")) .isEqualTo("a-method-name-in-camel-case-notation"); } @Test public void should_convert_a_class_name_in_hyphen_based_notation() throws Exception { - SleuthAssertions.then(SpanNameUtil.toLowerHyphen("MySuperClassName")) + BDDAssertions.then(SpanNameUtil.toLowerHyphen("MySuperClassName")) .isEqualTo("my-super-class-name"); } @Test public void should_not_shorten_a_name_that_is_below_max_threshold() throws Exception { - SleuthAssertions.then(SpanNameUtil.shorten("someName")) + BDDAssertions.then(SpanNameUtil.shorten("someName")) .isEqualTo("someName"); } @Test public void should_not_shorten_a_name_that_is_null() throws Exception { - SleuthAssertions.then(SpanNameUtil.shorten(null)).isNull(); + BDDAssertions.then(SpanNameUtil.shorten(null)).isNull(); } @Test @@ -50,7 +50,7 @@ public void should_shorten_a_name_that_is_above_max_threshold() throws Exception for (int i = 0; i < 60; i++) { sb.append("a"); } - SleuthAssertions.then(SpanNameUtil.shorten(sb.toString()).length()) + BDDAssertions.then(SpanNameUtil.shorten(sb.toString()).length()) .isEqualTo(SpanNameUtil.MAX_NAME_LENGTH); } } \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanUtil.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanUtil.java new file mode 100644 index 0000000000..029b20a549 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/SpanUtil.java @@ -0,0 +1,37 @@ +package org.springframework.cloud.sleuth.util; + +/** + * @author Marcin Grzejszczak + * @since + */ +public class SpanUtil { + + /** + * Represents given long id as 16-character lower-hex string + */ + public static String idToHex(long id) { + char[] data = new char[16]; + writeHexLong(data, 0, id); + return new String(data); + } + + /** Inspired by {@code okio.Buffer.writeLong} */ + static void writeHexLong(char[] data, int pos, long v) { + writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff)); + writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff)); + writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff)); + writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff)); + writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff)); + writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff)); + writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff)); + writeHexByte(data, pos + 14, (byte) (v & 0xff)); + } + + static void writeHexByte(char[] data, int pos, byte b) { + data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf]; + data[pos + 1] = HEX_DIGITS[b & 0xf]; + } + + static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java deleted file mode 100644 index 4f86694398..0000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/util/TextMapUtilTests.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.cloud.sleuth.util; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.junit.Test; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -public class TextMapUtilTests { - - @Test - public void should_convert_an_iterable_to_a_caseinsensitive_map() throws Exception { - List> iterable = new ArrayList<>(); - iterable.add(new AbstractMap.SimpleEntry<>("foo", "bar")); - - Map map = TextMapUtil.asMap(iterable); - - then(map) - .containsKey("foo") - .containsKey("FOO") - .containsKey("FoO") - .contains(new AbstractMap.SimpleEntry<>("foo", "bar")); - } - -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/resources/application-baggage.yml b/spring-cloud-sleuth-core/src/test/resources/application-baggage.yml new file mode 100644 index 0000000000..a17ab80d60 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/resources/application-baggage.yml @@ -0,0 +1,7 @@ +spring.sleuth: + baggage-keys: + - baz + - bizarrecase + propagation-keys: + - foo + - upper_case \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/resources/application.yml b/spring-cloud-sleuth-core/src/test/resources/application.yml index 746e7a03c7..1c2a7397e8 100644 --- a/spring-cloud-sleuth-core/src/test/resources/application.yml +++ b/spring-cloud-sleuth-core/src/test/resources/application.yml @@ -16,5 +16,8 @@ spring.sleuth.scheduled.skipPattern: "^org.*TestBeanWithScheduledMethodToBeIgnor # comma separated list of matchers spring.sleuth.rxjava.schedulers.ignoredthreads: HystixMetricPoller,^MyCustomThread.*$,^RxComputation.*$ +logging.level.org.springframework.cloud: DEBUG + + #disable hibernate by default spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-sleuth-dependencies/pom.xml b/spring-cloud-sleuth-dependencies/pom.xml index 7d1b4f2ac8..58dc830b98 100644 --- a/spring-cloud-sleuth-dependencies/pom.xml +++ b/spring-cloud-sleuth-dependencies/pom.xml @@ -14,9 +14,10 @@ spring-cloud-sleuth-dependencies Spring Cloud Sleuth Dependencies - 2.4.1 + 2.4.3 1.1.2 - 2.2.0 + 2.3.0 + 4.13.3 @@ -65,11 +66,33 @@ spring-cloud-starter-sleuth ${project.version} + + + io.zipkin.brave + brave + ${brave.version} + + + io.zipkin.brave + brave-context-log4j2 + ${brave.version} + + + io.zipkin.brave + brave-instrumentation-spring-web + ${brave.version} + + + io.zipkin.brave + brave-instrumentation-spring-webmvc + ${brave.version} + + io.zipkin.java zipkin - 2.4.0 + 2.4.2 io.zipkin.zipkin2 diff --git a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml index fdb45d41d1..8cf0355c36 100644 --- a/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml +++ b/spring-cloud-sleuth-samples/spring-cloud-sleuth-sample-feign/pom.xml @@ -1,5 +1,5 @@ -