in reply to Graceful handling of Log::Log4perl errors

I think your use of eval to implement a fail safe is a good way to go. One thing you could add to that is trying to figure out what directory it wants to use and then create it.

eval { bork_bork_bork }; if ( $@ =~ m{ \A Can't \s open \s ( .+ ) / ( [^/]+ ) \s }xms ) { my ( $missing_dir, $log_filename ) = ( $1, $2 ); if ( ! -d $missing_dir ) { # try to make the directory } }

If you manage to get the broken config working, the first thing you'll want to do is log that.

Replies are listed 'Best First'.
Re^2: Graceful handling of Log::Log4perl errors
by chaos_cat (Scribe) on May 28, 2008 at 21:57 UTC
    Thanks for the suggestion. I built the following code based on your idea, which seems to do the trick. It makes repeated attempts to load the config file and correct for missing path errors. If it encounters an error it doesn't recognize, or if it can't get a path created, it gives up and loads a blank config file (which it creates then and there to be sure it's blank). So far, it's passed all my tests but if anyone sees something broken here please drop me a note.
    my %paths = (); my $init_fail = 0; INIT: while (1) { eval {Log::Log4perl->init_once($LOGGER_CONF_FILE)}; if ($@) { # Config file error of some kind # Send some alert message here # if we get a missing directory error, try some (sane) recover +y if ($@ =~ m{Can't\sopen\s/my/base/path/(.+)\s\(No such file or + directory\)}) { my $path = $1; if ( $paths{$path}++ ) { # only try once to create a give +n path. This prevents infinite loops here $init_fail = 1; last INIT; } my $dir = "/my/base/path"; # if the base path isn't th +ere, creating it is above this module's pay grade my @dir_list = split '/', $path; my $file = pop @dir_list; foreach my $sub_dir (@dir_list) { $dir .= "/$sub_dir"; if (! -d$dir) { eval {mkdir ($dir)}; # not too concerned with catc +hing errors here, the next round will bail on duplicate path attempt } } } # for other config file errors, throw our hands up in disgust +and move on else { $init_fail = 1; last INIT; } } else { $init_fail = 0; last INIT; } } # if we couldn't get the config file to initlize, load a blank one and + move on without logging if ($init_fail) { warn "Couldn't initilize logger, defaulting to blank config file"; my $default_config_file = "/my/base/path/etc/default.logger.conf"; open (my $fh, ">", $default_config_file); close ($fh); Log::Log4perl::init_once($default_config_file); }

      That looks pretty good! I'm gratified that you took my suggestion and ran with it. Thanks for sharing it with us! Looking at what you have, I think it could be made a bit more compact.

      use File::Path qw( mkpath ); my $base_path = '/my/base/path'; my $init_done = 0; my %paths = (); INIT: while ( ! $init_done ) { $init_done = eval { Log::Log4perl->init_once($LOGGER_CONF_FILE); 1 }; # If there's an error, and it's THIS one, if ( $@ =~ m{\A Can't \s open \s \Q$base_path\E (?: / ( \S+ ) )? / [^/]+ \s \( No \s such \s file \s or \s directory \)}xms ) { # This is the path that's missing my $target_path = "$base_path/$1"; # If we've already tried to create it once, don't try again if ( $paths{ $target_path }++ ) { last INIT; } # Try to create the path eval { mkpath( $target_path ) }; if ( ! -d $target_path ) { warn "Can't create missing directory '$target_path': $@\n" +; } } } # if we couldn't get the config file to initlize, load a blank one and + move on without logging if ( ! $init_done ) { warn "Couldn't initilize logger, defaulting to blank config file"; Log::Log4perl->init( \'' ); }

      Note the differences, though (good and bad).

      • I use File::Path::mkpath instead of just mkdir. This will try to create every necessary directory right up to the root. Yours won't try to create anything above /my/base/path. If attempting to create directories above /my/base/path will be a problem, you should go back to your own code.
      • Instead of tracking $init_fail, I track $init_done. This way the while condition is more natural (and doesn't look infinite), and I get to set it in only one place inside the loop.
      • I use Log::Log4perl->init with an empty string instead of an empty file I just created. It's shorter, and I don't run the risk of failing to create the empty file.
      • I re-complicated the regular expression (in particular adding the /x form back).
      • I haven't tested this. There could even be a syntax error.

      Thanks again for posting your work.

        I incorporated a few of your changes. In particular, I like the $init_done mechanic; it's much cleaner than what I was doing.

        my %paths = (); my $init_done = 0; my $base_path = "/my/base/path/"; INIT: while ( !$init_done ) { $init_done = eval { Log::Log4perl->init($LOGGER_CONF_FILE); 1; }; if ($@) { warn "Logger error was: $@"; # if we get a missing directory error, try some (sane) recover +y if ( $@ =~ m{Can't\sopen\s$base_path(.+)/[^/]+\s\(No such file + or directory\)} ) { my $path = $1; $path = $base_path . $path; warn "log directory $path does not exist, attempting to cr +eate it"; if ( $paths{$path}++ ) { warn "alredy tried to create this path"; last INIT; } eval { mkpath($path) }; warn "unable to create path $path" if ( !-d $path ); } # for other config file errors, throw our hands up in disgust +and move on else { last INIT; } } } # if we couldn't get the config file to initlize, load a blank one and + move on without logging if ( ! $init_done ) { warn "Couldn't initilize logger, defaulting to blank config file"; Log::Log4perl::init(\''); # this will throw a warning, but that's +not really a bad thing }

        One thing I learned while doing this: Log4perl sets its initialized flag at the beginning of its initialization, not at the end (on line 113 of Log::Log4perl::Config, in _init, on my install at least), so using init_once here is problematic, as it will not reload after the directory is created. I switched over to using init and relying on the $init_done to guard against unintentional reinitialization. Once I did that, it was necessary to put back in the two level error message check (the else last bit), otherwise it infinite loops because it is dieing on an unrecognized message.

        Thanks for all your help!