test of CatLogger idiom
[p5sagit/Log-Contextual.git] / lib / Log / Contextual.pm
CommitLineData
0daa11f3 1package Log::Contextual;
2
a2777569 3use strict;
4use warnings;
2033c911 5
6d77ba42 6our $VERSION = '0.00304';
2033c911 7
ae9785e2 8my @levels = qw(debug trace warn info error fatal);
9
2033c911 10require Exporter;
f11f9542 11use Data::Dumper::Concise;
5b094c87 12use Scalar::Util 'blessed';
2033c911 13
a2777569 14BEGIN { our @ISA = qw(Exporter) }
2033c911 15
ae9785e2 16my @dlog = ((map "Dlog_$_", @levels), (map "DlogS_$_", @levels));
17
18my @log = ((map "log_$_", @levels), (map "logS_$_", @levels));
f11f9542 19
b144ba01 20eval {
21 require Log::Log4perl;
22 die if $Log::Log4perl::VERSION < 1.29;
23 Log::Log4perl->wrapper_register(__PACKAGE__)
24};
25
a2777569 26our @EXPORT_OK = (
f11f9542 27 @dlog, @log,
9b8e24d5 28 qw( set_logger with_logger )
f11f9542 29);
30
a2777569 31our %EXPORT_TAGS = (
f11f9542 32 dlog => \@dlog,
33 log => \@log,
3dc9bd3c 34 all => [@dlog, @log],
f11f9542 35);
36
37sub import {
a2777569 38 my $package = shift;
a2777569 39
1e9144f0 40 my $skipnext;
41 my @rest;
a2777569 42 for my $idx ( 0 .. $#_ ) {
1e9144f0 43 if ($skipnext) {
44 $skipnext--;
45 next;
46 }
47
3ccc9c47 48 my $val = $_[$idx];
1e9144f0 49 if (!defined $val) {
50 next;
51 } elsif ( $val eq '-logger' ) {
a2777569 52 set_logger($_[$idx + 1]);
1e9144f0 53 $skipnext = 1;
a74cad5e 54 } elsif ( $val eq '-args' ) {
55 _set_package_logger_args_for(scalar caller, $_[$idx + 1]);
56 $skipnext = 1;
1e9144f0 57 } elsif ( $val eq '-package_logger' ) {
e2b4b29c 58 _set_package_logger_for(scalar caller, $_[$idx + 1]);
1e9144f0 59 $skipnext = 1;
60 } elsif ( $val eq '-default_logger' ) {
3ccc9c47 61 _set_default_logger_for(scalar caller, $_[$idx + 1]);
1e9144f0 62 $skipnext = 1;
63 } else {
64 push @rest, $val;
a2777569 65 }
66 }
1e9144f0 67
68 die 'Log::Contextual does not have a default import list'
69 unless @rest;
70
71 $package->export_to_level(1, $package, @rest);
f11f9542 72}
2033c911 73
7cec609c 74our $Get_Logger;
5d8f2b84 75our %Default_Logger;
e2b4b29c 76our %Package_Logger;
a74cad5e 77our %Package_Logger_Args;
5d8f2b84 78
3ccc9c47 79sub _set_default_logger_for {
80 my $logger = $_[1];
81 if(ref $logger ne 'CODE') {
82 die 'logger was not a CodeRef or a logger object. Please try again.'
83 unless blessed($logger);
84 $logger = do { my $l = $logger; sub { $l } }
85 }
86 $Default_Logger{$_[0]} = $logger
87}
06e908c3 88
a74cad5e 89sub _set_package_logger_args_for {
90 my $args = $_[1];
91 die 'args are not an arrayref. Please try again.'
92 unless ref $args eq 'ARRAY';
93 $Package_Logger_Args{$_[0]} = $args
94}
95
e2b4b29c 96sub _set_package_logger_for {
97 my $logger = $_[1];
98 if(ref $logger ne 'CODE') {
99 die 'logger was not a CodeRef or a logger object. Please try again.'
100 unless blessed($logger);
101 $logger = do { my $l = $logger; sub { $l } }
102 }
103 $Package_Logger{$_[0]} = $logger
104}
105
5d8f2b84 106sub _get_logger($) {
107 my $package = shift;
108 (
e2b4b29c 109 $Package_Logger{$package} ||
5d8f2b84 110 $Get_Logger ||
111 $Default_Logger{$package} ||
112 die q( no logger set! you can't try to log something without a logger! )
a74cad5e 113 )->({
114 package => $package,
115 args => $Package_Logger_Args{$package},
116 });
5d8f2b84 117}
7cec609c 118
8dc5a747 119sub set_logger {
120 my $logger = $_[0];
5b094c87 121 if(ref $logger ne 'CODE') {
122 die 'logger was not a CodeRef or a logger object. Please try again.'
123 unless blessed($logger);
124 $logger = do { my $l = $logger; sub { $l } }
125 }
5d8f2b84 126
127 warn 'set_logger (or -logger) called more than once! This is a bad idea!'
128 if $Get_Logger;
8dc5a747 129 $Get_Logger = $logger;
7cec609c 130}
131
98833ffb 132sub with_logger {
133 my $logger = $_[0];
5b094c87 134 if(ref $logger ne 'CODE') {
135 die 'logger was not a CodeRef or a logger object. Please try again.'
136 unless blessed($logger);
137 $logger = do { my $l = $logger; sub { $l } }
138 }
98833ffb 139 local $Get_Logger = $logger;
80c3e48b 140 $_[1]->();
2daff231 141}
142
d11de6ae 143sub _do_log {
144 my $level = shift;
145 my $logger = shift;
146 my $code = shift;
147 my @values = @_;
148
d11de6ae 149 $logger->$level($code->(@_))
150 if $logger->${\"is_$level"};
151 @values
152}
153
154sub _do_logS {
155 my $level = shift;
156 my $logger = shift;
157 my $code = shift;
158 my $value = shift;
159
d11de6ae 160 $logger->$level($code->($value))
161 if $logger->${\"is_$level"};
709d11fe 162 $value
163}
164
ae9785e2 165for my $level (@levels) {
166 no strict 'refs';
a4273dbf 167
ae9785e2 168 *{"log_$level"} = sub (&@) {
169 _do_log( $level => _get_logger( caller ), shift @_, @_)
170 };
a4273dbf 171
ae9785e2 172 *{"logS_$level"} = sub (&$) {
173 _do_logS( $level => _get_logger( caller ), $_[0], $_[1])
174 };
a4273dbf 175
ae9785e2 176 *{"Dlog_$level"} = sub (&@) {
177 my $code = shift;
178 local $_ = (@_?Data::Dumper::Concise::Dumper @_:'()');
179 return _do_log( $level => _get_logger( caller ), $code, @_ );
180 };
a4273dbf 181
ae9785e2 182 *{"DlogS_$level"} = sub (&$) {
183 local $_ = Data::Dumper::Concise::Dumper $_[1];
184 _do_logS( $level => _get_logger( caller ), $_[0], $_[1] )
185 };
f11f9542 186}
187
0daa11f3 1881;
0a3750e2 189
190__END__
191
2daff231 192=head1 NAME
193
8bc568d2 194Log::Contextual - Simple logging interface with a contextual log
2daff231 195
196=head1 SYNOPSIS
197
9b8e24d5 198 use Log::Contextual qw( :log :dlog set_logger with_logger );
5b094c87 199 use Log::Contextual::SimpleLogger;
200 use Log::Log4perl ':easy';
201 Log::Log4perl->easy_init($DEBUG);
2daff231 202
2daff231 203
5b094c87 204 my $logger = Log::Log4perl->get_logger;
205
206 set_logger $logger;
2daff231 207
9b8e24d5 208 log_debug { 'program started' };
2daff231 209
210 sub foo {
9b8e24d5 211 with_logger(Log::Contextual::SimpleLogger->new({
212 levels => [qw( trace debug )]
21431192 213 }) => sub {
214 log_trace { 'foo entered' };
9b8e24d5 215 my ($foo, $bar) = Dlog_trace { "params for foo: $_" } @_;
2daff231 216 # ...
21431192 217 log_trace { 'foo left' };
9b8e24d5 218 });
2daff231 219 }
220
5b094c87 221 foo();
222
9fe4eeb3 223Beginning with version 1.008 L<Log::Dispatchouli> also works out of the box
224with C<Log::Contextual>:
225
226 use Log::Contextual qw( :log :dlog set_logger );
227 use Log::Dispatchouli;
228 my $ld = Log::Dispatchouli->new({
229 ident => 'slrtbrfst',
230 to_stderr => 1,
231 debug => 1,
232 });
233
234 set_logger $ld;
235
236 log_debug { 'program started' };
237
2daff231 238=head1 DESCRIPTION
239
3dc9bd3c 240This module is a simple interface to extensible logging. It is bundled with a
241really basic logger, L<Log::Contextual::SimpleLogger>, but in general you
242should use a real logger instead of that. For something more serious but not
9fe4eeb3 243overly complicated, try L<Log::Dispatchouli> (see L</SYNOPSIS> for example.)
3dc9bd3c 244
245=head1 OPTIONS
246
c154d18a 247=head2 -logger
248
3dc9bd3c 249When you import this module you may use C<-logger> as a shortcut for
250L<set_logger>, for example:
251
252 use Log::Contextual::SimpleLogger;
9b8e24d5 253 use Log::Contextual qw( :dlog ),
254 -logger => Log::Contextual::SimpleLogger->new({ levels => [qw( debug )] });
3dc9bd3c 255
256sometimes you might want to have the logger handy for other stuff, in which
257case you might try something like the following:
258
259 my $var_log;
260 BEGIN { $var_log = VarLogger->new }
9b8e24d5 261 use Log::Contextual qw( :dlog ), -logger => $var_log;
3dc9bd3c 262
e2b4b29c 263=head2 -package_logger
264
265The C<-package_logger> import option is similar to the C<-logger> import option
266except C<-package_logger> sets the the logger for the current package.
267
268Unlike L</-default_logger>, C<-package_logger> cannot be overridden with
269L</set_logger>.
270
271 package My::Package;
272 use Log::Contextual::SimpleLogger;
273 use Log::Contextual qw( :log ),
274 -package_logger => Log::Contextual::WarnLogger->new({
275 env_prefix => 'MY_PACKAGE'
276 });
277
278If you are interested in using this package for a module you are putting on
279CPAN we recommend L<Log::Contextual::WarnLogger> for your package logger.
280
c154d18a 281=head2 -default_logger
282
283The C<-default_logger> import option is similar to the C<-logger> import option
e2b4b29c 284except C<-default_logger> sets the the B<default> logger for the current package.
c154d18a 285
286Basically it sets the logger to be used if C<set_logger> is never called; so
287
288 package My::Package;
289 use Log::Contextual::SimpleLogger;
290 use Log::Contextual qw( :log ),
291 -default_logger => Log::Contextual::WarnLogger->new({
ae59bbe3 292 env_prefix => 'MY_PACKAGE'
c154d18a 293 });
294
3dc9bd3c 295=head1 A WORK IN PROGRESS
296
297This module is certainly not complete, but we will not break the interface
298lightly, so I would say it's safe to use in production code. The main result
299from that at this point is that doing:
300
301 use Log::Contextual;
302
303will die as we do not yet know what the defaults should be. If it turns out
304that nearly everyone uses the C<:log> tag and C<:dlog> is really rare, we'll
9b8e24d5 305probably make C<:log> the default. But only time and usage will tell.
2daff231 306
307=head1 FUNCTIONS
308
309=head2 set_logger
310
311 my $logger = WarnLogger->new;
21431192 312 set_logger $logger;
313
0e13e261 314Arguments: C<Ref|CodeRef $returning_logger>
2daff231 315
21431192 316C<set_logger> will just set the current logger to whatever you pass it. It
317expects a C<CodeRef>, but if you pass it something else it will wrap it in a
06e908c3 318C<CodeRef> for you. C<set_logger> is really meant only to be called from a
319top-level script. To avoid foot-shooting the function will warn if you call it
320more than once.
2daff231 321
322=head2 with_logger
323
324 my $logger = WarnLogger->new;
21431192 325 with_logger $logger => sub {
2daff231 326 if (1 == 0) {
327 log_fatal { 'Non Logical Universe Detected' };
328 } else {
329 log_info { 'All is good' };
330 }
80c3e48b 331 };
2daff231 332
0e13e261 333Arguments: C<Ref|CodeRef $returning_logger, CodeRef $to_execute>
2daff231 334
21431192 335C<with_logger> sets the logger for the scope of the C<CodeRef> C<$to_execute>.
0e13e261 336As with L</set_logger>, C<with_logger> will wrap C<$returning_logger> with a
21431192 337C<CodeRef> if needed.
2daff231 338
21431192 339=head2 log_$level
2daff231 340
0e13e261 341Import Tag: C<:log>
3dc9bd3c 342
0e13e261 343Arguments: C<CodeRef $returning_message, @args>
2daff231 344
21431192 345All of the following six functions work the same except that a different method
346is called on the underlying C<$logger> object. The basic pattern is:
2daff231 347
0e13e261 348 sub log_$level (&@) {
21431192 349 if ($logger->is_$level) {
0e13e261 350 $logger->$level(shift->(@_));
21431192 351 }
0e13e261 352 @_
21431192 353 }
2daff231 354
0e13e261 355Note that the function returns it's arguments. This can be used in a number of
356ways, but often it's convenient just for partial inspection of passthrough data
357
358 my @friends = log_trace {
359 'friends list being generated, data from first friend: ' .
360 Dumper($_[0]->TO_JSON)
361 } generate_friend_list();
362
363If you want complete inspection of passthrough data, take a look at the
364L</Dlog_$level> functions.
365
21431192 366=head3 log_trace
2daff231 367
21431192 368 log_trace { 'entered method foo with args ' join q{,}, @args };
2daff231 369
21431192 370=head3 log_debug
2daff231 371
21431192 372 log_debug { 'entered method foo' };
2daff231 373
21431192 374=head3 log_info
2daff231 375
21431192 376 log_info { 'started process foo' };
2daff231 377
21431192 378=head3 log_warn
2daff231 379
21431192 380 log_warn { 'possible misconfiguration at line 10' };
2daff231 381
21431192 382=head3 log_error
2daff231 383
21431192 384 log_error { 'non-numeric user input!' };
2daff231 385
21431192 386=head3 log_fatal
2daff231 387
388 log_fatal { '1 is never equal to 0!' };
389
0e13e261 390=head2 logS_$level
391
392Import Tag: C<:log>
393
394Arguments: C<CodeRef $returning_message, Item $arg>
395
396This is really just a special case of the L</log_$level> functions. It forces
397scalar context when that is what you need. Other than that it works exactly
398same:
399
400 my $friend = logS_trace {
401 'I only have one friend: ' . Dumper($_[0]->TO_JSON)
402 } friend();
403
404See also: L</DlogS_$level>.
405
21431192 406=head2 Dlog_$level
407
0e13e261 408Import Tag: C<:dlog>
3dc9bd3c 409
0e13e261 410Arguments: C<CodeRef $returning_message, @args>
2daff231 411
0e13e261 412All of the following six functions work the same as their L</log_$level>
9b8e24d5 413brethren, except they return what is passed into them and put the stringified
21431192 414(with L<Data::Dumper::Concise>) version of their args into C<$_>. This means
415you can do cool things like the following:
416
417 my @nicks = Dlog_debug { "names: $_" } map $_->value, $frew->names->all;
418
419and the output might look something like:
420
421 names: "fREW"
422 "fRIOUX"
423 "fROOH"
424 "fRUE"
425 "fiSMBoC"
426
427=head3 Dlog_trace
428
9b8e24d5 429 my ($foo, $bar) = Dlog_trace { "entered method foo with args: $_" } @_;
21431192 430
431=head3 Dlog_debug
432
433 Dlog_debug { "random data structure: $_" } { foo => $bar };
434
435=head3 Dlog_info
436
437 return Dlog_info { "html from method returned: $_" } "<html>...</html>";
438
439=head3 Dlog_warn
440
441 Dlog_warn { "probably invalid value: $_" } $foo;
442
443=head3 Dlog_error
444
445 Dlog_error { "non-numeric user input! ($_)" } $port;
2daff231 446
21431192 447=head3 Dlog_fatal
2daff231 448
21431192 449 Dlog_fatal { '1 is never equal to 0!' } 'ZOMG ZOMG' if 1 == 0;
2daff231 450
83b33eb5 451=head2 DlogS_$level
452
0e13e261 453Import Tag: C<:dlog>
3dc9bd3c 454
0e13e261 455Arguments: C<CodeRef $returning_message, Item $arg>
83b33eb5 456
0e13e261 457Like L</logS_$level>, these functions are a special case of L</Dlog_$level>.
458They only take a single scalar after the C<$returning_message> instead of
459slurping up (and also setting C<wantarray>) all the C<@args>
83b33eb5 460
461 my $pals_rs = DlogS_debug { "pals resultset: $_" }
462 $schema->resultset('Pals')->search({ perlers => 1 });
463
3dc9bd3c 464=head1 LOGGER INTERFACE
465
466Because this module is ultimately pretty looking glue (glittery?) with the
467awesome benefit of the Contextual part, users will often want to make their
468favorite logger work with it. The following are the methods that should be
469implemented in the logger:
470
471 is_trace
472 is_debug
473 is_info
474 is_warn
475 is_error
476 is_fatal
477 trace
478 debug
479 info
480 warn
481 error
482 fatal
483
484The first six merely need to return true if that level is enabled. The latter
485six take the results of whatever the user returned from their coderef and log
486them. For a basic example see L<Log::Contextual::SimpleLogger>.
487
2daff231 488=head1 AUTHOR
489
490frew - Arthur Axel "fREW" Schmidt <frioux@gmail.com>
491
492=head1 DESIGNER
493
494mst - Matt S. Trout <mst@shadowcat.co.uk>
495
496=head1 COPYRIGHT
497
498Copyright (c) 2010 the Log::Contextual L</AUTHOR> and L</DESIGNER> as listed
499above.
500
501=head1 LICENSE
502
503This library is free software and may be distributed under the same terms as
504Perl 5 itself.
505
506=cut
507