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;
295 my ($self, @slurp) = @_;
296 return 1 unless want_request_timer();
297 $self->_write("time", $self->get_request_time() . (@slurp ? " (@slurp)" : ''));
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>