package Apache::Voodoo::Debug::FirePHP;
$VERSION = "3.0200";
use strict;
use warnings;
no warnings 'uninitialized';
use base("Apache::Voodoo::Debug::Common");
use JSON::DWIW;
use constant {
DEBUG => 'LOG',
INFO => 'INFO',
WARN => 'WARN',
ERROR => 'ERROR',
DUMP => 'DUMP',
TRACE => 'TRACE',
EXCEPTION => 'EXCEPTION',
TABLE => 'TABLE'
};
use constant GROUP_START => 'GROUP_START';
use constant GROUP_END => 'GROUP_END';
use constant WF_VERSION => "2.00";
use constant WF_PROTOCOL => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
use constant WF_PLUGIN => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.WF_VERSION;
use constant WF_STRUCTURE1 => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
use constant WF_STRUCTURE2 => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1';
use constant BLOCK_LENGTH => 5000;
sub new {
my $class = shift;
my $id = shift;
my $conf = shift;
my $self = {};
bless $self,$class;
$self->{json} = JSON::DWIW->new({bad_char_policy => 'convert'});
$self->{setHeader} = sub { return; };
$self->{userAgent} = sub { return; };
my @flags = qw(debug info warn error exception table trace);
$self->{enabled} = 0;
if ($conf eq "1" || (ref($conf) eq "HASH" && $conf->{all})) {
$self->{conf}->{LOG} = 1;
$self->{conf}->{INFO} = 1;
$self->{conf}->{WARN} = 1;
$self->{conf}->{ERROR} = 1;
$self->{conf}->{DUMP} = 1;
$self->{conf}->{TRACE} = 1;
$self->{conf}->{EXCEPTION} = 1;
$self->{conf}->{TABLE} = 1;
$self->{conf}->{GROUP_START} = 1;
$self->{conf}->{GROUP_END} = 1;
$self->{enabled} = 1;
}
elsif (ref($conf) eq "HASH") {
$self->{conf}->{LOG} = 1 if $conf->{debug};
$self->{conf}->{INFO} = 1 if $conf->{info};
$self->{conf}->{WARN} = 1 if $conf->{warn};
$self->{conf}->{ERROR} = 1 if $conf->{error};
$self->{conf}->{DUMP} = 1 if $conf->{dump};
$self->{conf}->{TRACE} = 1 if $conf->{trace};
$self->{conf}->{EXCEPTION} = 1 if $conf->{exception};
$self->{conf}->{TABLE} = 1 if $conf->{table};
if (scalar keys %{$self->{'conf'}}) {
$self->{enabled} = 1;
$self->{conf}->{GROUP_START} = 1;
$self->{conf}->{GROUP_END} = 1;
}
}
return $self;
}
sub init {
my $self = shift;
$self->{mp} = shift;
$self->{enabled} = 0;
return unless $self->_detectClientExtension();
$self->{enable} = $self->{conf};
$self->{messageIndex} = 1;
}
sub shutdown { return; }
sub setProcessorUrl {
my $self = shift;
my $URL = shift;
$self->setHeader('X-FirePHP-ProcessorURL' => $URL);
}
sub setRendererUrl {
my $self = shift;
my $URL = shift;
$self->setHeader('X-FirePHP-RendererURL' => $URL);
}
sub debug { return $_[0]->_fb($_[1], $_[2], DEBUG); }
sub info { return $_[0]->_fb($_[1], $_[2], INFO); }
sub warn { return $_[0]->_fb($_[1], $_[2], WARN); }
sub error { return $_[0]->_fb($_[1], $_[2], ERROR); }
sub exception { return $_[0]->_fb($_[1], $_[2], EXCEPTION); }
sub trace { return $_[0]->_fb($_[1], undef, TRACE); }
sub table { return $_[0]->_fb($_[1], $_[2], TABLE); }
sub _group { return $_[0]->_fb($_[1], undef, GROUP_START); }
sub _groupEnd { return $_[0]->_fb(undef, undef, GROUP_END); }
#
# At some point in the future we might push this info out
# through FirePHP, but not right now.
#
sub mark { return; }
sub return_data { return; }
sub session_id { return; }
sub url { return; }
sub status { return; }
sub params { return; }
sub template_conf { return; }
sub session { return; }
# This is here for API compliance.
# FirePHP has no finalize step
sub finalize { return (); }
#
# Relies on having a callback setup in the constructor that returns the user agent
#
sub _detectClientExtension {
my $self = shift;
my $useragent = $self->{mp}->header_in('User-Agent');
if ($useragent =~ /\bFirePHP\/([.\d]+)/ && $self->_compareVersion($1,'0.0.6')) {
return 1;
}
else {
return 0;
}
}
sub _compareVersion {
my $self = shift;
my @f = split(/\./,shift);
my @s = split(/\./,shift);
my $c = (scalar(@f) > scalar(@s))?scalar(@f):scalar(@s);
for (my $i=0; $i < $c; $i++) {
if ($f[$i] < $s[$i] || (!defined($f[$i]) && defined($s[$i]))) {
return 0;
}
elsif ($f[$i] > $s[$i] || (defined($f[$i]) && !defined($s[$i]))) {
return 1;
}
}
return 1;
}
sub _fb {
my $self = shift;
my $Label = shift;
my $Object = shift;
my $Type = shift;
return unless $self->{enable}->{$Type};
unless (defined($Object) || $Type eq GROUP_START) {
$Object = $Label;
$Label = undef;
}
my %meta = ();
if ($Type eq EXCEPTION || $Type eq TRACE) {
my @trace = $self->stack_trace(1);
my $t = shift @trace;
$meta{'File'} = $t->{class}.$t->{type}.$t->{function};
$meta{'Line'} = $t->{line};
$Object = {
'Class' => $t->{class},
'Type' => $t->{type},
'Function'=> $t->{function},
'Message' => $Object,
'File' => $t->{file},
'Line' => $t->{line},
'Args' => $t->{args},
'Trace' => \@trace
};
}
else {
my @trace = $self->stack_trace(1);
$meta{'File'} = $trace[0]->{class}.$trace[0]->{type}.$trace[0]->{function};
$meta{'Line'} = $trace[0]->{line};
}
my $structure_index = 1;
if ($self->{messageIndex} == 1) {
$self->setHeader('X-Wf-Protocol-1',WF_PROTOCOL);
$self->setHeader('X-Wf-1-Plugin-1',WF_PLUGIN);
if ($Type eq DUMP) {
$structure_index = 2;
$self->setHeader('X-Wf-1-Structure-2',WF_STRUCTURE2);
}
else {
$self->setHeader('X-Wf-1-Structure-1',WF_STRUCTURE1);
}
}
my $msg;
if ($Type eq DUMP) {
$msg = '{"'.$Label.'":'.$self->jsonEncode($Object).'}';
}
else {
$meta{'Type'} = $Type;
$meta{'Label'} = $Label;
$msg = '['.$self->jsonEncode(\%meta).','.$self->jsonEncode($Object).']';
}
# FirePHP wants the number of bytes, not characters. So we can't use length() here, a 2 or 3 byte
# character counts as 1 as far as length is concerned.
my $l = length(unpack('b*',$msg))/8;
if ($l < BLOCK_LENGTH) {
# The message can be send in one block
$self->setHeader(
'X-Wf-1-'.$structure_index.'-1-'.$self->{'messageIndex'},
$l . '|' . $msg . '|'
);
$self->{'messageIndex'}++;
}
else {
# Message needs to be split into multiple parts
my $c = ($l % BLOCK_LENGTH)?int($l/BLOCK_LENGTH)+1:$l/BLOCK_LENGTH;
foreach (my $i=0; $i < $c; $i++) {
my $part = substr($msg, $i*BLOCK_LENGTH, BLOCK_LENGTH);
my $v;
# length prefix on the first part
$v .= $l if ($i==0);
# the data
$v .= '|'.$part.'|';
# \ on the end of the line, on all but the last part
$v .= '\\' if ($i < ($c-1));
$self->setHeader('X-Wf-1-'.$structure_index.'-1-'.$self->{'messageIndex'}, $v);
$self->{'messageIndex'}++;
if ($self->{'messageIndex'} > 99999) {
#throw new Exception('Maximum number (99,999) of messages reached!');
}
}
}
#$self->setHeader('X-Wf-1-Index',$self->{'messageIndex'}-1);
return 1;
}
sub setHeader() {
my $self = shift;
my $name = shift;
my $value = shift;
$self->{mp}->header_out($name,$value);
}
sub jsonEncode {
my $self = shift;
my $Object = shift;
return $self->{'json'}->to_json($Object);
}
1;
################################################################################
# Copyright (c) 2005-2010 Steven Edwards (maverick@smurfbane.org).
# All rights reserved.
#
# You may use and distribute Apache::Voodoo under the terms described in the
# LICENSE file include in this package. The summary is it's a legalese version
# of the Artistic License :)
#
################################################################################