NAME

OpenTracing::Manual::Instrumentation - monitor your application

SYNOPSIS

use OpenTracing::Implementation qw/FancyTracker/
use OpenTracing::WrapScope qw/long_sub/
use OpenTracing::AutoScope;
use OpenTracing::GlobalTracer qw/get_global_tracer/;

sub long_sub {
   ...
}

sub process_requests {
    my ($requests) = @_;

    foreach my $request (@$requests) {
        OpenTracing::AutoScope->start_guarded_span('request', tags => { id => $request->id });

        if ($request->needs_initialization) {
            OpenTracing::AutoScope->start_guarded_span('initialization');
            $request->initialize();
        }

        my $scope_process = get_global_tracer->start_active_span('processing');
        $request->process();
        if ($request->is_failure) {
            $scope_process->get_span->add_tag(error => 1);
        }
        $scope_process->close();
    }
}

DESCRIPTION

This manual documents the process of instrumenting a program, in order to gain insights about specific parts of the code.

TABLE OF CONTENTS

"Selecting a tracer"
"Adding OpenTracing to your Application"
"Adding extra information to spans"

Selecting a tracer

In order to instrument an application, an OpenTracing-compatible tracer is needed, it should be part of a set of modules implementing OpenTracing::Interface, usually living under the OpenTracing::Implementation::* namespace.

Find an OpenTracing implementation corresponding to your APM and proceed to the next step. Make sure it is compliant with OpenTracing::Interface. Some of the available implementations are listed in "Implementations" in OpenTracing::Manual::Ecosystem.

Adding OpenTracing to your Application

Manual

In order to start monitoring an application, a tracer is needed. In this example, a fictional FancyTracker implementation will be used.

First, load the implementation:

use OpenTracing::Implementation::FancyTracker;

Then, create a tracer object:

my $tracer = OpenTracinng::Implementation::FancyTracer->bootstrap_tracer;

Now the tracer can be used to create new spans (logical operations which will be reflected in the APM). Spans are contained within scopes - objects which manage the spans lifetime. See https://opentracing.io/specification/conventions/ for more details on the naming.

my $scope = $tracer->start_active_span('short operation name');
my $span  = $scope->get_span();
...
$scope->close();

Of course, $scope-get_span()> can be skipped if direct access to the span is not needed. It would be needed in order to add extra information to the span, see "Adding extra information to spans".

It's useful to be able to access the tracer anywhere in the program, hence OpenTracing::GlobalTracer exists. Set the tracer as global:

OpenTracing::GlobalTracer->set_global_tracer($tracer);

and access it anywhere with:

my $tracer = OpenTracing::GLobalTracer->get_global_tracer();

Since this is a common way of using OpenTracing, there is module which takes care of all the boilerplate, so instead of:

use OpenTracing::Implementation::FancyTracker;
use OpenTracing::GlobalTracer;

my $tracer = OpenTracing::Implementation::FancyTracker->bootstrap_tracer();
OpenTracing::GlobalTracer->set_global_tracer($tracer);

you can do:

use OpenTracing::Implementation 'FancyTracker';
...
my $tracer = OpenTracing::GlobalTracer->get_global_tracer();

However, even this is often not required, since the framework in use (like CGI::Application) might have an OpenTracing plugin available, which will take care of all the setup. See the next section for details.

Using a plugin

If the program is running under a framework, like CGI::Application an OpenTracing plugin might be available. In that case, it would most likely suffice to use the specific plugin.

If your microservices are 'small' and 'shallow' this might be just enough what you need. However, you will only get tracer information on the level provided by the plugin. That is, probably just one single rootspan. Some framework plugins might add another layer on top of that rootspan, like a setup-span, a run-span, a render-span, and a teardown-span.

Extras

There are some helper modules available for common use cases.

Auto-closing scopes

Each scope has to be closed at some point. When complex control flow is involved, this can get tricky and easy to miss.

Consider this loop:

foreach my $request (@requests) {
    my $scope = $TRACER->start_active_span('process_request');
    next unless _is_valid($request);

    _process($request);
    $scope->close();
}

Since next leaves the loop early, $scope-close()> never gets called for invalid requests. In order to account for that, the code would need to be changed to:

foreach my $request (@requests) {
    my $scope = $TRACER->start_active_span('process_request');

    unless (_is_valid($request)) {
        $scope->close();
        next;
    }

    _process($request);
    $scope->close();
}

This can get troublesome when more conditions come into play. For cases like this, OpenTracing::AutoScope can be used to create a span which will last until the end of current scope, regardless of how or when the scope is exited:

use OpenTracing::AutoScope;

foreach my $request (@requests) {
    OpenTracing::AutoScope->start_guarded_span('process_request');
    next unless _is_valid($request);

    _process($request);
}

See OpenTracing::AutoScope for details.

Tracing specific subroutines

Often the logical operations to monitor correspond directly to subroutines. Instead of adding OpenTracing code to each subroutine, you can use OpenTracing::WrapScope to automatically add it selected subroutines:

package Foo;
use OpenTracing::WrapScope qw[ foo bar ];

sub foo { ... } # will be surrounded with a span
sub bar { ... } # will be surrounded with a span
sub baz { ... } # will not be modified

Subroutines from other modules can also be wrapped by using fully qualified names:

use OpenTracing::WrapScope 'Some::Other::method';

See OpenTracing::WrapScope for details.

Adding extra information to spans

Tags

Tags are extra characteristics which apply to the whole span. For a database call, that could be the SQL statement and database name, for example.

These can be set during the span creation:

my $scope = $tracer->start_active_span('db_sql_query', {
  'db.statement' => $sql,
});

Or later, but never after the span has finished:

my $span = $scope->get_span;
$span->add_tag('db.statement' => $sql);

Only simple scalars can be used as tags, references are not allowed.

Baggage items

Baggage items are key-value pairs which cross process boundaries and are inherited by following spans. These are long-lived data pieces available throughout the trace.

They can be added to existing spans:

$span->add_baggage_item(customer_id => $customer_id);

See add_baggage_item.

SEE ALSO

OpenTracing::Interface

A role that defines the Tracer interface.

OpenTracing::Manual

A quick overview about Perl5 and OpenTracing

OpenTracing::Manual::Integration

For Framework or Integration Developers

OpenTracing::Manual::Implementation

For Tracing Service Implementations

OpenTracing::Manual::Ecosystem

An overview of the OpenTracing puzzle pieces.

OpenTracing Overview

The OpenTracing API standard.

AUTHOR

Theo van Hoesel <tvanhoesel@perceptyx.com>

CONTRIBUTORS

Szymon Nieznanski <snieznanski@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.