File Coverage

blib/lib/Log/Scrubber.pm
Criterion Covered Total %
statement 212 234 90.6
branch 87 104 83.6
condition 21 30 70.0
subroutine 36 39 92.3
pod 13 13 100.0
total 369 420 87.8


line stmt bran cond sub pod time code
1             package Log::Scrubber;
2              
3             # See the bottom of this file for the POD documentation.
4             # Search for the string '=head'.
5              
6             require 5.8.8;
7 9     9   485706 use strict;
  9         69  
  9         238  
8 9     9   40 use warnings;
  9         11  
  9         270  
9 9     9   40 use Scalar::Util qw{refaddr};
  9         13  
  9         432  
10 9     9   47 use Carp;
  9         23  
  9         480  
11 9     9   3859 use Clone;
  9         20136  
  9         354  
12 9     9   180 no warnings "redefine"; # We make this a few times
  9         18  
  9         287  
13 9     9   40 use Exporter;
  9         15  
  9         256  
14 9     9   36 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $SCRUBBER);
  9         14  
  9         18366  
15              
16             @ISA = qw(Exporter);
17             %EXPORT_TAGS = (
18             Carp => [ qw(scrubber_init) ],
19             Syslog => [ qw(scrubber_init) ],
20             all => [ qw($SCRUBBER scrubber_init scrubber scrubber_enabled
21             scrubber_add_scrubber scrubber_remove_scrubber
22             scrubber_add_signal scrubber_remove_signal
23             scrubber_add_method scrubber_remove_method
24             scrubber_add_package scrubber_remove_package
25             ) ],
26             );
27              
28             push @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}}
29             for grep { $_ ne 'all' } keys %EXPORT_TAGS;
30              
31             @EXPORT_OK = @{$EXPORT_TAGS{all}};
32             @EXPORT = qw(scrubber_init);
33              
34             $VERSION = '0.17';
35              
36             ###----------------------------------------------------------------###
37              
38             my $_SDATA = { # will be initialized in import below
39             'enabled' => 0,
40             'SIG' => {},
41             'METHOD' => {},
42             };
43              
44             tie $SCRUBBER, __PACKAGE__;
45              
46             sub TIESCALAR {
47 9     9   30 return bless [], __PACKAGE__;
48             }
49              
50             sub FETCH {
51 1     1   2 my ($self) = @_;
52 1         3 $_SDATA;
53             }
54              
55             sub STORE {
56 10     10   261 my ($self, $val) = @_;
57             #print ">>>>Calling STORE with (".(defined($val) ? $val : 'undef').")\n";
58 10 100       43 if (! defined $val) {
    100          
    100          
59 1         3 $_SDATA = _sdata_copy();
60             } elsif (ref($val) eq 'HASH') {
61 1         3 scrubber_stop();
62 1         3 $_SDATA = $val;
63 1 50       6 scrubber_start() if $_SDATA->{'enabled'};
64             } elsif ($val) {
65 4         10 scrubber_start();
66             } else {
67 4         9 scrubber_stop();
68             }
69             }
70              
71             ###----------------------------------------------------------------###
72              
73             sub _sdata_copy { # make a non-reference copy
74 6     6   20 my ($old_sdata) = @_;
75 6 50       22 if ( ! defined $old_sdata ) { $old_sdata = $_SDATA; } # if they didn't specify one, use the current one
  6         11  
76 6         224 my $new_SDATA = Clone::clone($old_sdata);
77 6         21 $new_SDATA->{'parent'} = $old_sdata;
78 6         16 return $new_SDATA;
79             }
80              
81             ###----------------------------------------------------------------###
82              
83             sub import {
84 9     9   54 my $change;
85 9         23 for my $i (reverse 1 .. $#_) {
86 12 100       64 if ($_[$i] eq ':Carp') {
    100          
    50          
    100          
87 1         4 scrubber_add_method('croak');
88 1         5 scrubber_add_method('confess');
89 1         2 scrubber_add_method('carp');
90 1         2 scrubber_add_method('cluck');
91             } elsif ($_[$i] eq ':Syslog') {
92 1         3 scrubber_add_method('main::syslog');
93             } elsif ($_[$i] =~ /^\+/) {
94 0         0 scrubber_add_method(substr($_[$i],1,999));
95 0         0 splice @_, $i, 1, ();
96             } elsif ($_[$i] =~ /^(dis|en)able$/) {
97 1 50       4 my $val = $1 eq 'dis' ? 0 : 1;
98 1         3 splice @_, $i, 1, ();
99 1 50 33     7 die 'Cannot both enable and disable $SCRUBBER during import' if defined $change && $change != $val;
100 1         2 $change = $val;
101             }
102             }
103              
104 9         33 scrubber_add_signal('WARN');
105 9         34 scrubber_add_signal('DIE');
106 9         20 scrubber_add_method('warnings::warn');
107 9         18 scrubber_add_method('warnings::warnif');
108 9 100 66     35 if ((! defined $change) || $change) {
109 8         18 scrubber_start();
110             } else {
111 1         2 scrubber_stop();
112             }
113              
114 9         12003 __PACKAGE__->export_to_level(1, @_);
115             }
116              
117             ###----------------------------------------------------------------###
118              
119 11 100   11 1 312 sub scrubber_enabled { $_SDATA->{'enabled'} ? 1 : 0 }
120              
121             sub scrubber_start {
122 18     18 1 44 $_SDATA->{'enabled'} = 1;
123 18         31 _scrubber_enable_signal( keys %{$_SDATA->{'SIG'}} );
  18         78  
124 18         31 _scrubber_enable_method( keys %{$_SDATA->{'METHOD'}} );
  18         62  
125             }
126              
127             sub scrubber_stop {
128 11     11 1 22 $_SDATA->{'enabled'} = 0;
129 11         21 _scrubber_disable_signal( keys %{$_SDATA->{'SIG'}} );
  11         61  
130 11         19 _scrubber_disable_method( keys %{$_SDATA->{'METHOD'}} );
  11         45  
131             }
132              
133             ###----------------------------------------------------------------###
134             # This is the core of our protection. Replace
135             # the data by the value provided
136              
137             sub _scrubber {
138 64     64   81 my $msg = $_[0];
139              
140 64         557 my @stack = ($msg);
141 64         79 my @stack_done = ();
142 64         73 my @data = ();
143 64         72 my @hashes = ();
144              
145 64         147 while ( my $sub_msg = pop @stack ) {
146 88         212 push @stack_done, "$sub_msg";
147 88 100       228 if ( ref $sub_msg eq 'ARRAY' ) {
    100          
    100          
148 10         12 foreach my $v ( @{$sub_msg} ) {
  10         19  
149 35 100       42 if (ref $v) {
150 5         6 my $found = 0;
151 5 100       6 foreach (@stack_done) { if ("$v" eq $_) { $found = 1; last; } }
  18         30  
  5         6  
  5         24  
152 5 50       15 push @stack, $v unless $found;
153             } else {
154 30         44 push @data, \$v;
155             }
156             }
157             } elsif ( ref $sub_msg eq 'HASH' ) {
158 20         31 push @hashes, $sub_msg;
159 20         18 foreach my $k ( keys %{$sub_msg} ) {
  20         40  
160 50 100       69 if (ref $sub_msg->{$k}) {
161 30         29 my $found = 0;
162 30 100       35 foreach (@stack_done) { if ("$sub_msg->{$k}" eq $_) { $found = 1; last; } }
  56         99  
  5         5  
  5         8  
163 30 100       62 push @stack, $sub_msg->{$k} unless $found;
164             } else {
165 20         35 push @data, \$sub_msg->{$k};
166             }
167             }
168             } elsif (ref $sub_msg) {
169             # TODO: currently only ARRAY, HASH and SCALAR are supported
170             } else {
171 49         122 push @data, \$msg;
172             }
173             }
174              
175 64         101 foreach my $sub_msg ( @data ) {
176 99 100       196 next if ! defined $$sub_msg;
177 74         86 foreach ( keys %{$_SDATA->{'scrub_data'}}) {
  74         168  
178 257 100       2180 ref $_SDATA->{'scrub_data'}{$_} eq 'CODE' ? $$sub_msg = $_SDATA->{'scrub_data'}{$_}->($_,$$sub_msg) : $$sub_msg =~ s/$_/$_SDATA->{'scrub_data'}{$_}/g;
179             }
180             }
181              
182 64         106 foreach my $hash ( @hashes ) {
183 20         32 foreach my $k ( keys %$hash ) {
184 50         66 my $tmp_val = $hash->{$k};
185 50         49 my $tmp_key = $k;
186 50         46 foreach ( keys %{$_SDATA->{'scrub_data'}}) {
  50         99  
187 300 50       1885 ref $_SDATA->{'scrub_data'}{$_} eq 'CODE' ? $tmp_key = $_SDATA->{'scrub_data'}{$_}->($_,$tmp_key) : $tmp_key =~ s/$_/$_SDATA->{'scrub_data'}{$_}/g;
188             }
189 50         94 delete $hash->{$k};
190 50         77 $hash->{$tmp_key} = $tmp_val;
191             }
192             }
193              
194 64         293 return $msg;
195             }
196              
197             sub scrubber {
198 42     42 1 10473 my $copy = Clone::clone(\@_);
199 42 100       138 if ($#$copy == 0) { return _scrubber $$copy[0]; }
  34         159  
200 8         16 return map { _scrubber $_ } @$copy;
  30         55  
201             }
202              
203             ###----------------------------------------------------------------###
204             # Add/Remove text values that will be scrubbed
205              
206             sub scrubber_remove_scrubber {
207 0     0 1 0 my $x = $_[0];
208 0 0       0 if (defined $x) {
209 0         0 foreach ( keys %$x ) {
210 0 0       0 delete $_SDATA->{'scrub_data'}{$_} if $_SDATA->{'scrub_data'}{$_} = $x->{$_};
211             }
212             }
213             }
214              
215             sub scrubber_add_scrubber {
216 9     9 1 6066 my $x = $_[0];
217 9 50       40 if (defined $x) {
218 9         32 foreach ( keys %$x ) {
219 18 50 33     85 next if ! defined $_ || $_ eq ''; # scrubbing nothing is VERY bad, ignore empty scrubbers
220 18         47 $_SDATA->{'scrub_data'}{$_} = $x->{$_};
221             }
222             }
223             }
224              
225             ###----------------------------------------------------------------###
226             # Add/Remove signals (ie DIE and WARN) to the scrubber
227              
228             sub _scrubber_disable_signal {
229 15     15   36 foreach ( @_ ) {
230 25 100 66     298 if (defined $_SDATA->{'SIG'}{$_}{'scrubber'} && defined $SIG{$_} && $SIG{$_} eq $_SDATA->{'SIG'}{$_}{'scrubber'}) {
    100 100        
231 17         43 $SIG{$_} = $_SDATA->{'SIG'}{$_}{'old'};
232 17         27 $_SDATA->{'SIG'}{$_}{'old'} = undef;
233 17         38 $_SDATA->{'SIG'}{$_}{'scrubber'} = undef;
234             } elsif ( defined $_SDATA->{'SIG'}{$_}{'old'} ) {
235 1         123 carp 'Log::Scrubber cannot disable the '.$_.' signal, it has been overridden somewhere else';
236             }
237             }
238             }
239              
240             sub scrubber_remove_signal {
241 4     4 1 1914 foreach ( @_ ) {
242 4         12 _scrubber_disable_signal($_);
243 4         43 delete $_SDATA->{'SIG'}{$_};
244             }
245             }
246              
247             sub _signal {
248 24     24   50 my $sig_name = shift;
249 24         57 @_ = scrubber @_;
250 24 100 66     107 if (defined $_SDATA->{'SIG'}{$sig_name}{'old'} && $_SDATA->{'SIG'}{$sig_name}{'old'} ne '') {
251 6         22 my $code_str1 = refaddr $_SDATA->{'SIG'}{$sig_name}{'old'};
252 6 100       19 if (!$_SDATA->{'SIG_USED'}->{$sig_name}->{$code_str1}) {
253 5         13 local $_SDATA->{'SIG_USED'}->{$sig_name}->{$code_str1} = 1;
254 5         18 return $_SDATA->{'SIG'}{$sig_name}{'old'}->(@_);
255             }
256 1         7 CORE::warn("Deep recursion detected in Log::Scrubber\n");
257             }
258 19 100       3039 CORE::warn(@_) if $sig_name eq '__WARN__';
259 19 100       193 CORE::die(@_) if $sig_name eq '__DIE__';
260             }
261              
262 5     5   824 sub _die_signal { _signal('__DIE__',@_); };
263 19     19   2156 sub _warn_signal { _signal('__WARN__',@_); };
264              
265             sub _scrubber_enable_signal {
266 39 100   39   106 return if ! $_SDATA->{'enabled'};
267 21         44 foreach ( @_ ) {
268 37         91 my $sig_name = $_;
269 37 100 100     148 next if defined $SIG{$sig_name} && defined $_SDATA->{'SIG'}{$sig_name}{'scrubber'} && $SIG{$sig_name} eq $_SDATA->{'SIG'}{$sig_name}{'scrubber'};
      100        
270              
271 35         70 $_SDATA->{'SIG'}{$sig_name}{'old'} = $SIG{$sig_name};
272 35 100       96 $_SDATA->{'SIG'}{$sig_name}{'scrubber'} = \&_warn_signal if $sig_name eq '__WARN__';
273 35 100       84 $_SDATA->{'SIG'}{$sig_name}{'scrubber'} = \&_die_signal if $sig_name eq '__DIE__';
274              
275 35         149 $SIG{$sig_name} = $_SDATA->{'SIG'}{$sig_name}{'scrubber'};
276             }
277             }
278              
279             sub scrubber_add_signal {
280 23     23 1 61 foreach ( @_ ) {
281 23         40 my $sig_name = '';
282 23 100       57 if ($_ eq 'WARN') { $sig_name = '__WARN__'; }
  11         21  
283 23 100       65 if ($_ eq '__WARN__') { $sig_name = '__WARN__'; }
  1         2  
284 23 100       48 if ($_ eq 'DIE') { $sig_name = '__DIE__'; }
  9         14  
285 23 100       47 if ($_ eq '__DIE__') { $sig_name = '__DIE__'; }
  2         3  
286              
287 23 100       53 next if defined $_SDATA->{'SIG'}{$sig_name};
288 21         53 $_SDATA->{'SIG'}{$sig_name} = {};
289 21         53 _scrubber_enable_signal($sig_name);
290             }
291             }
292              
293             ###----------------------------------------------------------------###
294             # Add/Remove methods to the scrubber
295              
296             sub _scrubber_disable_method {
297 9     9   70 no strict 'refs'; ## no critic
  9         16  
  9         1909  
298 13     13   26 foreach my $fullname ( @_ ) {
299 29         71 my $current_method = \&$fullname;
300 29 100 66     162 if (defined $_SDATA->{'METHOD'}{$fullname}{'scrubber'} && defined $current_method && $current_method eq $_SDATA->{'METHOD'}{$fullname}{'scrubber'}) {
    50 66        
301 23         61 *$fullname = $_SDATA->{'METHOD'}{$fullname}{'old'};
302 23         38 $_SDATA->{'METHOD'}{$fullname}{'old'} = undef;
303 23         61 $_SDATA->{'METHOD'}{$fullname}{'scrubber'} = undef;
304             } elsif ( defined $_SDATA->{'METHOD'}{$fullname}{'old'} ) {
305 0         0 carp 'Log::Scrubber cannot disable the '.$fullname.' method, it has been overridden somewhere else';
306             }
307             }
308             }
309              
310             sub scrubber_remove_method {
311 2     2 1 6 foreach my $fullname ( @_ ) {
312 2         5 _scrubber_disable_method($fullname);
313 2         5 delete $_SDATA->{'METHOD'}{$fullname};
314             }
315             }
316              
317             sub _scrubber_enable_method {
318 43 100   43   106 return if ! $_SDATA->{'enabled'};
319 9     9   54 no strict 'refs'; ## no critic
  9         15  
  9         1861  
320 20         39 foreach my $fullname ( @_ ) {
321 46         108 my $r_orig = \&$fullname;
322              
323 46 100       100 if ($fullname eq 'warnings::warnif') { $r_orig = \&warnings::warn; }
  17         57  
324              
325 46 50       89 if (! defined $r_orig) { croak "Log::Scrubber Cannot scrub $fullname, method does not exist."; }
  0         0  
326 46         78 $_SDATA->{'METHOD'}{$fullname}{'old'} = $r_orig;
327 46     16   163 $_SDATA->{'METHOD'}{$fullname}{'scrubber'} = sub { @_ = scrubber @_; goto $r_orig };
  16         3623  
  16         1458  
328 46         294 *$fullname = $_SDATA->{'METHOD'}{$fullname}{'scrubber'};
329             }
330             }
331              
332             sub scrubber_add_method {
333 25     25 1 34 foreach my $fullname ( @_ ) {
334 25 50       55 next if defined $_SDATA->{'METHOD'}{$fullname};
335 25         48 $_SDATA->{'METHOD'}{$fullname} = {};
336 25         38 _scrubber_enable_method($fullname);
337             }
338             }
339              
340             ###----------------------------------------------------------------###
341             # Add/Remove entire packages
342              
343             sub scrubber_remove_package {
344 9     9   53 no strict 'refs'; ## no critic
  9         14  
  9         1020  
345 0     0 1 0 foreach my $package ( @_ ) {
346 0         0 my @methods = grep { defined &{$package.'::'.$_} } keys %{$package.'::'};
  0         0  
  0         0  
  0         0  
347 0         0 foreach ( @methods ) {
348 0         0 scrubber_remove_method($_);
349             }
350             }
351             }
352              
353             sub scrubber_add_package {
354 9     9   55 no strict 'refs'; ## no critic
  9         21  
  9         1577  
355 0     0 1 0 foreach my $package ( @_ ) {
356 0         0 my @methods = grep { defined &{$package.'::'.$_} } keys %{$package.'::'};
  0         0  
  0         0  
  0         0  
357 0         0 foreach ( @methods ) {
358 0         0 scrubber_add_method($package.'::'.$_);
359             }
360             }
361             }
362              
363             ###----------------------------------------------------------------###
364             # Initilize the scrubber.
365              
366             sub scrubber_init {
367 5     5 1 374 my $x = $_[0];
368 5         24 scrubber_stop;
369 5 50       16 if (defined $x) {
370 5         25 $_SDATA = _sdata_copy($_SDATA->{'parent'});
371 5         22 scrubber_add_scrubber(@_);
372             }
373 5         28 scrubber_start();
374 5         14 return 1;
375             }
376              
377             1;
378              
379             __END__