blib/lib/Unix/Conf/Err.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 12 | 59 | 20.3 |
branch | 0 | 22 | 0.0 |
condition | n/a | ||
subroutine | 4 | 14 | 28.5 |
pod | 7 | 7 | 100.0 |
total | 23 | 102 | 22.5 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | # Error handling class. To be used by all modules. | ||||||
2 | # | ||||||
3 | # Copyright Karthik Krishnamurthy |
||||||
4 | # | ||||||
5 | =head1 NAME | ||||||
6 | |||||||
7 | Unix::Conf::Err - This module is an internal module for error handling | ||||||
8 | purposes. | ||||||
9 | |||||||
10 | =head1 SYNOPSIS | ||||||
11 | |||||||
12 | Refer to the documentation of Unix::Conf for creating error objects. | ||||||
13 | Accessing the class constructor for Unix::Conf::Err is not preferred | ||||||
14 | as the location of the class and consequently its namespace might | ||||||
15 | change. The preferred way is | ||||||
16 | |||||||
17 | use Unix::Conf; | ||||||
18 | sub foo () | ||||||
19 | { | ||||||
20 | return (Unix::Conf::->_err ('chdir')) | ||||||
21 | unless (chdir ('/etc')); | ||||||
22 | } | ||||||
23 | |||||||
24 | # or | ||||||
25 | |||||||
26 | sub foo () | ||||||
27 | { | ||||||
28 | return ( | ||||||
29 | Unix::Conf::->_err ( | ||||||
30 | 'object_method', | ||||||
31 | 'argument not an object of class BLAH' | ||||||
32 | ) | ||||||
33 | ) unless (ref ($obj) eq 'BLAH'); | ||||||
34 | } | ||||||
35 | |||||||
36 | In the calling function, save the return value, test it for | ||||||
37 | truth, print error message on STDERR and continue. | ||||||
38 | |||||||
39 | $ret->warn ("Error executing foo ()") | ||||||
40 | unless (($ret = foo ())); | ||||||
41 | |||||||
42 | Increase debugging information to print the cause of error | ||||||
43 | and a full stacktrace and die. | ||||||
44 | |||||||
45 | unless (($ret = foo ())) { | ||||||
46 | $ret->debuglevel (2); | ||||||
47 | $ret->die ("Error executing foo"); | ||||||
48 | } | ||||||
49 | |||||||
50 | Get state information from the error object and use it to | ||||||
51 | print error ourselves instead of using the provided 'warn' | ||||||
52 | and 'die' methods. | ||||||
53 | |||||||
54 | use CGI; | ||||||
55 | my $q = new CGI; | ||||||
56 | # do stuff | ||||||
57 | unless (($ret = foo ())) { | ||||||
58 | my $stacktrace = $ret->stacktrace (); | ||||||
59 | $stacktrace =~ s/\n/ /g; |
||||||
60 | print $q->header ('text/html'), | ||||||
61 | $q->start_html ( "Error" ), | ||||||
62 | $q->h1 ( "Error" ), | ||||||
63 | $q->p ( "Could not execute foo () "), |
||||||
64 | $q->p ( "because " ), |
||||||
65 | $q->p ( $ret->errmsg () ), | ||||||
66 | $q->p ("at "), |
||||||
67 | $q->p ( $ret->where () ), | ||||||
68 | $q->p ($stacktrace); | ||||||
69 | $q->end_html; | ||||||
70 | exit; | ||||||
71 | } | ||||||
72 | |||||||
73 | =head1 DESCRIPTION | ||||||
74 | |||||||
75 | A Unix::Conf::Err object saves the state of the call stack at the | ||||||
76 | time its creation. The idea behind a Unix::Conf::Err object style | ||||||
77 | error handling is allowing the caller to decide how to handle the | ||||||
78 | error without using eval blocks around all Unix::Conf::* library | ||||||
79 | calls. The error object can be used to throw exceptions too, as the | ||||||
80 | string operator is overloaded to return the error string, depending | ||||||
81 | on the debuglevel. | ||||||
82 | |||||||
83 | =cut | ||||||
84 | |||||||
85 | package Unix::Conf::Err; | ||||||
86 | |||||||
87 | 1 | 1 | 8 | use 5.6.0; | |||
1 | 3 | ||||||
1 | 34 | ||||||
88 | 1 | 1 | 4 | use strict; | |||
1 | 1 | ||||||
1 | 21 | ||||||
89 | 1 | 1 | 4 | use warnings; | |||
1 | 2 | ||||||
1 | 546 | ||||||
90 | |||||||
91 | =over 4 | ||||||
92 | |||||||
93 | =item new () | ||||||
94 | |||||||
95 | Arguments | ||||||
96 | PREFIX, | ||||||
97 | ERRMSG, | ||||||
98 | |||||||
99 | Unix::Conf::Err class constructor. If ERRMSG is not specified, a | ||||||
100 | stringified version of "$!" is used. Using Unix::Conf::Err->new is | ||||||
101 | deprecated. The preferred way to create a Unix::Conf::Err object is | ||||||
102 | to use the Unix::Conf->_err method. Call Unix::Conf->_err () at the | ||||||
103 | point of error so that it will store error data/stack at the time of | ||||||
104 | error to be used later. | ||||||
105 | |||||||
106 | =cut | ||||||
107 | |||||||
108 | sub new | ||||||
109 | { | ||||||
110 | 0 | 0 | 1 | my $class = shift; | |||
111 | 0 | my $errobj = {}; | |||||
112 | 0 | $errobj->{DEBUGLEVEL} = 0; | |||||
113 | 0 | ($errobj->{PREFIX}, $errobj->{ERRMSG}) = @_; | |||||
114 | 0 | 0 | $errobj->{ERRMSG} = "$!" unless ($errobj->{ERRMSG}); | ||||
115 | 0 | my $ctr = 0; | |||||
116 | # store the stack context at time of constructor | ||||||
117 | 0 | while (($errobj->{STACK}[$ctr]{PACKAGE}, $errobj->{STACK}[$ctr]{FILE}, $errobj->{STACK}[$ctr]{LINE}, $errobj->{STACK}[$ctr]{SUB}) = caller ($ctr)) { | |||||
118 | 0 | $ctr++; | |||||
119 | } | ||||||
120 | 0 | return (bless ($errobj, $class)); | |||||
121 | } | ||||||
122 | |||||||
123 | =item debuglevel () | ||||||
124 | |||||||
125 | Arguments | ||||||
126 | DEBUGLEVEL, | ||||||
127 | |||||||
128 | This method can be invoked through both a class and object. When | ||||||
129 | invoked through Unix::Conf, it sets the class wide debuglevel to | ||||||
130 | the argument. When invoked through an object, it sets only the | ||||||
131 | object private debuglevel to the argument. In case both debuglevels | ||||||
132 | are set, error message is printed at the maximum of the class wide | ||||||
133 | debuglevel and object specific debuglevel. Valid values for | ||||||
134 | DEBUGLEVEL are 0, 1, and 2. At level 0 only only the string passed | ||||||
135 | to warn ()/die () methods are printed. At 1, the output of | ||||||
136 | errmsg () and where () is added. At level 2, the output of | ||||||
137 | stacktrace () is added to the output. | ||||||
138 | |||||||
139 | =cut | ||||||
140 | |||||||
141 | my $Debug_Level = 0; | ||||||
142 | sub debuglevel | ||||||
143 | { | ||||||
144 | 0 | 0 | 1 | my ($self, $d) = @_; | |||
145 | 0 | 0 | if (defined ($d)) { | ||||
146 | # sanity check | ||||||
147 | 0 | 0 | $d = 2 if ($d > 2); | ||||
148 | 0 | 0 | $d = 0 if ($d < 0); | ||||
149 | 0 | 0 | if (ref ($self)) { | ||||
150 | 0 | $self->{DEBUGLEVEL} = $d; | |||||
151 | } | ||||||
152 | else { | ||||||
153 | 0 | $Debug_Level = $d; | |||||
154 | } | ||||||
155 | 0 | return ($d); | |||||
156 | } | ||||||
157 | # whichever is greater must have been set. so return that one. | ||||||
158 | return ( | ||||||
159 | 0 | 0 | $Debug_Level > $self->{DEBUGLEVEL} ? $Debug_Level : $self->{DEBUGLEVEL} | ||||
160 | ); | ||||||
161 | } | ||||||
162 | |||||||
163 | =item where () | ||||||
164 | |||||||
165 | Prints information about the stack frame in which the error occured | ||||||
166 | along with the line number and file. | ||||||
167 | |||||||
168 | =cut | ||||||
169 | |||||||
170 | sub where | ||||||
171 | { | ||||||
172 | 0 | 0 | 1 | my $self = $_[0]; | |||
173 | 0 | return ("in $self->{STACK}[1]{SUB}() at line $self->{STACK}[0]{LINE} in $self->{STACK}[0]{FILE}\n"); | |||||
174 | } | ||||||
175 | |||||||
176 | =item why () | ||||||
177 | |||||||
178 | Prints "PREFIX: ERRMSG". | ||||||
179 | |||||||
180 | =cut | ||||||
181 | |||||||
182 | sub why | ||||||
183 | { | ||||||
184 | 0 | 0 | 1 | my $self = $_[0]; | |||
185 | 0 | return ("$self->{PREFIX}: $self->{ERRMSG}\n"); | |||||
186 | } | ||||||
187 | |||||||
188 | =item stacktrace () | ||||||
189 | |||||||
190 | Prints the complete stacktrace information at the time of creation | ||||||
191 | of the object. | ||||||
192 | |||||||
193 | =cut | ||||||
194 | |||||||
195 | sub stacktrace | ||||||
196 | { | ||||||
197 | 0 | 0 | 1 | my $self = $_[0]; | |||
198 | 0 | my $errmsg; | |||||
199 | # caller invoked in _err returns 2 extra stack frames. don't know why | ||||||
200 | # need to debug later | ||||||
201 | 0 | my ($ctr, $stacklength) = (1, scalar (@{$self->{STACK}}) - 2); | |||||
0 | |||||||
202 | 0 | while ($ctr <= $stacklength) { | |||||
203 | 0 | $errmsg .= "$self->{STACK}[$ctr]{SUB}() called at line $self->{STACK}[$ctr]{LINE} in $self->{STACK}[$ctr]{FILE}\n"; | |||||
204 | 0 | $ctr++; | |||||
205 | } | ||||||
206 | 0 | return $errmsg; | |||||
207 | } | ||||||
208 | |||||||
209 | =item warn () | ||||||
210 | |||||||
211 | Arguments | ||||||
212 | ERRMSG, | ||||||
213 | |||||||
214 | Prints ERRMSG to STDERR. | ||||||
215 | |||||||
216 | =cut | ||||||
217 | |||||||
218 | # Arguments: errstr (optional) | ||||||
219 | sub warn (;$) | ||||||
220 | { | ||||||
221 | 0 | 0 | 1 | warn (&__stringify); | |||
222 | } | ||||||
223 | |||||||
224 | =item die () | ||||||
225 | |||||||
226 | Arguments | ||||||
227 | ERRMSG, | ||||||
228 | |||||||
229 | Prints ERRMSG to STDERR and die's. | ||||||
230 | |||||||
231 | =cut | ||||||
232 | |||||||
233 | # Arguments: errstr (optional) | ||||||
234 | sub die (;$) | ||||||
235 | { | ||||||
236 | 0 | 0 | 1 | die (&__stringify); | |||
237 | } | ||||||
238 | |||||||
239 | # Overloaded functions | ||||||
240 | 1 | 8 | use overload '""' => \&__interpret_as_string, | ||||
241 | 'bool' => \&__interpret_as_bool, | ||||||
242 | 1 | 1 | 1508 | 'eq' => \&__interpret_as_string; | |||
1 | 1054 | ||||||
243 | |||||||
244 | sub __interpret_as_string | ||||||
245 | { | ||||||
246 | 0 | 0 | my $self = shift; | ||||
247 | 0 | return (__stringify ($self)); | |||||
248 | } | ||||||
249 | |||||||
250 | # If the PREFIX key exists then the constructor has been called. | ||||||
251 | sub __interpret_as_bool | ||||||
252 | { | ||||||
253 | 0 | 0 | my $self = shift; | ||||
254 | #return (exists ($self->{PREFIX}) ? undef : 1); | ||||||
255 | 0 | 0 | return (exists ($self->{PREFIX}) ? 0 : 1); | ||||
256 | } | ||||||
257 | |||||||
258 | sub __stringify ($;$) | ||||||
259 | { | ||||||
260 | 0 | 0 | my ($self, $errstr) = @_; | ||||
261 | |||||||
262 | # The whole error message is constructed in $errmsg and returned | ||||||
263 | 0 | my $errmsg = ""; | |||||
264 | |||||||
265 | # if argument is present get it in $errmsg. it is usually present when | ||||||
266 | # called from the die/warn methods | ||||||
267 | 0 | 0 | if ($errstr) { | ||||
268 | 0 | $errmsg .= "$errstr\n"; | |||||
269 | } | ||||||
270 | |||||||
271 | # when debuglevel is 1 and above include reason and point of error | ||||||
272 | 0 | 0 | $self->debuglevel () >= 1 && do { | ||||
273 | # $errmsg might be empty because no argument was passed to die/warn | ||||||
274 | # meth or because __stringify was called from the string overload | ||||||
275 | # handler. | ||||||
276 | 0 | 0 | $errmsg .= "\nbecause\n" | ||||
277 | if ($errmsg); | ||||||
278 | 0 | $errmsg .= &why.&where; | |||||
279 | }; | ||||||
280 | 0 | 0 | $self->debuglevel () == 2 && do { | ||||
281 | 0 | $errmsg .= "\nPrinting stack backtrace\n"; | |||||
282 | 0 | $errmsg .= &stacktrace; | |||||
283 | }; | ||||||
284 | 0 | return ($errmsg); | |||||
285 | } | ||||||
286 | |||||||
287 | 1; | ||||||
288 | __END__ |