OpenTracing Plugins for C++
OpenTracing’s APIs provide a portable mechanism to instrument software for distributed tracing. The same code can be reused with any tracing vendor providing implementations of the OpenTracing APIs. The APIs cover the creation of traces, spans, and the propagation of span context across processes; but one area that has required interacting with tracing libraries in a vendor-specific manner is the construction and initialization of a tracer.
For OSS deployed as binary applications such as proxies and load balancers like NGINX, Envoy, and Varnish; or databases like PostgreSQL and MySQL; this has posed a challenge to providing turnkey tracing. Supporting a particular tracer requires writing vendor-specific code to expose that tracer’s configuration options and map those options to its construction. Yet end-users of these applications rarely have the ability to tweak the source code, so being able to work out-of-the-box with whatever tracer deployed is a desirable goal.
The Plugin Approach
As a solution to this problem, OpenTracing’s C++ API now supports a plugin approach where vendor tracing libraries are dynamically loaded at runtime and a tracer is created from a JSON representation of its configuration options.
The example application ot-chat shows how this can work in practice. It’s a chat room server written with Boost.Beast that communicates with clients over a WebSocket and broadcasts messages to all its connections. By using the plugin approach, the same binary for the server can be deployed with any tracer.
Configuring pluggable tracing
Any application, such as ot-chat, that uses the plugin approach needs to take options for specifying a vendor tracing library and the configuration to use when creating a tracer. By convention, the tracing libraries use JSON to represent the tracer’s options. This makes it possible for applications to embed a tracer’s configuration within their own configuration. Here’s what an example configuration for ot-chat looks like to set up Jaeger’s tracer:
Here, tracer_library
provides the path to Jaeger’s plugin and tracer_configuration
is an embedding of Jaeger’s configuration.
Constructing a tracer
The snippet below shows how we can dynamically load tracer_library
and construct an opentracing::Tracer
from it using DynamicallyLoadTracingLibrary
.
Once constructed, the standard OpenTracing API methods of opentracing::Tracer
are available to instrument the rest of our application.
Conclusion
By using OpenTracing’s plugin approach, applications can support distributed tracing in a way that minimizes their dependencies and offers vendor-neutrality. This opens the door to adding tracing support to a broad range of common infrastructure components. Pluggable tracing is already supported in NGINX and Envoy. Additionally, we have plans to offer an OpenTracing C API with similar plugin support for C applications.