
=head1 NAME



OpenTracing::Manual::Integration - For Framework or Integration Developers



=head1 DESCRIPTION



This part of the L<OpenTracing::Manual> will describe how distributed traces are
progressed from one service to the other, using the concept of carriers.



=head1 TABLE OF CONTENTS



=over

=item L</"Bootstrap an Implementation">

=item L</"OpenTracing Carriers">

=item L</"Extracting Context from an Incoming Request">

=item L</"Inject Context into an Outgoing Request">

=item L</"Testing your Framework Plugins">

=back



=head1 INTRODUCTION



Most of the time, a framework will have some sort of architecture that allows to
add plugins into the framework itself. These plugins, conveniently, bootstrap each
application running the framework. But with OpenTracing, backends can be easily swapped,
with only minor changes in the code itself. All calls should follow the API.

Any framework plugin has the following responsibilities:

=over

=item Bootstrap an Implementation and GlobalTracer

=item Extract any existing trace information to create a Context

=back

On the other hand, on the outgoing side of the server, the responsibility is to:

=over

=item Inject the current Context into a 'Carrier'

=back



=head1 THE DETAILS



=head2 Bootstrap an Implementation

Plugins should either use a generic way to bootstrap an implementation, or tailored
for just one implementation.

    use MyFramework::Plugin::OpenTracing;
    #
    # Bootstrap implementation from the `OPENTRACING_IMPLEMENTATION`
    # environment variable

or specifying it on the C<use> statement

    use MyFramework::Plugin::OpenTracing ( 'SomeImplementation',
        option => 'foo'
    );

or having a tailored plugin:

    use MyFrameWork::Plugin::OpenTracing::SomeImplementation;

But the latter approach requires writing multiple modules or subclasses.

Whatever way you choose, the responsibility of the Framework Plugin is to set
the L<OpenTracing::GlobalTracer> such that it can be used inside the
application.

    use OpenTracing::Implementation qw/SomeImplementation/;

Or a more verbose way:

    use OpenTracing::Implementation
    
    my $tracer = OpenTracing::Implementation->bootstrap_global_tracer(
        SomeImplementation,
        option_one => 'foo',
        option_two => 'bar',
    );

See L<OpenTracing::Implementation> for more on how to bootstrap.



=head2 OpenTracing Carriers

The OpenTracing specification requires a L<Tracer|
OpenTracing::Interface::Tracer> implementation to understand how a L<SpanContext
|OpenTracing::Interface::SpanContext> will be I<inject>ed into or I<extract>ed
from a so-called I<carrier> or I<request>
(L<https://opentracing.io/docs/overview/inject-extract/>).

The I<carrier formats> required are I<text>, I<http>, and I<binary>, but there
is no strict I<standard> on how those formats look exactly look. The formats are
implementation dependent.

For the time being, with this Perl implementation and definition, only
L<HTTP::Headers> will be used as a carrier. But once more, there is no
definition on what HTTP Header information is used and how it is formatted.

The L<HTTP::Headers> object is most common in Perl programming, and other
variants like L<HTTP::Headers::Fast> or L<HTTP:::Headers::Fast::XS> share the
same public interface. Most frameworks know how to handle those.

=head2 Extracting Context from an Incoming Request

To extract the tracing context from an upstream service, you will need to 
provide it as an HTTP::Headers object.

    my $http_headers = YourFramework->request->headers;
    #
    # as long as this is a HTTP::Headers object
    
    my $root_context = $TRACER->extract_context( $http_headers );

All Spans are part of a context. That is also true for the rootspan in a
framework. Spans are started as a C<child_of> a specific L<SpanContext|
OpenTracing::Interface::SpanContext>.

The SpanContext for a root-span is the incoming request that may or may not
contain tracer information from its requestor. Use the L<< C<extract_context>
from a I<Tracer> object|OpenTracing::Interface::Tracer/
"extract_context(-$carrier-)" >>


Since some implementations may use immutable objects and may have required
attributes for a L<SpanContext|OpenTracing::Interface::SpanContext> and since
the entire API is being accessed through the
L<Tracer|OpenTracing::Interface::Tracer> object, you may need to use I<builders>
as part of the attributes. Such builders can bridge the gap between the
framework and the implementation like so, at initialization time:

    $TRACER->set_default_context_builder sub {
        my $service_url = YourFramework->request->url;
        my $service_type = 'WEB',
        
        return {
            tracer_service_endpoint => $service_url,
            tracer_service_type     => $service_type,
        }
    };

And as such the returned hash reference might be merged with extracted tracer
info and used to create a complete
L<SpanContext|OpenTracing::Interface::SpanContext> object.

But remember, such mechanisms are entirely dependent on the implementation.



=head2 Inject Context into an Outgoing Request

To provide your tracing context to a downstream service, create the
HTTP::Headers object, then inject your current context into the headers before
passing the request on to the microservice.

    my $span_context = $TRACER->get_active_span->get_context;
    
    use HTTP::Headers;
    my $http_headers = HTTP::Headers->new( ... );
    
    my $cntx_headers =
        $tracer->inject_context( $http_headers, $opentracing_spancontext );
    
    my $request = HTTP::Request->new(
        GET => 'https://...', $cntx_headers
    );
    my response = LWP::UserAgent->request( $request );



=head3 NOTE

The OpenTracing specifications mention a I<CARRIER_FORMAT>, which was used in an
earlier version. But since the carrier type can be detected by perl, this has
been removed form the Perl interface for the C<inject_context> and
C<extract_context> definitions. (See also
L<OpenTracing::Manual::Implementation/"Propagating Tracer Information between Services">
for an example)



=head2 Testing your Framework Plugins

To test that your Framework Plugin is doing the right thing, all you need to do
is run it with the L<Test Implementation|OpenTracing::Implementation::Test>. And
then compare the collected span information using
L<< C<globaltracer_cmp_deeply>|Test::OpenTracing::Integration >>
with a expected C<bag> of L<Span|OpenTracing::Interface::Span>s.

    use Test::Most;
    use Test::OpenTracing::Integration;
    
    my $test_application = Test::Application->new;
    my $http_request = HTTP::Request->new( ... );
    
    lives_ok{ $test_application->execute_request( $http_request );
    } "Can do request";
    
    globaltracer_cmp_deeply [ ... ],
        "... and produced the expected spans";
    
    done_testing;
    
    package Test::Application;
    
    use YourFramework;
    
    use YourFramework::Plugin::OpenTracing qw/Test/
    
    sub execute_request { ... }

It is crucial is that you use the L<Test Implementation|
OpenTracing::Implementation::Test>.
This will keep an in-memory recording of all spans and have a additional
L<< C<get_recorded_trace>|OpenTracing::Implementation::Test::Tracer >> to get
the collected spans, which can be used to compare with expected spans.



=head3 Tracing database calls

If you use DBI for database handling, you can

  use DBIx::OpenTracing;

to automatically trace all database queries.

See L<DBIx::OpenTracing> for details.



=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::Instrumentation>

For Application developers and Devops.

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