<!-- category: hidden-gems -->
$SIG{WARN} - Intercepting Every Warning
Perl warnings are useful. Until there are 400 of them scrolling past while you try to find the one that matters.One assignment. Every$SIG{__WARN__} = sub { my $msg = shift; # you now control every warning in the program };
warn call, every internal Perl warning,
every "use of uninitialized value" message now passes through
your handler before it goes anywhere.
You can timestamp them. Redirect them to a log file. Filter the noise. Promote the important ones to fatal errors. Count them and summarize at the end. Whatever you need.
Part 1: THE BASIC HOOK
The$SIG{__WARN__} handler intercepts all warnings before they
reach STDERR:
The handler receives the full warning message as its first argument, including the "at file line N" suffix that Perl appends.$SIG{__WARN__} = sub { my $msg = shift; print STDERR "CAUGHT: $msg"; }; warn "something happened"; # Output: CAUGHT: something happened at script.pl line 7.
If your handler does nothing (empty sub), warnings are silently swallowed:
This is a terrible idea in production. But sometimes during development, when you are testing one specific thing and 300 deprecation warnings are drowning the output, it buys you five minutes of peace.$SIG{__WARN__} = sub { }; # silence everything
Part 2: ADDING TIMESTAMPS
The single most useful thing you can do with a warn handler is add timestamps:Theuse POSIX qw(strftime); $SIG{__WARN__} = sub { my $msg = shift; chomp $msg; my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime); print STDERR "[$ts] $msg\n"; }; warn "disk space low"; # Output: [2026-04-19 14:30:00] disk space low
chomp removes Perl's trailing newline, then we add our own
formatted line with the timestamp prefix. Every warning in the
entire program now gets a timestamp.
For long-running daemons, this is essential. Without timestamps, your log is just a pile of messages with no way to correlate them to events.
Part 3: REDIRECTING TO A LOG FILE
Send warnings to a file instead of STDERR:Notice the fallback. If the log file cannot be opened (permissions, disk full, directory missing), we fall back to STDERR instead of silently losing the warning. Never lose warnings. That is the whole point of having them.my $log_file = '/var/log/myapp/warnings.log'; $SIG{__WARN__} = sub { my $msg = shift; chomp $msg; if (open my $fh, '>>', $log_file) { my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime); print $fh "[$ts] $msg\n"; close $fh; } else { # fallback to STDERR if we can't open the log print STDERR $msg, "\n"; } };
The open-write-close pattern on every warning is intentional. It keeps the filehandle from staying open between warnings, which matters if your log rotator needs to move the file.
Part 4: FILTERING SPECIFIC WARNINGS
Some warnings are noise. You know about them. You have decided they are acceptable. Filter them out:Wait. That last line calls$SIG{__WARN__} = sub { my $msg = shift; # suppress specific known warnings return if $msg =~ m~Subroutine .+ redefined~; return if $msg =~ m~Use of uninitialized value in concatenation~; return if $msg =~ m~Wide character in print~; # let everything else through warn $msg; # NO! infinite loop! };
warn inside a $SIG{__WARN__}
handler. That triggers the handler again. Infinite loop. Perl
actually detects this and prints the warning directly to STDERR
on the second call, but it is still sloppy.
The correct way:
Use$SIG{__WARN__} = sub { my $msg = shift; return if $msg =~ m~Subroutine .+ redefined~; return if $msg =~ m~Use of uninitialized value in concatenation~; # print directly to STDERR, do NOT call warn print STDERR $msg; };
print STDERR inside the handler, not warn. Direct output.
No recursion.
Part 5: PROMOTING WARNINGS TO FATAL
Sometimes a warning is not just a warning. It is a bug waiting to happen:Now any use of an uninitialized variable kills the program immediately. Harsh? Yes. But if your code should never operate on undefined values, this catches the bug at the exact point it happens instead of letting it silently produce garbage.$SIG{__WARN__} = sub { my $msg = shift; # these warnings are bugs, not warnings if ($msg =~ m~Use of uninitialized value~) { die "FATAL (promoted from warning): $msg"; } # everything else stays a warning print STDERR $msg; };
You can also use use warnings FATAL => 'all' to make ALL
warnings fatal, but that is a blunt instrument. The
$SIG{__WARN__} handler lets you pick exactly which categories
get promoted.
# promote specific categories, leave others as warnings $SIG{__WARN__} = sub { my $msg = shift; my @fatal_patterns = ( qr~Use of uninitialized value~, qr~Argument .+ isn't numeric~, qr~Possible attempt to separate words~, ); for my $pat (@fatal_patterns) { if ($msg =~ $pat) { die "PROMOTED: $msg"; } } print STDERR $msg; };
Part 6: COUNTING WARNINGS
Track how many warnings fire and report at exit:Now at the end of your program, you get a summary:my %warn_counts; $SIG{__WARN__} = sub { my $msg = shift; chomp $msg; # strip the "at file line N" to group duplicates (my $key = $msg) =~ s~ at \S+ line \d+\.?$~~; $warn_counts{$key}++; print STDERR "$msg\n"; }; END { if (%warn_counts) { print STDERR "\n--- Warning Summary ---\n"; for my $msg (sort { $warn_counts{$b} <=> $warn_counts{$a} } keys %warn_counts) { printf STDERR " %4d x %s\n", $warn_counts{$msg}, $msg; } my $total = 0; $total += $_ for values %warn_counts; print STDERR " Total: $total warnings\n"; } }
Immediately tells you where the real problems are. The 142 uninitialized values are the priority. The 3 redefinitions are probably harmless.--- Warning Summary --- 142 x Use of uninitialized value $name in concatenation 37 x Argument "N/A" isn't numeric in addition 3 x Subroutine process_row redefined Total: 182 warnings
Part 7: CATEGORY-BASED HANDLING
Perl warnings have categories. You can detect them in the handler and route accordingly:Different warning types go to different files. Deprecation warnings pile up in their own log where you can review them at your leisure. Uninitialized value warnings get their own file for focused debugging. Redefinitions are silenced because you know about them already.use warnings; $SIG{__WARN__} = sub { my $msg = shift; if ($msg =~ m~uninitialized~) { log_to_file('undef.log', $msg); } elsif ($msg =~ m~deprecated~i) { log_to_file('deprecations.log', $msg); } elsif ($msg =~ m~redefine~i) { # silently ignore redefinition warnings return; } else { # everything else goes to STDERR print STDERR $msg; } }; sub log_to_file { my ($file, $msg) = @_; if (open my $fh, '>>', "/var/log/myapp/$file") { print $fh $msg; close $fh; } }
Part 8: LOCAL SCOPE WITH LOCAL
You can temporarily replace the warn handler for a specific block usinglocal:
The# global handler $SIG{__WARN__} = sub { print STDERR "GLOBAL: @_"; }; warn "this uses the global handler"; { local $SIG{__WARN__} = sub { print STDERR "LOCAL: @_"; }; warn "this uses the local handler"; some_function(); # warnings from here also use the local handler } warn "back to the global handler";
local keyword saves the current handler and restores it
when the block exits. This is perfect for wrapping a noisy
library call:
The handler is dynamically scoped, not lexically scoped. That means it affects all code called from within the block, not just code written inside it. If{ # silence warnings from a chatty module local $SIG{__WARN__} = sub { }; NoisyModule::do_stuff(); } # warnings are back to normal here
NoisyModule::do_stuff() calls ten
other functions, they all see the silenced handler.
Part 9: PRACTICAL DEBUGGING
A debugging handler that includes a stack trace with every warning:Now instead of:use Carp qw(longmess); $SIG{__WARN__} = sub { my $msg = shift; chomp $msg; my $trace = longmess("WARNING: $msg"); print STDERR $trace; };
You get:Use of uninitialized value in addition at process.pl line 42.
Now you know the full call chain. Line 42 is where the warning fired, but the real bug might be at line 28 whereWARNING: Use of uninitialized value in addition at process.pl line 42. at process.pl line 42. main::calculate_total(undef) called at process.pl line 28 main::process_row('HASH(0x...)') called at process.pl line 15 main::main() called at process.pl line 8
undef was
passed in, or at line 15 where the data was loaded.
A toned-down version that only adds the immediate caller:
$SIG{__WARN__} = sub { my $msg = shift; chomp $msg; my ($pkg, $file, $line) = caller(1); if (defined $file) { print STDERR "$msg (called from $file:$line)\n"; } else { print STDERR "$msg\n"; } };
Part 10: THE COMPLETE PATTERN
Putting it all together. A production-grade warning handler:Filters, timestamps, file logging, counting, and a summary at exit. All from one signal handler.#!/usr/bin/env perl use strict; use warnings; use feature 'say'; use POSIX qw(strftime); # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Warning handler configuration # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ my $log_dir = '/var/log/myapp'; my %warn_counts; my @suppress = ( qr~Subroutine .+ redefined~, ); my @fatal = ( qr~Attempt to free unreferenced scalar~, ); $SIG{__WARN__} = sub { my $msg = shift; chomp $msg; # suppress known noise for my $pat (@suppress) { return if $msg =~ $pat; } # promote critical warnings to fatal for my $pat (@fatal) { die "FATAL (promoted): $msg\n" if $msg =~ $pat; } # count for summary (my $key = $msg) =~ s~ at \S+ line \d+\.?$~~; $warn_counts{$key}++; # log with timestamp my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime); my $line = "[$ts] $msg\n"; if (open my $fh, '>>', "$log_dir/warnings.log") { print $fh $line; close $fh; } print STDERR $line; }; END { return unless %warn_counts; print STDERR "\n--- Warning Summary ---\n"; for my $msg (sort { $warn_counts{$b} <=> $warn_counts{$a} } keys %warn_counts) { printf STDERR " %4d x %s\n", $warn_counts{$msg}, $msg; } }
Perl does not hide warnings from you. It gives you a hook and says "do whatever you want with these." Most people ignore the hook and let warnings fly to STDERR unprocessed..--. |o_o | "Your warnings work |:_/ | for you now. // \ \ Not against you." (| | ) /'\_ _/`\ \___)=(___/
That works fine for small scripts. For anything that runs in
production, for anything that generates more than a screenful of
output, you need control. $SIG{__WARN__} is that control.
Intercept. Filter. Route. Count. Promote. Silence. Whatever the situation demands. The warning system is not just a firehose you point at STDERR. It is a programmable event stream.
Treat it like one.
perl.gg