NAME

OpenTracing::Manual::Implementation - For Tracing Service Implementations

DESCRIPTION

This part of the OpenTracing::Manual will describe how communication with the backend is established through some sort of Agent. This manual also provides information on how to extract_context or inject_context and carriers.

TABLE OF CONTENTS

"Bootstrapping a Tracer Implementation"
"Writing your own Implementation and using Roles"
"Adding Implementation Specific Information to Traces"
"Sending Span Information to a Service Provider Backend"
"Propagating Tracer Information between Services"
"OpenTracing Roles and Types"
"Testing your Implementation"

INTRODUCTION

OpenTracing merely describes the API, see the OpenTracing::Interface documentation. It only requires that any implementation has a minimal set of methods that have a signature, or defined argument list. It is a deliberate choice to have the specification as POD and leaving the implementation to the Service Provider. The OpenTracing SDK for Perl however, comes with quite some useful tools to help building your own.

THE DETAILS

Bootstrapping a Tracer Implementation

Because of directory structure, Perl best practices and more, an implementation consists of several files, grouped under a single namespace. However, the API has no higher level definition of what an implementation is, it only speaks of the Tracer being the entry point of the API. It only looks more natural to be able to do things like:

use "OpenTracing::Implementation qw/YourServiceProvider/;

Which bootstraps the OpenTracing::GlobalTracer.

Or be more specific in your own code:

use aliased
    "OpenTracing::Implementation::MyServiceProvider",
    "Implementation" ;

my $tracer = Implementation->bootstrap_tracer( %options );

Although the 'Implementation' could do all sorts of things with that call, it basically is the same as:

use aliased
    "OpenTracing::Implementation::MyServiceProvider::Tracer" ;

my $tracer = Tracer->new( %options );

Writing your own Implementation and using Roles

Since a lot of the responsibilities described in the <OpenTracing::Interface> are common across all implementations, there is a whole set of Moo::Roles files to quickly build your own classes.

package OpenTracing::Implementation::MyServiceProvider::Scope

use Moo;

...

with 'OpenTracing::Role::Scope'

1;

Look at OpenTracing::Roles to see what each of those roles provides.

Adding Implementation Specific Information to Traces.

The OpenTracing::Interface::SpanContext carries data across process boundaries. Specifically, it has two major components:

An implementation-dependent state to refer to the distinct span within a trace

for example the implementing Tracer's definition of spanID and traceID

Any Baggage Items

