File Coverage

blib/lib/Log/Scrubber.pm
Criterion Covered Total %
statement 201 225 89.3
branch 80 100 80.0
condition 21 33 63.6
subroutine 34 37 91.8
pod 13 13 100.0
total 349 408 85.5


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