|
|
|
|
@@ -10,15 +10,16 @@
|
|
|
|
|
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/{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
|
|
|
|
|
|
|
|
|
|
Spring Cloud Sleuth is a distributed tracing tool for Spring Cloud. It borrows from http://research.google.com/pubs/pub36356.html[Dapper], https://github.com/openzipkin/zipkin[Zipkin], and http://htrace.incubator.apache.org/[HTrace].
|
|
|
|
|
|
|
|
|
|
=== Quick Start
|
|
|
|
|
|
|
|
|
|
Add sleuth to the classpath of a Spring Boot application (see below
|
|
|
|
|
for Maven and Gradle examples), and you will see the correlation data being
|
|
|
|
|
collected in logs, as long as you are logging requests.
|
|
|
|
|
Add sleuth to the classpath of a Spring Boot application (see "`<<sleuth-adding-project>>`" for Maven and Gradle examples), and you can see the correlation data being collected in logs, as long as you are logging requests.
|
|
|
|
|
|
|
|
|
|
Example HTTP handler:
|
|
|
|
|
For example, consider the following HTTP handler:
|
|
|
|
|
|
|
|
|
|
[source,java]
|
|
|
|
|
----
|
|
|
|
|
@@ -34,13 +35,12 @@ public class DemoController {
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
You will see the calls to `home()` traced in the logs and in Zipkin, if that is configured.
|
|
|
|
|
If you add that handler to a controller, you can see the calls to `home()` being traced in the logs and in Zipkin, if Zipkin is configured.
|
|
|
|
|
|
|
|
|
|
NOTE: instead of logging the request in the handler explicitly, you
|
|
|
|
|
could set `logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG`
|
|
|
|
|
NOTE: Instead of logging the request in the handler explicitly, you
|
|
|
|
|
could set `logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG`.
|
|
|
|
|
|
|
|
|
|
NOTE: Set `spring.application.name=bar` (for instance) to see the
|
|
|
|
|
service name as well as the trace and span ids.
|
|
|
|
|
NOTE: Set `spring.application.name=myService` (for instance) to see the service name as well as the trace and span IDs.
|
|
|
|
|
|
|
|
|
|
:branch: master
|
|
|
|
|
|
|
|
|
|
@@ -52,140 +52,137 @@ Spring Cloud Sleuth implements a distributed tracing solution for http://cloud.s
|
|
|
|
|
|
|
|
|
|
Spring Cloud Sleuth borrows http://research.google.com/pubs/pub36356.html[Dapper's] terminology.
|
|
|
|
|
|
|
|
|
|
*Span:* The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an
|
|
|
|
|
RPC. Span's are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span
|
|
|
|
|
is a part of. Spans also have other data, such as descriptions, timestamped events, key-value
|
|
|
|
|
annotations (tags), the ID of the span that caused them, and process ID's (normally IP address).
|
|
|
|
|
*Span*: The basic unit of work. For example, sending an RPC is a new span, as is sending a response to an RPC.
|
|
|
|
|
Spans are identified by a unique 64-bit ID for the span and another 64-bit ID for the trace the span is a part of.
|
|
|
|
|
Spans also have other data, such as descriptions, timestamped events, key-value annotations (tags), the ID of the span that caused them, and process IDs (normally IP addresses).
|
|
|
|
|
|
|
|
|
|
Spans are started and stopped, and they keep track of their timing information. Once you create a
|
|
|
|
|
span, you must stop it at some point in the future.
|
|
|
|
|
Spans can be started and stopped, and they keep track of their timing information.
|
|
|
|
|
Once you create a span, you must stop it at some point in the future.
|
|
|
|
|
|
|
|
|
|
TIP: The initial span that starts a trace is called a `root span`. The value of span id
|
|
|
|
|
of that span is equal to trace id.
|
|
|
|
|
TIP: The initial span that starts a trace is called a `root span`. The value of the ID
|
|
|
|
|
of that span is equal to the 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.
|
|
|
|
|
*Trace:* A set of spans forming a tree-like structure.
|
|
|
|
|
For example, if you run 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. 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
|
|
|
|
|
*Annotation:* Used to record the 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, where
|
|
|
|
|
the request started, and where it ended. For learning purposes,
|
|
|
|
|
however, we 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.
|
|
|
|
|
If one subtracts the cs timestamp from this timestamp one will receive the network latency.
|
|
|
|
|
- *ss* - Server Sent - 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.
|
|
|
|
|
- *cr* - Client Received - 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.
|
|
|
|
|
* *cs*: Client Sent. The client has made a request. This annotation indicates the start of the span.
|
|
|
|
|
* *sr*: Server Received: The server side got the request and started processing it.
|
|
|
|
|
Subtracting the `cs` timestamp from this timestamp reveals the network latency.
|
|
|
|
|
* *ss*: Server Sent. Annotated upon completion of request processing (when the response got sent back to the client).
|
|
|
|
|
Subtracting the `sr` timestamp from this timestamp reveals the time needed by the server side to process the request.
|
|
|
|
|
* *cr*> Client Received. Signifies the end of the span.
|
|
|
|
|
The client has successfully received the response from the server side.
|
|
|
|
|
Subtracting the `cs` timestamp from this timestamp reveals the whole time needed by the client to receive the response from the server.
|
|
|
|
|
|
|
|
|
|
Visualization of what *Span* and *Trace* will look in a system together with the Zipkin annotations:
|
|
|
|
|
The following image shows how *Span* and *Trace* look in a system, together with the Zipkin annotations:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/trace-id.png[Trace Info propagation]
|
|
|
|
|
|
|
|
|
|
Each color of a note signifies a span (7 spans - from *A* to *G*). If you have such information in the note:
|
|
|
|
|
Each color of a note signifies a span (there are seven spans - from *A* to *G*).
|
|
|
|
|
Consider the following note:
|
|
|
|
|
|
|
|
|
|
[source]
|
|
|
|
|
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*. Also, the
|
|
|
|
|
*Client Sent* event took place.
|
|
|
|
|
This note indicats thatthe current span has *Trace Id* set to *X* and *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:
|
|
|
|
|
The following image shows how parent-child relationships of spans look:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/parents.png[Parent child relationship]
|
|
|
|
|
|
|
|
|
|
=== Purpose
|
|
|
|
|
|
|
|
|
|
In the following sections the example from the image above will be taken into consideration.
|
|
|
|
|
The following sections refer to the example shown in the preceding image.
|
|
|
|
|
|
|
|
|
|
==== Distributed tracing with Zipkin
|
|
|
|
|
==== Distributed Tracing with Zipkin
|
|
|
|
|
|
|
|
|
|
Altogether there are *7 spans* . If you go to traces in Zipkin you will see this number in the second trace:
|
|
|
|
|
This example has seven spans.
|
|
|
|
|
If you go to traces in Zipkin, you can see this number in the second trace, as shown in the following image:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-traces.png[Traces]
|
|
|
|
|
|
|
|
|
|
However if you pick a particular trace then you will see *4 spans*:
|
|
|
|
|
However, if you pick a particular trace, you can see four spans, as shown in the following image:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-ui.png[Traces Info propagation]
|
|
|
|
|
|
|
|
|
|
NOTE: When picking a particular trace you will see merged spans. That means that if there were 2 spans sent to
|
|
|
|
|
Zipkin with Server Received and Server Sent / Client Received and Client Sent
|
|
|
|
|
annotations then they will presented as a single span.
|
|
|
|
|
NOTE: When you pick a particular trace, you see merged spans.
|
|
|
|
|
That means that, if there were two spans sent to Zipkin with Server Received and Server Sent or Client Received and Client Sent annotations, they are presented as a single span.
|
|
|
|
|
|
|
|
|
|
Why is there a difference between the 7 and 4 spans in this case?
|
|
|
|
|
Why is there a difference between the seven and four 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. 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. 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. 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.
|
|
|
|
|
* Two spans come from the `http:/start` span. It has the Server Received (`sr`) and Server Sent (`ss`) annotations.
|
|
|
|
|
* Two 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 the `service1` side.
|
|
|
|
|
Server Received (`sr`) and Server Sent (`ss`) events took place on the `service2` side.
|
|
|
|
|
These two spans form one logical span related to an RPC call.
|
|
|
|
|
* Two 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 the `service2` side.
|
|
|
|
|
The Server Received (`sr`) and Server Sent (`ss`) events took place on the `service3` side.
|
|
|
|
|
These two spans form one logical span related to an RPC call.
|
|
|
|
|
* Two 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 the `service2` side.
|
|
|
|
|
Server Received (`sr`) and Server Sent (`ss`) events took place on the `service4` side.
|
|
|
|
|
These two spans form one 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`
|
|
|
|
|
calling `service3` and *2* from `service2` calling `service4`. Altogether *7* spans.
|
|
|
|
|
So, if we count the physical spans, we have one from `http:/start`, two from `service1` calling `service2`, two from `service2`
|
|
|
|
|
calling `service3`, and two from `service2` calling `service4`. In sum, we have a total of seven spans.
|
|
|
|
|
|
|
|
|
|
Logically we see the information of *Total Spans: 4* because we have *1* span related to the incoming request
|
|
|
|
|
to `service1` and *3* spans related to RPC calls.
|
|
|
|
|
Logically, we see the information of four total Spans because we have one span related to the incoming request
|
|
|
|
|
to `service1` and three spans related to RPC calls.
|
|
|
|
|
|
|
|
|
|
==== Visualizing errors
|
|
|
|
|
|
|
|
|
|
Zipkin allows you to visualize errors in your trace. When an exception was thrown and wasn't caught then we're
|
|
|
|
|
setting proper tags on the span which Zipkin can properly colorize. You could see in the list of traces one
|
|
|
|
|
trace that was in red color. That's because there was an exception thrown.
|
|
|
|
|
Zipkin lets you visualize errors in your trace.
|
|
|
|
|
When an exception was thrown and was not caught, we set proper tags on the span, which Zipkin can then properly colorize.
|
|
|
|
|
You could see in the list of traces one trace that is red. That appears because an exception was thrown.
|
|
|
|
|
|
|
|
|
|
If you click that trace then you'll see a similar picture
|
|
|
|
|
If you click that trace, you see a similar picture, as follows:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-traces.png[Error Traces]
|
|
|
|
|
|
|
|
|
|
Then if you click on one of the spans you'll see the following
|
|
|
|
|
If you then click on one of the spans, you see the following
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/zipkin-error-trace-screenshot.png[Error Traces Info propagation]
|
|
|
|
|
|
|
|
|
|
As you can see you can easily see the reason for an error and the whole stacktrace related to it.
|
|
|
|
|
The span shows the reason for the error and the whole stack trace related to it.
|
|
|
|
|
|
|
|
|
|
==== Distributed tracing with Brave
|
|
|
|
|
==== 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.
|
|
|
|
|
Starting with version `2.0.0`, Spring Cloud Sleuth uses https://github.com/openzipkin/brave[Brave] as the tracing library.
|
|
|
|
|
Consequently, Sleuth no longer takes care of storing the context but 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`.
|
|
|
|
|
Due to the fact that Sleuth had different naming and tagging conventions than Brave, we decided to follow Brave's conventions from now on.
|
|
|
|
|
However, if you want to use the legacy Sleuth approaches, you can 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!"]
|
|
|
|
|
.Click the Pivotal Web Services icon to see it live!
|
|
|
|
|
[caption="Click the 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:
|
|
|
|
|
The dependency graph in Zipkin should resemble the following image:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/dependencies.png[Dependencies]
|
|
|
|
|
|
|
|
|
|
.Click Pivotal Web Services icon to see it live!
|
|
|
|
|
[caption="Click Pivotal Web Services icon to see it live!"]
|
|
|
|
|
.Click the Pivotal Web Services icon to see it live!
|
|
|
|
|
[caption="Click the 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
|
|
|
|
|
|
|
|
|
|
When grepping the logs of those four applications by trace id equal to e.g. `2485ec27856c56f4` one would get the following:
|
|
|
|
|
When using grep to read the logs of those four applications by scanning for a trace ID equal to (for example) `2485ec27856c56f4`, you get output resembling the following:
|
|
|
|
|
|
|
|
|
|
[source]
|
|
|
|
|
service1.log:2016-02-26 11:15:47.561 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Hello from service1. Calling service2
|
|
|
|
|
@@ -196,13 +193,12 @@ service4.log:2016-02-26 11:15:48.134 INFO [service4,2485ec27856c56f4,1b1845262f
|
|
|
|
|
service2.log:2016-02-26 11:15:48.156 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service4 [Hello from service4]
|
|
|
|
|
service1.log:2016-02-26 11:15:48.182 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]
|
|
|
|
|
|
|
|
|
|
If you're using a log aggregating tool like https://www.elastic.co/products/kibana[Kibana],
|
|
|
|
|
http://www.splunk.com/[Splunk] etc. you can order the events that took place. An example of
|
|
|
|
|
Kibana would look like this:
|
|
|
|
|
If you use a log aggregating tool (such as https://www.elastic.co/products/kibana[Kibana], http://www.splunk.com/[Splunk], and others), you can order the events that took place.
|
|
|
|
|
An example from Kibana would resemble the following image:
|
|
|
|
|
|
|
|
|
|
image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-sleuth/{branch}/docs/src/main/asciidoc/images/kibana.png[Log correlation with Kibana]
|
|
|
|
|
|
|
|
|
|
If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash] here is the Grok pattern for Logstash:
|
|
|
|
|
If you want to use https://www.elastic.co/guide/en/logstash/current/index.html[Logstash], the following listing shows the Grok pattern for Logstash:
|
|
|
|
|
|
|
|
|
|
[source]
|
|
|
|
|
filter {
|
|
|
|
|
@@ -212,7 +208,7 @@ filter {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NOTE: If you want to use Grok together with the logs from Cloud Foundry you have to use this pattern:
|
|
|
|
|
NOTE: If you want to use Grok together with the logs from Cloud Foundry, you have to use the following pattern:
|
|
|
|
|
[source]
|
|
|
|
|
filter {
|
|
|
|
|
# pattern matching logback pattern
|
|
|
|
|
@@ -223,21 +219,17 @@ filter {
|
|
|
|
|
|
|
|
|
|
===== JSON Logback with Logstash
|
|
|
|
|
|
|
|
|
|
Often you do not want to store your logs in a text file but in a JSON file that Logstash can immediately pick. To do that you have to do the following (for readability
|
|
|
|
|
we're passing the dependencies in the `groupId:artifactId:version` notation.
|
|
|
|
|
Often, you do not want to store your logs in a text file but in a JSON file that Logstash can immediately pick.
|
|
|
|
|
To do so, you have to do the following (for readability, we pass the dependencies in the `groupId:artifactId:version` notation).
|
|
|
|
|
|
|
|
|
|
*Dependencies setup*
|
|
|
|
|
*Dependencies Setup*
|
|
|
|
|
|
|
|
|
|
- Ensure that Logback is on the classpath (`ch.qos.logback:logback-core`)
|
|
|
|
|
- Add Logstash Logback encode - example for version `4.6` : `net.logstash.logback:logstash-logback-encoder:4.6`
|
|
|
|
|
. Ensure that Logback is on the classpath (`ch.qos.logback:logback-core`).
|
|
|
|
|
. Add Logstash Logback encode. For example, to use version `4.6`, add `net.logstash.logback:logstash-logback-encoder:4.6`.
|
|
|
|
|
|
|
|
|
|
*Logback setup*
|
|
|
|
|
*Logback Setup*
|
|
|
|
|
|
|
|
|
|
Below you can find an example of a Logback configuration (file named https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]) that:
|
|
|
|
|
|
|
|
|
|
- logs information from the application in a JSON format to a `build/${spring.application.name}.json` file
|
|
|
|
|
- has commented out two additional appenders - console and standard log file
|
|
|
|
|
- has the same logging pattern as the one presented in the previous section
|
|
|
|
|
Consider the following example of a Logback configuration file (named https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml[logback-spring.xml]).
|
|
|
|
|
|
|
|
|
|
[source,xml]
|
|
|
|
|
-----
|
|
|
|
|
@@ -319,24 +311,30 @@ Below you can find an example of a Logback configuration (file named https://git
|
|
|
|
|
</configuration>
|
|
|
|
|
-----
|
|
|
|
|
|
|
|
|
|
NOTE: If you're using a custom `logback-spring.xml` then you have to pass the `spring.application.name` in
|
|
|
|
|
`bootstrap` instead of `application` property file. Otherwise your custom logback file won't read the property properly.
|
|
|
|
|
That Logback configuration file:
|
|
|
|
|
|
|
|
|
|
* Logs information from the application in a JSON format to a `build/${spring.application.name}.json` file.
|
|
|
|
|
* Has commented out two additional appenders: console and standard log file.
|
|
|
|
|
* Has the same logging pattern as the one presented in the previous section.
|
|
|
|
|
|
|
|
|
|
NOTE: If you use a custom `logback-spring.xml`, you must pass the `spring.application.name` in the `bootstrap` rather than the `application` property file.
|
|
|
|
|
Otherwise, your custom logback file does not properly read the property.
|
|
|
|
|
|
|
|
|
|
==== Propagating Span Context
|
|
|
|
|
|
|
|
|
|
The span context is the state that must get propagated to any child Spans across process boundaries.
|
|
|
|
|
The span context is the state that must get propagated to any child spans across process boundaries.
|
|
|
|
|
Part of the Span Context is the Baggage. The trace and span IDs are a required part of the span context.
|
|
|
|
|
Baggage is an optional part.
|
|
|
|
|
|
|
|
|
|
Baggage is a set of key:value pairs stored in the span context. Baggage travels together with the trace
|
|
|
|
|
and is attached to every span. Spring Cloud Sleuth will understand that a header is baggage related if the HTTP
|
|
|
|
|
header is prefixed with `baggage-` and for messaging it starts with `baggage_`.
|
|
|
|
|
Baggage is a set of key:value pairs stored in the span context.
|
|
|
|
|
Baggage travels together with the trace and is attached to every span.
|
|
|
|
|
Spring Cloud Sleuth understands that a header is baggage-related if the HTTP header is prefixed with `baggage-` and, for messaging, it starts with `baggage_`.
|
|
|
|
|
|
|
|
|
|
IMPORTANT: There's currently no limitation of the count or size of baggage items. However, keep in mind that
|
|
|
|
|
too many can decrease system throughput or increase RPC latency. In extreme cases, it could crash the app due
|
|
|
|
|
to exceeding transport-level message or header capacity.
|
|
|
|
|
IMPORTANT: There is currently no limitation of the count or size of baggage items.
|
|
|
|
|
However, keep in mind that too many can decrease system throughput or increase RPC latency.
|
|
|
|
|
In extreme cases, too much baggage can crash the application, due to exceeding transport-level message or header capacity.
|
|
|
|
|
|
|
|
|
|
Example of setting baggage on a span:
|
|
|
|
|
The following example shows setting baggage on a span:
|
|
|
|
|
|
|
|
|
|
[source,java]
|
|
|
|
|
----
|
|
|
|
|
@@ -347,17 +345,19 @@ try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) {
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
===== Baggage vs. Span Tags
|
|
|
|
|
===== Baggage versus Span Tags
|
|
|
|
|
|
|
|
|
|
Baggage travels with the trace (i.e. every child span contains the baggage of its parent). Zipkin has no knowledge of
|
|
|
|
|
baggage and will not even receive that information.
|
|
|
|
|
Baggage travels with the trace (every child span contains the baggage of its parent).
|
|
|
|
|
Zipkin has no knowledge of baggage and does not receive that information.
|
|
|
|
|
|
|
|
|
|
Tags are attached to a specific span - they are presented for that particular span only. However you
|
|
|
|
|
can search by tag to find the trace, where there exists a span having the searched tag value.
|
|
|
|
|
Tags are attached to a specific span. In other words, they are presented only for that particular span.
|
|
|
|
|
However, you can search by tag to find the trace, assuming a span having the searched tag value exists.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
If you want to be able to lookup a span based on baggage, you should add a corresponding entry as a tag in the root span.
|
|
|
|
|
|
|
|
|
|
IMPORTANT: Remember that the span needs to be in scope!
|
|
|
|
|
IMPORTANT: The span must be in scope.
|
|
|
|
|
|
|
|
|
|
The following listing shows integration tests that use baggage:
|
|
|
|
|
|
|
|
|
|
[source,java]
|
|
|
|
|
----
|
|
|
|
|
@@ -367,15 +367,18 @@ initialSpan.tag("UPPER_CASE",
|
|
|
|
|
ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));
|
|
|
|
|
----
|
|
|
|
|
|
|
|
|
|
=== Adding to the project
|
|
|
|
|
[[sleuth-adding-project]]
|
|
|
|
|
=== Adding Sleuth to the Project
|
|
|
|
|
|
|
|
|
|
IMPORTANT: To ensure that your application name is properly displayed in Zipkin
|
|
|
|
|
set the `spring.application.name` property in `bootstrap.yml`.
|
|
|
|
|
This section addresses how to add Sleuth to your project with either Maven or Gradle.
|
|
|
|
|
|
|
|
|
|
IMPORTANT: To ensure that your application name is properly displayed in Zipkin, set the `spring.application.name` property in `bootstrap.yml`.
|
|
|
|
|
|
|
|
|
|
==== Only Sleuth (log correlation)
|
|
|
|
|
|
|
|
|
|
If you want to profit only from Spring Cloud Sleuth without the Zipkin integration just add
|
|
|
|
|
the `spring-cloud-starter-sleuth` module to your project.
|
|
|
|
|
If you want to use only Spring Cloud Sleuth without the Zipkin integration, add the `spring-cloud-starter-sleuth` module to your project.
|
|
|
|
|
|
|
|
|
|
The following example shows how to add Sleuth with Maven:
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
|
|
|
|
.Maven
|
|
|
|
|
@@ -397,9 +400,10 @@ the `spring-cloud-starter-sleuth` module to your project.
|
|
|
|
|
<artifactId>spring-cloud-starter-sleuth</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-sleuth`
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-sleuth`.
|
|
|
|
|
|
|
|
|
|
The following example shows how to add Sleuth with Gradle:
|
|
|
|
|
|
|
|
|
|
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
|
|
|
|
.Gradle
|
|
|
|
|
@@ -414,13 +418,14 @@ dependencies { <2>
|
|
|
|
|
compile "org.springframework.cloud:spring-cloud-starter-sleuth"
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-sleuth`
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-sleuth`.
|
|
|
|
|
|
|
|
|
|
==== Sleuth with Zipkin via HTTP
|
|
|
|
|
|
|
|
|
|
If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` dependency.
|
|
|
|
|
If you want both Sleuth and Zipkin, add the `spring-cloud-starter-zipkin` dependency.
|
|
|
|
|
|
|
|
|
|
The following example shows how to do so for Maven:
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
|
|
|
|
.Maven
|
|
|
|
|
@@ -442,9 +447,10 @@ If you want both Sleuth and Zipkin just add the `spring-cloud-starter-zipkin` de
|
|
|
|
|
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`.
|
|
|
|
|
|
|
|
|
|
The following example shows how to do so for Gradle:
|
|
|
|
|
|
|
|
|
|
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
|
|
|
|
.Gradle
|
|
|
|
|
@@ -459,20 +465,21 @@ dependencies { <2>
|
|
|
|
|
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`.
|
|
|
|
|
|
|
|
|
|
==== Sleuth with Zipkin via RabbitMQ or Kafka
|
|
|
|
|
==== Sleuth with Zipkin over RabbitMQ or Kafka
|
|
|
|
|
|
|
|
|
|
If you want to use RabbitMQ or Kafka instead of http, add the `spring-rabbit` or `spring-kafka`
|
|
|
|
|
dependencies. The default destination name is `zipkin`.
|
|
|
|
|
If you want to use RabbitMQ or Kafka instead of HTTP, add the `spring-rabbit` or `spring-kafka` dependency.
|
|
|
|
|
The default destination name is `zipkin`.
|
|
|
|
|
|
|
|
|
|
_Note: `spring-cloud-sleuth-stream` is deprecated and incompatible with these destinations_
|
|
|
|
|
CAUTION: `spring-cloud-sleuth-stream` is deprecated and incompatible with these destinations.
|
|
|
|
|
|
|
|
|
|
If you want Sleuth over RabbitMQ add the `spring-cloud-starter-zipkin` and `spring-rabbit`
|
|
|
|
|
If you want Sleuth over RabbitMQ, add the `spring-cloud-starter-zipkin` and `spring-rabbit`
|
|
|
|
|
dependencies.
|
|
|
|
|
|
|
|
|
|
The following example shows how to do so for Gradle:
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
|
|
|
|
.Maven
|
|
|
|
|
----
|
|
|
|
|
@@ -497,10 +504,9 @@ dependencies.
|
|
|
|
|
<artifactId>spring-rabbit</artifactId>
|
|
|
|
|
</dependency>
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin` - that way all dependent dependencies will be downloaded
|
|
|
|
|
<3> To automatically configure rabbit, simply add the spring-rabbit dependency
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`. That way, all nested dependencies get downloaded.
|
|
|
|
|
<3> To automatically configure RabbitMQ, add the `spring-rabbit` dependency.
|
|
|
|
|
|
|
|
|
|
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
|
|
|
|
.Gradle
|
|
|
|
|
@@ -516,14 +522,13 @@ dependencies {
|
|
|
|
|
compile "org.springframework.amqp:spring-rabbit" <3>
|
|
|
|
|
}
|
|
|
|
|
----
|
|
|
|
|
<1> In order not to pick versions by yourself it's much better if you add the dependency management via
|
|
|
|
|
the Spring BOM
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin` - that way all dependent dependencies will be downloaded
|
|
|
|
|
<3> To automatically configure rabbit, simply add the spring-rabbit dependency
|
|
|
|
|
<1> We recommend that you add the dependency management through the Spring BOM so that you need not manage versions yourself.
|
|
|
|
|
<2> Add the dependency to `spring-cloud-starter-zipkin`. That way, all nested dependencies get downloaded.
|
|
|
|
|
<3> To automatically configure RabbitMQ, add the `spring-rabbit` dependency.
|
|
|
|
|
|
|
|
|
|
== Additional resources
|
|
|
|
|
== Additional Resources
|
|
|
|
|
|
|
|
|
|
*Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin*
|
|
|
|
|
You can watch a video of Marcin Grzejszczak talking about Spring Cloud Sleuth and Zipkin:
|
|
|
|
|
|
|
|
|
|
video::eQV71Mw1u1c[youtube]
|
|
|
|
|
|
|
|
|
|
@@ -531,7 +536,7 @@ https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video]
|
|
|
|
|
|
|
|
|
|
== Features
|
|
|
|
|
|
|
|
|
|
* Adds trace and span ids to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator. Example logs:
|
|
|
|
|
* Adds trace and span IDs to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator, as shown in the following example logs:
|
|
|
|
|
+
|
|
|
|
|
----
|
|
|
|
|
2016-02-02 15:30:57.902 INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
|
|
|
|
|
@@ -539,53 +544,59 @@ https://www.youtube.com/watch?v=eQV71Mw1u1c[click here to see the video]
|
|
|
|
|
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...
|
|
|
|
|
----
|
|
|
|
|
+
|
|
|
|
|
notice the `[appname,traceId,spanId,exportable]` entries from the MDC:
|
|
|
|
|
Notice the `[appname,traceId,spanId,exportable]` entries from the MDC:
|
|
|
|
|
|
|
|
|
|
- *spanId* - the id of a specific operation that took place
|
|
|
|
|
- *appname* - the name of the application that logged the span
|
|
|
|
|
- *traceId* - the id of the latency graph that contains the span
|
|
|
|
|
- *exportable* - whether the log should be exported to Zipkin or not. When would you like the span not to be
|
|
|
|
|
exportable? In the case in which you want to wrap some operation in a Span and have it written to the logs
|
|
|
|
|
only.
|
|
|
|
|
** *`spanId`*: The ID of a specific operation that took place.
|
|
|
|
|
** *`appname`*: The name of the application that logged the span.
|
|
|
|
|
** *`traceId`*: The ID of the latency graph that contains the span.
|
|
|
|
|
** *`exportable`*: Whether the log should be exported to Zipkin.
|
|
|
|
|
When would you like the span not to be exportable?
|
|
|
|
|
When you want to wrap some operation in a Span and have it written to the logs only.
|
|
|
|
|
|
|
|
|
|
* Provides an abstraction over common distributed tracing data models: traces, spans (forming a DAG), annotations,
|
|
|
|
|
key-value annotations. Loosely based on HTrace, but Zipkin (Dapper) compatible.
|
|
|
|
|
* Provides an abstraction over common distributed tracing data models: traces, spans (forming a DAG), annotations, and key-value annotations.
|
|
|
|
|
Spring Cloud Slwuth is loosely based on HTrace but is compatible with Zipkin (Dapper).
|
|
|
|
|
|
|
|
|
|
* Sleuth records timing information to aid in latency analysis. Using sleuth, you can pinpoint causes of
|
|
|
|
|
latency in your applications. Sleuth is written to not log too much, and to not cause your production application to crash.
|
|
|
|
|
- propagates structural data about your call-graph in-band, and the rest out-of-band.
|
|
|
|
|
- includes opinionated instrumentation of layers such as HTTP
|
|
|
|
|
- includes sampling policy to manage volume
|
|
|
|
|
- can report to a Zipkin system for query and visualization
|
|
|
|
|
* Sleuth records timing information to aid in latency analysis.
|
|
|
|
|
By using sleuth, you can pinpoint causes of latency in your applications.
|
|
|
|
|
|
|
|
|
|
* Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints,
|
|
|
|
|
rest template, scheduled actions, message channels, zuul filters, feign client).
|
|
|
|
|
* Sleuth is written to not log too much and to not cause your production application to crash.
|
|
|
|
|
To that end, Sleuth:
|
|
|
|
|
** Propagates structural data about your call graph in-band and the rest out-of-band.
|
|
|
|
|
** Includes opinionated instrumentation of layers such as HTTP.
|
|
|
|
|
** Includes a sampling policy to manage volume.
|
|
|
|
|
** Can report to a Zipkin system for query and visualization.
|
|
|
|
|
|
|
|
|
|
* Sleuth includes default logic to join a trace across http or messaging boundaries. For example, http propagation
|
|
|
|
|
works via Zipkin-compatible request headers. This propagation logic is defined and customized via
|
|
|
|
|
`SpanInjector` and `SpanExtractor` implementations.
|
|
|
|
|
* Instruments common ingress and egress points from Spring applications (servlet filter, async endpoints, rest template, scheduled actions, message channels, Zuul filters, and Feign client).
|
|
|
|
|
|
|
|
|
|
* Sleuth gives you the possibility to propagate context (also known as baggage) between processes. That means that if you set on a Span
|
|
|
|
|
a baggage element then it will be sent downstream either via HTTP or messaging to other processes.
|
|
|
|
|
* Sleuth includes default logic to join a trace across HTTP or messaging boundaries.
|
|
|
|
|
For example, HTTP propagation works over Zipkin-compatible request headers.
|
|
|
|
|
This propagation logic is defined and customized through `SpanInjector` and `SpanExtractor` implementations.
|
|
|
|
|
|
|
|
|
|
* Provides a way to create / continue spans and add tags and logs via annotations.
|
|
|
|
|
* Sleuth can propagate context (also known as baggage) between processes.
|
|
|
|
|
Consequently, if you set a baggage element on a Span, it is sent downstream to other processes over either HTTP or messaging.
|
|
|
|
|
|
|
|
|
|
* If `spring-cloud-sleuth-zipkin` is on the classpath then the app will generate and collect Zipkin-compatible traces.
|
|
|
|
|
By default it sends them via HTTP to a Zipkin server on localhost (port 9411).
|
|
|
|
|
Configure the location of the service using `spring.zipkin.baseUrl`.
|
|
|
|
|
- If you depend on `spring-rabbit` or `spring-kafka` your app will send traces to a broker instead of http.
|
|
|
|
|
- Note: `spring-cloud-sleuth-stream` is deprecated and should no longer be used.
|
|
|
|
|
* Provides a way to create or continue spans and add tags and logs through annotations.
|
|
|
|
|
|
|
|
|
|
* Spring Cloud Sleuth is http://opentracing.io/[OpenTracing] compatible
|
|
|
|
|
* If `spring-cloud-sleuth-zipkin` is on the classpath, the app generates and collects Zipkin-compatible traces.
|
|
|
|
|
By default, it sends them over HTTP to a Zipkin server on localhost (port 9411).
|
|
|
|
|
You can configure the location of the service by setting `spring.zipkin.baseUrl`.
|
|
|
|
|
** If you depend on `spring-rabbit` or `spring-kafka`, your app sends traces to a broker instead of HTTP.
|
|
|
|
|
**
|
|
|
|
|
|
|
|
|
|
IMPORTANT: If using Zipkin, configure the percentage of spans exported using `spring.sleuth.sampler.percentage`
|
|
|
|
|
(default 0.1, i.e. 10%). *Otherwise you might think that Sleuth is not working cause it's omitting some spans.*
|
|
|
|
|
CAUTION: `spring-cloud-sleuth-stream` is deprecated and should no longer be used.
|
|
|
|
|
|
|
|
|
|
NOTE: the SLF4J MDC is always set and logback users will immediately see the trace and span ids in logs per the example
|
|
|
|
|
above. Other logging systems have to configure their own formatter to get the same result. The default is
|
|
|
|
|
`logging.pattern.level` set to `%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]`
|
|
|
|
|
(this is a Spring Boot feature for logback users).
|
|
|
|
|
*This means that if you're not using SLF4J this pattern WILL NOT be automatically applied*.
|
|
|
|
|
* Spring Cloud Sleuth is http://opentracing.io/[OpenTracing] compatible.
|
|
|
|
|
|
|
|
|
|
IMPORTANT: If you use Zipkin, configure the percentage of spans exported by setting `spring.sleuth.sampler.percentage`
|
|
|
|
|
(default: 0.1, which is 10 percent). Otherwise, you might think that Sleuth is not working be cause it omits some spans.
|
|
|
|
|
|
|
|
|
|
NOTE: The SLF4J MDC is always set and logback users immediately see the trace and span IDs in logs per the example
|
|
|
|
|
shown earlier.
|
|
|
|
|
Other logging systems have to configure their own formatter to get the same result.
|
|
|
|
|
The default is as follows:
|
|
|
|
|
`logging.pattern.level` set to `%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]`
|
|
|
|
|
(this is a Spring Boot feature for logback users).
|
|
|
|
|
If you do not use SLF4J, this pattern is NOT automatically applied.
|
|
|
|
|
|
|
|
|
|
== Building
|
|
|
|
|
|
|
|
|
|
@@ -678,10 +689,10 @@ The generated eclipse projects can be imported by selecting `import existing pro
|
|
|
|
|
from the `file` menu.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IMPORTANT: There are 2 different versions of language level used in Spring Cloud Sleuth. Java 1.7 is used for main sources and
|
|
|
|
|
Java 1.8 is used for tests. When importing your project to an IDE please activate the `ide` Maven profile to turn on
|
|
|
|
|
Java 1.8 for both main and test sources. Of course remember that you MUST NOT use Java 1.8 features in the main sources. If you do
|
|
|
|
|
so your app will break during the Maven build.
|
|
|
|
|
IMPORTANT: Spring Cloud Sleuth uses two different versions of language level. Java 1.7 is used for main sources, and
|
|
|
|
|
Java 1.8 is used for tests. When importing your project to an IDE, you should activate the `ide` Maven profile to turn on
|
|
|
|
|
Java 1.8 for both main and test sources. You MUST NOT use Java 1.8 features in the main sources. If you do
|
|
|
|
|
so, your app breaks during the Maven build.
|
|
|
|
|
|
|
|
|
|
== Contributing
|
|
|
|
|
|
|
|
|
|
|