ateague has asked for the wisdom of the Perl Monks concerning the following question:

UPDATE: I have since wrapped all my logging gubbins in custom $SIG{__DIE__} and $SIG{__WARN__} handlers.

use Log::Log4perl qw/:easy :no_extra_logdie_message/; $SIG{__DIE__} = sub { return if $^S; # ignore die in an eval block # Get the actual caller for the "die" and not the wrapper local $Log::Log4perl::caller_depth; $Log::Log4perl::caller_depth++; LOGDIE($_[0]); };
This works as expected.




I am currently working on implementing Log::Log4perl in one of my scripts. I have never used this before, and I would like some advise/critiques please.

perl -v This is perl 5, version 16, subversion 3 (v5.16.3) built for MSWin32-x +86-multi-thread (with 1 registered patch, see perl -V for more detail) Copyright 1987-2012, Larry Wall Binary build 1603 [296746] provided by ActiveState http://www.ActiveSt +ate.com Built Mar 13 2013 11:29:21
Some background:
  1. I wrote a logging wrapper in $SIG{__DIE__} so I would not have to hunt down and replace all of my die(...) calls with $log->logdie(...)
    $SIG{__DIE__} = sub { my ($message) = @_; chomp $message; # Get the actual caller for the "die" and not the wrapper local $Log::Log4perl::caller_depth; $Log::Log4perl::caller_depth++; my $log = get_logger(''); $log->logdie($message); };
  2. This broke all my eval{...} error handling:
    # the new $SIG{__DIE__} causes everything to die here: eval{ do_something(\@args) or die "'do_something()' failed: $!"; }; # This never gets reached if ( $@ ) { warn "Could not do_something(): [$@]"; something_else(); }
  3. $SIG{__DIE__} also catches errors in other modules. I discovered this when I was working with YAML.pm:
    use YAML qw/LoadFile/; # dies with the following error: # Can't locate Mo/builder.pm in @INC (@INC contains: C:/Perl/site/lib +C:/Perl/lib .) at (eval 65) line 2, <$IN> line 1. my $yaml = LoadFile('./config.yml');
    (I fixed this particular issue by installing Mo via ppm, but it appears I have opened my self up to a veritable minefield...)

  4. I discovered the $^S variable on perlvar and how it can be used to selectively disable die() in an eval{...} block:
    $SIG{__DIE__} = sub { return if $^S; # ignore die in an eval block ... };
    This appears to work the way I expect, but the %SIG and $^S sections in perlvar and the die section in perldoc has some dire sounding warnings about this. (e.g. "Having to even think about the $^S variable in your exception handlers is simply wrong. $SIG{__DIE__} as currently implemented invites grievous and difficult to track down errors."). I am inexperienced enough to be worried about this.

With all that being said, is there a better way to write a Log4perl wrapper for my script's existing die() calls?

Thank you for your time

Full sample code:

#!/usr/bin/perl use Log::Log4perl qw/get_logger :no_extra_logdie_message/; use strict; use warnings; use YAML qw/LoadFile/; CHECK { LOG_INIT: { # Initialize logging local $/ = undef; Log::Log4perl::init(\<DATA>); } LOG_WRAPPERS: { # Wrapper for logging fatal errors $SIG{__DIE__} = sub { #return if $^S; # ignore die in an eval block my ($message) = @_; chomp $message; # Get the actual caller for the "die" and not the wrapper local $Log::Log4perl::caller_depth; $Log::Log4perl::caller_depth++; my $log = get_logger(''); $log->logdie($message); }; } } #################### ## MAIN { #Can't locate Mo/builder.pm in @INC (@INC contains: C:/Perl/site/l +ib C:/Perl/lib .) at (eval 65) line 2, <$IN> line 1. my $yaml = LoadFile('./path/to/yaml/file.yml') or die "Could not p +arse config file:\n$!\n$^E"; eval { die "a hot potato"; }; # dies here if ($@) { print "I caught something:\n[$@]"; exit; } # this is nev +er reached } __DATA__ layout_class = Log::Log4perl::Layout::PatternLayout # Layout for screen logging # [Priority] [File::Caller] [Line Number] > Message debug_layout_pattern = [%p] [%F{1}::%M] [%L] > %m %n ## ## LOGGERS ## log4perl.logger = TRACE, LogScreen # Logging to screen # (Includes TRACE and DEBUG priority level messages) log4perl.appender.LogScreen = Log::Log4perl::Appender::Screen log4perl.appender.LogScreen.utf8 = 1 log4perl.appender.LogScreen.layout = ${layout_class} log4perl.appender.LogScreen.layout.ConversionPattern = ${debug_layout_ +pattern}

Replies are listed 'Best First'.
Re: YAML, $SIG{__DIE__}, Log4perl, $^S, and eval{}
by jellisii2 (Hermit) on Mar 21, 2014 at 11:58 UTC

    There's no need to die within $SIG{__DIE__}. Log at whatever level you wish (error, fatal, whatever), and let perl die properly. It's already going to anyway.

    How to fix your other problems that using $SIG{__DIE__} causes is another story entirely. I can't really see a way around it, as its working as intended. When you start noodling with that kind of stuff, where you selectively say "Oh, I didn't meant to actually die" I see this as a road to insanity. You're going to have to insert all of the logging statements anyway, unless your existing logging already kind of conforms to how Log::Log4perl does.

    That said, the $^S may be a "temporary" (haha) solution until you can get the code base to conform to something a bit more sane, along with not using logdie in $SIG{__DIE__}.

      I can't really see a way around it, as its working as intended. When you start noodling with that kind of stuff, where you selectively say "Oh, I didn't meant to actually die" I see this as a road to insanity.

      No argument here! I realized I may have been going down the wrong path when I had to start using conditional tests against the Perl interpreter state...

      That being said, the whole purpose of the $SIG{__DIE__} wrapper was to (try to) seamlessly add Log4perl output to an existing script without having to hunt down and change all the die() calls in the code.

Re: YAML, $SIG{__DIE__}, Log4perl, $^S, and eval{}
by Anonymous Monk on Mar 21, 2014 at 04:23 UTC
    How about adding
    use MyModuleWhichExports_die;
    where MyModuleWhichExports_die exports a custom sub die { } which does whatever you need it to do?

    Or how about replacing each die with LOGDIE from  use Log::Log4perl qw(:easy);

      How about adding use MyModuleWhichExports_die; where MyModuleWhichExports_die exports a custom sub die { } which does whatever you need it to do?

      Thanks for that. I did not realize you could overwrite Perl's built-in functions that way. This is pretty much what I am looking for.

      Quick (possibly dumb)question:
      Is it possible to inline MyModuleWhichExports_die in the main program and have it "export" the custom die() function?
      e.g.:

      package MyModuleWhichExports_die; # Something like Export's "@EXPORT" list here sub die { CORE::die "New and improved!: @_"; } package main; die "I want to be prefixed with 'New and improved'";

      I know I can do that by using the fully qualified name of my custom die() (MyModuleWhichExports_die::die), but that defeats the whole purpose of writing a wrapper so I don't have to change my original die() calls in the code

      (Sorry for the double reply, I can't seem to edit my first reply yet)
      Or how about replacing each die with LOGDIE from use Log::Log4perl qw(:easy);

      That is what I am trying to avoid. The whole raison d'être of this is to seamlessly add Log4perl to an existing script without having to go hunt down and change all the die() (and warn()) calls in the code.

        without having to go hunt down and change all the die() (and warn()) calls in the code.

        But you don't have to do that, you just have to add use whateveritwasthatexportsdie;; to the top of each module ... no search/replace

Re: YAML, $SIG{__DIE__}, Log4perl, $^S, and eval{}
by ateague (Monk) on Mar 23, 2014 at 21:17 UTC

    Here are the results of testing Anon and jellisii2's suggestions:
    (Sample code used for testing available if needed)

    1. Log4perl qw/:easy/;

      PROS:

      • Easy to implement.

      CONS:

      • (minor) Manual work needed to find and replace all die() calls with LOGDIE
      • Does not catch and log errors in external modules that do not use Log4perl
    2. Export custom die() in Logger.pm

      PROS:

      • Easy to implement.

      CONS:

      • (minor) Extra file to be distributed with script.
      • Does not catch and log errors in external modules that do not use Log4perl
      • Catches die() in an eval
    3. Wrap logging in custom $SIG{__DIE__} handler

      PROS:

      CONS:

    Any further suggestions on how I should proceed? I am leaning towards using $SIG{__DIE__} and then testing to see if anything breaks unexpectedly. If things do break, I plan on either installing the missing modules, or if ActiveState does not have them, localising $SIG{__DIE__} and wrapping the offending call in an eval.