diff --git a/multi/multi__features.html b/multi/multi__features.html index 0965f3d2d..ec8619c5c 100644 --- a/multi/multi__features.html +++ b/multi/multi__features.html @@ -41,23 +41,32 @@ It also includes libraries to propagate the trace context over network boundarie Your names have to be explicit and concrete. Big names lead to latency issues and sometimes even thrown exceptions.

The tracer creates and joins spans that model the latency of potentially distributed work. It can employ sampling to reduce overhead during the process, to reduce the amount of data sent to Zipkin, or both.

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 that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.

3.1.2 Local Tracing

When tracing local code, you can run it inside a span, as shown in the following example:

@Autowired Tracer tracer;
+After starting a span, you can annotate events of interest or add tags containing details or lookup keys.

Spans have a context that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.

3.1.2 Local Tracing

When tracing code that never leaves your process, run it inside a scoped span.

@Autowired Tracer tracer;
 
-Span span = tracer.newTrace().name("encode").start();
+// Start a new trace or a span within an existing trace representing an operation
+ScopedSpan span = tracer.startScopedSpan("encode");
 try {
-  doSomethingExpensive();
+  // The span is in "scope" meaning downstream code such as loggers can see trace IDs
+  return encoder.encode();
+} catch (RuntimeException | Error e) {
+  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
+  throw e;
 } finally {
-  span.finish();
-}

In the preceding example, the span is the root of the trace. -In many cases, the span is part of an existing trace. -When this is the case, call newChild instead of newTrace, as shown in the following example:

@Autowired Tracer tracer;
+  span.finish(); // always finish the span
+}

When you need more features, or finer control, use the Span type:

@Autowired Tracer tracer;
 
-Span span = tracer.newChild(root.context()).name("encode").start();
-try {
-  doSomethingExpensive();
+// Start a new trace or a span within an existing trace representing an operation
+Span span = tracer.nextSpan().name("encode").start();
+// Put the span in "scope" so that downstream code such as loggers can see trace IDs
+try (SpanInScope ws = tracer.withSpanInScope(span)) {
+  return encoder.encode();
+} catch (RuntimeException | Error e) {
+  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
+  throw e;
 } finally {
-  span.finish();
-}

3.1.3 Customizing Spans

Once you have a span, you can add tags to it. + span.finish(); // note the scope is independent of the span. Always finish a span. +}

Both of the above examples report the exact same span on finish!

In the above example, the span will be either a new root span or the +next child in an existing trace.

3.1.3 Customizing Spans

Once you have a span, you can add tags to it. The tags can be used as lookup keys or details. For example, you might add a tag with your runtime version, as shown in the following example:

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 does not tempt users with span lifecycle hooks.

interface MyTraceCallback {
@@ -71,32 +80,36 @@ The former is simpler to understand and test and does not tempt users with span
 void userCode() {
   span.annotate("tx.started");
   ...
-}

3.1.5 RPC tracing

[Tip]Tip

Check for instrumentation written here and Zipkin’s list before rolling your own RPC instrumentation.

RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation.

The following example shows how to add a client span:

@Autowired Tracer tracer;
+}

3.1.5 RPC tracing

[Tip]Tip

Check for instrumentation written here and Zipkin’s list before rolling your own RPC instrumentation.

RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation.

The following example shows how to add a client span:

@Autowired Tracing tracing;
+@Autowired Tracer tracer;
 
 // 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());
+span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
+span.tag("myrpc.version", "1.0.0");
+span.remoteServiceName("backend");
+span.remoteIpAndPort("172.3.4.1", 8108);
+
+// Add the trace context to the request, so it can be propagated in-band
+tracing.propagation().injector(Request::addHeader)
+                     .inject(span.context(), request);
 
 // 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);
+// if there is an error, tag the span
+span.tag("error", error.getCode());
+// or if there is an exception
+span.error(exception);
 
 // 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() to indicate that the response was received. In one-way tracing, you use -span.flush() instead, as you do not expect a response.

The following example shows how a client might model a one-way operation:

@Autowired Tracer tracer;
+span.flush() instead, as you do not expect a response.

The following example shows how a client might model a one-way operation:

@Autowired Tracing tracing;
+@Autowired Tracer tracer;
 
 // start a new span representing a client request
-oneWaySend = tracer.newSpan(parent).kind(Span.Kind.CLIENT);
+oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
 
 // Add the trace context to the request, so it can be propagated in-band
 tracing.propagation().injector(Request::addHeader)
diff --git a/multi/multi__sampling.html b/multi/multi__sampling.html
index 115979dc0..31d88638c 100644
--- a/multi/multi__sampling.html
+++ b/multi/multi__sampling.html
@@ -3,31 +3,44 @@
    4. Sampling

4. Sampling

Sampling may be employed to reduce the data collected and reported out of process. When a span is not sampled, it adds no overhead (a 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, a global sampler applies a single rate to all traced operations. Tracer.Builder.sampler controls this setting, and it defaults to tracing every request.

4.1 Declarative sampling

Some applications need to sample based on the type or annotations of a java method.

Most users use a framework interceptor to automate this sort of policy. -The following example shows how that might work internally:

@Autowired Tracing tracing;
+The following example shows how that might work internally:

@Autowired Tracer tracer;
 
 // derives a sample rate from an annotation on a java method
 DeclarativeSampler<Traced> 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))...
