NAME
OpenTracing::Manual::Integration - For Framework or Integration Developers
DESCRIPTION
This part of the OpenTracing::Manual will describe how distributed traces are progressed from one service to the other, using the concept of carriers.
TABLE OF CONTENTS
- "Bootstrap an Implementation"
- "OpenTracing Carriers"
- "Extracting Context from an Incoming Request"
- "Inject Context into an Outgoing Request"
- "Testing your Framework Plugins"
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:
- Bootstrap an Implementation and GlobalTracer
- Extract any existing trace information to create a Context
On the other hand, on the outgoing side of the server, the responsibility is to:
THE DETAILS
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 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 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 OpenTracing::Implementation for more on how to bootstrap.
OpenTracing Carriers
The OpenTracing specification requires a Tracer implementation to understand how a SpanContext will be injected into or extracted from a so-called carrier or request (https://opentracing.io/docs/overview/inject-extract/).
The carrier formats required are text, http, and binary, but there is no strict standard on how those formats look exactly look. The formats are implementation dependent.
For the time being, with this Perl implementation and definition, only 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 HTTP::Headers object is most common in Perl programming, and other variants like HTTP::Headers::Fast or HTTP:::Headers::Fast::XS share the same public interface. Most frameworks know how to handle those.
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 child_of
a specific SpanContext.
The SpanContext for a root-span is the incoming request that may or may not contain tracer information from its requestor. Use the extract_context
from a Tracer object
Since some implementations may use immutable objects and may have required attributes for a SpanContext and since the entire API is being accessed through the Tracer object, you may need to use 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 SpanContext object.
But remember, such mechanisms are entirely dependent on the implementation.
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 );
NOTE
The OpenTracing specifications mention a 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 inject_context
and extract_context
definitions. (See also "Propagating Tracer Information between Services" in OpenTracing::Manual::Implementation for an example)
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 Test Implementation. And then compare the collected span information using globaltracer_cmp_deeply
with a expected bag
of Spans.
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 Test Implementation. This will keep an in-memory recording of all spans and have a additional get_recorded_trace
to get the collected spans, which can be used to compare with expected spans.
Tracing database calls
If you use DBI for database handling, you can
use DBIx::OpenTracing;
to automatically trace all database queries.
See DBIx::OpenTracing for details.
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::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>
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.