Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: a73a375585
Fetching contributors…

Cannot retrieve contributors at this time

file 296 lines (221 sloc) 8.504 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
package Poet::Log;
use Poet qw($conf $poet);
use File::Spec::Functions qw(rel2abs);
use Log::Any::Adapter;
use Method::Signatures::Simple;
use Poet::Tools qw(can_load dirname mkpath read_file write_file);
use strict;
use warnings;

method get_logger ($class: %params) {
    my $category = $params{category} || caller();
    return Log::Any->get_logger( category => $category );
}

method initialize_logging ($class:) {
    if ( can_load('Log::Log4perl')
        && can_load('Log::Any::Adapter')
        && can_load('Log::Any::Adapter::Log4perl') )
    {
        unless ( Log::Log4perl->initialized() ) {
            my $config_string = $class->generate_log4perl_config();
            Log::Log4perl->init( \$config_string );
        }
        Log::Any::Adapter->set('Log4perl');
    }
    else {
        write_file(
            $poet->logs_path("poet.log.ERROR"),
            sprintf(
                "[%s] Could not load Log::Log4perl or Log::Any::Adapter::Log4perl. Install them to enable logging, or modify logging for your application (see Poet::Manual::Subclassing).\n",
                scalar(localtime) )
        );
    }
}

method generate_log4perl_config ($class:) {
    my %log_config = %{ $conf->get_hash('log') };
    if ( my $log4perl_conf = $log_config{log4perl_conf} ) {
        $log4perl_conf = rel2abs( $log4perl_conf, $poet->conf_dir );
        return read_file($log4perl_conf);
    }

    my %defaults = (
        level => 'info',
        output => 'poet.log',
        layout => '%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n',
        %{ $log_config{'defaults'} || {} }
    );
    my %classes = %{ $log_config{'class'} || {} };

    foreach my $set ( values(%classes) ) {
        foreach my $key (qw(level output layout)) {
            $set->{$key} = $defaults{$key} if !exists( $set->{$key} );
        }
    }
    foreach my $set ( \%defaults, values(%classes) ) {
        if ( $set->{output} =~ /^(?:stderr|stdout)$/ ) {
            $set->{appender_class} = "Log::Log4perl::Appender::Screen";
            $set->{stderr} = 0 if $set->{output} eq 'stdout';
        }
        else {
            $set->{appender_class} = "Log::Log4perl::Appender::File";
            $set->{filename} = rel2abs( $set->{output}, $poet->logs_dir );
            mkpath( dirname( $set->{filename} ), 0, 0775 );
        }
    }
    return join(
        "\n",
        $class->_generate_lines( 'log4perl.logger', 'default', \%defaults ),
        map {
            $class->_generate_lines( "log4perl.logger.$_", $class->_flatten_class_name($_),
                $classes{$_} )
        } sort( keys(%classes) ),
    );
}

method _generate_lines ($class: $logger, $appender, $set) {
    my $full_appender = "log4perl.appender.$appender";
    my @pairs = (
        [ $logger => join( ", ", uc( $set->{level} ), $appender ) ],
        [ $full_appender => $set->{appender_class} ],
        [ "$full_appender.layout" => 'Log::Log4perl::Layout::PatternLayout' ],
        [ "$full_appender.layout.ConversionPattern" => $set->{layout} ]
    );
    foreach my $key (qw(filename stderr)) {
        if ( exists( $set->{$key} ) ) {
            push( @pairs, [ "$full_appender.$key" => $set->{$key} ] );
        }
    }

    my $lines = join( "\n", map { join( " = ", @$_ ) } @pairs ) . "\n";
    return $lines;
}

method _flatten_class_name ($class: $class_name) {
    $class_name =~ s/(::|\.)/_/g;
    return $class_name;
}

1;

__END__

=pod

=head1 NAME

Poet::Log -- Poet logging

=head1 SYNOPSIS

# In a conf file...
log:
defaults:
level: info
output: poet.log
layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n"
category:
CHI:
level: debug
output: chi.log
layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} %m - %P%n"
MyApp::Foo:
output: stdout

# In a script...
use Poet::Script qw($log);

# In a module...
use Poet qw($log);

# In a component...
my $log = $m->log;

