The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package FirePHP::LogConnector::Catalyst;

=pod

=head1 NAME

FirePHP::LogConnector::Catalyst

=head1 SYNOPSIS

 use FirePHP::LogConnector::Catalyst;
 my $foo = FirePHP::LogConnector::Catalyst->new();

=head1 DESCRIPTION

B<FirePHP::LogConnector::Catalyst> provides some simple
Catalyst related infrastructure to log connectors

=cut

use strict;
use warnings;

use base qw/FirePHP::LogConnector/;

use Carp;
use Scalar::Util qw/blessed weaken/;
__PACKAGE__->mk_accessors( qw/catalyst logger / );

use FirePHP::SimpleTable;

=head1 METHODS

=head2 $class->new( $app )

Returns a new abstract log connector with a
reference to the application class in C<catalyst>
and enabled based on the classes debug mode.

=cut

sub new {
  my ($class, $catalyst ) = @_;

  my $app = blessed( $catalyst ) || $catalyst;

  my $self = $class->SUPER::new({
    catalyst  => $app,
    enabled   => $app->debug ? 1 : 0,
    logger    => $app->log,
  });

  weaken $self->{logger};
  return $self;
}


=head2 $self->enabled

Returns true if the FirePHP dispatcher is enabled and the logger not aborted

=cut

sub enabled {
  my $self = shift;
  return if $self->logger->{abort};
  return $self->SUPER::enabled;
}


=head2 $self->prepare_dispatcher

Support method for LogConnector instances. Requires the
L<FirePHP::Dispatcher> instance to be already in place.

Right now, this basically handles logging request parameters.

=cut

sub prepare_dispatcher {
  my ( $self, $c ) = @_;

  # error reporting is unreliable in this stage
  local $SIG{__DIE__} = sub{
    $c->log->error( @_ );
    die @_ if $^S;
  };

  my $fire_php = $self->fetch_dispatcher
    or die( "Couldn't find FirePHP::Dispatcher while preparing dispatcher" );

  # mostly verbatim from Catalyst.pm (prepare_parameters)
  if ( keys %{ $c->request->query_parameters } ) {
    my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
    for my $key ( sort keys %{ $c->req->query_parameters } ) {
      my $param = $c->req->query_parameters->{$key};
      my $value = defined($param) ? $param : '';
      $t->row( $key, ref $value eq 'ARRAY' ? join( ', ', @$value ) : $value );
    }
    $fire_php->table( "Query Parameters" => $t );
  }

  # mostly verbatim from Catalyst.pm (prepare_body)
  if ( keys %{ $c->req->body_parameters } ) {
    my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
    for my $key ( sort keys %{ $c->req->body_parameters } ) {
      my $param = $c->req->body_parameters->{$key};
      my $value = defined($param) ? $param : '';
      $t->row( $key, ref $value eq 'ARRAY' ? join( ', ', @$value ) : $value );
    }
    $fire_php->table( "Body Parameters" => $t );
  }

  # mostly verbatim from Catalyst.pm (prepare_uploads)
  if ( keys %{ $c->request->uploads } ) {
    my $t = Text::SimpleTable->new(
      [ 12, 'Parameter' ],
      [ 26, 'Filename' ],
      [ 18, 'Type' ],
      [ 9,  'Size' ]
    );
    for my $key ( sort keys %{ $c->request->uploads } ) {
      my $upload = $c->request->uploads->{$key};
      for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) {
        $t->row( $key, $u->filename, $u->type, $u->size );
      }
    }
    $fire_php->table( "File Uploads" => $t );
  }

  for my $model ( map{ $c->model( $_ ) } $c->models ) {
    if ( $model->isa('Catalyst::Model::DBIC::Schema') ) {
      my $profiler = 'DBIx::Class::Storage::Statistics::SimpleTable';
      eval "require $profiler";
      if ( my $err = $@ ) { warn "Error loading ${profiler}: ${err}"; next }
      $profiler->new('FirePHP::SimpleTable')->install( $model->storage );
    }
  }

}

=head2 $self->finalization_method

Returns a closure that can be used by subclassed during their dispatch cycle

=cut

sub finalization_method {
  my ( $self, $c ) = @_;
  my $fire_php = $self->fetch_dispatcher
    or die( "Couldn't find FirePHP::Dispatcher for finalization method" );

  return sub{

    # error reporting is unreliable in this stage
    local $SIG{__WARN__} = sub{ $c->log->warn( @_ ); };
    local $SIG{__DIE__}  = sub{ $c->log->error( @_ ); die @_ if $^S; };

    # flushing the log before we loose our headers object
    $self->flush_log;

    my $finalize = Scope::Guard->new(sub{ $fire_php->finalize });

    # scan our models for supported base classes
    for my $name ( $c->models ) {
      my $model = $c->model( $name );
      if ( $model->isa( 'Catalyst::Model::DBIC::Schema' ) ) {
        my $stats = $model->storage->debugobj;
        next unless $stats->isa(
          'DBIx::Class::Storage::Statistics::SimpleTable'
        ) and $stats->query_count;
        $fire_php->table( sprintf(
          "SQL Profile for ${name} (%d queries in %.4f sec)",
          $stats->query_count, $stats->elapsed_time,
        ) => $stats->report );
        $stats->uninstall;
      }
    }

    # override Text::SimpleTable to use the reporting of facility of
    # Catalyst::Stats (or generic stats packages that use Text::SimpleTable)
    my $new_table = \ &Text::SimpleTable::new;
    my $t = do{
      no warnings 'redefine';
      local *Text::SimpleTable::new = sub {
        bless $new_table->( @_ ), 'FirePHP::SimpleTable';
      };
      $c->stats->report;
    };
    $fire_php->table( "Request Statistics" => $t );

  };
}


=head2 $self->flush_log

Generic log flush for catalyst loggers

=cut

sub flush_log {
  my $self = shift;
  # $log->{abort} fits most Catalyst loggers $log->abort is inconsistent
  $self->logger->_flush unless $self->logger->{abort};
  return;
}

1;

__END__

=head1 SEE ALSO

L<http://www.firephp.org>, L<FirePHP::Dispatcher>

=head1 AUTHOR

Sebastian Willert, C<willert@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright 2009 by Sebastian Willert E<lt>willert@cpan.orgE<gt>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut