Writing Plack Debugging Middleware for Catalyst
I now have our work project running (sort of) on Catalyst 5.80007. This is because it's the oldest version of Catalyst I can use with Plack. I wanted that just because the debugging middleware for Plack is just so friggin' awesome and I wanted to write my own. Now I have and here's how easy it is (with screenshots).
First, you'll have to get your Catalyst project running 5.80007 or later. Then you install the following:
- Plack
- PSGI
- Starman (not really needed, but it's a really, really fast server that already knows about Plack/PSGI)
- Catalyst::Engine::PSGI
- Plack::Middleware::Debug (yes!)
Then you need to create a .psgi file for your project. Here's what mine looks like:
#!/usr/bin/env perl
use Dynamite;
use Plack::Builder;
Dynamite->setup_engine('PSGI');
my $app = sub { Dynamite->run(@_) };
builder {
enable 'Debug', panels => [qw(DBITrace Memory Timer Environment Dynamite::Cache)];
$app;
};
You probably don't want the Dynamite::Cache panel in there as it's something I created locally.
I saved that file as script/dynamite.psgi and launched my application. Here's my incantation:
plackup -R lib -s Starman script/dynamite.psgi
If you have an HTML interface to your Web site, this is probably all you have to do. Easy and powerful. Just start browsing your Web site and be amazed.
Our application actually has an XML/JSON interface, so I had to do some work internally to recognize when I was debugging with PSGI and rewrite the responses as HTML and set a "200 OK" header (otherwise you won't get the debugging goodies):
require HTML::Entities;
my $content = HTML::Entities::encode_entities($c->response->body);
$c->response->body(<<"END");
<html>
<head><title>PSGI HTML Wrapper</title></head>
<body><pre><tt>$content</tt></pre></body>
</html>
END
$c->response->content_type('text/html');
$c->response->status(RC_OK);
It's a quick hack, but it works. Here's what a request of our episode detail looks like (click to see larger size):
You'll notice a series of "panels" running down the right side of the screen. If you click the one which reads "Memory" you get this:
But what I wanted was this, cache debugging:
Here's how I made it work:
package Plack::Middleware::Debug::Dynamite::Cache;
use strict;
use warnings;
use parent 'Plack::Middleware::Debug::Base';
use Dynamite;
use Cache::Memcached;
sub run {
my ( $self, $env, $panel ) = @_;
return sub {
my $res = shift;
$panel->nav_subtitle('Dynamite Cache Info');
$panel->content(
$self->render_list_pairs([
_get_cache_headers( $res, 'Fetcher' ),
_get_cache_headers( $res, 'Searcher' ),
'Dynamite version' => $Dynamite::VERSION
])
);
};
}
sub _get_cache_headers {
my ( $res, $type ) = @_;
my $m = Cache::Memcached->new( { 'servers' => [ "127.0.0.1:11211", ], } );
my %headers = @{ $res->[1] };
my @keys;
my $header_key = "X-Dynamite-$type-Cache-Key";
my $i = 1;
foreach my $cache_key ( split "\0" => $headers{$header_key} || '' ) {
my $size = length( $m->get($cache_key) );
push @headers => ( "$type\_cache_keys\_$i" => "($size) $key" );
$i++;
}
@headers = ( "$type\_cache_keys" => "None found" );
return @headers;
}
1;
Yeah, that's a nasty hack, but it's the first pass, just to make sure it works.
You'll note that the first argument to _get_cache_headers is the $res and that's simply the Plack standard of passing around an array ref of:
[ $status, $headers, $body ]
Everything else is just a subref which operates on that. It's really, really simple.
With my nasty cache debugging hack, I was quite surprised at the size of some of the cached items, but it was easy to track them. I was going to clean it up, but we have to go through rounds of load testing before we can upgrade our Catalyst to 5.80007. I don't think I can finish this before I move to Amsterdam at the end of the October. It's a shame, but Plack is so easy that anyone on our team should be able to pick this up and finish it off.