# For an arbitrary category...
my $log = Poet::Log->get_logger(category => 'MyApp::Bar');

# then...
$log->error("an error occurred");

$log->debugf("arguments are: %s", \@_)
if $log->is_debug();

=head1 DESCRIPTION

Poet uses L<Log::Any|Log::Any> and L<Log::Log4perl|Log::Log4perl> for logging,
with simplified configuration for the common case.

Log::Any is a logging abstraction that allows CPAN modules to log without
knowing about which logging framework is in use. It supports standard logging
methods (C<$log-E<gt>debug>, C<$log-E<gt>is_debug>) along with sprintf variants
(C<$log-E<gt>debugf>).

Log4perl is a powerful logging package that provides just about any
logging-related feature you'd want. One of its only drawbacks is its somewhat
cumbersome configuration. So, we provide a way to configure Log4perl simply
through L<Poet configuration|Poet::Conf> if you just want common features.

Note: Log4perl is not a strict dependency for Poet. Log messages will simply
not get logged until you install it or until you L<modify logging|/MODIFIABLE
METHODS> for your app.

=head1 CONFIGURATION

The configurations below can go in any L<Poet conf
file|Poet::Conf/CONFIGURATION FILES>, e.g. C<local.cfg> or C<global/log.cfg>.

Here's a simple configuration that caches everything to C<logs/poet.log> at
C<info> level. This is also the default if no configuration is present.

log:
defaults:
level: info
output: poet.log
layout: %d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n

Here's a more involved configuration that maintains the same default, but adds
several I<categories> that are logged differently:

log:
defaults:
level: info
output: poet.log
layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} [%p] %c - %m - %F:%L - %P%n"
category:
CHI:
level: debug
output: chi.log
layout: "%d{dd/MMM/yyyy:HH:mm:ss.SS} %m - %P%n"
MyApp::Foo:
output: stdout

For the default and for each category, you can specify three different
settings:

=over

=item *

level - one of the valid log4perl levels (fatal, error, warn, info, debug,
trace)

=item *

output - can be a relative filename (which will be placed in the Poet log
directory), an absolute filename, or the special names "stdout" or "stderr"

=item *

layout - a valid log4perl L<PatternLayout|Log::Log4perl::Layout::PatternLayout>
string.

=back

If a setting isn't defined for a specific category then it falls back to the
default. In this example, C<MyApp::Foo> will inherit the default level and
layout.

Notice that we use '::' instead of '.' to specify hierarchical category names,
because '.' would interfere with L<Poet::Conf dot notation|Poet::Conf/Dot
notation for hash access>.

Finally, if you must use a full L<Log4perl configuration
file|Log::Log4perl::Config>, you can specify it this way:

log:
log4perl_conf: /path/to/log4perl.conf

=head1 USAGE

=head2 Obtaining log handle

=over

=item *

In a script (log category will be 'main'):

use Poet::Script qw($log);

=item *

In a module C<MyApp::Foo> (log category will be 'MyApp::Foo'):

use Poet qw($log);

=item *

In a component C</foo/bar> (log category will be 'Mason::Component::foo::bar'):

my $log = $m->log;

=item *

Manually for an arbitrary log category:

my $log = Poet::Log->get_logger(category => 'Some::Category');
# or
my $log = MyApp::Log->get_logger(category => 'Some::Category');

=back

=head2 Using log handle

$log->error("an error occurred");

$log->debugf("arguments are: %s", \@_)
if $log->is_debug();

See C<Log::Any|Log::Any> for more details.

=head1 MODIFIABLE METHODS

These methods are not intended to be called externally, but may be useful to
override or modify with method modifiers in
L<subclasses|Poet::Manual::Subclassing>. Their APIs will be kept as stable as
possible.

=over

=item initialize_logging

Called once when the Poet environment is initialized. By default, initializes
log4perl with the results of L</generate_log4perl_config> and then calls C<<
Log::Any::Adapter->set('Log4perl') >>. You can modify this to initialize
log4perl in your own way, or use a different Log::Any adapter, or use a
completely different logging system.

=item generate_log4perl_config

Returns a log4perl config string based on Poet configuration. You can modify
this to construct and return your own config.

=back
Something went wrong with that request. Please try again.