+  // When there is no trace in progress, this decides using an annotation
+  Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced);
+  Tracer tracer = tracer.withSampler(decideUsingAnnotation);
+
+  // This code looks the same as if there was no declarative override
+  ScopedSpan span = tracer.startScopedSpan(spanName(pjp));
   try {
     return pjp.proceed();
+  } catch (RuntimeException | Error e) {
+    span.error(e);
+    throw e;
   } finally {
     span.finish();
   }
 }

4.2 Custom sampling

Depending on what the operation is, you may want to apply different policies. 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 use a framework interceptor to automate this sort of policy. The following example shows how that might work internally:

@Autowired Tracer tracer;
+@Autowired Sampler fallback;
 
-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);
+Span nextSpan(final Request input) {
+  Sampler requestBased = Sampler() {
+    @Override public boolean isSampled(long traceId) {
+      if (input.url().startsWith("/experimental")) {
+        return true;
+      } else if (input.url().startsWith("/static")) {
+        return false;
+      }
+      return fallback.isSampled(traceId);
+    }
+  };
+  return tracer.withSampler(requestBased).nextSpan();
 }

4.3 Sampling in Spring Cloud Sleuth

By default Spring Cloud Sleuth sets all spans to non-exportable. That means that traces appear in logs but not in any remote store. For testing the default is often enough, and it probably is all you need if you use only the logs (for example, with an ELK aggregator). diff --git a/single/spring-cloud-sleuth.html b/single/spring-cloud-sleuth.html index 44848c0ca..5a225a8d6 100644 --- a/single/spring-cloud-sleuth.html +++ b/single/spring-cloud-sleuth.html @@ -284,23 +284,32 @@ It also includes libraries to propagate the trace context over network boundarie Your names have to be explicit and concrete. Big names lead to latency issues and sometimes even thrown exceptions.

The tracer creates and joins spans that model the latency of potentially distributed work. It can employ sampling to reduce overhead during the process, to reduce the amount of data sent to Zipkin, or both.

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 that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.

3.1.2 Local Tracing

When tracing local code, you can run it inside a span, as shown in the following example:

@Autowired Tracer tracer;
+After starting a span, you can annotate events of interest or add tags containing details or lookup keys.

Spans have a context that includes trace identifiers that place the span at the correct spot in the tree representing the distributed operation.

3.1.2 Local Tracing

When tracing code that never leaves your process, run it inside a scoped span.

@Autowired Tracer tracer;
 
-Span span = tracer.newTrace().name("encode").start();
+// Start a new trace or a span within an existing trace representing an operation
+ScopedSpan span = tracer.startScopedSpan("encode");
 try {
-  doSomethingExpensive();
+  // The span is in "scope" meaning downstream code such as loggers can see trace IDs
+  return encoder.encode();
+} catch (RuntimeException | Error e) {
+  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
+  throw e;
 } finally {
-  span.finish();
-}

In the preceding example, the span is the root of the trace. -In many cases, the span is part of an existing trace. -When this is the case, call newChild instead of newTrace, as shown in the following example:

@Autowired Tracer tracer;
+  span.finish(); // always finish the span
+}

When you need more features, or finer control, use the Span type:

@Autowired Tracer tracer;
 
