File Coverage

blib/lib/Log/Dispatchouli/Global.pm
Criterion Covered Total %
statement 44 56 78.5
branch 3 10 30.0
condition 8 15 53.3
subroutine 14 16 87.5
pod 5 6 83.3
total 74 103 71.8


line stmt bran cond sub pod time code
1 2     2   216428 use strict;
  2         23  
  2         60  
2 2     2   11 use warnings;
  2         5  
  2         97  
3             package Log::Dispatchouli::Global;
4             # ABSTRACT: a system for sharing a global, dynamically-scoped logger
5             $Log::Dispatchouli::Global::VERSION = '2.023';
6 2     2   13 use Carp ();
  2         4  
  2         47  
7 2     2   1053 use Log::Dispatchouli;
  2         7  
  2         70  
8 2     2   13 use Scalar::Util ();
  2         4  
  2         53  
9              
10 2     2   1006 use Sub::Exporter::GlobExporter 0.002 qw(glob_exporter); # pass-through args
  2         2060  
  2         13  
11 2         7 use Sub::Exporter -setup => {
12             collectors => {
13             '$Logger' => glob_exporter(Logger => \'_build_logger'),
14             },
15 2     2   501 };
  2         5  
16              
17             #pod =head1 DESCRIPTION
18             #pod
19             #pod B<Warning>: This interface is still experimental.
20             #pod
21             #pod Log::Dispatchouli::Global is a framework for a global logger object. In your
22             #pod top-level programs that are actually executed, you'd add something like this:
23             #pod
24             #pod use Log::Dispatchouli::Global '$Logger' => {
25             #pod init => {
26             #pod ident => 'My::Daemon',
27             #pod facility => 'local2',
28             #pod to_stdout => 1,
29             #pod },
30             #pod };
31             #pod
32             #pod This will import a C<$Logger> into your program, and more importantly will
33             #pod initialize it with a new L<Log::Dispatchouli> object created by passing the
34             #pod value for the C<init> parameter to Log::Dispatchouli's C<new> method.
35             #pod
36             #pod Much of the rest of your program, across various libraries, can then just use
37             #pod this:
38             #pod
39             #pod use Log::Dispatchouli::Global '$Logger';
40             #pod
41             #pod sub whatever {
42             #pod ...
43             #pod
44             #pod $Logger->log("about to do something");
45             #pod
46             #pod local $Logger = $Logger->proxy({ proxy_prefix => "whatever: " });
47             #pod
48             #pod for (@things) {
49             #pod $Logger->log([ "doing thing %s", $_ ]);
50             #pod ...
51             #pod }
52             #pod }
53             #pod
54             #pod This eliminates the need to pass around what is effectively a global, while
55             #pod still allowing it to be specialized within certain contexts of your program.
56             #pod
57             #pod B<Warning!> Although you I<could> just use Log::Dispatchouli::Global as your
58             #pod shared logging library, you almost I<certainly> want to write a subclass that
59             #pod will only be shared amongst your application's classes.
60             #pod Log::Dispatchouli::Global is meant to be subclassed and shared only within
61             #pod controlled systems. Remember, I<sharing your state with code you don't
62             #pod control is dangerous>.
63             #pod
64             #pod =head1 USING
65             #pod
66             #pod In general, you will either be using a Log::Dispatchouli::Global class to get
67             #pod a C<$Logger> or to initialize it (and then get C<$Logger>). These are both
68             #pod demonstrated above. Also, when importing C<$Logger> you may request it be
69             #pod imported under a different name:
70             #pod
71             #pod use Log::Dispatchouli::Global '$Logger' => { -as => 'L' };
72             #pod
73             #pod $L->log( ... );
74             #pod
75             #pod There is only one class method that you are likely to use: C<current_logger>.
76             #pod This provides the value of the shared logger from the caller's context,
77             #pod initializing it to a default if needed. Even this method is unlikely to be
78             #pod required frequently, but it I<does> allow users to I<see> C<$Logger> without
79             #pod importing it.
80             #pod
81             #pod =head1 SUBCLASSING
82             #pod
83             #pod Before using Log::Dispatchouli::Global in your application, you should subclass
84             #pod it. When you subclass it, you should provide the following methods:
85             #pod
86             #pod =head2 logger_globref
87             #pod
88             #pod This method should return a globref in which the shared logger will be stored.
89             #pod Subclasses will be in their own package, so barring any need for cleverness,
90             #pod every implementation of this method can look like the following:
91             #pod
92             #pod sub logger_globref { no warnings 'once'; return \*Logger }
93             #pod
94             #pod =cut
95              
96             sub logger_globref {
97 2     2   899 no warnings 'once';
  2         5  
  2         1146  
98 6     6 1 12 \*Logger;
99             }
100              
101             sub current_logger {
102 0     0 0 0 my ($self) = @_;
103              
104 0         0 my $globref = $self->logger_globref;
105              
106 0 0       0 unless (defined $$$globref) {
107 0         0 $$$globref = $self->default_logger;
108             }
109              
110 0         0 return $$$globref;
111             }
112              
113             #pod =head2 default_logger
114             #pod
115             #pod If no logger has been initialized, but something tries to log, it gets the
116             #pod default logger, created by calling this method.
117             #pod
118             #pod The default implementation calls C<new> on the C<default_logger_class> with the
119             #pod result of C<default_logger_args> as the arguments.
120             #pod
121             #pod =cut
122              
123             sub default_logger {
124 7     7 1 16 my ($self) = @_;
125              
126 7         20 my $ref = $self->default_logger_ref;
127              
128 7   66     38 $$ref ||= $self->default_logger_class->new(
129             $self->default_logger_args
130             );
131             }
132              
133             #pod =head2 default_logger_class
134             #pod
135             #pod This returns the class on which C<new> will be called when initializing a
136             #pod logger, either from the C<init> argument when importing or the default logger.
137             #pod
138             #pod Its default value is Log::Dispatchouli.
139             #pod
140             #pod =cut
141              
142 4     4 1 13 sub default_logger_class { 'Log::Dispatchouli' }
143              
144             #pod =head2 default_logger_args
145             #pod
146             #pod If no logger has been initialized, but something tries to log, it gets the
147             #pod default logger, created by calling C<new> on the C<default_logger_class> and
148             #pod passing the results of calling this method.
149             #pod
150             #pod Its default return value creates a sink, so that anything logged without an
151             #pod initialized logger is lost.
152             #pod
153             #pod =cut
154              
155             sub default_logger_args {
156             return {
157 1     1 1 9 ident => "default/$0",
158             facility => undef,
159             }
160             }
161              
162             #pod =head2 default_logger_ref
163             #pod
164             #pod This method returns a scalar reference in which the cached default value is
165             #pod stored for comparison. This is used when someone tries to C<init> the global.
166             #pod When someone tries to initialize the global logger, and it's already set, then:
167             #pod
168             #pod =for :list
169             #pod * if the current value is the same as the default, the new value is set
170             #pod * if the current value is I<not> the same as the default, we die
171             #pod
172             #pod Since you want the default to be isolated to your application's logger, the
173             #pod default behavior is default loggers are associated with the glob reference to
174             #pod which the default might be assigned. It is unlikely that you will need to
175             #pod interact with this method.
176             #pod
177             #pod =cut
178              
179             my %default_logger_for_glob;
180              
181             sub default_logger_ref {
182 5     5 1 11 my ($self) = @_;
183              
184 5         13 my $glob = $self->logger_globref;
185 5         18 my $addr = Scalar::Util::refaddr($glob);
186 5         17 return \$default_logger_for_glob{ $addr };
187             }
188              
189             sub _equiv {
190 0     0   0 my ($self, $x, $y) = @_;
191              
192 0 0       0 return 1 if Scalar::Util::refaddr($x) == Scalar::Util::refaddr($y);
193 0 0       0 return 1 if $x->config_id eq $y->config_id;
194             return
195 0         0 }
196              
197             sub _build_logger {
198 7     7   3297 my ($self, $arg) = @_;
199              
200 7         29 my $globref = $self->logger_globref;
201 7         33 my $default = $self->default_logger;
202              
203 7         21 my $Logger = $$$globref;
204              
205 7 100 100     28 if ($arg and $arg->{init}) {
206 1         3 my $new_logger = $self->default_logger_class->new($arg->{init});
207              
208 1 50 0     8 if ($Logger
      33        
209             and not(
210             $self->_equiv($Logger, $new_logger)
211             or
212             $self->_equiv($Logger, $default)
213             )
214             ) {
215             # We already set up a logger, so we'll check that our new one is
216             # equivalent to the old. If so, we'll keep the old, since it's good
217             # enough. If not, we'll raise an exception: you can't configure the
218             # logger twice, with different configurations, in one program!
219             # -- rjbs, 2011-01-21
220 0         0 my $old = $Logger->config_id;
221 0         0 my $new = $new_logger->config_id;
222              
223 0         0 Carp::confess(sprintf(
224             "attempted to initialize %s logger twice; old config %s, new config %s",
225             $self,
226             $old,
227             $new,
228             ));
229             }
230              
231 1         4 $$$globref = $new_logger;
232             } else {
233 6   66     23 $$$globref ||= $default;
234             }
235              
236 7         21 return $globref;
237             }
238              
239             #pod =head1 COOKBOOK
240             #pod
241             #pod =head2 Common Logger Recipes
242             #pod
243             #pod Say you often use the same configuration for one kind of program, like
244             #pod automated tests. You've already written your own subclass to get your own
245             #pod storage and defaults, maybe C<MyApp::Logger>.
246             #pod
247             #pod You can't just write a subclass with a different default, because if another
248             #pod class using the same global has set the global with I<its> default, yours won't
249             #pod be honored. You don't just want this new value to be the default, you want it
250             #pod to be I<the> logger. What you want to do in this case is to initialize your
251             #pod logger normally, then reexport it, like this:
252             #pod
253             #pod package MyApp::Logger::Test;
254             #pod use parent 'MyApp::Logger';
255             #pod
256             #pod use MyApp::Logger '$Logger' => {
257             #pod init => {
258             #pod ident => "Tester($0)",
259             #pod to_self => 1,
260             #pod facility => undef,
261             #pod },
262             #pod };
263             #pod
264             #pod This will set up the logger and re-export it, and will properly die if anything
265             #pod else attempts to initialize the logger to something else.
266             #pod
267             #pod =cut
268              
269             1;
270              
271             __END__
272              
273             =pod
274              
275             =encoding UTF-8
276              
277             =head1 NAME
278              
279             Log::Dispatchouli::Global - a system for sharing a global, dynamically-scoped logger
280              
281             =head1 VERSION
282              
283             version 2.023
284              
285             =head1 DESCRIPTION
286              
287             B<Warning>: This interface is still experimental.
288              
289             Log::Dispatchouli::Global is a framework for a global logger object. In your
290             top-level programs that are actually executed, you'd add something like this:
291              
292             use Log::Dispatchouli::Global '$Logger' => {
293             init => {
294             ident => 'My::Daemon',
295             facility => 'local2',
296             to_stdout => 1,
297             },
298             };
299              
300             This will import a C<$Logger> into your program, and more importantly will
301             initialize it with a new L<Log::Dispatchouli> object created by passing the
302             value for the C<init> parameter to Log::Dispatchouli's C<new> method.
303              
304             Much of the rest of your program, across various libraries, can then just use
305             this:
306              
307             use Log::Dispatchouli::Global '$Logger';
308              
309             sub whatever {
310             ...
311              
312             $Logger->log("about to do something");
313              
314             local $Logger = $Logger->proxy({ proxy_prefix => "whatever: " });
315              
316             for (@things) {
317             $Logger->log([ "doing thing %s", $_ ]);
318             ...
319             }
320             }
321              
322             This eliminates the need to pass around what is effectively a global, while
323             still allowing it to be specialized within certain contexts of your program.
324              
325             B<Warning!> Although you I<could> just use Log::Dispatchouli::Global as your
326             shared logging library, you almost I<certainly> want to write a subclass that
327             will only be shared amongst your application's classes.
328             Log::Dispatchouli::Global is meant to be subclassed and shared only within
329             controlled systems. Remember, I<sharing your state with code you don't
330             control is dangerous>.
331              
332             =head1 PERL VERSION SUPPORT
333              
334             This module has a long-term perl support period. That means it will not
335             require a version of perl released fewer than five years ago.
336              
337             Although it may work on older versions of perl, no guarantee is made that the
338             minimum required version will not be increased. The version may be increased
339             for any reason, and there is no promise that patches will be accepted to lower
340             the minimum required perl.
341              
342             =head1 USING
343              
344             In general, you will either be using a Log::Dispatchouli::Global class to get
345             a C<$Logger> or to initialize it (and then get C<$Logger>). These are both
346             demonstrated above. Also, when importing C<$Logger> you may request it be
347             imported under a different name:
348              
349             use Log::Dispatchouli::Global '$Logger' => { -as => 'L' };
350              
351             $L->log( ... );
352              
353             There is only one class method that you are likely to use: C<current_logger>.
354             This provides the value of the shared logger from the caller's context,
355             initializing it to a default if needed. Even this method is unlikely to be
356             required frequently, but it I<does> allow users to I<see> C<$Logger> without
357             importing it.
358              
359             =head1 SUBCLASSING
360              
361             Before using Log::Dispatchouli::Global in your application, you should subclass
362             it. When you subclass it, you should provide the following methods:
363              
364             =head2 logger_globref
365              
366             This method should return a globref in which the shared logger will be stored.
367             Subclasses will be in their own package, so barring any need for cleverness,
368             every implementation of this method can look like the following:
369              
370             sub logger_globref { no warnings 'once'; return \*Logger }
371              
372             =head2 default_logger
373              
374             If no logger has been initialized, but something tries to log, it gets the
375             default logger, created by calling this method.
376              
377             The default implementation calls C<new> on the C<default_logger_class> with the
378             result of C<default_logger_args> as the arguments.
379              
380             =head2 default_logger_class
381              
382             This returns the class on which C<new> will be called when initializing a
383             logger, either from the C<init> argument when importing or the default logger.
384              
385             Its default value is Log::Dispatchouli.
386              
387             =head2 default_logger_args
388              
389             If no logger has been initialized, but something tries to log, it gets the
390             default logger, created by calling C<new> on the C<default_logger_class> and
391             passing the results of calling this method.
392              
393             Its default return value creates a sink, so that anything logged without an
394             initialized logger is lost.
395              
396             =head2 default_logger_ref
397              
398             This method returns a scalar reference in which the cached default value is
399             stored for comparison. This is used when someone tries to C<init> the global.
400             When someone tries to initialize the global logger, and it's already set, then:
401              
402             =over 4
403              
404             =item *
405              
406             if the current value is the same as the default, the new value is set
407              
408             =item *
409              
410             if the current value is I<not> the same as the default, we die
411              
412             =back
413              
414             Since you want the default to be isolated to your application's logger, the
415             default behavior is default loggers are associated with the glob reference to
416             which the default might be assigned. It is unlikely that you will need to
417             interact with this method.
418              
419             =head1 COOKBOOK
420              
421             =head2 Common Logger Recipes
422              
423             Say you often use the same configuration for one kind of program, like
424             automated tests. You've already written your own subclass to get your own
425             storage and defaults, maybe C<MyApp::Logger>.
426              
427             You can't just write a subclass with a different default, because if another
428             class using the same global has set the global with I<its> default, yours won't
429             be honored. You don't just want this new value to be the default, you want it
430             to be I<the> logger. What you want to do in this case is to initialize your
431             logger normally, then reexport it, like this:
432              
433             package MyApp::Logger::Test;
434             use parent 'MyApp::Logger';
435              
436             use MyApp::Logger '$Logger' => {
437             init => {
438             ident => "Tester($0)",
439             to_self => 1,
440             facility => undef,
441             },
442             };
443              
444             This will set up the logger and re-export it, and will properly die if anything
445             else attempts to initialize the logger to something else.
446              
447             =head1 AUTHOR
448              
449             Ricardo SIGNES <rjbs@semiotic.systems>
450              
451             =head1 COPYRIGHT AND LICENSE
452              
453             This software is copyright (c) 2021 by Ricardo SIGNES.
454              
455             This is free software; you can redistribute it and/or modify it under
456             the same terms as the Perl 5 programming language system itself.
457              
458             =cut