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