The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

use Carp ();
has format => (
is => 'ro',
isa => 'Str',
default => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"',
);
after_handle {
my ( $c, $self, $req, $res ) = @_;
if ($res) {
my $msg = $self->format;
$msg =~ s/\%\{([\w-]+)\}i/$req->header($1) || '-'/ge; # %{User-Agent}
$msg =~ s/\%(?:[><])?([a-z])/handle_char($req, $res, $1) || '-'/ge; # %r
$msg =~ s/\%\%/%/g; # %%
$self->log($msg);
}
return $res;
};
sub handle_char {
my ($req, $res, $char) = @_;
my $code = +{
'h' => sub {
$req->address, # remote host
},
'l' => sub {
'-', # remote log name
},
'u' => sub {
$req->user; # user name
},
't' => sub {
my $dt = DateTime->now;
$dt->strftime("[%d/%b/%y:%H:%M:%S %z]");
},
'r' => sub {
join ' ', $req->method, $req->uri->path_query, ($req->protocol||'HTTP/1.0'); # ?
},
'b' => sub {
$res->content_length || '-'; # size of response in bytes, excluding HTTP headers
},
's' => sub {
$res->status
},
}->{$char};
if ($code) {
return $code->();
} else {
Carp::croak "unknown log char '$char'";
}
}
__MIDDLEWARE__
__END__
=head1 NAME
HTTP::Engine::Middleware::AccessLog - write access log
=head1 SYNOPSIS
my $mw = HTTP::Engine::Middleware->new;
$mw->install( 'HTTP::Engine::Middleware::AccessLog' => {
logger => sub {
warn @_; # your own callback routine
},
format => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"',
});
HTTP::Engine->new(
interface => {
module => 'YourFavoriteInterfaceHere',
request_handler => $mw->handler( \&handler ),
}
)->run();
=head1 DESCRIPTION
This middleware prints access log like apache.
This module's log format string is a subset of Apache.
If you want to use more syntax, patches welcome :)
=head1 BUG
%r should contains query string.
=head1 AUTHOR
Tokuhiro Matsuno
=head1 SEE ALSO
L<HTTP::Engine::Middleware>