
=head1 NAME

OpenTracing::WrapScope - Automatically add spans to selected subroutines



=head1 SYNOPSIS

    use OpenTracing::WrapScope qw/foo Foo::bar/;
    use Foo;
      
    sub foo { ... }
   
    package Foo {
        sub bar { ... }
    }

or:

    use OpenTracing::WrapScope;

    OpenTracing::WrapScope::install_wrapped('foo');
    OpenTracing::WrapScope::install_wrapped('Foo::Bar');

which is roughly equivalent to:

    use OpenTracing::AutoScope;
    
    sub foo {
        OpenTracing::AutoScope->start_guarded_span();
        ...
    }
    
    package Foo {
        sub bar {
            OpenTracing::AutoScope->start_guarded_span();
            ...
        }
    }



=head1 IMPORT ARGUMENTS

import takes subroutine names (with optional signatures) as arguments,
these need to be fully qualified if they are not in the current package.
All specified subroutines will have spans attached to them.
Context and caller frames will be preserved.

The following tags will be automatically added to each span:

=over 4

=item caller.file - filename where the subroutine was called

=item caller.line - line number on which the subroutine was called

=item caller.package - the calling package

=item caller.subname - the name of the calling subroutine (won't be added if there is none)

=item source.file - filename where the subroutine is defined

=item source.line - line on which the subroutine is defined

=item source.package - package in which the subroutine is located

=item source.subname - the name of the subroutine

=back

Additionally, if a wrapped subroutine dies, an additional C<error> tag
(set to a true value) and C<message> tag, containing (C<$@>) will be
added to the span.

=head2 Signatures

If a signature is used, tags containing the argument values will be added to the span.
The syntax is similar to the built-in signatures, although simplified.
A signature is a parenthesised list of arguments:

  use OpenTracing::WrapScope 'Foo::bar($arg, \%config)'

In this example, calling

  Foo::bar('test', { date => '2020-01-01', limit => 16 })

will produce the following tags:

 arguments.arg = 'test'
 arguments.config.date = '2020-01-01'
 arguments.config.limit = 10

The following argument types are supported:

=over 4

=item Scalar

Captures a single argument and assigns it to a tag with the specified name.

  use OpenTracing::WrapScope 'foo($bar)';

  foo('username')

Produces:

  arguments.bar = 'username'

=item undef

Causes an argument to be skipped.

  use OpenTracing::WrapScope 'foo($first, undef, $third)';

  foo(1, 2, 3)

Produces:

  arguments.first = 1
  arguments.third = 3

=item Array

Captures all remaining arguments and puts them into indexed tags.

  use OpenTracing::WrapScope 'foo(@args)';

  foo('a', 'b', 'c')

Produces:

  arguments.args.0 = 'a'
  arguments.args.1 = 'b'
  arguments.args.2 = 'c'

Optionally, slices can be used to limit which elements are to be captured:

  use OpenTracing::WrapScope 'foo(@args[ 0, 2 ])';
  use OpenTracing::WrapScope 'foo(@args[ 3, 5..8 ])';

B<Note>: Only literal integers and ranges can be used for slicing.

=item ArrayRef

Captures a single array reference argument and puts it into indexed tags.

  use OpenTracing::WrapScope 'foo(\@args)';

  foo([ 'x', 'y', 'z' ])

Produces:

  arguments.args.0 = 'x'
  arguments.args.1 = 'y'
  arguments.args.2 = 'z'

It supports slicing in the same way as the regular L<Array>.

  use OpenTracing::WrapScope 'foo(\@args[ 0, 2 ])';
  use OpenTracing::WrapScope 'foo(\@args[ 3, 5..8 ])';

B<Note>: If at the time of the call, the specified argument is not an array
reference, it will be silently ignored and no tags will be produced for that
argument.

=item Hash (or key-value pairs)

Captures all remaining arguments and treats them as a hash.

  use OpenTracing::WrapScope 'foo(%args)';

  foo(x => 1, y => 2, z => 3)

Produces:

  arguments.foo.x = 1
  arguments.foo.y = 2
  arguments.foo.y = 3

Optionally, slices can be used to limit which elements are to be captured:

  use OpenTracing::WrapScope 'foo(%args{"x", "y"})';

B<Note>: Only single or double quoted strings can be used for slicing.

=item HashRef

Captures a single hash reference argument and puts it into tags.

  use OpenTracing::WrapScope 'foo(\%args)';

  foo( { x => 1, y => 2, z => 3 } )

Produces:

  arguments.foo.x = 1
  arguments.foo.y = 2
  arguments.foo.y = 3

It supports slicing in the same way as the regular L<Hash>.

  use OpenTracing::WrapScope 'foo(\%args{"x", "y"})';

B<Note>: If at the time of the call, the specified argument is not a hash
reference, it will be silently ignored and no tags will be produced for that
argument.

=back

=head2 -file

List of subroutine names can be read from text files.
Only a simple list of subroutines is supported with each line containing
a single subroutine name, the subroutine needs to be fully qualified,
regardless of where L<OpenTracing::WrapScope> is used.
The effect is the same as using the subroutine names directly.
Multiple files can be specified at once.

Comments are supported, any line starting with C<#> will be skipped,
trailing comments beginning with C<#> will be ignored.

Suppose you have a file called C<wrapscope.conf> with the following contents:

  Foo::bar
  Foo::baz
  main::run

You could wrap these subroutines by using:

  use OpenTracing::WrapScope -file => 'wrapscope.conf';

Mutiple filenames can be specified by using an arrayref:

  use OpenTracing::WrapScope -file => [ 'config/wrap_base.conf', 'config/wrap_extra.conf' ];

or a shell glob (expanded using the L<glob> builtin):

  use OpenTracing::WrapScope -file => 'config/wrap*.conf';

=head2 -env

If this argument is specified, arguments for L<-file> will be read from
B<OPENTRACING_WRAPSCOPE_FILE>. This environment variable can contain a single
filename/glob or multiple ones separated by colons:

  OPENTRACING_WRAPSCOPE_FILE=wrapscope.conf
  OPENTRACING_WRAPSCOPE_FILE=config/wrap_base.conf:config/wrap_extra.conf
  OPENTRACING_WRAPSCOPE_FILE='config/wrap*.conf'

=head2 -quiet

Normally, if any of the requested subs (either directly or through a file)
is not found, a warning will be issued at the end of the programs runtime.
Passing this option will disable that warning.

=head1 FUNCTIONS

=head2 install_wrapped($sub)

Replaces the specified subroutine with a span-handling version.
$sub needs to be a fully qualified subroutine name or an unqualified name
from the current package, it can optionally include a signature (see L<Signatures>).

The subroutine needs to be defined or a warning will be thrown. This warning
can be made fatal with:

  use warnings FATAL => 'OpenTracing::WrapScope';

or disabled with:

  no warnings 'OpenTracing::WrapScope';

=head2 wrapped($code_ref, $signature)

Returns a version of the given code reference with span handling attached.
Useful for adding spans to anonymous subroutines.

A signature may be optionally included to track the arguments,
it B<should not> include parentheses:

  my $coderef = OpenTracing::WrapScope::wrapped(sub { ... }, '$arg1, $arg2, @others')

See L<Signatures> for details.

=head2 wrap_from_file($filename)

Read a list of subroutines from $filename and install scope handlers in them.
The file should contain a list of subroutines (identical to the one accepted
by C<-file> in import()).

=head1 CAVEATS

=head2 C<caller>

Because this module overrides caller, it's best to use it as soon as possible,
before caller-using code is compiled. It likely won't work well with other
modules which override caller themselves.

=head2 C<Exporter>

Subroutines exported using L<Exporter> or a similar module could split into
two versions. If the export happens before the span handling is applied to
a subroutine, only the original version will have a span, the exported
version will be unmodified.

In order to wrap subroutines in modules utilising L<Exporter>,
use L<OpenTracing::WrapScope> directly in those modules.

=head2 C<Moose>

Since L<OpenTracing::WrapScope> works by replacing subroutines with modified
versions, it's not possible to apply to L<Moose> classes which have been made
immutable.


=head1 AUTHOR

Szymon Nieznanski <snieznanski@perceptyx.com>



=head1 COPYRIGHT AND LICENSE

'OpenTracing::WrapScope'
is Copyright (C) 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