These are key:value pairs that cross process-boundaries. These may be useful to have some data available for access throughout the trace (https://opentracing.io/docs/overview/tags-logs-baggage/#baggage-items).

Depending on the purpose, it is most likely that you want to add additional information like a ServiceEndpoint to the SpanContext as 'private' attributes. As an implementor you do want to have a reliable way to persist that information. The BaggageItems can be altered at application level, as they are part of the 'public' API.

package OpenTracing::Implementation::MyServiceProvider::SpanContext

use Moo;

with 'OpenTracing::Role::SpanContext'

has service_endpoint => (
    is      => 'ro',
    default => { 'index.cgi' },
    isa     =>  Str,
);

1;

As implementor, it's your own responsibility to send that information back to the service provider.

Sending Span Information to a Service Provider Backend

How information is being send back to a service provider backend is beyond the scope of this manual. There are different scenarios to do so. Some may want to collect a larger number of spans and send those straight to the backend. Others may have a locally installed agent that will gather spans coming from multiple threads and send them as a batch to the backend.

Either way, as a implementor, you will need to add to the Tracer a send method that will communicate with the outer world.

package OpenTracing::Implementation::MyServiceProvider::Tracer

use Moo;

with 'OpenTracing::Role::Tracer'

has your_agent => (
    is      => 'lazy',
    isa     => 'OpenTracing::Implementation::MyServiceProvider::Agent',
    handles => qw/send_the_span/,
);

1;

Then, at the time you call finish, calling such method as mentioned (send_the_span) in the above example through a call back added as a on_finish attribute, would transmit the span.

Propagating Tracer Information between Services

At the boundary or edges of an application, Frameworks use the two methods inject_context and extract_context (https://opentracing.io/docs/overview/tracers/#propagating-a-trace-with-inject-extract).

It is required that these methods are provided in the implementation. There are at least three OpenTracing required formats that need to be support. Only the OPENTRACING_CARRIER_FORMAT_HTTP_HEADERS is being described in the manual.

An inject_context might be implemented like:

package OpenTracing::Implementation::MyServiceProvider::Tracer

our $injectors = {
    OPENTRACING_CARRIER_FORMAT_HTTP_HEADERS => sub {
        my $http_headers = shift;
        my $context = shift;
        
        return $http_headers->clone(
            X_YOUR_IMPLEMENTATION_TRACE_ID = $context->trace_id,
            ...
    },
    OPENTRACING_CARRIER_FORMAT_BINARY => sub { ... },
    OPENTRACING_CARRIER_FORMAT_TEXT_MAP => sub { ... },
}

sub inject_tracer {
    my $self = shift;
    my $carrier_format = shift;
    my $carrier;
    
    croak "unsupported carrier format [$carrier_format]"
        unless exists %$injectors{$carrier_format};
    
    my $context = $self->get_active_span->get_context;
    
    return $injectors->{$carrier_format}->($carrier, $context)
}

Where the X_YOUR_IMPLEMENTATION_TRACE_ID is fully provider dependent. The other (micro) service you want to talk may be implemented using a complete different technology stack or language. But since (most likely) that service will use the same Distributed Tracing Backend, it expects the carrier to hold the trace information in a known format.

OpenTracing Roles and Types

The entire API is described in POD, it also provides a set of Roles. These roles can optionally be consumed to do all the type-checking for parameters, options, and returned results.

It also provides OpenTracing::Types. These duck-type checking types check that a object will at least have the methods described in the API. A isa check will dictate a subclassing, which is what is deliberately avoided.

Testing your Implementation

There are a few tests available for Implementation developers. Those will check that the implementation is at least compliant with the OpenTracing::Interface and can be found at Test::OpenTracing::Interface.

use Test::Most;
use Test::OpenTracing::Interface::Span;

use YourImplementation::Span;

my $test_span = new_ok( 'YourImplementation::Span' => { %options },
    "Created a Span object"
);

interface_can_ok( $test_span,
    "... and can do all the required methods defined"
);

interface_lives_ok( $test_span,
    "... and each method accepts described parameters and options"
);

interface_dies_ok( $test_span,
    "... and will not tollerate bad input"
);

The latter one should work, but only if your implementation does do some sort of checking.

WARNING: If you do not check for the parameters and their types, please do check manually that the child_of and references options are mutual exclusive in start_active_span and start_span

Testing that your implementation is executing the inject_context and extract_context correctly, is entirely up to you. Also, it is up to you to check that the correct span information is being send to the tracer backend at finish.

SEE ALSO

OpenTracing::Interface

A role that defines the Tracer interface.

OpenTracing::Manual

A quick overview about Perl5 and OpenTracing

OpenTracing::Manual::Instrumentation

For Application developers and Devops.

OpenTracing::Manual::Integration

For Framework or Integration Developers

OpenTracing::Manual::Ecosystem

An overview of the OpenTracing puzzle pieces.

OpenTracing Overview

The OpenTracing API standard.

AUTHOR

Theo van Hoesel <tvanhoesel@perceptyx.com>

COPYRIGHT AND LICENSE

'OpenTracing API for Perl' is Copyright (C) 2019 .. 2020, Perceptyx Inc

This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.

This library is distributed in the hope that it will be useful, but it is provided "as is" and without any express or implied warranties.

For details, see the full text of the license in the file LICENSE.