FU: Add super awesome and butt-ugly FU::debug_info web interface
This is so much more useful than embedding debugging info inside pages, as I've been doing before.
This commit is contained in:
parent
de36b90cde
commit
b06cc24826
2 changed files with 430 additions and 23 deletions
100
FU.pm
100
FU.pm
|
|
@ -130,21 +130,27 @@ sub init_db($info) {
|
|||
}
|
||||
|
||||
|
||||
my @before_request;
|
||||
my @after_request;
|
||||
sub before_request :prototype(&) ($f) { push @before_request, $f }
|
||||
sub after_request :prototype(&) ($f) { unshift @after_request, $f }
|
||||
sub _caller_info {
|
||||
my($i, @c, @x) = (1);
|
||||
$x[0] !~ /^FU(?:$|::)/ && push @c, [ @x[0..3] ] while (@x = caller $i++);
|
||||
\@c
|
||||
}
|
||||
|
||||
our @before_request;
|
||||
our @after_request;
|
||||
sub before_request :prototype(&) ($f) { push @before_request, [ $f, _caller_info ] }
|
||||
sub after_request :prototype(&) ($f) { unshift @after_request, [ $f, _caller_info ] }
|
||||
|
||||
|
||||
my %path_routes;
|
||||
my %re_routes;
|
||||
our %path_routes;
|
||||
our %re_routes;
|
||||
|
||||
sub _add_route($path, $sub, $method) {
|
||||
if (ref $path eq 'REGEXP' || ref $path eq 'Regexp') {
|
||||
push $re_routes{$method}->@*, [ qr/^$path$/, $sub ];
|
||||
push $re_routes{$method}->@*, [ qr/^$path$/, $sub, _caller_info ];
|
||||
} elsif (!ref $path) {
|
||||
confess("A route has already been registered for $method $path") if $path_routes{$method}{$path};
|
||||
$path_routes{$method}{$path} = $sub;
|
||||
$path_routes{$method}{$path} = [ $sub, _caller_info ];
|
||||
} else {
|
||||
confess('Path argument in route registration must be a string or regex');
|
||||
}
|
||||
|
|
@ -207,6 +213,12 @@ sub _monitor {
|
|||
}
|
||||
|
||||
|
||||
our $debug_info = [];
|
||||
sub debug_info($path, $storage=undef, $history=100) {
|
||||
$debug_info = { path => $path, storage => $storage, history => $history }
|
||||
}
|
||||
|
||||
|
||||
our $hdrname_re = qr/[!#\$\%&'\*\+-\.^_`\|~0-9a-zA-Z]{1,127}/;
|
||||
our $method_re = qr/(?:HEAD|GET|POST|DELETE|OPTIONS|PUT|PATCH|QUERY)/;
|
||||
|
||||
|
|
@ -305,7 +317,7 @@ sub _log_err($e) {
|
|||
}
|
||||
|
||||
sub _do_req($c) {
|
||||
local $REQ = { hdr => {}, trace_start => time };
|
||||
local $REQ = { hdr => {}, trace_start => time, trace_id => sprintf('%010x%08x%04x', int time, $$, int rand 1<<16) };
|
||||
local $fu = bless {}, 'FU::obj';
|
||||
|
||||
$REQ->{ip} = $c->{client_sock} isa 'IO::Socket::INET' ? $c->{client_sock}->peerhost : '127.0.0.1';
|
||||
|
|
@ -315,15 +327,27 @@ sub _do_req($c) {
|
|||
_read_req $c;
|
||||
$REQ->{trace_start} = time;
|
||||
|
||||
for my $h (@before_request) { $h->() }
|
||||
|
||||
my $path = fu->path;
|
||||
my $method = fu->method eq 'HEAD' ? 'GET' : fu->method;
|
||||
|
||||
# Intercept requests for debug_info, ensuring no website hooks get called.
|
||||
if (debug && $method eq 'GET' && $debug_info->{path} && $path eq $debug_info->{path}) {
|
||||
require FU::DebugImpl;
|
||||
FU::DebugImpl::render();
|
||||
fu->_flush($c->{fcgi_obj} || $c->{client_sock});
|
||||
fu->error(-1);
|
||||
}
|
||||
|
||||
for my $h (@before_request) { $h->[0]->() }
|
||||
|
||||
my $r = $path_routes{$method}{$path};
|
||||
if ($r) { $r->() }
|
||||
else {
|
||||
if ($r) {
|
||||
$REQ->{trace_han} = [ $path, $r->[1] ];
|
||||
$r->[0]->();
|
||||
} else {
|
||||
for $r ($re_routes{ fu->method }->@*) {
|
||||
if($path =~ $r->[0]) {
|
||||
$REQ->{trace_han} = [ $r->[0], $r->[2] ];
|
||||
$r->[1]->(@{^CAPTURE});
|
||||
fu->done;
|
||||
}
|
||||
|
|
@ -333,11 +357,12 @@ sub _do_req($c) {
|
|||
1;
|
||||
};
|
||||
return if !$ok && ref $@ eq 'FU::err' && $@->[0] == -1;
|
||||
$REQ->{trace_exn} = $ok ? undef : $@;
|
||||
my $err = $ok || _is_done($@) ? undef : $@;
|
||||
_log_err $err;
|
||||
|
||||
for my $h (@after_request) {
|
||||
$ok = eval { $h->(); 1 };
|
||||
$ok = eval { $h->[0]->(); 1 };
|
||||
_log_err $@ if !$ok;
|
||||
$err = $@ if !$err && !$ok && !_is_done($@);
|
||||
}
|
||||
|
|
@ -361,7 +386,12 @@ sub _do_req($c) {
|
|||
$REQ->{trace_end} = time;
|
||||
fu->_flush($c->{fcgi_obj} || $c->{client_sock});
|
||||
|
||||
my $proc_ms = (time - $REQ->{trace_start}) * 1000;
|
||||
if (debug && $REQ->{trace_id} && $debug_info->{history} && $debug_info->{storage}) {
|
||||
require FU::DebugImpl;
|
||||
FU::DebugImpl::save();
|
||||
}
|
||||
|
||||
my $proc_ms = ($REQ->{trace_end} - $REQ->{trace_start}) * 1000;
|
||||
log_write(sprintf "%.0fms%s %s-%s %s-%d\n", $proc_ms,
|
||||
$REQ->{trace_nsql} ?
|
||||
sprintf ' (sql %.0f+%.0fms, %d/%d/%d)',
|
||||
|
|
@ -639,11 +669,15 @@ sub formdata {
|
|||
|
||||
# Response generation methods
|
||||
|
||||
sub done { die bless [200,'Done'], 'FU::err' }
|
||||
sub error($,$code,$msg=$code) { die bless [$code,$msg], 'FU::err' }
|
||||
sub done { die bless [200,'Done',FU::_caller_info], 'FU::err' }
|
||||
sub error($,$code,$msg=$code) { die bless [$code,$msg,FU::_caller_info], 'FU::err' }
|
||||
|
||||
sub status($, $code) { $FU::REQ->{status} = $code }
|
||||
sub set_body($, $data) { $FU::REQ->{resbody} = $data }
|
||||
sub set_body($, $data) {
|
||||
confess "Invalid undef body" if !defined $data;
|
||||
confess "Invalid attempt to set body to $data" if ref $data;
|
||||
$FU::REQ->{resbody} = $data;
|
||||
}
|
||||
|
||||
sub reset {
|
||||
fu->status(200);
|
||||
|
|
@ -950,9 +984,29 @@ handling and performance tracing.
|
|||
Enable or disable debug mode. Returns the current mode when no argument is
|
||||
given.
|
||||
|
||||
Debug mode currently only enables more verbose logging, but it may influence
|
||||
other features in the future as well. You're of course free to use the debug
|
||||
setting to enable or disable debugging features in your own code.
|
||||
Debug mode currently enables more verbose logging and the C<debug_info>
|
||||
interface below. It may influence other features in the future as well. You're
|
||||
of course free to use the debug setting to enable or disable debugging features
|
||||
in your own code.
|
||||
|
||||
=item FU::debug_info($path, $storage, $history)
|
||||
|
||||
Enable the built-in web interface for inspecting debug info. The interface is
|
||||
accessible from your browser at the given C<$path>, which is matched against
|
||||
C<< fu->path >>.
|
||||
|
||||
When the optional C<$storage> argument is given and set to an existing
|
||||
directory, detailed request data is logged and stored in that directory, which
|
||||
is then made available through the web interface. The C<$history> argument sets
|
||||
the number of requests to keep, which defaults to 100.
|
||||
|
||||
Request logging and the web interface are only available when C<FU::debug> mode
|
||||
is enabled.
|
||||
|
||||
B<WARNING:> This interface exposes internal and potentially sensitive
|
||||
information. When this option is configured, make sure to B<ABSOLUTELY NEVER>
|
||||
enable debug mode in production! Or at least set an absolutely impossible to
|
||||
guess C<$path>.
|
||||
|
||||
=item FU::log_slow_reqs($ms)
|
||||
|
||||
|
|
@ -1271,7 +1325,7 @@ though.
|
|||
|
||||
This method loads the entire file contents in memory and does not support range
|
||||
requests, so DO NOT use it to send large files. Actual web servers are much
|
||||
more efficient at sending static files.
|
||||
more efficient at serving static files.
|
||||
|
||||
The content-type header is determined from the file extension in C<$path>,
|
||||
using the configured C<FU::mime_types>. As fallback, files that look like they
|
||||
|
|
@ -1424,7 +1478,7 @@ external process manager.
|
|||
|
||||
=back
|
||||
|
||||
When C<--monitor> or C<--max-reqs> are set or C<<--proc>> is larger than 1, FU
|
||||
When C<--monitor> or C<--max-reqs> are set or C<--proc> is larger than 1, FU
|
||||
starts a supervisor process to ensure the requested number of worker processes
|
||||
are running and that they are restarted when necessary. When FU has been loaded
|
||||
with the C<-spawn> flag, this supervisor process runs directly from the context
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue