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

So before retirement, a former employee wrote a series of perl scripts that capture and analyze SNMP and other types of events within the intranet, sending important ones on to the operator console used by support staff. The line $EVmsg = sprintf($DIcas_text, @HR) ; rarely, but occasionally, produces the runtime error: Use of uninitialized value in sprintf at evmon.pl line 1194. What I would like to do is add some perl that would write to an error log, in this singular case, not only this error, but the values of $DIcas_text, @HR, and possibly a few other variables that would help us debug the situation. I'm not certain how best to intercept and augment the message with the additional information. This is perl5 revision 5 version 8 subversion 4, running on Linux. Can anyone point me to relevant tutorials, etc. on how to approach this situation? I don't want a lot (any?) extraneous output when there are no errors. Thank you

Replies are listed 'Best First'.
Re: Troubleshooting perl runtime errors
by BrowserUk (Patriarch) on Nov 16, 2016 at 14:52 UTC

    This shows the type of error you are getting:

    $e = sprintf( '%s', undef );; Use of uninitialized value in sprintf at (eval 28) line 1, <STDIN> lin +e 20.

    And this shows how to trap that error, get access to its text (as the first argument to the $SIG{__WARN__} pseudo-signal handler routine):

    eval{ local $SIG{__WARN__} = sub{ printf STDERR "Warning detected: %s", $_[0]; }; $e = sprintf( '%s', undef ); };; Warning detected: Use of uninitialized value in sprintf at (eval 29) l +ine 1, <STDIN> line 21.

    Substitute your log file handle for STDERR; and decorate or modify the output as you see fit.

    See perlvar (search for "__WARN__") for more information.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Troubleshooting perl runtime errors
by pryrt (Abbot) on Nov 16, 2016 at 15:13 UTC

    For logging, use something like Log::Any. In the line just before where the error is occuring, I would $log->debugf("DIcas_text='%s', HR=%s", \@HR); -- but that will add to your log whether or not there was an error.

    As for debugging the problem: see perldiag#Use of uninitialized value: something in your @HR is coming up as an undef.

    Armed with that knowledge, I might change the logging to something like what's used in the following SSCCE:

    use warnings; use strict; use Log::Any qw($log); use Log::Any::Adapter ('File', './debug.log'); sub errcheck_array { my $logger = shift; my $prefix = shift; my $count = grep {!defined} @_; # count the number of undefined el +ements in input array return unless $count; # this line will prevent a log mes +sage unless there's an undef; comment it out to log _all_ calls, whet +her or not there's an undefined value $logger->debugf("%s: %d undefined values found in %s", $prefix, $c +ount, \@_); 1; } foreach my $hr ( [qw(a b c)], [a => undef, b => undef, 'c'], [qw(x y z +)] ) { my @HR = @$hr; my $DIcas_text = '%s' . ',%s'x$#HR; errcheck_array($log, "sprintf('$DIcas_text', \@HR)", @HR); my $EVmsg = sprintf($DIcas_text, @HR); print "STD OUTPUT> $EVmsg\ +n"; }
      ... something in your @HR is coming up as an undef.

      I was going to write that a simple empty array could also produce an "... uninitialized value in printf ..." warning, but warnings apparently got a little bit smarter in version 5.12:

      c:\@Work\Perl>perl -wMstrict -le "print 'perl version ', $]; ;; my @HR; ;; printf qq{A: %s \n}, @HR; ;; ++$#HR; printf qq{B: %s \n}, @HR; " perl version 5.008009 Use of uninitialized value in printf at -e line 1. A: Use of uninitialized value in printf at -e line 1. B: perl version 5.010001 Use of uninitialized value in printf at -e line 1. A: Use of uninitialized value in printf at -e line 1. B: perl version 5.012003 Missing argument in printf at -e line 1. A: Use of uninitialized value in printf at -e line 1. B: perl version 5.014004 Missing argument in printf at -e line 1. A: Use of uninitialized value in printf at -e line 1. B:
      (See 'Missing argument in %s' in perldiag in version 5.12 and after.)

      Update: Posted this reply before seeing other, closely related replies: this from Monk::Thomas and this from hippo.


      Give a man a fish:  <%-{-{-{-<

        Yeah, with 5.8.x, the warnings are indistinguishable. And thanks for pointing out that the empty array will also create that error: my errcheck_array() didn't handle the empty array condition.

        Further, while both my method and the BrowserUk/stevieb method will help you find what's wrong, neither will help sanitize the input to prevent the warning in a live-system (for example, if you need your debug logging live until the unlikely data triggers it, but you still need the production code to set a reasonable value for $EVmsg): it's safer in production code to try to sanitize the input, to prevent such a condition from affecting your users. For that, you could wrap an error-checker-and-sanitizer around the sprintf command, or otherwise sanitize before running the sprintf. This example has a wrapper sprintf_prevent_error() and a sanitize() function which cleans and logs, but keeps the sprintf separate:

        use warnings; use strict; use Log::Any qw($log); use Log::Any::Adapter ('File', './debug.log', log_level => 'debug'); sub sprintf_prevent_error { my $format = shift; my @a = @_; # if there isn't a format, log an error message and return an erro +r string # might want to return undef instead, and check $EVmsg for undef + before blindly doing anything else with it... unless(defined $format) { $log->debugf("sprintf('%s',%s): format is not defined", $forma +t, \@_); return "<WARNING: sprintf format not defined>"; } # if there aren't any arguments, the array is undefined; log an er +ror message and return an error string # might want to return undef instead, and check $EVmsg for undef + before blindly doing anything else with it... unless(@a) { $log->debugf("sprintf('%s',%s): array is empty", $format, \@_) +; return "<WARNING: array is empty>"; } # count the number of undefined elements in input array, and log t +he error message # but _don't_ return; we will sanitize the inputs and continue t +o create the sprintf my $count = grep {!defined} @a; $log->debugf("sprintf('%s',%s): sanitizing %d undefined values", $ +format, \@_, $count) if $count; # sanitizes the input: replace any undefined elements with the emp +ty string $_//='' for @a; # return the final sprintf of the sanitized input sprintf $format, @a; } sub sanitize { $log->debugf("sanitize(%s): calling", \@_); unless(@_) { # log and return if there's nothing to sanitize $log->debugf("sanitize(%s): nothing to sanitize", \@_); return; }; my $count = grep {!defined} @_; # count the undefined argument +s $log->debugf("sprintf(%s): sanitizing %d undefined values", \@_, $ +count) if $count; $_//='' for @_; } foreach my $hr ( [qw(a b c)], [d => undef, e => undef, 'f'], [qw(x y z +)], [] ) { my @HR = @$hr; my $DIcas_text = @HR ? '%s' . ',%s'x$#HR : 'empty array format'; my $EVmsg = sprintf_prevent_error($DIcas_text, @HR); print "STD OUTPUT> $EVmsg\n"; $EVmsg = sprintf_prevent_error(undef, @HR); print "STD OUTPUT> $EVmsg\n"; sanitize($DIcas_text, @HR); $EVmsg = sprintf($DIcas_text, @HR); }
Re: Troubleshooting perl runtime errors
by stevieb (Canon) on Nov 16, 2016 at 15:31 UTC

    I'm going to bite on BrowserUK's post, and add a bit to it. Here, still using his technique, we create a hash reference with the variables we want to log if the warning contains uninitialized, and dump them all with their names and values to the log file.

    Since this appears to be happening only in one spot in your script, doing it this way is trivial. It's quite a bit more complex if you are seeing these things in numerous spots in a 1194+ line script.

    use warnings; use strict; use Data::Dumper; open my $log_fh, '>', 'log.txt' or die $!; my @HR = (1, 2, undef, 3); my $thing = undef; my $blah = 99; { local $SIG{__WARN__} = sub { if ($_[0] =~ /uninitialized/){ my $warn = { '@HR' => \@HR, '$thing' => $thing, '$blah' => $blah, }; print $log_fh Dumper $warn; print $log_fh "\n"; } }; my $x = sprintf $thing, @HR; }

    ...and this is what is logged:

    $VAR1 = { '$blah' => 99, '$thing' => undef, '@HR' => [ 1, 2, undef, 3 ] };
    update: typo fixes, removed eval. /update
Re: Troubleshooting perl runtime errors
by Monk::Thomas (Friar) on Nov 16, 2016 at 16:00 UTC

    Using perl 5.10 I get the 'Use of uninitialized value in sprintf at <script> line <line>.' only if $DIcas_text string specifies more values then @HR actually contains. If a value in @HR is undefined or the format string is undefined I get a different error message.

    #!/usr/bin/perl use strict; use warnings; my ($DIcas_text, @HR, $s); $DIcas_text = "%s %s %s %s"; @HR = qw(a b c); $s = sprintf($DIcas_text, @HR); $DIcas_text = "%s %s %s"; @HR = ('a', undef, 'c'); $s = sprintf($DIcas_text, @HR); $DIcas_text = undef; @HR = qw(a b c); $s = sprintf($DIcas_text, @HR);

    output

    Use of uninitialized value in sprintf at /tmp/u.pl line 10. Use of uninitialized value $HR[1] in sprintf at /tmp/u.pl line 14. Use of uninitialized value $DIcas_text in sprintf at /tmp/u.pl line 18 +.

    If you get something similar with Perl 5.8 then this might help to figure out what exactly is wrong.

      If you get something similar with Perl 5.8 then this might help to figure out what exactly is wrong.

      Alas not. The extra data you see is missing from 5.8.8:

      $ perl -v This is perl, v5.8.8 built for x86_64-linux-thread-multi Copyright 1987-2006, Larry Wall Perl may be copied only under the terms of either the Artistic License + or the GNU General Public License, which may be found in the Perl 5 source ki +t. Complete documentation for Perl, including FAQ lists, should be found +on this system using "man perl" or "perldoc perl". If you have access to + the Internet, point your browser at http://www.perl.org/, the Perl Home Pa +ge. $ cat uninit.pl #!/usr/bin/perl use strict; use warnings; my ($DIcas_text, @HR, $s); $DIcas_text = "%s %s %s %s"; @HR = qw(a b c); $s = sprintf($DIcas_text, @HR); $DIcas_text = "%s %s %s"; @HR = ('a', undef, 'c'); $s = sprintf($DIcas_text, @HR); $DIcas_text = undef; @HR = qw(a b c); $s = sprintf($DIcas_text, @HR); $ ./uninit.pl Use of uninitialized value in sprintf at ./uninit.pl line 10. Use of uninitialized value in sprintf at ./uninit.pl line 14. Use of uninitialized value in sprintf at ./uninit.pl line 18.
        That's a pity. Thanks for verifying!
Re: Troubleshooting perl runtime errors
by lvirden (Novice) on Nov 16, 2016 at 19:20 UTC
    All of these suggestions are great. I will have to study them to determine the best general answer. As I mentioned, the script is dealing with all sorts of events streaming from our thousands of devices. New devices in particular stream in SNMP that may not be patterned like the rest of the inputs. I have to study the input more to determine how best to sanitize/normalize/ensure that all the required fields are present with expected values, etc. I really appreciate all your help! Thank you and have a wonderful day.