forked from exodist/Trace-Mask
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
291 lines (233 loc) · 12 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
NAME
Trace::Mask - Standard for masking frames in stack traces.
DESCRIPTION
This is a specification packages can follow to define behaviors stack
tracers may choose to honor. If a module implements this specification
than any compliant stack tracer will render the stack trace as desired.
Implementing the spec will have no effect on non-complient stack
tracers. This specification does not effect "caller()" in any way.
PRACTICAL APPLICATIONS
Masking stack traces is not something you want to do every day, but
there are situations where it can be helpful, if not essential.
Emulate existing language structures
sub foo {
if ($cond) { trace() }
}
In the example above a stack trace is produced, the call to "foo()"
will show up, but the "if" block will not. This is useful as the
conditional is part of the sub, and should not be listed.
Emulating this behavior would be a useful feature for exception
tools that provide try/catch/finally or similar control structures.
try { ... }
catch { ... };
In perl the above would be emulated with 2 subs that take codeblocks
in their prototype. In a stack trace you see a call to try, and a
call to an anonymous block. In a stack trace this is distracting at
best. Further it is hard to distinguish which anonymous block you
are in, though tools like Sub::Name mitigate this some.
Testing Tools
Tools like Test::Exception use Sub::Uplevel to achieve a similar
effect. This is done by globally overriding "caller()", which can
have some unfortunate side effects. Using Trace::Mask instead would
avoid the nasty side effects, would be much faster than overriding
"caller()", and give more control over what makes it into the trace.
One interface to many tools
Currently Carp provides several configuration variables such as
@CARP_NOT to give you control over where a trace starts. Other
modules that provide stack traces all provide their own variables.
If you want to control stack traces you need to account for all the
possible tracing tools that could be used. Many tracing tools do not
provide enough control, including "Carp" itself.
SPECIFICATION
No module (including this one) is required when implementing the spec.
Though it is a good idea to list the version of the spec you have
implemented in the runtime recommendations for your module. There are no
undesired side effects as the specification is completely opt-in, both
for modules that want to effect stack traces, and for the stack tracers
themselves.
%Trace::Mask::MASKS
Packages that wish to mask stack frames may do so by altering the
%Trace::Mask::MASKS package variable. Packages may change this variable
at any time, so consumers should not cache the contents, however they
may cache the reference to the hash itself.
This is an overview of the MASKS structure:
%Trace::Mask::MASKS = (
FILE => {
LINE => {
SUBNAME => {
# Behaviors
no_start => BOOL, # Do not start a trace on this frame
stop => BOOL, # Stop tracing at this frame
restart => BOOL, # Start tracing again at this frame
hide => COUNT, # Hide the frames completely
shift => COUNT, # Pretend this frame started X frames before it did
# Replacements
0 => PACKAGE, # Replace the package listed in the frame
1 => FILE, # Replace the filename listed in the frame
2 => LINE, # Replace the linenum listed in the frame
3 => NAME, # Replace the subname listen in the frame
..., # Replace any index listed in the frame
}
}
}
);
No package should ever reset/erase the %Trace::Mask::MASKS variable.
They should only ever remove entries they added, even that is not
recommended.
You CAN add entries for files+lines that are not under your control.
This is an important allowance as it allows a function to hide the call
to itself.
A stack frame is defined based on the return from "caller()" which
returns the "($package, $file, $line, $subname)" data of a call in the
stack. To manipulate a call you define the
$MASKS{$file}->{$line}->{$subname} path in the hash that matches the
call itself.
'FILE', 'LINE', and 'SUBNAME' can all be replaced with the wildcard '*'
string to apply to all:
# Effect all calls to Foo::foo in any file
('*' => { '*' => { Foo::foo => { ... }}})
# Effect all sub calls in Foo.pm
('Foo.pm' => { '*' => { '*' => { ... }}});
You cannot use 3 wildcards to effect all subs. The 3 wildcard entry will
be ignored by a compliant tracer.
# This is not allowed, the entry will be ignored
('*' => { '*' => { '*' => { ... }}});
CALL MASK HASHES
Numeric keys in the behavior structures are replacement values. If you
want to replace the package listed in the frame then you specify a value
for field '0'. If you want to replace the filename you would put a value
for field '1'. Numeric fields always correspond to the same index in the
list returned from "caller()".
{
# Behaviors
no_start => BOOL, # Do not start a trace on this frame
stop => BOOL, # Stop tracing at this frame
restart => BOOL, # Start tracing again at this frame
hide => COUNT, # Hide the frames completely
shift => COUNT, # Pretend this frame started X frames before it did
# Replacements
0 => PACKAGE, # Replace the package listed in the frame
1 => FILE, # Replace the filename listed in the frame
2 => LINE, # Replace the linenum listed in the frame
3 => NAME, # Replace the subname listen in the frame
..., # Replace any index listed in the frame
}
The following additional behaviors may be specified:
no_start => $BOOL
This prevents a stack trace from starting at the given call. This is
similar to Carp's @CARP_NOT variable. These frames will still appear
in the stack trace if they are not the start.
stop => $BOOL
This tells the stack tracer to stop tracing at this frame. The frame
itself will be listed in the trace, unless this is combined with the
'hide' option.
restart => $BOOL
This tells the stack tracer to start again after a stop, effectively
skipping all the frames between the stop and this start. This may be
combined with 'stop' in order to show a single frame.
hide => $COUNT
This completely hides the frame from a stack trace. This does not
modify the values of any surrounding frames, the frame is simply
dropped from the trace. If $COUNT is greater than 1, then additional
frames below the hidden one will also be dropped.
This has the same effect on a stack trace as Sub::Uplevel.
shift => $COUNT
This is like hide with one important difference, all components of
the shifted call, except for package, file, and line, will replace
the values of the next frame to be kept in the trace. If $COUNT is
large than 1, the shift will hide frames between the shifted frame
and the new frame. If $COUNT is larger than the remaining stack, the
lowest unhidden/unshifted stack frame will be the recipient of the
shift operation, even if the shift frame itself is the lowest.
This has the same effect on a stack trace as "goto &sub".
MASK RESOLUTION
Multiple masks in the %Trace::Mask::MASKS structure may apply to any
given stack frame, a compliant tracer will account for all of them. A
simple hash merge is sufficient so long as they are merged in the
correct order. Here is an example:
my $masks_ref = \%Trace::Mask::MASKS;
my @all = grep { defined $_ } (
$masks_ref->{$file}->{'*'}->{'*'},
$masks_ref->{$file}->{$line}->{'*'},
$masks_ref->{'*'}->{'*'}->{$name},
$masks_ref->{$file}->{'*'}->{$name},
$masks_ref->{$file}->{$line}->{$name},
);
my %final = map { %{$_} } @all;
The most specific path should win out (override others). Rightmost path
component is considered the most important. More wildcards means less
specific. Paths may never have wildcards for all 3 components.
$ENV{'NO_TRACE_MASK'}
If this environment variable is set to true then all masking rules
should be ignored, tracers should produce full and complete stack
traces.
TRACES STARTING AT $LEVEL
If a tracing tool starts at the call to the tool (such as
"Carp::confess()") then it should account for all the masks starting
with the call to confess itself going all the way until the bottom of
the stack, or until a mask with 'stop' is found. If a tracing tool
allows you to start tracing from a specific level, the tracer should
still account for the masks of the frames at the top of the stack on
which it is not reporting.
MASK NUMERIC KEYS
Numeric keys in a mask represent items in the list returned from
"caller()". If you provide numeric keys their values will replace the
corresponding value in the caller list before it is used in the trace.
You can use this to replace the package, file, etc. This will work for
any VALID index into the list. This cannot be used to extend the list.
Numeric keys outside the bounds of the list are simply ignored, this is
for compatability as different perl versions may have a different size
list.
SPECIAL/MAGIC subs
Traces must NEVER hide or alter the following special/magic subs:
BEGIN
UNITCHECK
CHECK
INIT
END
DESTROY
import
unimport
These subs are all special in one way or another, hiding them would be
hiding critical information.
CLASS METHODS
The "masks()" method is defined in Trace::Mask, it returns a reference
to the %Trace::Mask::MASKS hash for easy access. It is fine to cache
this reference, but not the data it contains.
REFERENCE
Trace::Mask::Reference is included in this distribution. The Reference
module contains example tracers, and example tools that benefit from
masking stack traces. The examples in this module should NOT be used in
production code.
UTILS
Trace::Mask::Util is included in this distribution. The util module
provides utilities for adding stack trace masking behavior. The
utilities provided by this module are considered usable in production
code.
TEST
Trace::Mask::Test is included in this distribution. This module provides
test cases and tools useful for verifying your tracing tools are
compliant with the spec.
PLUGINS
Carp
Trace::Mask::Carp is included in this distribution. This module can make
Carp compliant with Trace::Mask.
Try::Tiny
Trace::Mask::TryTiny is included in this ditribution. Simply loading
theis module will cause Try::Tiny framework to be hidden in compliant
stack traces.
SEE ALSO
Sub::Uplevel - Tool for hiding stack frames from all callers, not just
stack traces.
SOURCE
The source code repository for Trace-Mask can be found at
http://github.com/exodist/Trace-Mask.
MAINTAINERS
Chad Granum <[email protected]>
AUTHORS
Chad Granum <[email protected]>
COPYRIGHT
Copyright 2015 Chad Granum <[email protected]>.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
See http://www.perl.com/perl/misc/Artistic.html