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;
 
  16 use constant FILE_TARGET   => 0;
 
  17 use constant STDERR_TARGET => 1;
 
  19 use POSIX qw(strftime getppid);
 
  20 use Time::HiRes qw(gettimeofday tv_interval);
 
  25 my ($data_dumper_available, $text_diff_available);
 
  32   eval("use Data::Dumper");
 
  33   $data_dumper_available = $@ ? 0 : 1;
 
  43   _init_globals_from_config();
 
  45   $self->{"calldepth"}  = 0;
 
  46   $self->{"file"}       = $file_name || "/tmp/lx-office-debug.log";
 
  47   $self->{"target"}     = FILE_TARGET;
 
  51     $self->{ $_[0] } = $_[1];
 
  59 my $globals_inited_from_config;
 
  60 sub _init_globals_from_config {
 
  61   return if $globals_inited_from_config;
 
  62   $globals_inited_from_config = 1;
 
  64   my $cfg = $::lx_office_conf{debug} || {};
 
  66   $global_level = NONE() if $cfg->{global_level} =~ /NONE/;
 
  67   foreach my $level (grep { $_} split(m/\s+/, $cfg->{global_level})) {
 
  68     $global_level |= eval "${level}()";
 
  71   $watch_form = $cfg->{watch_form};
 
  72   $file_name  = $cfg->{file_name} || "/tmp/lx-office-debug.log";
 
  76   my ($self, $target, $file) = @_;
 
  78   if ((FILE_TARGET == $target) && $file) {
 
  79     $self->{"file"}   = $file;
 
  80     $self->{"target"} = FILE_TARGET;
 
  82   } elsif (STDERR_TARGET == $target) {
 
  83     $self->{"target"} = STDERR_TARGET;
 
  89   my $level = shift || 0;
 
  91   return 1 unless ($global_level & TRACE);          # ignore if traces aren't active
 
  92   return 1 if $level && !($global_level & TRACE2);  # ignore if level of trace isn't active
 
  94   my ($package, $filename, $line, $subroutine) = caller(1);
 
  95   my ($dummy1, $self_filename, $self_line) = caller(0);
 
  97   my $indent = " " x $self->{"calldepth"}++;
 
  98   my $time = $self->get_request_time || '';
 
 100   if (!defined($package)) {
 
 101     $self->_write('sub' . $level, $indent . "\\ $time top-level?\n");
 
 103     $self->_write('sub' . $level, $indent
 
 104                     . "\\ $time ${subroutine} in "
 
 105                     . "${self_filename}:${self_line} called from "
 
 106                     . "${filename}:${line}\n");
 
 113   my $level = shift || 0;
 
 115   return 1 unless ($global_level & TRACE);           # ignore if traces aren't active
 
 116   return 1 if $level && !($global_level & TRACE2);   # ignore if level of trace isn't active
 
 118   my ($package, $filename, $line, $subroutine) = caller(1);
 
 119   my ($dummy1, $self_filename, $self_line) = caller(0);
 
 121   my $indent = " " x --$self->{"calldepth"};
 
 122   my $time = $self->want_request_timer ? $self->get_request_time : '';
 
 124   if (!defined($package)) {
 
 125     $self->_write('sub' . $level, $indent . "/ $time top-level?\n");
 
 127     $self->_write('sub' . $level, $indent . "/ $time ${subroutine} in " . "${self_filename}:${self_line}\n");
 
 133   my ($self, $force) = @_;
 
 135   return 1 unless ($force || ($global_level & BACKTRACE_ON_ERROR));
 
 137   $self->message(BACKTRACE_ON_ERROR, "Starting full caller dump:");
 
 139   while (my ($dummy, $filename, $line, $subroutine) = caller $level) {
 
 140     $self->message(BACKTRACE_ON_ERROR, "  ${subroutine} from ${filename}:${line}");
 
 149   my ($self, $level, $message) = @_;
 
 151   $self->_write(level2string($level), $message) if (($self->{"level"} | $global_level) & $level || !$level);
 
 155   my ($self, $message) = @_;
 
 156   $self->message(WARN, $message);
 
 160   my ($self, $level, $name, $variable) = @_;
 
 162   if ($data_dumper_available) {
 
 164     if ($variable && ('Form' eq ref $variable) && defined $variable->{password}) {
 
 165       $password             = $variable->{password};
 
 166       $variable->{password} = 'X' x 8;
 
 169     my $dumper = Data::Dumper->new([$variable]);
 
 170     $dumper->Sortkeys(1);
 
 171     $self->message($level, "dumping ${name}:\n" . $dumper->Dump());
 
 173     $variable->{password} = $password if (defined $password);
 
 175     # Data::Dumper does not reset the iterator belonging to this hash
 
 176     # if 'Sortkeys' is true. Therefore clear the iterator manually.
 
 177     # See "perldoc -f each".
 
 178     if ($variable && (('HASH' eq ref $variable) || ('Form' eq ref $variable))) {
 
 183     $self->message($level,
 
 184                    "dumping ${name}: Data::Dumper not available; "
 
 185                      . "variable cannot be dumped");
 
 190   my ($self, $level, $name, $variable) = @_;
 
 192   $self->message($level, "dumping ${name}:\n" . YAML::Dump($variable));
 
 195 sub dump_sql_result {
 
 196   my ($self, $level, $prefix, $results) = @_;
 
 198   if (!$results || !scalar @{ $results }) {
 
 199     $self->message($level, "Empty result set");
 
 203   my %column_lengths = map { $_, length $_ } keys %{ $results->[0] };
 
 205   foreach my $row (@{ $results }) {
 
 206     map { $column_lengths{$_} = length $row->{$_} if (length $row->{$_} > $column_lengths{$_}) } keys %{ $row };
 
 209   my @sorted_names = sort keys %column_lengths;
 
 210   my $format       = join '|', map { '%' . $column_lengths{$_} . 's' } @sorted_names;
 
 212   $prefix .= ' ' if $prefix;
 
 214   $self->message($level, $prefix . sprintf($format, @sorted_names));
 
 215   $self->message($level, $prefix . join('+', map { '-' x $column_lengths{$_} } @sorted_names));
 
 217   foreach my $row (@{ $results }) {
 
 218     $self->message($level, $prefix . sprintf($format, map { $row->{$_} } @sorted_names));
 
 220   $self->message($level, $prefix . sprintf('(%d row%s)', scalar @{ $results }, scalar @{ $results } > 1 ? 's' : ''));
 
 224   my ($self, $level, $item1, $item2, %params) = @_;
 
 226   if (!$self->_load_text_diff) {
 
 227     $self->warn("Perl module Text::Diff is not available");
 
 231   my @texts = map { ref $_ ? YAML::Dump($_) : $_ } ($item1, $item2);
 
 233   $self->message($level, Text::Diff::diff(\$texts[0], \$texts[1], \%params));
 
 236 sub _load_text_diff {
 
 237   $text_diff_available = eval("use Text::Diff (); 1;") ? 1 : 0 unless defined $text_diff_available;
 
 238   return $text_diff_available;
 
 241 sub enable_sub_tracing {
 
 243   $global_level |= TRACE;
 
 246 sub disable_sub_tracing {
 
 248   $global_level &= ~ TRACE;
 
 251 sub is_tracing_enabled {
 
 253   return $global_level & TRACE;
 
 258   my ($self, $prefix, $message) = @_;
 
 259   my $date = strftime("%Y-%m-%d %H:%M:%S $$ [" . getppid() . "] ${prefix}: ", localtime(time()));
 
 264   if ((FILE_TARGET == $self->{"target"})
 
 265       && open(FILE, ">>", $self->{"file"})) {
 
 266     print(FILE "${date}${message}\n");
 
 269   } elsif (STDERR_TARGET == $self->{"target"}) {
 
 270     print(STDERR "${date}${message}\n");
 
 276   # use $_[0] as a bit mask and return levelstrings separated by /
 
 277   join '/', qw(info debug1 debug2 query trace error_call_trace request_timer WARNING)[ grep { (reverse split //, sprintf "%08b", $_[0])[$_] } 0..7 ]
 
 282   return 1 unless want_request_timer();
 
 283   $self->set_request_timer;
 
 288   return 1 unless want_request_timer();
 
 289   $self->_write("time", $self->get_request_time);
 
 291   $self->{calldepth} = 0;
 
 296   return 1 unless want_request_timer();
 
 297   $self->_write("time", $self->get_request_time);
 
 300 sub get_request_time {
 
 302   return $self->want_request_timer && $self->{request_start} ? tv_interval($self->{request_start}) : undef;
 
 305 sub set_request_timer {
 
 307   $self->{request_start} = [gettimeofday];
 
 310 sub want_request_timer {
 
 311   $global_level & REQUEST_TIMER;
 
 315   @_ == 2 ? $_[0]->{file} = $_[1] : $_[0]->{file};
 
 319   my ($self, $level) = @_;
 
 320   my $meth = $self->can(uc $level);
 
 321   die 'unknown level' unless $meth;
 
 326   my ($self, $level, $val) = @_;
 
 328     $global_level |=  $self->_by_name($level) if  $val;
 
 329     $global_level &= ~$self->_by_name($level) if !$val;
 
 331   return $global_level & $self->_by_name($level);
 
 343 LXDebug - Lx-Office debugging facilities
 
 347 This module provides functions for debugging Lx-Office. An instance is
 
 348 always created as the global variable C<$::lxdebug> at the earliest
 
 351 Debugging is mostly logging of information. Each log function has a
 
 352 I<level> and an I<object> to be logged. The configuration file as well
 
 353 as this module's functions determine which levels get logged, and
 
 354 which file they're logged to.
 
 358 The available log levels are:
 
 364 Always output the message regardless of the active levels. Only use
 
 369 Informational, not an error, more important than C<DEBUG1>.
 
 373 Important debugging information.
 
 377 Less important debugging information that occurs often and spams the
 
 382 Log all queries executed by the L<SL::DBUtils> utility methods.
 
 386 Log sub calls and exits via the L<enter_sub>/L<leave_sub> functions,
 
 387 but only if they're called with a log level that is falsish
 
 388 (e.g. none, C<undef>, 0).
 
 392 Log sub calls and exits via the L<enter_sub>/L<leave_sub> functions
 
 393 even if they're called with a log level of 2. Will only have an effect
 
 394 if C<TRACE> is set as well.
 
 396 =item C<BACKTRACE_ON_ERROR>
 
 398 Log a stack trace when an error is output.
 
 400 =item C<REQUEST_TIMER>
 
 402 Log each request's total execution time when it finishes.
 
 414 Shortcut for C<INFO | QUERY | TRACE | BACKTRACE_ON_ERROR | REQUEST_TIMER>.
 
 420 C<SL::LXDebug> gets its configuration from the C<[debug]> section of
 
 421 the C<config/lx_office.conf> configuration file. The available options
 
 426 =item C<global_level>
 
 428 A string of log level names that should be activated by
 
 429 default. Multiple log levels are separated by C<|>.
 
 433 A boolean (C<1> or C<0>). Turns on the C<$::form> watch facility. If
 
 434 this is enabled then any key inside C<$::form> can be monitored for
 
 435 changes. For example:
 
 437   # Start watching 'action'
 
 438   $::form->{"Watchdog::action"} = 1;
 
 439   # Stop watching 'invtotal'
 
 440   $::form->{"Watchdog::invtotal"} = 0;
 
 442 A log message is written when the watchdog is enabled for a variable
 
 443 and for each subsequent change. The log message includes the place
 
 444 (file name and line number) of the instruction changing the key.
 
 446 Note that this entails a performance penalty. Also only the keys
 
 447 themselves are monitored -- not the references they point to. E.g. the
 
 448 following would not trigger a change:
 
 450   $::form->{"Watchdog::some_hash"} = 1;
 
 452   $::form->{some_hash}->{some_value} = 42;
 
 454   $::form->{some_hash} = { something => 'else' };
 
 456 =item C<show_debug_menu>
 
 458 A boolean (C<1> or C<0>). If turned on then certain debug facilities
 
 459 are available from the v1 menu. These include e.g.
 
 465 restarting the FastCGI process by forcefully exiting after the
 
 470 enabling and disabling function tracing,
 
 474 enabling and disabling certain debug levels.
 
 478 Note that these are only useful if Lx-Office is running as a FastCGI
 
 479 application because otherwise the changes would be lost when the
 
 480 process exits in a normal CGI environment.
 
 482 =item C<keep_temp_files>
 
 484 A boolean (C<1> or C<0>). If turned on then certain temporary files
 
 485 are not removed but kept in the C<users> directory. These include the
 
 486 temporary files used during printing, e.g. LaTeX files.
 
 490 The path and file name of the debug log file. Must be a location
 
 491 writeable by the web server process.
 
 499 =item C<enter_sub [$level]>
 
 501 =item C<leave_sub [$level]>
 
 503 Pairs of these can be put near the beginning/end of a sub. They'll
 
 504 cause a trace to be written to the log file if the C<TRACE> level is
 
 507 If C<$level> is given then the log messages will only be logged if the
 
 508 global log level C<TRACE2> is active as well.
 
 510 =item C<enable_sub_tracing>
 
 512 =item C<disable_sub_tracing>
 
 514 Enables/disables sub tracing with L<enter_sub>/L<leave_sub> temporarily.
 
 516 =item C<is_tracing_enabled>
 
 518 Returns whether or not the C<TRACE> debug level is active.
 
 520 =item C<show_backtrace [$force]>
 
 522 Logs a stack backtrace if C<$force> is trueish or if the log level
 
 523 C<BACKTRACE_ON_ERROR> is active.
 
 525 =item C<message $level, $message>
 
 527 Logs the message C<$message> if the log level C<$level> is active. The
 
 528 message will be prefixed with a word describing the log level.
 
 530 =item C<warn $message>
 
 532 Equivalent to C<message WARN(), $message>.
 
 534 =item C<dump $level, $name, $variable>
 
 536 Logs a message that the variable named C<$name> is dumped along with a
 
 537 dump of the variable C<$variable> created by the L<Data::Dumper>
 
 538 module. Will log a warning if said module is not available. Will only
 
 539 log if the log level C<$level> is active.
 
 541 =item C<dump_yaml $level, $name, $variable>
 
 543 Logs a message that the variable named C<$name> is dumped along with a
 
 544 dump of the variable C<$variable> created by the C<YAML> module. Will
 
 545 only log if the log level C<$level> is active.
 
 547 =item C<dump_sql $level, $prefix, $results>
 
 549 Dumps the result of an SQL query in tabular form. Will only log if the
 
 550 log level C<$level> is active.
 
 552 =item C<show_diff $level, $item1, $item2, %params>
 
 554 Logs a unified diff of the textual representations of C<$item1> and
 
 555 C<$item2>. Requires the module L<Text::Diff> and logs a warning if
 
 556 said module is not available.
 
 558 C<$item1> and C<$item2> are dumped via L<YAML::Dumper> before diffing
 
 559 if they're non-scalars.
 
 561 Will only log if the log level C<$level> is active.
 
 563 =item C<begin_request>
 
 569 =item C<set_request_timer>
 
 571 =item C<want_request_timer>
 
 573 Internal functions used to log the current request's exeuction time
 
 574 (log level C<REQUEST_TIMER>).
 
 576 =item C<get_request_time>
 
 578 Returns the current request's elapsed execution time in seconds.
 
 580 =item C<file [$file_name]>
 
 582 Sets and/or returns the file name this instance logs to.
 
 584 =item C<level_by_name $level[, $val]>
 
 586 Returns if a log level C<$level> is active. C<$level> is a string
 
 587 representation, not one of the level constants from above.
 
 589 If C<$val> is given then said level will be turned on (if C<$val> is
 
 590 trueish) or off (if C<$val> is falsish).
 
 600 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
 
 601 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>