
=head1 NAME

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



=head1 VERSION

This is documentation for version 'v0.104.2'



=head1 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',
   }



=head1 DESCRIPTION

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

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

=over

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

=back

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

=over

=item setup

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

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

=item teardown

OOPS C<...> not implemented (yet)

=back



=head1 ADDING DEEPER INSTRUMENTATION

This plugin only initiates the tracer, and three childspans. For more detail at
deeper call levels, one would need the L<OpenTracing::GlobalTracer> and add
manual instrumentation using C<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 L<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 L<OpenTracing::Wrap>, to automagically wrap scopes and traces around
a list of mentioned (fully qualified) subroutine names.

=head1 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 C<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
C<$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:

=over 4

=item opentracing_process_tags_query_params

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

=item opentracing_process_tags_form_fields

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

=item opentracing_process_tags

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

=back

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:

=over 4

=item undef - will cause the tag to be skipped altogether

=item string - will be used as the tag value directly

=item arrayref - will be joined with C<$CGI::Application::Plugin::TAG_JOIN_CHAR>

=item coderef - will be called with all values as arguments, and the return value will be used

=back

B<Note>: values returned by a coderef specification will be treated as
non-coderef formatters. For example, if a coderef formatter returns C<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

=head2 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,
  }

=head2 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 (L<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 L<opentracing_process_tags_query_params> or
L<opentracing_process_tags_form_fields> will be used B<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

=head1 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 L<OpenTracing::Implementation::NoOp> which ... does nothing.

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

=head1 AUTHOR

Theo van Hoesel <tvanhoesel@perceptyx.com>



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


=cut
