Skip to content

Brave 5.11

Compare
Choose a tag to compare
@codefromthecrypt codefromthecrypt released this 08 Apr 04:45
· 295 commits to master since this release

Brave 5.11 adds new Apis for tags, baggage (propagated fields) and correlation fields (MDC). These apis were designed over many weeks of hard effort, with a goal of availing features that would otherwise take a major release to accomplish. The result is you can ease into this with no code impact.

Brave 5.11 also adds MongoDB instrumentation, something requested for a long time and obviates custom code sites formerly used to fill the gaps.

As the bulk of the load is volunteer work, please thank people directly using any means you can, or chat on https://gitter.im/openzipkin/zipkin to say the same. If you rely on code here, make sure you star Brave.

Volunteers trade "couch time" to help make sure your tracing works. Stars are an easy way volunteers to see their efforts are impactful and appreciated.

Note: Do not use Brave 5.11.0 or 5.11.1 as there were problems in these distributions. Use 5.11.2 or higher.

Tag, Tags and HttpTags

Brave 5.11 adds a long overdue feature to ease support of tagging spans. Tag bakes in all logic needed to add a tag to a span, leaving the user left only to decide what the key is and how to parse it. Many thanks to volunteer @anuraaga for design and review work on this.

This not only works for both in-flight and already finished spans, but also takes care of null checking and error handling.

Here's an example of a potentially expensive tag:

SUMMARY_TAG = new Tag<Summarizer>("summary") {
  @Override protected String parseValue(Summarizer input, TraceContext context) {
    return input.computeSummary();
  }
}

// This works for any variant of span
SUMMARY_TAG.tag(summarizer, span);

We also have constants in Tags and HttpTags you can make type-safe updates on standard fields.

Ex.

httpTracing = httpTracing.toBuilder()
    .clientRequestParser((req, context, span) -> {
      HttpClientRequestParser.DEFAULT.parse(req, context, span);
      HttpTags.URL.tag(req, context, span); // add the url in addition to defaults
    })
    .build();

All these types have Javadoc and there are introductions in Markdown here:

History

We once had constants for tag names based on thrift definitions, but they were removed when Brave decoupled from the zipkin library.

The closest type we had recently is ErrorParser as that does a similar dispatch. Externally, the closest is OpenTracing Tag.

brave.Tag and OpenTracing's Tag share ability to set tags on Spans before they start and while they are in progress. However, there are some differences:

  • brave.Tag has Javadoc explaining how and why you would use it.
  • brave.Tag integrates with FinishedSpanHandler, so it can change tags regardless of instrumentation policy, even after they complete.
  • brave.Tag is sealed except how to parse the value, which means error handling can be built in.

BaggagePropagation and BaggageField

Sometimes you need to propagate additional fields, such as a request ID or an alternate trace context. Thanks to many weeks of design and review from @anuraaga as well input from site owner @jorgheymans, we now have formal support for "baggage".

For example, you need a specific request's country code, you can propagate it through the trace as an HTTP header with the same name:

import brave.baggage.BaggagePropagationConfig.SingleBaggageField;

// Configure your baggage field
COUNTRY_CODE = BaggageField.create("country-code");

// When you initialize the builder, add the baggage you want to propagate
tracingBuilder.propagationFactory(
  BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY)
                    .add(SingleBaggageField.remote(COUNTRY_CODE))
                    .build()
);

// Later, you can retrieve that country code in any of the services handling the trace
// and add it as a span tag or do any other processing you want with it.
countryCode = COUNTRY_CODE.getValue(context);

This may look familiar to ExtraFieldPropagation, as it includes all the features it had and more. BaggagePropagation can also integrate with logging contexts and cleanly encapsulate field configuration.

Currently, BaggagePropagationConfig only supports predefined fields. However, dynamic fields will be supported in a future version, with no API break to you. Dynamic fields must either be in-process only, or use single header encoding. We will likely default to W3C encoding once they decide on a header name that works with JMS.

All these types have Javadoc and there is an introduction in Markdown here:

History

The name Baggage was first introduced by Brown University in Pivot Tracing as maps, sets and tuples. They then spun baggage out as a standalone component, BaggageContext and considered some of the nuances of making it general purpose. The implementations proposed in these papers are different to the implementation here, but conceptually the goal is the same: to propagate "arbitrary stuff" with a request.

Even though OpenTracing named propagated fields Baggage initially, we decided not to, as the Apis were not safe for arbitrary usage. For example, there was no implementation which could allow control over which fields to propagate, set limits or how to redact them. We didn't want to call anything Baggage until we could do that safely.

Instead, Brave 4.9 introduced ExtraFieldPropagation as a way to push other fields, such as a country code or request ID, alongside the trace context. It had get() and set() methods to retrieve values anywhere a span is active, but the above issues remained.. hard issues described here #577

The current baggage apis resolve the design problems that limited us in the past. It took many weeks of full-time effort from volunteer co-designer @anuraaga as well site input from @jorgheymans to surmount these hurdles.

CorrelationScopeDecorator (MDC integration)

CorrelationScopeDecorator is an advanced implementation of correlation shared by all implementations (like log4j, log4j2, slf4j). It can map field names, even allow you to flush updates of baggage synchronously to the underlying context. This integrates seamlessly with BaggagePropagation thanks to many volunteered weeks of design and review from @anuraaga as well input from site owner @jorgheymans.

All context integrations extend CorrelationScopeDecorator.Builder which means you can make portable configuration.

Ex. this is the only part that has to do with the implementation:

CorrelationScopeDecorator.Builder builder = MDCScopeDecorator.newBuilder();

By default, if you call build(), only traceId and spanId integrate with the underlying context. This is great for performance( only better if you customize to only include traceId!).

A common configuration would be to integrate a BaggageField as a correlation field in logs.

Assuming the above setup for COUNTRY_CODE, you can integrate like this:

import brave.baggage.CorrelationScopeConfig.SingleCorrelationField;

decorator = MDCScopeDecorator.newBuilder()
                             .add(SingleCorrelationField.create(COUNTRY_CODE))
                             .build();

tracingBuilder.currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
    .addScopeDecorator(decorator)
    .build()
);

// Any scope operations (updates to the current span) apply the fields defined by the decorator.
ScopedSpan span = tracing.tracer().startScopedSpan("encode");
try {
  // The below log message will have %X{country-code} in the context!
  logger.info("Encoding the span, hope it works");
--snip--

All these types have Javadoc and there is an introduction in Markdown here:

History

Before, we had types like MDCScopeDecorator for integrating extra fields as correlation fields in logging contexts. However, they were not customizable. In order to satisfy any user that needs "parentId", all scope decorators set this. This meant overhead in all cases, which adds up especially in reactive code.

MongoDB instrumentation

brave-instrumentation-mongodb includes a TraceMongoCommandListener, a CommandListener for the Mongo Java driver that will report via Brave how long each command takes, along with relevant tags like the collection/view name, the command's name (insert, update, find, etc.).

Volunteer @csabakos spent a month developing this for you and is owed a lot of thanks, also to volunteers @anuraaga and @kojilin for review and advice

https://github.com/openzipkin/brave/tree/master/instrumentation/mongodb

An application registers command listeners with a MongoClient by configuring MongoClientSettings as follows:

CommandListener listener = MongoDBTracing.create(Tracing.current())
        .commandListener();
MongoClientSettings settings = MongoClientSettings.builder()
        .addCommandListener(listener)
        .build();
MongoClient client = MongoClients.create(settings);

Support for asynchronous clients is unimplemented. To request support for this, add your thumbs up to #1113