fix warnings caused by importing twice
[p5sagit/Log-Contextual.git] / lib / Log / Contextual.pm
CommitLineData
0daa11f3 1package Log::Contextual;
2
dc4fd4b0 3# ABSTRACT: Simple logging interface with a contextual log
4
a2777569 5use strict;
6use warnings;
2033c911 7
ae9785e2 8my @levels = qw(debug trace warn info error fatal);
9
675503c7 10use Exporter::Declare;
11use Exporter::Declare::Export::Generator;
f11f9542 12use Data::Dumper::Concise;
5b094c87 13use Scalar::Util 'blessed';
333fc861 14use Sub::Identify 'stash_name';
2033c911 15
249b9eb6 16my @dlog = ((map "Dlog_$_", @levels), (map "DlogS_$_", @levels));
17
18my @log = ((map "log_$_", @levels), (map "logS_$_", @levels));
19
333fc861 20sub _maybe_export {
21 my ($spec, $target, $name, $new_code) = @_;
22
23 if (my $code = $target->can($name)) {
24
25 # this will warn
26 $spec->add_export("&$name", $new_code)
27 unless (stash_name($code) eq __PACKAGE__);
28 } else {
29 $spec->add_export("&$name", $new_code)
30 }
31}
32
b144ba01 33eval {
34 require Log::Log4perl;
35 die if $Log::Log4perl::VERSION < 1.29;
36 Log::Log4perl->wrapper_register(__PACKAGE__)
37};
38
5fd26f45 39# ____ is because tags must have at least one export and we don't want to
40# export anything but the levels selected
6ae293d7 41sub ____ { }
5fd26f45 42
489f71b2 43exports('____', @dlog, @log, qw( set_logger with_logger ));
f11f9542 44
5fd26f45 45export_tag dlog => ('____');
46export_tag log => ('____');
675503c7 47import_arguments qw(logger package_logger default_logger);
48
139ded54 49sub router {
0092c26a 50 our $Router_Instance ||= do {
51 require Log::Contextual::Router;
52 Log::Contextual::Router->new
6ae293d7 53 }
0092c26a 54}
55
e06303cd 56sub default_import {
57 my ($class) = shift;
58
59 die 'Log::Contextual does not have a default import list';
60
61 ()
62}
63
6ae293d7 64sub arg_logger { $_[1] }
65sub arg_levels { $_[1] || [qw(debug trace warn info error fatal)] }
8112b699 66sub arg_package_logger { $_[1] }
67sub arg_default_logger { $_[1] }
68
675503c7 69sub before_import {
70 my ($class, $importer, $spec) = @_;
6ae293d7 71 my $router = $class->router;
72 my $exports = $spec->exports;
73 my %router_args = (
74 exporter => $class,
75 target => $importer,
76 arguments => $spec->argument_info
77 );
f11f9542 78
e06303cd 79 my @tags = $class->default_import($spec)
6ae293d7 80 if $spec->config->{default};
675503c7 81
e06303cd 82 for (@tags) {
83 die "only tags are supported for defaults at this time"
84 unless $_ =~ /^:(.*)$/;
85
86 $spec->config->{$1} = 1;
87 }
88
a5454e75 89 $router->before_import(%router_args);
8112b699 90
84d7d9ee 91 if ($exports->{'&set_logger'}) {
92 die ref($router) . " does not support set_logger()"
93 unless $router->does('Log::Contextual::Role::Router::SetLogger');
4d605164 94
333fc861 95 _maybe_export($spec, $importer, 'set_logger',
96 sub { $router->set_logger(@_) },
97 );
84d7d9ee 98 }
4d605164 99
84d7d9ee 100 if ($exports->{'&with_logger'}) {
101 die ref($router) . " does not support with_logger()"
102 unless $router->does('Log::Contextual::Role::Router::WithLogger');
4d605164 103
333fc861 104 _maybe_export($spec, $importer, 'with_logger',
105 sub { $router->with_logger(@_) },
106 );
84d7d9ee 107 }
4d605164 108
5fd26f45 109 my @levels = @{$class->arg_levels($spec->config->{levels})};
675503c7 110 for my $level (@levels) {
249b9eb6 111 if ($spec->config->{log} || $exports->{"&log_$level"}) {
333fc861 112 _maybe_export(
113 $spec,
114 $importer,
115 "log_$level",
6ae293d7 116 sub (&@) {
117 my ($code, @args) = @_;
118 $router->handle_log_request(
119 exporter => $class,
120 caller_package => scalar(caller),
121 caller_level => 1,
122 message_level => $level,
123 message_sub => $code,
124 message_args => \@args,
125 );
126 return @args;
333fc861 127 },
128 );
249b9eb6 129 }
130 if ($spec->config->{log} || $exports->{"&logS_$level"}) {
333fc861 131 _maybe_export(
132 $spec,
133 $importer,
134 "logS_$level",
6ae293d7 135 sub (&@) {
136 my ($code, @args) = @_;
137 $router->handle_log_request(
138 exporter => $class,
139 caller_package => scalar(caller),
140 caller_level => 1,
141 message_level => $level,
142 message_sub => $code,
143 message_args => \@args,
144 );
145 return $args[0];
333fc861 146 },
147 );
675503c7 148 }
249b9eb6 149 if ($spec->config->{dlog} || $exports->{"&Dlog_$level"}) {
333fc861 150 _maybe_export(
151 $spec,
152 $importer,
153 "Dlog_$level",
6ae293d7 154 sub (&@) {
155 my ($code, @args) = @_;
156 my $wrapped = sub {
157 local $_ = (@_ ? Data::Dumper::Concise::Dumper @_ : '()');
158 &$code;
159 };
160 $router->handle_log_request(
161 exporter => $class,
162 caller_package => scalar(caller),
163 caller_level => 1,
164 message_level => $level,
165 message_sub => $wrapped,
166 message_args => \@args,
167 );
168 return @args;
333fc861 169 },
170 );
249b9eb6 171 }
172 if ($spec->config->{dlog} || $exports->{"&DlogS_$level"}) {
333fc861 173 _maybe_export(
174 $spec,
175 $importer,
176 "DlogS_$level",
6ae293d7 177 sub (&$) {
178 my ($code, $ref) = @_;
179 my $wrapped = sub {
180 local $_ = Data::Dumper::Concise::Dumper($_[0]);
181 &$code;
182 };
183 $router->handle_log_request(
184 exporter => $class,
185 caller_package => scalar(caller),
186 caller_level => 1,
187 message_level => $level,
188 message_sub => $wrapped,
189 message_args => [$ref],
190 );
191 return $ref;
192 });
a2777569 193 }
194 }
675503c7 195}
196
a5454e75 197sub after_import {
198 my ($class, $importer, $spec) = @_;
6ae293d7 199 my %router_args = (
200 exporter => $class,
201 target => $importer,
202 arguments => $spec->argument_info
203 );
a5454e75 204 $class->router->after_import(%router_args);
205}
709d11fe 206
31285520 207for (qw(set with)) {
208 no strict 'refs';
209 my $sub = "${_}_logger";
210 *{"Log::Contextual::$sub"} = sub {
489f71b2 211 die "$sub is no longer a direct sub in Log::Contextual. "
212 . 'Note that this feature was never tested nor documented. '
213 . "Please fix your code to import $sub instead of trying to use it directly"
214 }
31285520 215}
216
0daa11f3 2171;
0a3750e2 218
219__END__
220
2daff231 221=head1 SYNOPSIS
222
9b8e24d5 223 use Log::Contextual qw( :log :dlog set_logger with_logger );
5b094c87 224 use Log::Contextual::SimpleLogger;
225 use Log::Log4perl ':easy';
226 Log::Log4perl->easy_init($DEBUG);
2daff231 227
5b094c87 228 my $logger = Log::Log4perl->get_logger;
229
230 set_logger $logger;
2daff231 231
9b8e24d5 232 log_debug { 'program started' };
2daff231 233
234 sub foo {
f9bf084b 235
236 my $minilogger = Log::Contextual::SimpleLogger->new({
237 levels => [qw( trace debug )]
238 });
239
2ae9c121 240 my @args = @_;
241
f9bf084b 242 with_logger $minilogger => sub {
21431192 243 log_trace { 'foo entered' };
2ae9c121 244 my ($foo, $bar) = Dlog_trace { "params for foo: $_" } @args;
2daff231 245 # ...
21431192 246 log_trace { 'foo left' };
f9bf084b 247 };
2daff231 248 }
249
5b094c87 250 foo();
251
9fe4eeb3 252Beginning with version 1.008 L<Log::Dispatchouli> also works out of the box
253with C<Log::Contextual>:
254
255 use Log::Contextual qw( :log :dlog set_logger );
256 use Log::Dispatchouli;
257 my $ld = Log::Dispatchouli->new({
258 ident => 'slrtbrfst',
259 to_stderr => 1,
260 debug => 1,
261 });
262
263 set_logger $ld;
264
265 log_debug { 'program started' };
266
2daff231 267=head1 DESCRIPTION
268
30d7027a 269Major benefits:
270
271=over 2
272
273=item * Efficient
274
275The logging functions take blocks, so if a log level is disabled, the
276block will not run:
277
278 # the following won't run if debug is off
279 log_debug { "the new count in the database is " . $rs->count };
280
281Similarly, the C<D> prefixed methods only C<Dumper> the input if the level is
282enabled.
283
284=item * Handy
285
286The logging functions return their arguments, so you can stick them in
287the middle of expressions:
288
289 for (log_debug { "downloading:\n" . join qq(\n), @_ } @urls) { ... }
290
291=item * Generic
292
293C<Log::Contextual> is an interface for all major loggers. If you log through
294C<Log::Contextual> you will be able to swap underlying loggers later.
295
296=item * Powerful
297
39cd2f65 298C<Log::Contextual> chooses which logger to use based on L<< user defined C<CodeRef>s|/LOGGER CODEREF >>.
30d7027a 299Normally you don't need to know this, but you can take advantage of it when you
300need to later
301
302=item * Scalable
303
304If you just want to add logging to your extremely basic application, start with
305L<Log::Contextual::SimpleLogger> and then as your needs grow you can switch to
306L<Log::Dispatchouli> or L<Log::Dispatch> or L<Log::Log4perl> or whatever else.
307
308=back
309
310This module is a simple interface to extensible logging. It exists to
311abstract your logging interface so that logging is as painless as possible,
312while still allowing you to switch from one logger to another.
3dc9bd3c 313
30d7027a 314It is bundled with a really basic logger, L<Log::Contextual::SimpleLogger>,
315but in general you should use a real logger instead of that. For something
316more serious but not overly complicated, try L<Log::Dispatchouli> (see
317L</SYNOPSIS> for example.)
a2af6976 318
e36f2183 319=head1 A WORK IN PROGRESS
320
321This module is certainly not complete, but we will not break the interface
322lightly, so I would say it's safe to use in production code. The main result
323from that at this point is that doing:
324
325 use Log::Contextual;
326
327will die as we do not yet know what the defaults should be. If it turns out
328that nearly everyone uses the C<:log> tag and C<:dlog> is really rare, we'll
329probably make C<:log> the default. But only time and usage will tell.
330
331=head1 IMPORT OPTIONS
332
333See L</SETTING DEFAULT IMPORT OPTIONS> for information on setting these project
334wide.
3dc9bd3c 335
c154d18a 336=head2 -logger
337
3dc9bd3c 338When you import this module you may use C<-logger> as a shortcut for
436c4b82 339L</set_logger>, for example:
3dc9bd3c 340
341 use Log::Contextual::SimpleLogger;
9b8e24d5 342 use Log::Contextual qw( :dlog ),
343 -logger => Log::Contextual::SimpleLogger->new({ levels => [qw( debug )] });
3dc9bd3c 344
345sometimes you might want to have the logger handy for other stuff, in which
346case you might try something like the following:
347
348 my $var_log;
349 BEGIN { $var_log = VarLogger->new }
9b8e24d5 350 use Log::Contextual qw( :dlog ), -logger => $var_log;
3dc9bd3c 351
5fd26f45 352=head2 -levels
353
354The C<-levels> import option allows you to define exactly which levels your
355logger supports. So the default,
356C<< [qw(debug trace warn info error fatal)] >>, works great for
357L<Log::Log4perl>, but it doesn't support the levels for L<Log::Dispatch>. But
358supporting those levels is as easy as doing
359
360 use Log::Contextual
361 -levels => [qw( debug info notice warning error critical alert emergency )];
362
e2b4b29c 363=head2 -package_logger
364
365The C<-package_logger> import option is similar to the C<-logger> import option
44d86a53 366except C<-package_logger> sets the logger for the current package.
e2b4b29c 367
368Unlike L</-default_logger>, C<-package_logger> cannot be overridden with
369L</set_logger>.
370
371 package My::Package;
372 use Log::Contextual::SimpleLogger;
373 use Log::Contextual qw( :log ),
374 -package_logger => Log::Contextual::WarnLogger->new({
375 env_prefix => 'MY_PACKAGE'
376 });
377
378If you are interested in using this package for a module you are putting on
379CPAN we recommend L<Log::Contextual::WarnLogger> for your package logger.
380
c154d18a 381=head2 -default_logger
382
383The C<-default_logger> import option is similar to the C<-logger> import option
44d86a53 384except C<-default_logger> sets the B<default> logger for the current package.
c154d18a 385
386Basically it sets the logger to be used if C<set_logger> is never called; so
387
388 package My::Package;
389 use Log::Contextual::SimpleLogger;
390 use Log::Contextual qw( :log ),
391 -default_logger => Log::Contextual::WarnLogger->new({
ae59bbe3 392 env_prefix => 'MY_PACKAGE'
c154d18a 393 });
394
e36f2183 395=head1 SETTING DEFAULT IMPORT OPTIONS
3dc9bd3c 396
e36f2183 397Eventually you will get tired of writing the following in every single one of
398your packages:
3dc9bd3c 399
e36f2183 400 use Log::Log4perl;
401 use Log::Log4perl ':easy';
402 BEGIN { Log::Log4perl->easy_init($DEBUG) }
3dc9bd3c 403
e36f2183 404 use Log::Contextual -logger => Log::Log4perl->get_logger;
405
406You can set any of the import options for your whole project if you define your
407own C<Log::Contextual> subclass as follows:
408
409 package MyApp::Log::Contextual;
410
411 use base 'Log::Contextual';
412
413 use Log::Log4perl ':easy';
414 Log::Log4perl->easy_init($DEBUG)
415
2b40dee5 416 sub arg_default_logger { $_[1] || Log::Log4perl->get_logger }
e36f2183 417 sub arg_levels { [qw(debug trace warn info error fatal custom_level)] }
e06303cd 418 sub default_import { ':log' }
e36f2183 419
2b40dee5 420 # or maybe instead of default_logger
e36f2183 421 sub arg_package_logger { $_[1] }
e36f2183 422
2b40dee5 423 # and almost definitely not this, which is only here for completeness
424 sub arg_logger { $_[1] }
e36f2183 425
2b40dee5 426Note the C<< $_[1] || >> in C<arg_default_logger>. All of these methods are
427passed the values passed in from the arguments to the subclass, so you can
428either throw them away, honor them, die on usage, or whatever. To be clear,
429if you define your subclass, and someone uses it as follows:
e36f2183 430
2b40dee5 431 use MyApp::Log::Contextual -default_logger => $foo,
432 -levels => [qw(bar baz biff)];
433
434Your C<arg_default_logger> method will get C<$foo> and your C<arg_levels>
e36f2183 435will get C<[qw(bar baz biff)]>;
2daff231 436
e06303cd 437Additionally, the C<default_import> method is what happens if a user tries to
438use your subclass with no arguments. The default just dies, but if you'd like
439to change the default to import a tag merely return the tags you'd like to
440import. So the following will all work:
441
442 sub default_import { ':log' }
443
444 sub default_import { ':dlog' }
445
446 sub default_import { qw(:dlog :log ) }
447
436c4b82 448See L<Log::Contextual::Easy::Default> for an example of a subclass of
449C<Log::Contextual> that makes use of default import options.
450
2daff231 451=head1 FUNCTIONS
452
453=head2 set_logger
454
455 my $logger = WarnLogger->new;
21431192 456 set_logger $logger;
457
27141a7a 458Arguments: L</LOGGER CODEREF>
2daff231 459
21431192 460C<set_logger> will just set the current logger to whatever you pass it. It
461expects a C<CodeRef>, but if you pass it something else it will wrap it in a
06e908c3 462C<CodeRef> for you. C<set_logger> is really meant only to be called from a
463top-level script. To avoid foot-shooting the function will warn if you call it
464more than once.
2daff231 465
466=head2 with_logger
467
468 my $logger = WarnLogger->new;
21431192 469 with_logger $logger => sub {
2daff231 470 if (1 == 0) {
471 log_fatal { 'Non Logical Universe Detected' };
472 } else {
473 log_info { 'All is good' };
474 }
80c3e48b 475 };
2daff231 476
27141a7a 477Arguments: L</LOGGER CODEREF>, C<CodeRef $to_execute>
2daff231 478
21431192 479C<with_logger> sets the logger for the scope of the C<CodeRef> C<$to_execute>.
0e13e261 480As with L</set_logger>, C<with_logger> will wrap C<$returning_logger> with a
21431192 481C<CodeRef> if needed.
2daff231 482
21431192 483=head2 log_$level
2daff231 484
0e13e261 485Import Tag: C<:log>
3dc9bd3c 486
0e13e261 487Arguments: C<CodeRef $returning_message, @args>
2daff231 488
a4d67519 489C<log_$level> functions all work the same except that a different method
21431192 490is called on the underlying C<$logger> object. The basic pattern is:
2daff231 491
0e13e261 492 sub log_$level (&@) {
21431192 493 if ($logger->is_$level) {
0e13e261 494 $logger->$level(shift->(@_));
21431192 495 }
0e13e261 496 @_
21431192 497 }
2daff231 498
0e13e261 499Note that the function returns it's arguments. This can be used in a number of
500ways, but often it's convenient just for partial inspection of passthrough data
501
502 my @friends = log_trace {
503 'friends list being generated, data from first friend: ' .
504 Dumper($_[0]->TO_JSON)
505 } generate_friend_list();
506
507If you want complete inspection of passthrough data, take a look at the
508L</Dlog_$level> functions.
509
a4d67519 510Which functions are exported depends on what was passed to L</-levels>. The
511default (no C<-levels> option passed) would export:
2daff231 512
a4d67519 513=over 2
2daff231 514
a4d67519 515=item log_trace
2daff231 516
a4d67519 517=item log_debug
2daff231 518
a4d67519 519=item log_info
2daff231 520
a4d67519 521=item log_warn
2daff231 522
a4d67519 523=item log_error
2daff231 524
a4d67519 525=item log_fatal
2daff231 526
a4d67519 527=back
2daff231 528
0e13e261 529=head2 logS_$level
530
531Import Tag: C<:log>
532
533Arguments: C<CodeRef $returning_message, Item $arg>
534
535This is really just a special case of the L</log_$level> functions. It forces
536scalar context when that is what you need. Other than that it works exactly
537same:
538
539 my $friend = logS_trace {
540 'I only have one friend: ' . Dumper($_[0]->TO_JSON)
541 } friend();
542
543See also: L</DlogS_$level>.
544
21431192 545=head2 Dlog_$level
546
0e13e261 547Import Tag: C<:dlog>
3dc9bd3c 548
0e13e261 549Arguments: C<CodeRef $returning_message, @args>
2daff231 550
0e13e261 551All of the following six functions work the same as their L</log_$level>
9b8e24d5 552brethren, except they return what is passed into them and put the stringified
21431192 553(with L<Data::Dumper::Concise>) version of their args into C<$_>. This means
554you can do cool things like the following:
555
556 my @nicks = Dlog_debug { "names: $_" } map $_->value, $frew->names->all;
557
558and the output might look something like:
559
560 names: "fREW"
561 "fRIOUX"
562 "fROOH"
563 "fRUE"
564 "fiSMBoC"
565
a4d67519 566Which functions are exported depends on what was passed to L</-levels>. The
567default (no C<-levels> option passed) would export:
21431192 568
a4d67519 569=over 2
21431192 570
a4d67519 571=item Dlog_trace
21431192 572
a4d67519 573=item Dlog_debug
21431192 574
a4d67519 575=item Dlog_info
21431192 576
a4d67519 577=item Dlog_warn
21431192 578
a4d67519 579=item Dlog_error
2daff231 580
a4d67519 581=item Dlog_fatal
2daff231 582
a4d67519 583=back
2daff231 584
83b33eb5 585=head2 DlogS_$level
586
0e13e261 587Import Tag: C<:dlog>
3dc9bd3c 588
0e13e261 589Arguments: C<CodeRef $returning_message, Item $arg>
83b33eb5 590
0e13e261 591Like L</logS_$level>, these functions are a special case of L</Dlog_$level>.
592They only take a single scalar after the C<$returning_message> instead of
593slurping up (and also setting C<wantarray>) all the C<@args>
83b33eb5 594
595 my $pals_rs = DlogS_debug { "pals resultset: $_" }
596 $schema->resultset('Pals')->search({ perlers => 1 });
597
27141a7a 598=head1 LOGGER CODEREF
599
600Anywhere a logger object can be passed, a coderef is accepted. This is so
601that the user can use different logger objects based on runtime information.
602The logger coderef is passed the package of the caller the caller level the
603coderef needs to use if it wants more caller information. The latter is in
604a hashref to allow for more options in the future.
605
37a8266a 606Here is a basic example of a logger that exploits C<caller> to reproduce the
607output of C<warn> with a logger:
608
609 my @caller_info;
610 my $var_log = Log::Contextual::SimpleLogger->new({
611 levels => [qw(trace debug info warn error fatal)],
612 coderef => sub { chomp($_[0]); warn "$_[0] at $caller_info[1] line $caller_info[2].\n" }
613 });
614 my $warn_faker = sub {
615 my ($package, $args) = @_;
616 @caller_info = caller($args->{caller_level});
617 $var_log
618 };
619 set_logger($warn_faker);
620 log_debug { 'test' };
621
27141a7a 622The following is an example that uses the information passed to the logger
623coderef. It sets the global logger to C<$l3>, the logger for the C<A1>
624package to C<$l1>, except the C<lol> method in C<A1> which uses the C<$l2>
625logger and lastly the logger for the C<A2> package to C<$l2>.
626
37a8266a 627Note that it increases the caller level as it dispatches based on where
628the caller of the log function, not the log function itself.
629
27141a7a 630 my $complex_dispatcher = do {
631
632 my $l1 = ...;
633 my $l2 = ...;
634 my $l3 = ...;
635
636 my %registry = (
637 -logger => $l3,
638 A1 => {
639 -logger => $l1,
640 lol => $l2,
641 },
642 A2 => { -logger => $l2 },
643 );
644
645 sub {
646 my ( $package, $info ) = @_;
647
648 my $logger = $registry{'-logger'};
649 if (my $r = $registry{$package}) {
650 $logger = $r->{'-logger'} if $r->{'-logger'};
37a8266a 651 my (undef, undef, undef, $sub) = caller($info->{caller_level} + 1);
27141a7a 652 $sub =~ s/^\Q$package\E:://g;
653 $logger = $r->{$sub} if $r->{$sub};
654 }
655 return $logger;
656 }
657 };
658
659 set_logger $complex_dispatcher;
660
3dc9bd3c 661=head1 LOGGER INTERFACE
662
663Because this module is ultimately pretty looking glue (glittery?) with the
664awesome benefit of the Contextual part, users will often want to make their
665favorite logger work with it. The following are the methods that should be
666implemented in the logger:
667
668 is_trace
669 is_debug
670 is_info
671 is_warn
672 is_error
673 is_fatal
674 trace
675 debug
676 info
677 warn
678 error
679 fatal
680
681The first six merely need to return true if that level is enabled. The latter
682six take the results of whatever the user returned from their coderef and log
683them. For a basic example see L<Log::Contextual::SimpleLogger>.
684
eab2ca3c 685=head1 LOG ROUTING
686
a5454e75 687In between the loggers and the log functions is a log router that is responsible for
eab2ca3c 688finding a logger to handle the log event and passing the log information to the
a5454e75 689logger. This relationship is described in the documentation for C<Log::Contextual::Role::Router>.
eab2ca3c 690
a5454e75 691C<Log::Contextual> and packages that extend it will by default share a router singleton that
692implements the with_logger() and set_logger() functions and also respects the -logger,
693-package_logger, and -default_logger import options with their associated default value
694functions. The router singleton is available as the return value of the router() function. Users
695of Log::Contextual may overload router() to return instances of custom log routers that
696could for example work with loggers that use a different interface.
eab2ca3c 697
a5454e75 698=head1 CONTRIBUTORS
699
436c4b82 700=encoding utf8
701
eab2ca3c 702triddle - Tyler Riddle <t.riddle@shadowcat.co.uk>
703
436c4b82 704voj - Jakob Voß <voss@gbv.de>
705
2daff231 706=head1 DESIGNER
707
708mst - Matt S. Trout <mst@shadowcat.co.uk>
709
2daff231 710=cut
711