@@ -11,24 +11,25 @@ use Log::Any::Proxy;
11
11
our @ISA = qw/ Log::Any::Proxy/ ;
12
12
13
13
use Devel::StackTrace 2.00;
14
- use Devel::StackTrace::Extract qw( extract_stack_trace) ;
15
14
use Log::Any::Adapter::Util ();
15
+ use Scalar::Util qw/ blessed reftype/ ;
16
16
use overload;
17
17
18
18
=head1 SYNOPSIS
19
19
20
20
use Log::Any qw( $log, proxy_class => 'WithStackTrace' );
21
21
22
- # Turns on argument logging in stack traces
23
- use Log::Any qw( $log, proxy_class => 'WithStackTrace', proxy_show_stack_trace_args => 1 );
22
+ # Allow stack trace call stack arguments to be logged:
23
+ use Log::Any qw( $log, proxy_class => 'WithStackTrace',
24
+ proxy_show_stack_trace_args => 1 );
24
25
25
- # Some adapter that knows how to handle both structured data,
26
- # and log messages which are actually objects with a
27
- # "stack_trace" method:
28
- #
26
+ # Configure some adapter that knows how to:
27
+ # 1) handle structured data, and
28
+ # 2) handle message objects which have a "stack_trace" method:
29
29
Log::Any::Adapter->set($adapter);
30
30
31
- $log->error("Help!"); # stack trace gets automatically added
31
+ $log->error("Help!"); # stack trace gets automatically added,
32
+ # starting from this line of code
32
33
33
34
=head1 DESCRIPTION
34
35
@@ -39,13 +40,20 @@ used to generate one, the resulting trace can be confusing if it begins
39
40
relative to where the log adapter was called, and not relative to where
40
41
the logging method was originally called.
41
42
42
- With this proxy in place, if any logging method is called with a message
43
- that is a non-reference scalar, that message will be upgraded into a
44
- C<Log::Any::MessageWithStackTrace > object with a C<stack_trace > method,
45
- and that method will return a trace relative to where the logging method
46
- was called. A string overload is provided on the object to return the
47
- original message. Unless a C<proxy_show_stack_trace_args > flag is specified, arguments
48
- in the stack trace will be scrubbed.
43
+ With this proxy in place, if any logging method is called with a log
44
+ message that is a non-reference scalar (i.e. a string), that log message
45
+ will be upgraded into a C<Log::Any::MessageWithStackTrace > object with a
46
+ C<stack_trace > method, and that method will return a trace relative to
47
+ where the logging method was called. A string overload is provided on
48
+ the object to return the original log message.
49
+
50
+ Additionally, any call stack arguments in the stack trace will be
51
+ deleted before logging, to avoid accidentally logging sensitive data.
52
+ This happens both for message objects that were auto-generated from
53
+ string messages, as well as for message objects that were passed in
54
+ directly (if they appear to have a stack trace method). This default
55
+ argument scrubbing behavior can be turned off by specifying a true value
56
+ for the C<proxy_show_stack_trace_args > import flag.
49
57
50
58
B<Important: > This proxy should be used with a L<Log::Any::Adapter> that
51
59
is configured to handle structured data. Otherwise the object created
@@ -60,18 +68,16 @@ trace.
60
68
61
69
use overload ' ""' => \&stringify;
62
70
63
- use Carp qw( croak ) ;
64
-
65
71
sub new
66
72
{
67
- my ($class , $proxy_show_stack_trace_args , $message ) = @_ ;
68
- croak ' no "message" ' unless defined $message ;
73
+ my ($class , $message , %opts ) = @_ ;
74
+
69
75
return bless {
70
76
message => $message ,
71
77
stack_trace => Devel::StackTrace-> new(
72
78
# Filter e.g "Log::Any::Proxy", "My::Log::Any::Proxy", etc.
73
79
ignore_package => [ qr / (?:^|::)Log::Any(?:::|$ )/ ],
74
- no_args => ! $proxy_show_stack_trace_args ,
80
+ no_args => $opts { no_args } ,
75
81
),
76
82
}, $class ;
77
83
}
@@ -85,10 +91,14 @@ trace.
85
91
86
92
=head2 maybe_upgrade_with_stack_trace
87
93
88
- This is an internal use method that will convert a non-reference scalar
94
+ @args = $self->maybe_upgrade_with_stack_trace(@args);
95
+
96
+ This is an internal-use method that will convert a non-reference scalar
89
97
message into a C<Log::Any::MessageWithStackTrace > object with a
90
98
C<stack_trace > method. A string overload is provided to return the
91
- original message. Args are scrubbed out in case they contain sensitive data,
99
+ original message.
100
+
101
+ Stack trace args are scrubbed out in case they contain sensitive data,
92
102
unless the C<proxy_show_stack_trace_args > option has been set.
93
103
94
104
=cut
@@ -97,28 +107,73 @@ sub maybe_upgrade_with_stack_trace
97
107
{
98
108
my ($self , @args ) = @_ ;
99
109
100
- # Only want a non-ref arg, optionally followed by a structured data
101
- # context hashref:
102
- #
103
- unless ($self -> {proxy_show_stack_trace_args }) {
104
- for my $i (0 .. $#args ) { # Check if there's a stack trace to scrub args from
105
- my $trace = extract_stack_trace($args [$i ]);
106
- if ($trace ) {
107
- $self -> delete_args_from_stack_trace($trace );
108
- $args [$i ] = $trace ;
109
- }
110
- }
111
- }
112
-
110
+ # We expect a message, optionally followed by a structured data
111
+ # context hashref. Bail if we get anything other than that rather
112
+ # than guess what the caller might be trying to do:
113
113
return @args unless @args == 1 ||
114
114
( @args == 2 && ref $args [1] eq ' HASH' );
115
- return @args if ref $args [0];
116
115
117
- $args [0] = Log::Any::MessageWithStackTrace-> new($self -> {proxy_show_stack_trace_args }, $args [0]);
116
+ if (ref $args [0]) {
117
+ $self -> maybe_delete_stack_trace_args($args [0])
118
+ unless $self -> {proxy_show_stack_trace_args };
119
+ }
120
+ else {
121
+ $args [0] = Log::Any::MessageWithStackTrace-> new(
122
+ $args [0],
123
+ no_args => !$self -> {proxy_show_stack_trace_args },
124
+ );
125
+ }
118
126
119
127
return @args ;
120
128
}
121
129
130
+ =head2 maybe_delete_stack_trace_args
131
+
132
+ $self->maybe_delete_stack_trace_args($arg);
133
+
134
+ This is an internal-use method that, given a single argument that is a
135
+ reference, tries to figure out whether the argument is an object with a
136
+ stack trace, and if so tries to delete any stack trace args.
137
+
138
+ The logic is based on L<Devel::StackTrace::Extract> .
139
+
140
+ It specifically looks for objects with a C<stack_trace > method (which
141
+ should catch anything that does L<StackTrace::Auto> , including anything
142
+ that does L<Throwable::Error> ), or a C<trace > method (used by
143
+ L<Exception::Class> and L<Moose::Exception> and friends).
144
+
145
+ It specifically ignores L<Mojo::Exception> objects, because their stack
146
+ traces don't contain any call stack args.
147
+
148
+ =cut
149
+
150
+ sub maybe_delete_stack_trace_args
151
+ {
152
+ my ($self , $arg ) = @_ ;
153
+
154
+ return unless blessed $arg ;
155
+
156
+ if ($arg -> can(' stack_trace' )) {
157
+ # This should catch anything that does StackTrace::Auto,
158
+ # including anything that does Throwable::Error.
159
+ my $trace = $arg -> stack_trace;
160
+ $self -> delete_args_from_stack_trace($trace );
161
+ }
162
+ elsif ($arg -> isa(' Mojo::Exception' )) {
163
+ # Skip these, they don't have args in their stack traces.
164
+ }
165
+ elsif ($arg -> can(' trace' )) {
166
+ # This should catch Exception::Class and Moose::Exception and
167
+ # friends. Make sure to check for the "trace" method *after*
168
+ # skipping the Mojo::Exception objects, because those also have
169
+ # a "trace" method.
170
+ my $trace = $arg -> trace;
171
+ $self -> delete_args_from_stack_trace($trace );
172
+ }
173
+
174
+ return ;
175
+ }
176
+
122
177
my %aliases = Log::Any::Adapter::Util::log_level_aliases();
123
178
124
179
# Set up methods/aliases and detection methods/aliases
@@ -137,22 +192,26 @@ foreach my $name ( Log::Any::Adapter::Util::logging_methods(), keys(%aliases) )
137
192
138
193
=head2 delete_args_from_stack_trace($trace)
139
194
140
- $self->delete_args_from_stack_trace($trace)
195
+ $self->delete_args_from_stack_trace($trace)
141
196
142
- To scrub potentially sensitive data from C<Devel::StackTrace > arguments, this method deletes
143
- arguments from all of the C<Devel::StackTrace::Frame > in the trace.
197
+ To scrub potentially sensitive data from C<Devel::StackTrace > arguments,
198
+ this method deletes arguments from all of the C<Devel::StackTrace::Frame >
199
+ in the trace.
144
200
145
201
=cut
146
202
147
203
sub delete_args_from_stack_trace
148
204
{
149
205
my ($self , $trace ) = @_ ;
150
206
151
- return unless $trace ;
207
+ return unless $trace && $trace -> can( ' frames ' ) ;
152
208
153
209
foreach my $frame ($trace -> frames) {
210
+ next unless $frame -> {args };
154
211
$frame -> {args } = [];
155
212
}
213
+
214
+ return ;
156
215
}
157
216
158
217
1;
0 commit comments