File Coverage

blib/lib/Log/Scrubber.pm
Criterion Covered Total %
statement 199 223 89.2
branch 80 100 80.0
condition 21 33 63.6
subroutine 34 37 91.8
pod 13 13 100.0
total 347 406 85.4


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