25d742250a6572037ee6f4589a2cf54a4bd4a53f
[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 require Exporter;
28 @ISA = qw(Exporter);
29 @EXPORT = qw(dbginit dbg dbgadd dbgsub dbglist dbgdump isdbg dbgclose confess croak cluck carp);
30
31 use strict;
32 use vars qw(%dbglevel $fp $callback $cleandays $keepdays $dbgringlth);
33
34 use DXUtil;
35 use DXLog ();
36 use Carp ();
37 use POSIX qw(isatty);
38
39 %dbglevel = ();
40 $fp = undef;
41 $callback = undef;
42 $keepdays = 10;
43 $cleandays = 100;
44 $dbgringlth = 500;
45
46 our $no_stdout;                                 # set if not running in a terminal
47 our @dbgring;
48
49 # Avoid generating "subroutine redefined" warnings with the following
50 # hack (from CGI::Carp):
51 if (!defined $DB::VERSION) {
52         local $^W=0;
53         eval qq( sub confess { 
54             \$SIG{__DIE__} = 'DEFAULT'; 
55         DXDebug::dbgprintring() if DXDebug::isdbg('nologchan');
56         DXDebug::dbg(\$@);
57                 DXDebug::dbg(Carp::shortmess(\@_));
58             exit(-1); 
59         }
60         sub croak { 
61                 \$SIG{__DIE__} = 'DEFAULT'; 
62         DXDebug::dbgprintring() if DXDebug::isdbg('nologchan');
63         DXDebug::dbg(\$@);
64                 DXDebug::dbg(Carp::longmess(\@_));
65                 exit(-1); 
66         }
67         sub carp { 
68         DXDebug::dbgprintring(25) if DXDebug('nologchan');
69         DXDebug::dbg(Carp::shortmess(\@_)); 
70     }
71         sub cluck { 
72         DXDebug::dbgprintring(25) if DXDebug('nologchan');
73         DXDebug::dbg(Carp::longmess(\@_)); 
74     } );
75
76     CORE::die(Carp::shortmess($@)) if $@;
77 } else {
78     eval qq( sub confess { die Carp::longmess(\@_); }; 
79                          sub croak { die Carp::shortmess(\@_); }; 
80                          sub cluck { warn Carp::longmess(\@_); }; 
81                          sub carp { warn Carp::shortmess(\@_); }; 
82    );
83
84
85
86 my $_isdbg;                                             # current dbg level we are processing
87
88 sub dbg
89 {
90 #       return unless $fp;
91         my $t = time; 
92         for (@_) {
93                 my $r = $_;
94                 chomp $r;
95                 my @l = split /\n/, $r;
96                 foreach my $l (@l) {
97                         $l =~ s/([\x00-\x08\x0B-\x1f\x7f-\xff])/sprintf("%%%02X",ord($1))/eg;
98                         print "$l\n" if defined \*STDOUT && !$no_stdout;
99                         my $tag = $_isdbg ? "($_isdbg) " : '(*) ';
100                         my $str = "$t^$tag$l";
101                         &$callback($str) if $callback;
102                         if ($dbgringlth) {
103                                 shift @dbgring while (@dbgring > $dbgringlth);
104                                 push @dbgring, $str;
105                         }
106                         $fp->writeunix($t, $str) unless !$fp || $dbglevel{"nolog$_isdbg"} ; 
107                 }
108         }
109         $_isdbg = '';
110 }
111
112 sub dbginit
113 {
114         $callback = shift;
115         
116         # add sig{__DIE__} handling
117         unless (defined $DB::VERSION) {
118                 $SIG{__WARN__} = sub { 
119                         if ($_[0] =~ /Deep\s+recursion/i) {
120                                 dbg($@);
121                                 dbg(Carp::longmess(@_)); 
122                                 CORE::die;
123                         }
124                         else { 
125                                 dbg($@);
126                                 dbg(Carp::shortmess(@_));
127                         }
128                 };
129                 
130                 $SIG{__DIE__} = sub { dbg($@); dbg(Carp::longmess(@_)); };
131
132                 # switch off STDOUT printing if we are not talking to a TTY
133                 unless ($^O =~ /^MS/ || $^O =~ /^OS-2/) {
134                         unless (isatty(STDOUT->fileno)) {
135                                 ++$no_stdout;
136                         }
137                 }
138         }
139
140         $fp = DXLog::new('debug', 'dat', 'd');
141         dbgclearring();
142 }
143
144 sub dbgclose
145 {
146         $SIG{__DIE__} = $SIG{__WARN__} = 'DEFAULT';
147         if ($fp) {
148                 dbgprintring() if grep /nolog/, keys %dbglevel;
149                 $fp->close();
150         }
151         dbgclearring();
152         undef $fp;
153 }
154
155 sub dbgdump
156 {
157         return unless $fp;
158         
159         my $l = shift;
160         my $m = shift;
161         if ($dbglevel{$l} || $l eq 'err') {
162                 foreach my $l (@_) {
163                         for (my $o = 0; $o < length $l; $o += 16) {
164                                 my $c = substr $l, $o, 16;
165                                 my $h = unpack "H*", $c;
166                                 $c =~ s/[\x00-\x1f\x7f-\xff]/./g;
167                                 my $left = 16 - length $c;
168                                 $h .= ' ' x (2 * $left) if $left > 0;
169                                 dbg($m . sprintf("%4d:", $o) . "$h $c");
170                                 $m = ' ' x (length $m);
171                         }
172                 }
173         }
174 }
175
176 sub dbgadd
177
178         my $entry;
179         
180         foreach $entry (@_) {
181                 $dbglevel{$entry} = 1;
182         }
183 }
184
185 sub dbgsub
186 {
187         my $entry;
188         
189         foreach $entry (@_) {
190                 delete $dbglevel{$entry};
191         }
192 }
193
194 sub dbglist
195 {
196         return keys (%dbglevel);
197 }
198
199 sub isdbg($)
200 {
201         return unless $fp;
202         if ($dbglevel{$_[0]}) {
203                 $_isdbg = $_[0];
204                 return 1;
205     }
206 }
207
208 sub shortmess 
209 {
210         return Carp::shortmess(@_);
211 }
212
213 sub longmess 
214 {
215         return Carp::longmess(@_);
216 }
217
218 sub dbgprintring
219 {
220         return unless $fp;
221         my $count = shift;
222         my $first;
223         my $l;
224         my $i = defined $count ? @dbgring-$count : 0;
225         $count = @dbgring;
226         for ( ; $i < $count; ++$i) {
227                 my ($t, $str) = split /\^/, $dbgring[$i], 2;
228                 next unless $t;
229                 my $lt = time;
230                 unless ($first) {
231                         $fp->writeunix($lt, "$lt^###");
232                         $fp->writeunix($lt, "$lt^### RINGBUFFER START at line $i (zero base)");
233                         $fp->writeunix($lt, "$lt^###");
234                         $first = $t;
235                 }
236                 my $buf = sprintf "%02d:%02d:%02d", (gmtime($t))[2,1,0];
237                 $fp->writeunix($lt, "$lt^RING: $buf^$str");
238         }
239         my $et = time;
240         $fp->writeunix($et, "$et^###");
241         $fp->writeunix($et, "$et^### RINGBUFFER END");
242         $fp->writeunix($et, "$et^###");
243 }
244
245 sub dbgclearring
246 {
247         @dbgring = ();
248 }
249
250 # clean out old debug files, stop when you get a gap of more than a month
251 sub dbgclean
252 {
253         my $date = $fp->unixtoj($main::systime)->sub($keepdays+1);
254         my $i = 0;
255
256         while ($i < 31) {
257                 my $fn = $fp->_genfn($date);
258                 if (-e $fn) {
259                         unlink $fn;
260                         $i = 0;
261                 }
262                 else {
263                         $i++;
264                 }
265                 $date = $date->sub(1);
266         }
267 }
268
269 1;
270 __END__
271
272
273
274
275
276
277