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