How to capture and save warnings in Perl

On one hand it is recommended to have warnings turned on in every Perl script and module,
on the other hand you don't want your customers to see warnings from Perl.

On one hand you want to use warnings at the beginning of the code and have your safety
net

On the other hand, usually showing up on their screen.
In most cases they won't know what to do with them. If you are lucky those would just frighten them.
If you are unlucky they'd try to fix them... (and I am not thinking about other Perl programmers now.)

On the third hand (is this an octopus or what?) you would like to save the warnings for later analysis.

Furthermore, there are lots of Perl scripts and applications in various places that neither use warnings
nor have -w on the sh-bang line. Adding use warnings will likely generate lots of warnings.

The long term solution is, of course, to eliminate all the warnings but
what do you do in the short term?

Even in the long term, just as you cannot make totally bug-free code, you cannot ensure
that your application will never print a warning.

Or can you?

You can catch the warnings before they are printed to the screen.

Signals

Perl has a built-in hash called %SIG, in which the keys are the names of
the signals available in you operating system. The values are subroutines
(more specifically references to subroutines), that will be called when
the specific signal arrives.

In addition to the standard signals of your operating system Perl added
two internal "signals". One of them is called __WARN__ and is triggered
every time some code calls the warn() function.
The other one is called __DIE__ and it is triggered when die()
is called.

In this article we are going to see how this works for warnings.

Anonymous subroutines

sub { } is an anonymous subroutine, that is, a subroutine that does not have a name
but it has a body. (In this example even the body, the block, is empty but I hope you
get the point.)

Capture warnings - do nothing

If you added code like this:

local $SIG{__WARN__} = sub {
# here we get the warning
};

You effectively said that every time there is a waning anywhere in the code - don't do anything.
Basically you hide all the warnings.

Capture warnings - and turn them into exceptions

You could also write:

local $SIG{__WARN__} = sub {
die;
};

Which would call die() whenever a warning happened.
Which means you turn every warning into an exception.

If you also wanted to keep the warning message in that exception you could write:

local $SIG{__WARN__} = sub {
my $message = shift;
die $message;
};

the actual warning message is passed as the first (and only) parameter to the
anonymous function.

Capture warnings - and log them

Where we assume that logger() is your implementation of a, well logger.

Logging

Hopefully your application already has a logging mechanism. If not, this might be a good
reason to add one. Even if you cannot add one, you might be able to use the built-in
logging mechanism of your operating system. That means syslog on Linux and
Event Logger on MS Windows. I am sure other
OS-es have their own built-in logging mechanism too.

In our example we use a simple home made logger() function just to represent
the idea.

Use of uninitialized value in addition (+) at code_with_warnings.pl line 14.

The $counter variable and the count() subroutine are just part of a simple
example that will generate a warning.

Warning in the warn handle

One lucky aspect of __WARN__ is that when the code in the __WARN__ handle
is executed it is automatically disabled. So warnings in a warn handle
won't cause an infinite loop.

You can read the details of __WARN__ in perldoc perlvar.

Avoid multiple warnings

There is a concern of having the same warning printed very often filling
the log-file with a lot of repeated lines. We can easily reduce the
number of copies of the same warning by a simple use of a cache-like feature.

Before calling the logger we check if the current string is in the %WARNS hash.
If it isn't, we add it and then call logger(). If it was already there, we just call return
and don't log the event a second time.

What is this local?

In all the examples you saw, I used the local function to
localize the effect. Strictly speaking we did not need that in these
examples as we were assuming the above code would be the first thing
in your main script. In that case it does not matter as you are still
in a global scope.

Yet, better to get used to it. (thanks to Peter Rabbitson for reminding me)

The local is important to limit the effect of our change if you are
using this code within a module. Especially if you distribute it.
Without the localization the effect would be felt in all the application.
The local limits it to the enclosing block.

Avoiding the global %WARNS hash

If you are using Perl 5.10 or newer you can improve that code and eliminate the
global %WARNS. In order for that to work you have to write use v5.10;
at the beginning of your script and then you can use the state keyword
inside the anonymous subroutine to declare the variable.