Fix DXDebug::DXDebug issue
[spider.git] / perl / DXDebug.pm
1 #
2 # The system variables - those indicated will need to be changed to suit your
3 # circumstances (and callsign)
4 #
5 # Copyright (c) 1998-2019 - Dirk Koopman G1TLH
6 #
7 # Note: Everything is recorded into the ring buffer (in perl terms: a numerically max sized array).
8 #       To allow debugging of a category (e.g. 'chan') but not onto disc (just into the ring buffer)
9 #       do: set/debug chan nologchan
10 #
11 #       To print the current contents into the debug log: show/debug_ring
12 #
13 #       On exit or serious error the ring buffer is printed to the current debug log
14 #
15 # In Progress:
16 #       Expose a localhost listener on port (default) 27755 to things like watchdbg so that they can carry on
17 #       as normal, possibly with a "remember" button to permanently capture stuff observed.
18 #
19 # Future:
20 #       This is likely to be some form of triggering or filtering controlling (some portion
21 #       of) ring_buffer dumping.
22 #
23 #
24
25 package DXDebug;
26
27 use 5.10.1;
28
29 require Exporter;
30 @ISA = qw(Exporter);
31 @EXPORT = qw(dbginit dbg dbgadd dbgsub dbglist dbgdump isdbg dbgclose dbgtrace dbgprintring confess croak cluck carp);
32
33 use strict;
34 use vars qw(%dbglevel $fp $callback $cleandays $keepdays $dbgringlth);
35
36 use DXUtil;
37 use DXLog ();
38 use Carp ();
39 use POSIX qw(isatty);
40
41 %dbglevel = ();
42 $fp = undef;
43 $callback = undef;
44 $keepdays = 10;
45 $cleandays = 100;
46 $dbgringlth = 500;
47
48 our $no_stdout;                                 # set if not running in a terminal
49 our @dbgring;
50
51 # Avoid generating "subroutine redefined" warnings with the following
52 # hack (from CGI::Carp):
53 if (!defined $DB::VERSION) {
54         local $^W=0;
55         eval qq( sub confess { 
56             \$SIG{__DIE__} = 'DEFAULT'; 
57         DXDebug::dbgprintring() if DXDebug::isdbg('nologchan');
58         DXDebug::dbg(\$@);
59 #               DXDebug::dbg(Carp::shortmess(\@_));
60         DXDebug::longmess(\@_);
61             exit(-1); 
62         }
63         sub croak { 
64                 \$SIG{__DIE__} = 'DEFAULT'; 
65         DXDebug::dbgprintring() if DXDebug::isdbg('nologchan');
66         DXDebug::dbg(\$@);
67 #               DXDebug::dbg(Carp::longmess(\@_));
68         DXDebug::shortmess(\@_);
69                 exit(-1); 
70         }
71         sub carp { 
72         DXDebug::dbgprintring(25) if DXDebug::isdbg('nologchan');
73 #        DXDebug::dbg(Carp::shortmess(\@_)); 
74         DXDebug::longmess(\@_);
75     }
76         sub cluck { 
77         DXDebug::dbgprintring(25) if DXDebug::isdbg('nologchan');
78 #        DXDebug::dbg(Carp::longmess(\@_)); 
79         DXDebug::longmess(\@_);
80     } );
81
82     CORE::die(Carp::shortmess($@)) if $@;
83 } else {
84     eval qq( sub confess { die Carp::longmess(\@_); }; 
85                          sub croak { die Carp::shortmess(\@_); }; 
86                          sub cluck { warn Carp::longmess(\@_); }; 
87                          sub carp { warn Carp::shortmess(\@_); }; 
88    );
89
90
91
92 my $_isdbg = '';                                                # current dbg level we are processing
93
94 # print stack trace
95 sub dbgtrace
96 {
97 #       say "*** in dbgtrace";
98         $_isdbg = 'trace';
99         dbg(@_);
100         for (my $i = 1; (my ($pkg, $fn, $l, $subr) = caller($i)); ++$i) {
101 #               say "*** in dbgtrace $i";
102                 next if $pkg eq 'DXDebug';
103 #               say "*** in dbgtrace after package";
104                 last if $pkg =~ /Mojo/;
105 #               say "*** in dbgtrace $i after mojo";
106                 $_isdbg = 'trace';
107                 dbg("Stack ($i): ${pkg}::$subr in $fn line: $l");
108         }
109         $_isdbg = '';
110 }
111
112 sub dbg
113 {
114 #       return unless $fp;
115         my $t = time; 
116         for (@_) {
117                 my $r = $_;
118                 chomp $r;
119                 my @l = split /\n/, $r;
120                 foreach my $l (@l) {
121                         $l =~ s/([\x00-\x08\x0B-\x1f\x7f-\xff])/sprintf("%%%02X",ord($1))/eg;
122                         my $tag = $_isdbg ? "($_isdbg) " : '(*) ';
123                         print "$tag$l\n" if defined \*STDOUT && !$no_stdout;
124                         my $str = "$t^$tag$l";
125                         &$callback($str) if $callback;
126                         if ($dbgringlth) {
127                                 shift @dbgring while (@dbgring > $dbgringlth);
128                                 push @dbgring, $str;
129                         }
130                         $fp->writeunix($t, $str) unless !$fp || $dbglevel{"nolog$_isdbg"} ; 
131                 }
132         }
133         $_isdbg = '';
134 }
135
136 sub dbginit
137 {
138         my $basename = shift || 'debug';
139         $callback = shift;
140         
141         # add sig{__DIE__} handling
142         unless (defined $DB::VERSION) {
143                 $SIG{__WARN__} = sub { 
144                         if ($_[0] =~ /Deep\s+recursion/i) {
145                                 dbg($@);
146                                 dbg(Carp::longmess(@_)); 
147                                 CORE::die;
148                         }
149                         else { 
150                                 dbg($@);
151                                 dbg(Carp::shortmess(@_));
152                         }
153                 };
154                 
155                 $SIG{__DIE__} = sub { dbg($@); dbg(Carp::longmess(@_)); };
156
157                 # switch off STDOUT printing if we are not talking to a TTY
158                 unless ($^O =~ /^MS/ || $^O =~ /^OS-2/) {
159                         unless (isatty(STDOUT->fileno)) {
160                                 ++$no_stdout;
161                         }
162                 }
163         }
164
165         $fp = DXLog::new($basename, 'dat', 'd');
166         dbgclearring();
167 }
168
169 sub dbgclose
170 {
171         $SIG{__DIE__} = $SIG{__WARN__} = 'DEFAULT';
172         if ($fp) {
173                 dbgprintring() if grep /nolog/, keys %dbglevel;
174                 $fp->close();
175         }
176         dbgclearring();
177         undef $fp;
178 }
179
180 sub dbgdump
181 {
182         return unless $fp;
183         
184         my $l = shift;
185         my $m = shift;
186         if ($dbglevel{$l} || $l eq 'err') {
187                 my @out;
188                 foreach my $l (@_) {
189                         for (my $o = 0; $o < length $l; $o += 16) {
190                                 my $c = substr $l, $o, 16;
191                                 my $h = unpack "H*", $c;
192                                 $c =~ s/[\x00-\x1f\x7f-\xff]/./g;
193                                 my $left = 16 - length $c;
194                                 $h .= ' ' x (2 * $left) if $left > 0;
195                                 push @out, $m . sprintf("%4d:", $o) . "$h $c";
196                                 $m = ' ' x (length $m);
197                         }
198                 }
199                 dbg(@out) if isdbg($l); # yes, I know, I have my reasons;
200         } 
201 }
202
203 sub dbgadd
204
205         my $entry;
206         
207         foreach $entry (@_) {
208                 $dbglevel{$entry} = 1;
209         }
210 }
211
212 sub dbgsub
213 {
214         my $entry;
215         
216         foreach $entry (@_) {
217                 delete $dbglevel{$entry};
218         }
219 }
220
221 sub dbglist
222 {
223         return keys (%dbglevel);
224 }
225
226 sub isdbg($)
227 {
228         return unless $fp;
229         if ($dbglevel{$_[0]}) {
230                 $_isdbg = $_[0];
231                 return 1;
232     }
233 }
234
235 sub shortmess 
236 {
237         return dbgtrace(@_);
238 }
239
240 sub longmess 
241 {
242         return dbgtrace(@_);
243 }
244
245 sub dbgprintring
246 {
247         return unless $fp;
248         my $i = shift || 0;
249         my $count = @dbgring;
250         $i =  @dbgring-$i if $i;
251         return 0 unless $i < $count;    # do nothing if there is nothing to print
252
253         my $first;
254         my $l;
255         for ( ; $i < $count; ++$i) {
256                 my ($t, $str) = split /\^/, $dbgring[$i], 2;
257                 next unless $t;
258                 my $lt = time;
259                 unless ($first) {
260                         $fp->writeunix($lt, "$lt^###");
261                         $fp->writeunix($lt, "$lt^### RINGBUFFER START at line $i (zero base)");
262                         $fp->writeunix($lt, "$lt^###");
263                         $first = $t;
264                 }
265                 my $buf = sprintf "%02d:%02d:%02d", (gmtime($t))[2,1,0];
266                 $fp->writeunix($lt, "$lt^RING: $buf^$str");
267                 ++$l;
268         }
269         my $et = time;
270         $fp->writeunix($et, "$et^###");
271         $fp->writeunix($et, "$et^### RINGBUFFER END $l debug lines written");
272         $fp->writeunix($et, "$et^###");
273         return $l;
274 }
275
276 sub dbgclearring
277 {
278         @dbgring = ();
279 }
280
281 # clean out old debug files, stop when you get a gap of more than a month
282 sub dbgclean
283 {
284         my $date = $fp->unixtoj($main::systime)->sub($keepdays+1);
285         my $i = 0;
286
287         while ($i < 31) {
288                 my $fn = $fp->_genfn($date);
289                 if (-e $fn) {
290                         unlink $fn;
291                         $i = 0;
292                 }
293                 else {
294                         $i++;
295                 }
296                 $date = $date->sub(1);
297         }
298 }
299
300 1;
301 __END__
302
303
304
305
306
307
308