13f86a71ab1f4df7fc6ea8ae63bda2f85696ac49
[kivitendo-erp.git] / SL / LXDebug.pm
1 package LXDebug;
2
3 use constant NONE               =>  0;
4 use constant INFO               =>  1;
5 use constant DEBUG1             =>  1 << 1;
6 use constant DEBUG2             =>  1 << 2;
7 use constant QUERY              =>  1 << 3;
8 use constant TRACE              =>  1 << 4;
9 use constant BACKTRACE_ON_ERROR =>  1 << 5;
10 use constant REQUEST_TIMER      =>  1 << 6;
11 use constant WARN               =>  1 << 7;
12 use constant TRACE2             =>  1 << 8;
13 use constant ALL                => (1 << 9) - 1;
14 use constant DEVEL              => INFO | DEBUG1 | QUERY | TRACE | BACKTRACE_ON_ERROR | REQUEST_TIMER;
15
16 use constant FILE_TARGET   => 0;
17 use constant STDERR_TARGET => 1;
18
19 use Data::Dumper;
20 use POSIX qw(strftime getppid);
21 use Time::HiRes qw(gettimeofday tv_interval);
22 use YAML;
23
24 use strict;
25
26 my ($text_diff_available);
27
28 our $global_level = NONE();
29 our $watch_form   = 0;
30 our $file_name;
31
32 sub new {
33   my $type = shift;
34   my $self = {};
35
36   _init_globals_from_config();
37
38   $self->{"calldepth"}  = 0;
39   $self->{"file"}       = $file_name || "/tmp/lx-office-debug.log";
40   $self->{"target"}     = FILE_TARGET;
41   $self->{"level"}      = 0;
42
43   while ($_[0]) {
44     $self->{ $_[0] } = $_[1];
45     shift;
46     shift;
47   }
48
49   bless($self, $type);
50 }
51
52 my $globals_inited_from_config;
53 sub _init_globals_from_config {
54   return if $globals_inited_from_config;
55   $globals_inited_from_config = 1;
56
57   my $cfg = $::lx_office_conf{debug} || {};
58
59   $global_level = NONE() if $cfg->{global_level} =~ /NONE/;
60   foreach my $level (grep { $_} split(m/\s+/, $cfg->{global_level})) {
61     $global_level |= eval "${level}()";
62   }
63
64   $watch_form = $cfg->{watch_form};
65   $file_name  = $cfg->{file_name} || "/tmp/lx-office-debug.log";
66 }
67
68 sub set_target {
69   my ($self, $target, $file) = @_;
70
71   if ((FILE_TARGET == $target) && $file) {
72     $self->{"file"}   = $file;
73     $self->{"target"} = FILE_TARGET;
74
75   } elsif (STDERR_TARGET == $target) {
76     $self->{"target"} = STDERR_TARGET;
77   }
78 }
79
80 sub enter_sub {
81   my $self  = shift;
82   my $level = shift || 0;
83
84   return 1 unless ($global_level & TRACE);          # ignore if traces aren't active
85   return 1 if $level && !($global_level & TRACE2);  # ignore if level of trace isn't active
86
87   my ($package, $filename, $line, $subroutine) = caller(1);
88   my ($dummy1, $self_filename, $self_line) = caller(0);
89
90   my $indent = " " x $self->{"calldepth"}++;
91   my $time = $self->get_request_time || '';
92
93   if (!defined($package)) {
94     $self->_write('sub' . $level, $indent . "\\ $time top-level?\n");
95   } else {
96     $self->_write('sub' . $level, $indent
97                     . "\\ $time ${subroutine} in "
98                     . "${self_filename}:${self_line} called from "
99                     . "${filename}:${line}\n");
100   }
101   return 1;
102 }
103
104 sub leave_sub {
105   my $self  = shift;
106   my $level = shift || 0;
107
108   return 1 unless ($global_level & TRACE);           # ignore if traces aren't active
109   return 1 if $level && !($global_level & TRACE2);   # ignore if level of trace isn't active
110
111   my ($package, $filename, $line, $subroutine) = caller(1);
112   my ($dummy1, $self_filename, $self_line) = caller(0);
113
114   my $indent = " " x --$self->{"calldepth"};
115   my $time = $self->want_request_timer ? $self->get_request_time || '' : '';
116
117   if (!defined($package)) {
118     $self->_write('sub' . $level, $indent . "/ $time top-level?\n");
119   } else {
120     $self->_write('sub' . $level, $indent . "/ $time ${subroutine} in " . "${self_filename}:${self_line}\n");
121   }
122   return 1;
123 }
124
125 sub show_backtrace {
126   my ($self, $force) = @_;
127
128   return 1 unless ($force || ($global_level & BACKTRACE_ON_ERROR));
129
130   $self->message(BACKTRACE_ON_ERROR, "Starting full caller dump:");
131   my $level = 0;
132   while (my ($dummy, $filename, $line, $subroutine) = caller $level) {
133     $self->message(BACKTRACE_ON_ERROR, "  ${subroutine} from ${filename}:${line}");
134     $level++;
135   }
136
137   return 1;
138 }
139
140 sub message {
141   no warnings;
142   my ($self, $level, $message) = @_;
143
144   $self->_write(level2string($level), $message) if (($self->{"level"} | $global_level) & $level || !$level);
145 }
146 sub warn {
147   no warnings;
148   my ($self, $message) = @_;
149   $self->message(WARN, $message);
150 }
151
152 sub dump {
153   my ($self, $level, $name, $variable, %options) = @_;
154
155   my $password;
156   if ($variable && ('Form' eq ref $variable) && defined $variable->{password}) {
157     $password             = $variable->{password};
158     $variable->{password} = 'X' x 8;
159   }
160
161   my $dumper = Data::Dumper->new([$variable]);
162   $dumper->Sortkeys(1);
163   $dumper->Indent(2);
164   $dumper->$_($options{$_}) for keys %options;
165   my $output = $dumper->Dump();
166   $self->message($level, "dumping ${name}:\n" . $output);
167
168   $variable->{password} = $password if (defined $password);
169
170   # Data::Dumper does not reset the iterator belonging to this hash
171   # if 'Sortkeys' is true. Therefore clear the iterator manually.
172   # See "perldoc -f each".
173   if ($variable && (('HASH' eq ref $variable) || ('Form' eq ref $variable))) {
174     keys %{ $variable };
175   }
176
177   return $output;
178 }
179
180 sub dump_yaml {
181   my ($self, $level, $name, $variable) = @_;
182
183   $self->message($level, "dumping ${name}:\n" . YAML::Dump($variable));
184 }
185
186 sub dump_sql_result {
187   my ($self, $level, $prefix, $results) = @_;
188
189   if (!$results || !scalar @{ $results }) {
190     $self->message($level, "Empty result set");
191     return;
192   }
193
194   my %column_lengths = map { $_, length $_ } keys %{ $results->[0] };
195
196   foreach my $row (@{ $results }) {
197     map { $column_lengths{$_} = length $row->{$_} if (length $row->{$_} > $column_lengths{$_}) } keys %{ $row };
198   }
199
200   my @sorted_names = sort keys %column_lengths;
201   my $format       = join '|', map { '%' . $column_lengths{$_} . 's' } @sorted_names;
202
203   $prefix .= ' ' if $prefix;
204
205   $self->message($level, $prefix . sprintf($format, @sorted_names));
206   $self->message($level, $prefix . join('+', map { '-' x $column_lengths{$_} } @sorted_names));
207
208   foreach my $row (@{ $results }) {
209     $self->message($level, $prefix . sprintf($format, map { $row->{$_} } @sorted_names));
210   }
211   $self->message($level, $prefix . sprintf('(%d row%s)', scalar @{ $results }, scalar @{ $results } > 1 ? 's' : ''));
212 }
213
214 sub show_diff {
215   my ($self, $level, $item1, $item2, %params) = @_;
216
217   if (!$self->_load_text_diff) {
218     $self->warn("Perl module Text::Diff is not available");
219     return;
220   }
221
222   my @texts = map { ref $_ ? YAML::Dump($_) : $_ } ($item1, $item2);
223
224   $self->message($level, Text::Diff::diff(\$texts[0], \$texts[1], \%params));
225 }
226
227 sub _load_text_diff {
228   $text_diff_available = eval("use Text::Diff (); 1;") ? 1 : 0 unless defined $text_diff_available;
229   return $text_diff_available;
230 }
231
232 sub enable_sub_tracing {
233   my ($self) = @_;
234   $global_level |= TRACE;
235 }
236
237 sub disable_sub_tracing {
238   my ($self) = @_;
239   $global_level &= ~ TRACE;
240 }
241
242 sub is_tracing_enabled {
243   my ($self) = @_;
244   return $global_level & TRACE;
245 }
246
247 sub _write {
248   no warnings;
249   my ($self, $prefix, $message) = @_;
250   my $date = strftime("%Y-%m-%d %H:%M:%S $$ [" . getppid() . "] ${prefix}: ", localtime(time()));
251   local *FILE;
252
253   chomp($message);
254
255   if ((FILE_TARGET == $self->{"target"})
256       && open(FILE, ">>", $self->{"file"})) {
257     print(FILE "${date}${message}\n");
258     close(FILE);
259
260   } elsif (STDERR_TARGET == $self->{"target"}) {
261     print(STDERR "${date}${message}\n");
262   }
263 }
264
265 sub level2string {
266   no warnings;
267   # use $_[0] as a bit mask and return levelstrings separated by /
268   join '/', qw(info debug1 debug2 query trace error_call_trace request_timer WARNING)[ grep { (reverse split //, sprintf "%08b", $_[0])[$_] } 0..7 ]
269 }
270
271 sub begin_request {
272   my $self = shift;
273   return 1 unless want_request_timer();
274   $self->set_request_timer;
275 }
276
277 sub end_request {
278   my $self = shift;
279   return 1 unless want_request_timer();
280   $self->_write("time", $self->get_request_time);
281
282   $self->{calldepth} = 0;
283 }
284
285 sub log_time {
286   my ($self, @slurp) = @_;
287   return 1 unless want_request_timer();
288   $self->_write("time", $self->get_request_time() . (@slurp ? " (@slurp)" : ''));
289 }
290
291 sub get_request_time {
292   my $self = shift;
293   return $self->want_request_timer && $self->{request_start} ? tv_interval($self->{request_start}) : undef;
294 }
295
296 sub set_request_timer {
297   my $self = shift;
298   $self->{request_start} = [gettimeofday];
299 }
300
301 sub want_request_timer {
302   $global_level & REQUEST_TIMER;
303 }
304
305 sub file {
306   @_ == 2 ? $_[0]->{file} = $_[1] : $_[0]->{file};
307 }
308
309 sub _by_name {
310   my ($self, $level) = @_;
311   my $meth = $self->can(uc $level);
312   die 'unknown level' unless $meth;
313   $meth->();
314 }
315
316 sub level_by_name {
317   my ($self, $level, $val) = @_;
318   if (@_ == 3) {
319     $global_level |=  $self->_by_name($level) if  $val;
320     $global_level &= ~$self->_by_name($level) if !$val;
321   }
322   return $global_level & $self->_by_name($level);
323 }
324
325 1;
326 __END__
327
328 =pod
329
330 =encoding utf8
331
332 =head1 NAME
333
334 LXDebug - kivitendo debugging facilities
335
336 =head1 SYNOPSIS
337
338 This module provides functions for debugging kivitendo. An instance is
339 always created as the global variable C<$::lxdebug> at the earliest
340 possible moment.
341
342 Debugging is mostly logging of information. Each log function has a
343 I<level> and an I<object> to be logged. The configuration file as well
344 as this module's functions determine which levels get logged, and
345 which file they're logged to.
346
347 =head1 LOG LEVELS
348
349 The available log levels are:
350
351 =over 4
352
353 =item C<NONE>
354
355 Always output the message regardless of the active levels. Only use
356 this temporarily.
357
358 =item C<INFO>
359
360 Informational, not an error, more important than C<DEBUG1>.
361
362 =item C<DEBUG1>
363
364 Important debugging information.
365
366 =item C<DEBUG2>
367
368 Less important debugging information that occurs often and spams the
369 log.
370
371 =item C<QUERY>
372
373 Log all queries executed by the L<SL::DBUtils> utility methods.
374
375 =item C<TRACE>
376
377 Log sub calls and exits via the L<enter_sub>/L<leave_sub> functions,
378 but only if they're called with a log level that is falsish
379 (e.g. none, C<undef>, 0).
380
381 =item C<TRACE2>
382
383 Log sub calls and exits via the L<enter_sub>/L<leave_sub> functions
384 even if they're called with a log level of 2. Will only have an effect
385 if C<TRACE> is set as well.
386
387 =item C<BACKTRACE_ON_ERROR>
388
389 Log a stack trace when an error is output.
390
391 =item C<REQUEST_TIMER>
392
393 Log each request's total execution time when it finishes.
394
395 =item C<WARN>
396
397 Important warnings.
398
399 =item C<ALL>
400
401 All of the above.
402
403 =item C<DEVEL>
404
405 Shortcut for C<INFO | QUERY | TRACE | BACKTRACE_ON_ERROR | REQUEST_TIMER>.
406
407 =back
408
409 =head1 CONFIGURATION
410
411 C<SL::LXDebug> gets its configuration from the C<[debug]> section of
412 the C<config/kivitendo.conf> configuration file. The available options
413 are:
414
415 =over 4
416
417 =item C<global_level>
418
419 A string of log level names that should be activated by
420 default. Multiple log levels are separated by C<|>.
421
422 =item C<watch_form>
423
424 A boolean (C<1> or C<0>). Turns on the C<$::form> watch facility. If
425 this is enabled then any key inside C<$::form> can be monitored for
426 changes. For example:
427
428   # Start watching 'action'
429   $::form->{"Watchdog::action"} = 1;
430   # Stop watching 'invtotal'
431   $::form->{"Watchdog::invtotal"} = 0;
432
433 A log message is written when the watchdog is enabled for a variable
434 and for each subsequent change. The log message includes the place
435 (file name and line number) of the instruction changing the key.
436
437 Note that this entails a performance penalty. Also only the keys
438 themselves are monitored -- not the references they point to. E.g. the
439 following would not trigger a change:
440
441   $::form->{"Watchdog::some_hash"} = 1;
442   # Does not trigger:
443   $::form->{some_hash}->{some_value} = 42;
444   # This does trigger:
445   $::form->{some_hash} = { something => 'else' };
446
447 =item C<keep_temp_files>
448
449 A boolean (C<1> or C<0>). If turned on then certain temporary files
450 are not removed but kept in the C<users> directory. These include the
451 temporary files used during printing, e.g. LaTeX files.
452
453 =item C<file_name>
454
455 The path and file name of the debug log file. Must be a location
456 writeable by the web server process.
457
458 =back
459
460 =head1 FUNCTIONS
461
462 =over 4
463
464 =item C<enter_sub [$level]>
465
466 =item C<leave_sub [$level]>
467
468 Pairs of these can be put near the beginning/end of a sub. They'll
469 cause a trace to be written to the log file if the C<TRACE> level is
470 active.
471
472 If C<$level> is given then the log messages will only be logged if the
473 global log level C<TRACE2> is active as well.
474
475 =item C<enable_sub_tracing>
476
477 =item C<disable_sub_tracing>
478
479 Enables/disables sub tracing with L<enter_sub>/L<leave_sub> temporarily.
480
481 =item C<is_tracing_enabled>
482
483 Returns whether or not the C<TRACE> debug level is active.
484
485 =item C<show_backtrace [$force]>
486
487 Logs a stack backtrace if C<$force> is trueish or if the log level
488 C<BACKTRACE_ON_ERROR> is active.
489
490 =item C<message $level, $message>
491
492 Logs the message C<$message> if the log level C<$level> is active. The
493 message will be prefixed with a word describing the log level.
494
495 =item C<warn $message>
496
497 Equivalent to C<message WARN(), $message>.
498
499 =item C<dump $level, $name, $variable>
500
501 Logs a message that the variable named C<$name> is dumped along with a
502 dump of the variable C<$variable> created by the L<Data::Dumper>
503 module. Will log a warning if said module is not available. Will only
504 log if the log level C<$level> is active.
505
506 =item C<dump_yaml $level, $name, $variable>
507
508 Logs a message that the variable named C<$name> is dumped along with a
509 dump of the variable C<$variable> created by the C<YAML> module. Will
510 only log if the log level C<$level> is active.
511
512 =item C<dump_sql $level, $prefix, $results>
513
514 Dumps the result of an SQL query in tabular form. Will only log if the
515 log level C<$level> is active.
516
517 =item C<show_diff $level, $item1, $item2, %params>
518
519 Logs a unified diff of the textual representations of C<$item1> and
520 C<$item2>. Requires the module L<Text::Diff> and logs a warning if
521 said module is not available.
522
523 C<$item1> and C<$item2> are dumped via L<YAML::Dumper> before diffing
524 if they're non-scalars.
525
526 Will only log if the log level C<$level> is active.
527
528 =item C<begin_request>
529
530 =item C<end_request>
531
532 =item C<log_time>
533
534 =item C<set_request_timer>
535
536 =item C<want_request_timer>
537
538 Internal functions used to log the current request's exeuction time
539 (log level C<REQUEST_TIMER>).
540
541 =item C<get_request_time>
542
543 Returns the current request's elapsed execution time in seconds.
544
545 =item C<file [$file_name]>
546
547 Sets and/or returns the file name this instance logs to.
548
549 =item C<level_by_name $level[, $val]>
550
551 Returns if a log level C<$level> is active. C<$level> is a string
552 representation, not one of the level constants from above.
553
554 If C<$val> is given then said level will be turned on (if C<$val> is
555 trueish) or off (if C<$val> is falsish).
556
557 =back
558
559 =head1 BUGS
560
561 Nothing here yet.
562
563 =head1 AUTHOR
564
565 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
566 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
567
568 =cut