Search code examples
.net-coremicroservicesopentracing12factorjaeger

OpenTracing in .NET core without taking dependency on specific solution library


For logging in our microservice applications we simply log to stdout/console and the docker logging driver handles and redirects these logs somewhere e.g. gelf/logstash, fluentd, etc. Basically, we're following 12 factor guidelines for logging. This means that developers working on the application code don't need to know anything about the underlying logging solution (e.g. Elasticsearch, Graylog, Splunk, etc) - it's entirely an ops/configuration concern. In theory we should be able to change the underlying logging solution without any code changes.

I'd like something similar for traces and my research has led me to OpenTracing. Developers shouldn't need to know the underlying tracing solution (e.g. Jaeger, Zipkin, Elastic APM, etc) and as per logging; in theory we should be able to change the underlying tracing solution without any code changes.

I've successfully got a .NET core POC sending traces to Jaeger using the opentracing/opentracing-csharp and jaegertracing/jaeger-client-csharp libraries.

I'm still trying to fully get my head around OpenTracing, but I'm wondering if there's a way to send traces to an OpenTracing compliant API without having to take a hard dependency on a particular solution such as Jaeger (i.e. the jaeger-client-csharp library). Based on my understanding OpenTracing is just a standard. Shouldn't I just be able to configure an OpenTracing endpoint with some sampling options without needing the jaeger-client-csharp library? Or is it that the jaeger-client-csharp is not actually Jaeger specific and can actually send traces to any OpenTracing API?

Example configuration shown below, which uses jaeger client library:

services.AddOpenTracing();

if (appSettings.TracerEnabled)
{
   services.AddSingleton(serviceProvider =>
   {
      var loggerFactory = new LoggerFactory();
      var config = Jaeger.Configuration.FromEnv(loggerFactory);

      var tracer = config.GetTracer();

      GlobalTracer.Register(tracer);

      return tracer;
   });
}

Solution

  • OpenTracing is a set of standard APIs that consistently model and describe the behavior of distributed systems)

    OpenTracing did not describe how to collect, report, store or represent the data of interrelated traces and spans. It is implementation details (such as jaeger or wavefront).

    jaeger-client-csharp is very jaeger-specific. But there is one exception, called zipkin which in turns is not fully OpenTracing compliant, even it has similar terms.

    If you are OK with opentracing-contrib/csharp-netcore (hope you are using this library) then if you want to achieve "no code change" (in target microservice) in order to configure tracing subsystem, you should use some plug-in model.

    Good news that aspnetcore has concept of hosted startup assemblies, which allow you to configure tracing system. So, you can have some library called JaegerStartup where you will implement IHostedStartup like follows:

    public class JaegerStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((ctx, services) =>
            {
                services.AddOpenTracing();
    
                if (ctx.Configuration.IsTracerEnabled()) // implement it by observing your section in configuration.
                {
                    services.AddSingleton(serviceProvider =>
                    {
                        var loggerFactory = new LoggerFactory();
                        var config = Jaeger.Configuration.FromEnv(loggerFactory);
    
                        var tracer = config.GetTracer();
    
                        GlobalTracer.Register(tracer);
    
                        return tracer;
                    });
                }
            });
        }
    }
    

    When you decide to switch the tracing system - you need to create another library, which can be loaded automatically, and target microservice code will not be touched.