-Span span = tracer.newChild(root.context()).name("encode").start();
-try {
-  doSomethingExpensive();
+// Start a new trace or a span within an existing trace representing an operation
+Span span = tracer.nextSpan().name("encode").start();
+// Put the span in "scope" so that downstream code such as loggers can see trace IDs
+try (SpanInScope ws = tracer.withSpanInScope(span)) {
+  return encoder.encode();
+} catch (RuntimeException | Error e) {
+  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
+  throw e;
 } finally {
-  span.finish();
-}

3.1.3 Customizing Spans

Once you have a span, you can add tags to it. + span.finish(); // note the scope is independent of the span. Always finish a span. +}

Both of the above examples report the exact same span on finish!

In the above example, the span will be either a new root span or the +next child in an existing trace.

3.1.3 Customizing Spans

Once you have a span, you can add tags to it. The tags can be used as lookup keys or details. For example, you might add a tag with your runtime version, as shown in the following example:

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 does not tempt users with span lifecycle hooks.

interface MyTraceCallback {
@@ -314,32 +323,36 @@ The former is simpler to understand and test and does not tempt users with span
 void userCode() {
   span.annotate("tx.started");
   ...
-}

3.1.5 RPC tracing

[Tip]Tip

Check for instrumentation written here and Zipkin’s list before rolling your own RPC instrumentation.

RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation.

The following example shows how to add a client span:

@Autowired Tracer tracer;
+}

3.1.5 RPC tracing

[Tip]Tip

Check for instrumentation written here and Zipkin’s list before rolling your own RPC instrumentation.

RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation.

The following example shows how to add a client span:

@Autowired Tracing tracing;
+@Autowired Tracer tracer;
 
 // 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());
+span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
+span.tag("myrpc.version", "1.0.0");
+span.remoteServiceName("backend");
+span.remoteIpAndPort("172.3.4.1", 8108);
+
+// Add the trace context to the request, so it can be propagated in-band
+tracing.propagation().injector(Request::addHeader)
+                     .inject(span.context(), request);
 
 // 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);
+// if there is an error, tag the span
+span.tag("error", error.getCode());
+// or if there is an exception
+span.error(exception);
 
 // 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() to indicate that the response was received. In one-way tracing, you use -span.flush() instead, as you do not expect a response.

The following example shows how a client might model a one-way operation:

@Autowired Tracer tracer;
+span.flush() instead, as you do not expect a response.

The following example shows how a client might model a one-way operation:

@Autowired Tracing tracing;
+@Autowired Tracer tracer;
 
 // start a new span representing a client request
-oneWaySend = tracer.newSpan(parent).kind(Span.Kind.CLIENT);
+oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
 
 // Add the trace context to the request, so it can be propagated in-band
 tracing.propagation().injector(Request::addHeader)
@@ -369,31 +382,44 @@ oneWayReceive.start().flush();
 next = tracer.newSpan(oneWayReceive.context()).name("step2").start();

4. Sampling

Sampling may be employed to reduce the data collected and reported out of process. When a span is not sampled, it adds no overhead (a 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, a global sampler applies a single rate to all traced operations. Tracer.Builder.sampler controls this setting, and it defaults to tracing every request.

4.1 Declarative sampling

Some applications need to sample based on the type or annotations of a java method.

Most users use a framework interceptor to automate this sort of policy. -The following example shows how that might work internally:

@Autowired Tracing tracing;
+The following example shows how that might work internally:

@Autowired Tracer tracer;
 
 // derives a sample rate from an annotation on a java method
 DeclarativeSampler<Traced> 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))...
+  // When there is no trace in progress, this decides using an annotation
+  Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced);
+  Tracer tracer = tracer.withSampler(decideUsingAnnotation);
+
+  // This code looks the same as if there was no declarative override
+  ScopedSpan span = tracer.startScopedSpan(spanName(pjp));
   try {
     return pjp.proceed();
+  } catch (RuntimeException | Error e) {
+    span.error(e);
+    throw e;
   } finally {
     span.finish();
   }
 }

4.2 Custom sampling

Depending on what the operation is, you may want to apply different policies. 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 use a framework interceptor to automate this sort of policy. The following example shows how that might work internally:

@Autowired Tracer tracer;
+@Autowired Sampler fallback;
 
-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);
+Span nextSpan(final Request input) {
+  Sampler requestBased = Sampler() {
+    @Override public boolean isSampled(long traceId) {
+      if (input.url().startsWith("/experimental")) {
+        return true;
+      } else if (input.url().startsWith("/static")) {
+        return false;
+      }
+      return fallback.isSampled(traceId);
+    }
+  };
+  return tracer.withSampler(requestBased).nextSpan();
 }

4.3 Sampling in Spring Cloud Sleuth

By default Spring Cloud Sleuth sets all spans to non-exportable. That means that traces appear in logs but not in any remote store. For testing the default is often enough, and it probably is all you need if you use only the logs (for example, with an ELK aggregator). diff --git a/spring-cloud-sleuth.xml b/spring-cloud-sleuth.xml index 7383857e2..7a54cecfc 100644 --- a/spring-cloud-sleuth.xml +++ b/spring-cloud-sleuth.xml @@ -757,26 +757,37 @@ After starting a span, you can annotate events of interest or add tags containin

