=pod

=head1 NAME

OpenTracing::Manual::Instrumentation - monitor your application

=head1 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();
      }
  }

=head1 DESCRIPTION

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

=head1 TABLE OF CONTENTS



=over

=item L</"Selecting a tracer">

=item L</"Adding OpenTracing to your Application">

=item L</"Adding extra information to spans">

=back

=head1 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
L<OpenTracing::Interface>, usually living under
the L<OpenTracing::Implementation::*> namespace.

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

=head1 Adding OpenTracing to your Application

=head2 Manual

In order to start monitoring an application, a tracer is needed.
In this example, a fictional C<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 L<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, C<$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 L<Adding extra information to spans>.

It's useful to be able to access the tracer anywhere in the program,
hence L<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 L<CGI::Application>) might have an OpenTracing plugin available,
which will take care of all the setup. See the next section for details.

=head2 Using a plugin

If the program is running under a framework, like L<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.

=head2 Extras

There are some helper modules available for common use cases.

=head3 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 C<next> leaves the loop early, C<$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, L<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 L<OpenTracing::AutoScope> for details.

=head3 Tracing specific subroutines

Often the logical operations to monitor correspond directly to subroutines.
Instead of adding OpenTracing code to each subroutine, you can use
L<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 L<OpenTracing::WrapScope> for details.

=head1 Adding extra information to spans

=head2 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.

=head2 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 L<< add_baggage_item|OpenTracing::Interface::Span/add_baggage_item >>.

=head1 SEE ALSO

=over

=item L<OpenTracing::Interface>

A role that defines the Tracer interface.

=item L<OpenTracing::Manual>

A quick overview about Perl5 and OpenTracing

=item L<OpenTracing::Manual::Integration>

For Framework or Integration Developers

=item L<OpenTracing::Manual::Implementation>

For Tracing Service Implementations

=item L<OpenTracing::Manual::Ecosystem>

An overview of the OpenTracing puzzle pieces.

=item L<OpenTracing Overview|https://opentracing.io/docs/overview/>

The OpenTracing API standard.

=back

=head1 AUTHOR

Theo van Hoesel <tvanhoesel@perceptyx.com>

=head1 CONTRIBUTORS

Szymon Nieznanski <snieznanski@perceptyx.com>

=head1 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.
