File Coverage

blib/lib/Log/Any/Manager.pm
Criterion Covered Total %
statement 98 98 100.0
branch 26 28 92.8
condition 9 10 90.0
subroutine 21 21 100.0
pod 0 7 0.0
total 154 164 93.9


line stmt bran cond sub pod time code
1 25     25   418 use 5.008001;
  25         82  
2 25     25   151 use strict;
  25         43  
  25         553  
3 25     25   190 use warnings;
  25         62  
  25         6905  
4              
5             package Log::Any::Manager;
6              
7             our $VERSION = '1.715';
8              
9             sub new {
10 25     25 0 59 my $class = shift;
11 25         142 my $self = {
12             # The stack of adapter entries
13             entries => [],
14             # A cache of keys with category names and values of a hashref
15             # with stack entries (from the entries attribute) and adapters
16             category_cache => {},
17             # The adapter to use if no other adapter is appropriate
18             default_adapter => {},
19             # The context hashref that is passed to all proxies
20             context => {},
21             };
22 25         67 bless $self, $class;
23              
24 25         95 return $self;
25             }
26              
27             sub has_consumer {
28 37     37 0 188 my ( $self ) = @_;
29 37   100     65 return !!( @{ $self->{entries} } || keys %{ $self->{default_adapter} } || $ENV{LOG_ANY_DEFAULT_ADAPTER} );
30             }
31              
32             sub get_adapter {
33 64     64 0 193 my ( $self, $category ) = @_;
34              
35             # Create a new adapter for this category if it is not already in cache
36             #
37 64         144 my $category_cache = $self->{category_cache};
38 64 100       180 if ( !defined( $category_cache->{$category} ) ) {
39 34         99 my $entry = $self->_choose_entry_for_category($category);
40 34         159 my $adapter = $self->_new_adapter_for_entry( $entry, $category );
41 34         157 $category_cache->{$category} = { entry => $entry, adapter => $adapter };
42             }
43 64         168 return $category_cache->{$category}->{adapter};
44             }
45              
46             {
47 25     25   194 no warnings 'once';
  25         67  
  25         30923  
48             *get_logger = \&get_adapter; # backwards compatibility
49             }
50              
51             sub get_context {
52 64     64 0 126 my ( $self ) = @_;
53 64         185 return $self->{context};
54             }
55              
56             sub _choose_entry_for_category {
57 129     129   258 my ( $self, $category ) = @_;
58              
59 129         183 foreach my $entry ( @{ $self->{entries} } ) {
  129         268  
60 83 100       455 if ( $category =~ $entry->{pattern} ) {
61 74         197 return $entry;
62             }
63             }
64             # nothing requested so fallback to default
65 55   100     268 my $default_adapter_name = $ENV{LOG_ANY_DEFAULT_ADAPTER} || "Null";
66 55   100     225 my $default = $self->{default_adapter}{$category}
67             || [ $self->_get_adapter_class($default_adapter_name), [] ];
68 55         148 my ($adapter_class, $adapter_params) = @$default;
69 55         141 _require_dynamic($adapter_class);
70             return {
71 55         304 adapter_class => $adapter_class,
72             adapter_params => $adapter_params,
73             };
74             }
75              
76             sub _new_adapter_for_entry {
77 125     125   351 my ( $self, $entry, $category ) = @_;
78              
79             return $entry->{adapter_class}
80 125         217 ->new( @{ $entry->{adapter_params} }, category => $category );
  125         518  
81             }
82              
83             sub set_default {
84 4     4 0 69 my ( $self, $category, $adapter_name, @adapter_params ) = @_;
85 4         21 Log::Any::Proxy::Null->inflate_nulls;
86 4         13 my $adapter_class = $self->_get_adapter_class($adapter_name);
87 4         41 $self->{default_adapter}{$category} = [$adapter_class, \@adapter_params];
88             }
89              
90             # =head2 set
91             #
92             # $mgr->set( $options );
93             #
94             # Set the current adapter. Called from
95             # L, the
96             # standard API for setting the current adapter. Adds a new entry to the
97             # C stack and refreshes all the matching adapters.
98             #
99             # See L
100             # for available options.
101             #
102             # Returns the newly-created entry in the stack.
103             sub set {
104 44     44 0 102 my $self = shift;
105 44         72 my $options;
106 44 100       146 if ( ref( $_[0] ) eq 'HASH' ) {
107 18         36 $options = shift(@_);
108             }
109 44         121 my ( $adapter_name, @adapter_params ) = @_;
110              
111 44 100 66     387 unless ( defined($adapter_name) && $adapter_name =~ /\S/ ) {
112 1         6 require Carp;
113 1         195 Carp::croak("expected adapter name");
114             }
115              
116 43         101 my $pattern = $options->{category};
117 43 100       135 if ( !defined($pattern) ) {
    100          
118 40         162 $pattern = qr/.*/;
119             }
120             elsif ( !ref($pattern) ) {
121 1         24 $pattern = qr/^\Q$pattern\E$/;
122             }
123              
124 43         121 my $adapter_class = $self->_get_adapter_class($adapter_name);
125 43         144 _require_dynamic($adapter_class);
126              
127 41         169 my $entry = $self->_new_entry( $pattern, $adapter_class, \@adapter_params );
128 41         87 unshift( @{ $self->{entries} }, $entry );
  41         163  
129              
130 41         139 $self->_reselect_matching_adapters($pattern);
131              
132 37 100       142 if ( my $lex_ref = $options->{lexically} ) {
133             $$lex_ref = Log::Any::Manager::_Guard->new(
134 16 50   16   107 sub { $self->remove($entry) unless _in_global_destruction() } );
  16         519  
135             }
136              
137 37         290 Log::Any::Proxy::Null->inflate_nulls;
138 37         5084 return $entry;
139             }
140              
141             sub remove {
142 18     18 0 41 my ( $self, $entry ) = @_;
143              
144 18         35 my $pattern = $entry->{pattern};
145 18         22 $self->{entries} = [ grep { $_ ne $entry } @{ $self->{entries} } ];
  38         116  
  18         68  
146 18         81 $self->_reselect_matching_adapters($pattern);
147             }
148              
149             sub _new_entry {
150 41     41   118 my ( $self, $pattern, $adapter_class, $adapter_params ) = @_;
151              
152             return {
153 41         154 pattern => $pattern,
154             adapter_class => $adapter_class,
155             adapter_params => $adapter_params,
156             };
157             }
158              
159             # =head2 _reselect_matching_adapters
160             #
161             # $self->_reselect_matching_adapters( $pattern )
162             #
163             # Given a pattern, reselect which adapter should match. This is called
164             # after entries are added/removed from the C attribute.
165             #
166             # XXX Does not actually use $pattern, so do we need to pass it in?
167             sub _reselect_matching_adapters {
168 59     59   118 my ( $self, $pattern ) = @_;
169              
170 59 50       1384 return if _in_global_destruction();
171              
172             # Reselect adapter for each category matching $pattern
173             #
174 59         124 for my $category ( keys %{ $self->{category_cache} } ) {
  59         247  
175 95         178 my $category_info = $self->{category_cache}->{$category};
176 95         208 my $new_entry = $self->_choose_entry_for_category($category);
177 95 100       284 if ( $new_entry ne $category_info->{entry} ) {
178 91         182 my $new_adapter =
179             $self->_new_adapter_for_entry( $new_entry, $category );
180             # Replace existing references to the adapter with the new
181             # adapter
182 87         318 %{ $category_info->{adapter} } = %$new_adapter;
  87         438  
183 87         272 bless( $category_info->{adapter}, ref($new_adapter) );
184 87         802 $category_info->{entry} = $new_entry;
185             }
186             }
187             }
188              
189             sub _get_adapter_class {
190 100     100   244 my ( $self, $adapter_name ) = @_;
191 100 100       244 return $Log::Any::OverrideDefaultAdapterClass if $Log::Any::OverrideDefaultAdapterClass;
192 94         174 $adapter_name =~ s/^Log:://; # Log::Dispatch -> Dispatch, etc.
193 94 100       424 my $adapter_class = (
194             substr( $adapter_name, 0, 1 ) eq '+'
195             ? substr( $adapter_name, 1 )
196             : "Log::Any::Adapter::$adapter_name"
197             );
198 94         268 return $adapter_class;
199             }
200              
201             # This is adapted from the pure perl parts of Devel::GlobalDestruction
202             if ( defined ${^GLOBAL_PHASE} ) {
203 75     75   424 eval 'sub _in_global_destruction () { ${^GLOBAL_PHASE} eq q[DESTRUCT] }; 1' ## no critic
204             or die $@;
205             }
206             else {
207             require B;
208             my $started = !B::main_start()->isa(q[B::NULL]);
209             unless ($started) {
210             eval '0 && $started; CHECK { $started = 1 }; 1' ## no critic
211             or die $@;
212             }
213             eval ## no critic
214             '0 && $started; sub _in_global_destruction () { $started && B::main_start()->isa(q[B::NULL]) }; 1'
215             or die $@;
216             }
217              
218             # XXX not DRY and not a great way to do this, but oh, well.
219             sub _require_dynamic {
220 98     98   174 my ($class) = @_;
221              
222 98 100       755 return 1 if $class->can('new'); # duck-type that class is loaded
223              
224 30 100       2190 unless ( defined( eval "require $class; 1" ) )
225             { ## no critic (ProhibitStringyEval)
226 2         18 die $@;
227             }
228             }
229              
230             package # hide from PAUSE
231             Log::Any::Manager::_Guard;
232              
233 16     16   40 sub new { bless $_[1], $_[0] }
234              
235 16     16   8564 sub DESTROY { $_[0]->() }
236              
237             1;
238              
239             __END__