# ex:ts=8 sw=4: # $OpenBSD: Trace.pm,v 1.3 2023/05/09 15:37:54 espie Exp $ # # Copyright (c) 2015-2019 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use v5.36; package OpenBSD::Trace; # this is a base class meant to be inherited from # 'smart' dump param # -> objects with a debug_dump will be stringified by it # -> other objects will be passed to Data::Dumper for writing # -> 'weird' characters get filtered out (think SHA contents) my $forbidden = qr{[^[:print:]\s]}; sub dumper($self, @parms) { require Data::Dumper; return Data::Dumper->new(@parms)-> Indent(0)->Maxdepth(1)->Quotekeys(0)->Sortkeys(1)-> Deparse(1); } sub dump_param($self, $arg = undef, $full = 0) { if (!defined $arg) { return ''; } else { my $string; eval { $string = $arg->debug_dump }; if (defined $string) { return "$arg($string)"; } } if ($full) { my $msg = $self->dumper([$arg])->Dump; $msg =~ s/^\$VAR1 = //; $msg =~ s/\;$//; $msg =~ s/$forbidden/?/g; return $msg; } else { return $arg; } } # the stack, mostly identical to Carp::Always sub stack($self, $full = 0) { my $msg = ''; my $x = 1; while (1) { my @c; { package DB; our @args; @c = caller($x+1); } last if !@c; # XXX do better with anon fn ? my $fn = "$c[3]"; $msg .= $fn."(". join(', ', map { $self->dump_param($_, $full); } @DB::args). ") called at $c[1] line $c[2]\n"; $x++; } return $msg; } # note that derived classes like DPB::Trace use the extra parms sub new($class, @p) { my $o = bless {}, $class; $o->init(@p); return $o; } # XXX this is necessary to avoid endless recursion END { $SIG{__DIE__} = 'DEFAULT'; $SIG{__WARN__} = 'DEFAULT'; } sub init($self) { $self->setup_warn; $self->setup_die; } sub setup_warn($self) { $SIG{__WARN__} = sub { local $SIG{__WARN__} = 'DEFAULT'; # let CORE:: do its job during compile and evals unless (defined $^S and !$^S) { warn @_; return; } my $a = pop @_; # XXX need copy because contents of @_ are RO. $a =~ s/(.*)( at .*? line .*?)\n$/$1$2/s; push @_, $a; my $msg = join("\n", @_, $self->stack(0)); $self->do_warn($msg); }; } sub setup_die($self) { $SIG{__DIE__} = sub { local $SIG{__DIE__} = 'DEFAULT'; # let CORE:: do its job during compile and evals unless (defined $^S and !$^S) { die @_; return; } my $a = pop @_; # XXX need copy because contents of @_ are RO. $a =~ s/(.*)( at .*? line .*?)\n$/$1$2/s; push @_, $a; my $msg = join("\n", @_, $self->stack(1)); $self->do_die($msg); }; } sub do_warn($self, $msg) { warn $msg; } sub dump_data($self, @p) { require Data::Dumper; my $msg = Data::Dumper->new(@p)-> Quotekeys(0)->Sortkeys(1)->Deparse(1)->Dump; $msg =~ s/$forbidden/?/g; return $msg; } sub do_die($self, $msg) { die $msg; } 1; =pod =head1 NAME OpenBSD::Trace - Base class for run time diagnostics =head1 SYNOPSIS package MyTrace; use parent qw(OpenBSD::Trace); # ... specialize methods # ... at start of main program package main; my $t = MyTrace; =head1 DESCRIPTION L provides a base class for stack dumping on warn and die. By default, it does a standard stack dump on warn, and a "full" stack dump on die (with arguments passed through L with limited recursion). Additionally, arguments for classes that define a C method will be stringified by calling C instead. L also provides an Cdata_dump> which does the same thing as L's C with some extras: =over 4 =item * non printable characters are replaced with '?' =item * nice options, such as Deparse and Sortkeys are set. =back Inheriting from L offers the following overridable methods: =over 4 =item * the stack dumping message is built using C<$self-Estack_dump($full)> =item * the Data::Dumper object with options is built using C<$self-Edumper(@_)> =item * the C<$SIG{__WARN__}> handler calls C<$self-Edo_warn($msg)> after doing a C =item * the C<$SIG{__DIE__}> handler calls C<$self-Edo_die($msg)> after doing a C =item * those handlers are set using C<$self-Esetup_warn> and C<$self->Esetup_die> respectively =item * once the base constructor is done, it calls C<$self-Einit(@_)> with the rest of the parameters. =item * the base object is an empty hash where keys may be set as needed. =back Thus, a derived class can easily reuse the message creation in other contexts (doing a C on C<$SIG{STATUS}> for instance). It's also easy to override C or C to log the message somewhere or to ensure terminal consistency. =cut