Group
Extension

SlapbirdAPM-Agent-CGI/lib/SlapbirdAPM/Agent/CGI.pm

package SlapbirdAPM::Agent::CGI;

use strict;
use warnings;

use Carp;
use CGI;
use LWP::UserAgent;
use POSIX ();
use SlapbirdAPM::CGI::DBIx::Tracer;
use IO::Tee;
use IO::Pipe;
use Time::HiRes;
use HTTP::Request;
use HTTP::Response;
use System::Info;
use JSON;

$Carp::Internal{__PACKAGE__} = 1;

our $VERSION = '0.05';

my %request_headers;
our $cgi = CGI->new();
our $handler;
our $start_time;
our @error;
our $queries = [];
our $key;
pipe( our $reader, our $writer );

SlapbirdAPM::CGI::DBIx::Tracer->new(
    sub {
        my %args = @_;
        push @$queries, { sql => $args{sql}, total_time => $args{time} };
    }
);

sub import {
    $key = $ENV{SLAPBIRDAPM_API_KEY};

    if ( !$key ) {
        warn(
"Your SlapbirdAPM key is not set, set the SLAPBIRDAPM_API_KEY environment variable to use SlapbirdAPM."
        );
        return;
    }

    $start_time = time * 1_000;
    ($handler) = caller;
    %request_headers = map { $_ => $cgi->http($_) } $cgi->http();
    local *tee = IO::Tee->new( $writer, *STDOUT{IO} );

    *{OLD_STDOUT} = *STDOUT{IO};
    *{STDOUT}     = *tee;

    $SIG{__DIE__} = sub {
        @error = @_;
    };

    return;
}

END {
    if ( !$key ) {
        close($writer);
        close($reader);
        return;
    }

    my $end_time = time * 1_000;

    close($writer);
    my $raw_response = do {
        local $/ = undef;
        <$reader>;
    };
    close($reader);

    if ( $raw_response !~ /^HTTP\/\d+\s\d+\s[A-Za-z].*/mxi ) {
        if ( !@error ) {
            $raw_response = "HTTP/1.1 200 OK\r\n$raw_response";
        }
        else {
            $raw_response =
              "HTTP/1.1 500 Internal Server Error\r\n$raw_response";
        }
    }

    local $SIG{CHLD} = 'IGNORE';

    if ( fork() ) {
        return;
    }

    my $res = HTTP::Response->parse($raw_response);

    my $slapbird_hash = {
        type       => 'cgi',
        method     => $ENV{REQUEST_METHOD},
        end_point  => $cgi->url( -path_info => 1, -query => 1, -absolute => 1 ),
        start_time => $start_time,
        end_time   => $end_time,
        response_code => +$res->code,
        response_size => $res->header('content-length')
          // length( $res->content ) // 0,
        response_headers => +{ $res->headers->flatten() },
        request_headers  => \%request_headers,
        request_size     => $request_headers{HTTP_CONTENT_LENGTH} // 0,
        error            => join( "\n", @error ),
        requestor        => $request_headers{HTTP_X_SLAPBIRD_NAME} // 'UNKNOWN',
        handler          => $handler eq 'main' ? 'CGI' : $handler,
        stack       => [],  # We don't trace stacks in CGI, because the overhead
        os          => System::Info->new->os,
        queries     => $queries,
        num_queries => scalar @$queries
    };

    my $ua = LWP::UserAgent->new();

    my $uri =
        $ENV{SLAPBIRD_APM_URI}
      ? $ENV{SLAPBIRD_APM_URI} . '/apm'
      : 'https://slapbirdapm.com/apm';

    my $sb_request = HTTP::Request->new( POST => $uri );
    $sb_request->content_type('application/json');
    $sb_request->content( encode_json($slapbird_hash) );
    $sb_request->header( 'x-slapbird-apm' => $key );
    my $result = $ua->request($sb_request);

    if ( !$result->is_success ) {
        warn( "Unable to communicate with Slapbird, got status code: "
              . $result->code );
    }

    POSIX::_exit(0);
}

1;

=pod

=encoding utf8

=head1 NAME

SlapbirdAPM::Agent::CGI

The L<SlapbirdAPM|https://www.slapbirdapm.com> user-agent for L<CGI> applications.

=head1 SYNOPSIS

=over 2

=item *

Create an application on L<SlapbirdAPM|https://www.slapbirdapm.com>

=item *

Install this ie C<cpanm SlapbirdAPM::Agent::CGI>, C<cpan -I SlapbirdAPM::Agent::CGI>

=item *

Add C<use SlapbirdAPM::Agent::CGI> near the top of your L<CGI> script

=item *

Add your API key to your environment, in Apache that looks like: C<SetEnv SLAPBIRDAPM_API_KEY ...>

=item *

Restart your web-server

=back

=head1 EXAMPLE

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    use DBI;
    use SlapbirdAPM::Agent::CGI;
    use CGI;
    
    my $dbh = DBI->connect('dbi:SQLite:dbname=file.db', '', '');
    
    my $sth = $dbh->prepare(q[select time('now');]);
    $sth->execute();
    my $time     = $sth->fetch->[0];
    my $response = 'Hello World! It is ' . $time . " o'clock";
    
    my $cgi = CGI->new();
    
    print $cgi->header();
    print <<"END"
    <!DOCTYPE html>
    <html>
      <body>
        <p>$response</p>
      </body>
    </html>
    END

=head1 SEE ALSO

L<SlapbirdAPM::Agent::Plack>

L<SlapbirdAPM::Agent::Mojo>

L<SlapbirdAPM::Agent::Dancer2>

=head1 AUTHOR

Mollusc Software Solutions (formerly Mollusc Labs), C<https://github.com/mollusc-labs>

=head1 LICENSE

SlapbirdAPM::Agent::CGI like all SlapbirdAPM user-agents is licensed under the MIT license.

SlapbirdAPM (the website) however, is licensed under the GNU AGPL version 3.0.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.