NAME

CGI::Application::Plugin::OpenTracing - Use OpenTracing in CGI Applications

VERSION

This is documentation for version 'v0.104.2'

SYNOPSIS

Inside your own CGI Application:

package MyCGI;

use strict;
use warnings;

use base qw/CGI::Application/;

use CGI::Application::Plugin::OpenTracing ( YourImplementation => %options);

Before Setup, initialize the Global Tracer with the following callback to add a list of implementation specific context parameters:

sub opentracing_bootstrap_options {
    service_name    => __PACKAGE__,
    service_type    => 'web',
    resource_name   => 'test.cgi',
}

After Setup, before Run, initialize the Global Tracer with the following callback:

 # any key/values that will be carried over to child spans
 #
 sub opentracing_baggage_items {
     client_id => '123XXX',
     database  => 'secret_toys',
}

DESCRIPTION

This plugin will use the return value from the opentracing_implementation callback to bootstrap a OpenTracing::GlobalTracer, with OpenTracing::Implementation. It uses all the parameters as mentioned in that set method.

It will automatically create a new tracer, with a total of four spans:

cgi_application

This is the root span, which will have a start time at the beginning of the cgi request, and last till the entire request will be finished.

On top of this rootspan, the following three child-spans will be created:

setup

A childspan, that will take the duration of the entire setup process.

run

After the tracer has been setup (using the information from the callback), this span will be started for the actual duration of the request handling

teardown

OOPS ... not implemented (yet)

ADDING DEEPER INSTRUMENTATION

This plugin only initiates the tracer, and three childspans. For more detail at deeper call levels, one would need the OpenTracing::GlobalTracer and add manual instrumentation using start_active_span:

use OpenTracing::GlobalTracer qw/$TRACER/;

and later per subroutine:

sub foo {
    my $opentracing_scope = $TRACER->start_active_span( foo => \%options );
    
    ...
    
    $opentracing_scope->close( )
}

Alternatively, on can use OpenTracing::AutoScope, which handles all the work in one go:

use OpenTracing::AutoScope;

and later per subroutine:

sub foo {
    OpenTracing::AutoScope->start_guarded_span;
    
    ...
    
}

Lastly, use OpenTracing::Wrap, to automagically wrap scopes and traces around a list of mentioned (fully qualified) subroutine names.

FORMATTING TAGS

Query parameters and form-data fields will be added to the request span as tags. For example, given a request URL:

http://test-app.com/data?id=1&view=compact

The following tags would be added to the request span:

'http.query.id'   => 1,
'http.query.view' => 'compact',

However, if a parameter is repeated:

http://test-app.com/data?id=1&id=2&id=3&view=compact

there would be multiple values for the http.query.id tag. OpenTracing span tags need to be scalars. The plugin will always turn multiple values into a simple string. There are a few ways to customize this behaviour.

By default, the parameter values are joined with the contents of $CGI::Application::Plugin::TAG_JOIN_CHAR, which is a comma by default. Without any customization, the example above would yield the following tags:

'http.query.id'   => '1,2,3',
'http.query.view' => 'compact',

Simple join is often not enough and there are parameters which should be skipped or obscured. The plugin allows to specify the formatting with the following callbacks:

opentracing_process_tags_query_params

Used to match and format URL query parameters (http.query.* tags).

opentracing_process_tags_form_fields

Used to match and format form data fields (http.form.* tags).

opentracing_process_tags

Used to match both query parameters and form data, when the specific callbacks fail to match (see "Fallbacks and matching order").

Their expected return values all follow the same format of key-value pairs with an optional odd element at the end. For example:

sub opentracing_process_tags_query_params {
    id           => sub { "[@_]" },
    location     => 'REDACTED',
    access_token => undef,
    sub { join ';', @_ },
}

Each key represents a parameter name to match. The values define how the parameter should be formatted. The last odd element is a fallback entry, used to format parameters not matched elsewhere.

The values can be:

undef - will cause the tag to be skipped altogether
string - will be used as the tag value directly
arrayref - will be joined with $CGI::Application::Plugin::TAG_JOIN_CHAR
coderef - will be called with all values as arguments, and the return value will be used

Note: values returned by a coderef specification will be treated as non-coderef formatters. For example, if a coderef formatter returns undef, the parameter will be skipped, if it returns an arrayref, it will be joined, etc. It can't return another coderef.

Given the following example:

http://test-app.com/data?id=1&id=2&id=3

Specification        | http.query.id
---------------------+--------------
                     | 1,2,3
id => undef          | 
id => 'XXX'          | XXX
id => [ 'X', 'Y' ]   | X,Y
id => sub { "[@_]" } | [1 2 3]
id => sub { \@_ }    | 1,2,3
id => sub { undef }  |
id => sub { 'XXX' }  | XXX

Matching multiple parameters

Sometimes, a single formatter fits multiple parameters:

sub opentracing_process_tags {
    pwd      => undef,
    password => undef,
}

Instead of repeating pairs, it's possible to specify multiple names with an array reference:

sub opentracing_process_tags {
    ['pwd', 'password'] => undef,
}

Or match names with a regular expression:

sub opentracing_process_tags {
    qr/\A(?:pwd|password)\z/ => undef,
}

Fallbacks and matching order

Whether it's a query parameter or a form field, their specific callback will be used first (if present) to check for named matches. If none match, the generic callback (opentracing_process_tags) is checked.

Fallback entries, when present, are used to format params which were not specifically matched by name. Note that when a generic callback is available, the fallback from either opentracing_process_tags_query_params or opentracing_process_tags_form_fields will be used after checking for generic named matches.

Matching order for a query parameter:

opentracing_process_tags_query_params - named matches
opentracing_process_tags              - named matches
opentracing_process_tags_query_params - fallback
opentracing_process_tags              - fallback
join with $CGI::Application::Plugin::TAG_JOIN_CHAR

Matching order for a form field:

opentracing_process_tags_form_fields - named matches
opentracing_process_tags             - named matches
opentracing_process_tags_form_fields - fallback
opentracing_process_tags             - fallback
join with $CGI::Application::Plugin::TAG_JOIN_CHAR

CAVEATS

Probably a few...

Originally, OpenTracing Implementations should extract any context and build new spans based on that. Sadly, no implementations are providing these features.

Since the tracer is only initialised, right before 'run' phase, it is useless to even try to add deeper level of instrumentation during 'setup'. Those will use the default OpenTracing::Implementation::NoOp which ... does nothing.

Traces will get lost, when the rootspan is not closed properly.

AUTHOR

Theo van Hoesel <tvanhoesel@perceptyx.com>

COPYRIGHT AND LICENSE

'CGI::Application::Plugin::OpenTracing' 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 package 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.