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   207394 use strict;
  2         21  
  2         59  
2 2     2   12 use warnings;
  2         4  
  2         85  
3             package Log::Dispatchouli::Global;
4             # ABSTRACT: a system for sharing a global, dynamically-scoped logger
5             $Log::Dispatchouli::Global::VERSION = '2.022';
6 2     2   11 use Carp ();
  2         3  
  2         50  
7 2     2   1077 use Log::Dispatchouli;
  2         7  
  2         72  
8 2     2   13 use Scalar::Util ();
  2         5  
  2         53  
9              
10 2     2   941 use Sub::Exporter::GlobExporter 0.002 qw(glob_exporter); # pass-through args
  2         1903  
  2         13  
11 2         8 use Sub::Exporter -setup => {
12             collectors => {
13             '$Logger' => glob_exporter(Logger => \'_build_logger'),
14             },
15 2     2   518 };
  2         6  
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   898 no warnings 'once';
  2         5  
  2         1070  
98 6     6 1 10 \*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 13 my ($self) = @_;
125              
126 7         22 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 14 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 10 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 8 my ($self) = @_;
183              
184 5         12 my $glob = $self->logger_globref;
185 5         68 my $addr = Scalar::Util::refaddr($glob);
186 5         16 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   3017 my ($self, $arg) = @_;
199              
200 7         23 my $globref = $self->logger_globref;
201 7         34 my $default = $self->default_logger;
202              
203 7         15 my $Logger = $$$globref;
204              
205 7 100 100     37 if ($arg and $arg->{init}) {
206 1         5 my $new_logger = $self->default_logger_class->new($arg->{init});
207              
208 1 50 0     6 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         2 $$$globref = $new_logger;
232             } else {
233 6   66     21 $$$globref ||= $default;
234             }
235              
236 7         17 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.022
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 USING
333              
334             In general, you will either be using a Log::Dispatchouli::Global class to get
335             a C<$Logger> or to initialize it (and then get C<$Logger>). These are both
336             demonstrated above. Also, when importing C<$Logger> you may request it be
337             imported under a different name:
338              
339             use Log::Dispatchouli::Global '$Logger' => { -as => 'L' };
340              
341             $L->log( ... );
342              
343             There is only one class method that you are likely to use: C<current_logger>.
344             This provides the value of the shared logger from the caller's context,
345             initializing it to a default if needed. Even this method is unlikely to be
346             required frequently, but it I<does> allow users to I<see> C<$Logger> without
347             importing it.
348              
349             =head1 SUBCLASSING
350              
351             Before using Log::Dispatchouli::Global in your application, you should subclass
352             it. When you subclass it, you should provide the following methods:
353              
354             =head2 logger_globref
355              
356             This method should return a globref in which the shared logger will be stored.
357             Subclasses will be in their own package, so barring any need for cleverness,
358             every implementation of this method can look like the following:
359              
360             sub logger_globref { no warnings 'once'; return \*Logger }
361              
362             =head2 default_logger
363              
364             If no logger has been initialized, but something tries to log, it gets the
365             default logger, created by calling this method.
366              
367             The default implementation calls C<new> on the C<default_logger_class> with the
368             result of C<default_logger_args> as the arguments.
369              
370             =head2 default_logger_class
371              
372             This returns the class on which C<new> will be called when initializing a
373             logger, either from the C<init> argument when importing or the default logger.
374              
375             Its default value is Log::Dispatchouli.
376              
377             =head2 default_logger_args
378              
379             If no logger has been initialized, but something tries to log, it gets the
380             default logger, created by calling C<new> on the C<default_logger_class> and
381             passing the results of calling this method.
382              
383             Its default return value creates a sink, so that anything logged without an
384             initialized logger is lost.
385              
386             =head2 default_logger_ref
387              
388             This method returns a scalar reference in which the cached default value is
389             stored for comparison. This is used when someone tries to C<init> the global.
390             When someone tries to initialize the global logger, and it's already set, then:
391              
392             =over 4
393              
394             =item *
395              
396             if the current value is the same as the default, the new value is set
397              
398             =item *
399              
400             if the current value is I<not> the same as the default, we die
401              
402             =back
403              
404             Since you want the default to be isolated to your application's logger, the
405             default behavior is default loggers are associated with the glob reference to
406             which the default might be assigned. It is unlikely that you will need to
407             interact with this method.
408              
409             =head1 COOKBOOK
410              
411             =head2 Common Logger Recipes
412              
413             Say you often use the same configuration for one kind of program, like
414             automated tests. You've already written your own subclass to get your own
415             storage and defaults, maybe C<MyApp::Logger>.
416              
417             You can't just write a subclass with a different default, because if another
418             class using the same global has set the global with I<its> default, yours won't
419             be honored. You don't just want this new value to be the default, you want it
420             to be I<the> logger. What you want to do in this case is to initialize your
421             logger normally, then reexport it, like this:
422              
423             package MyApp::Logger::Test;
424             use parent 'MyApp::Logger';
425              
426             use MyApp::Logger '$Logger' => {
427             init => {
428             ident => "Tester($0)",
429             to_self => 1,
430             facility => undef,
431             },
432             };
433              
434             This will set up the logger and re-export it, and will properly die if anything
435             else attempts to initialize the logger to something else.
436              
437             =head1 AUTHOR
438              
439             Ricardo SIGNES <rjbs@cpan.org>
440              
441             =head1 COPYRIGHT AND LICENSE
442              
443             This software is copyright (c) 2020 by Ricardo SIGNES.
444              
445             This is free software; you can redistribute it and/or modify it under
446             the same terms as the Perl 5 programming language system itself.
447              
448             =cut