Local Tracing -When tracing local code, you can run it inside a span, as shown in the following example: +When tracing code that never leaves your process, run it inside a scoped span. @Autowired Tracer tracer; -Span span = tracer.newTrace().name("encode").start(); +// Start a new trace or a span within an existing trace representing an operation +ScopedSpan span = tracer.startScopedSpan("encode"); try { - doSomethingExpensive(); + // The span is in "scope" meaning downstream code such as loggers can see trace IDs + return encoder.encode(); +} catch (RuntimeException | Error e) { + span.error(e); // Unless you handle exceptions, you might not know the operation failed! + throw e; } finally { - span.finish(); + span.finish(); // always finish the span } -In the preceding example, the span is the root of the trace. -In many cases, the span is part of an existing trace. -When this is the case, call newChild instead of newTrace, as shown in the following example: +When you need more features, or finer control, use the Span type: @Autowired Tracer tracer; -Span span = tracer.newChild(root.context()).name("encode").start(); -try { - doSomethingExpensive(); +// Start a new trace or a span within an existing trace representing an operation +Span span = tracer.nextSpan().name("encode").start(); +// Put the span in "scope" so that downstream code such as loggers can see trace IDs +try (SpanInScope ws = tracer.withSpanInScope(span)) { + return encoder.encode(); +} catch (RuntimeException | Error e) { + span.error(e); // Unless you handle exceptions, you might not know the operation failed! + throw e; } finally { - span.finish(); + span.finish(); // note the scope is independent of the span. Always finish a span. } +Both of the above examples report the exact same span on finish! +In the above example, the span will be either a new root span or the +next child in an existing trace.
Customizing Spans @@ -814,23 +825,26 @@ void userCode() { RPC tracing is often done automatically by interceptors. Behind the scenes, they add tags and events that relate to their role in an RPC operation. The following example shows how to add a client span: -@Autowired Tracer tracer; +@Autowired Tracing tracing; +@Autowired Tracer tracer; // 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()); +span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT); +span.tag("myrpc.version", "1.0.0"); +span.remoteServiceName("backend"); +span.remoteIpAndPort("172.3.4.1", 8108); + +// Add the trace context to the request, so it can be propagated in-band +tracing.propagation().injector(Request::addHeader) + .inject(span.context(), request); // 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); +// if there is an error, tag the span +span.tag("error", error.getCode()); +// or if there is an exception +span.error(exception); // when the response is complete, finish the span span.finish(); @@ -841,10 +855,11 @@ request but no response. In normal RPC tracing, you use span.finish()span.flush() instead, as you do not expect a response. The following example shows how a client might model a one-way operation: -@Autowired Tracer tracer; +@Autowired Tracing tracing; +@Autowired Tracer tracer; // start a new span representing a client request -oneWaySend = tracer.newSpan(parent).kind(Span.Kind.CLIENT); +oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT); // Add the trace context to the request, so it can be propagated in-band tracing.propagation().injector(Request::addHeader) @@ -890,16 +905,24 @@ When a span is not sampled, it adds no overhead (a noop). Some applications need to sample based on the type or annotations of a java method. Most users use a framework interceptor to automate this sort of policy. The following example shows how that might work internally: -@Autowired Tracing tracing; +@Autowired Tracer tracer; // derives a sample rate from an annotation on a java method DeclarativeSampler<Traced> 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))... + // When there is no trace in progress, this decides using an annotation + Sampler decideUsingAnnotation = declarativeSampler.toSampler(traced); + Tracer tracer = tracer.withSampler(decideUsingAnnotation); + + // This code looks the same as if there was no declarative override + ScopedSpan span = tracer.startScopedSpan(spanName(pjp)); try { return pjp.proceed(); + } catch (RuntimeException | Error e) { + span.error(e); + throw e; } finally { span.finish(); } @@ -912,15 +935,20 @@ For example, you might not want to trace requests to static resources such as im Most users use a framework interceptor to automate this sort of policy. The following example shows how that might work internally: @Autowired Tracer tracer; +@Autowired Sampler fallback; -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); +Span nextSpan(final Request input) { + Sampler requestBased = Sampler() { + @Override public boolean isSampled(long traceId) { + if (input.url().startsWith("/experimental")) { + return true; + } else if (input.url().startsWith("/static")) { + return false; + } + return fallback.isSampled(traceId); + } + }; + return tracer.withSampler(requestBased).nextSpan(); }