Capture warning information, exception information and write log details in Perl

While it is recommended to enable warnings in every Perl script and module, you don't want users to see warnings from Perl.

On the one hand you want to use use warnings at the front of your code as your safety net, on the other hand, usually the warnings will appear on the screen. In most cases, customers do not know how to deal with these warnings. If you're lucky these warnings are just a surprise to the client, of course, unfortunately they try to fix them... (Not talking about Perl programmers here.)

Thirdly, you may want to save these warnings for later analysis.

Also, there are many Perl scripts and applications that don't use use warnings or use -w on the #! line in many places. Adding use warnings may generate a large number of warnings.

In the long run, of course, the warnings are to be removed, but what about the short term?

Even with a long-term plan, you can't write completely bug-free code, and you can't ensure that your application will never print warning messages in the future.

can you

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

Signals
Perl has a built-in hash table called %SIG, where the keys are the names of operating system signals. The corresponding values ​​are functions (mostly function references) that will be called when a particular signal fires.

In addition to the standard signals provided by the system, Perl adds two internal "signals". One of them is <h__warn__<span="">, which fires every time the code calls the warn() function. The other is __DIE__, which fires every time die() is called.

In this article, we'll see how these affect warning messages.

anonymous function

sub { } is an anonymous function, that is, a function with only a function body but no name. (The function body is also empty in this example, but I hope you get my point.)

Catch warnings -- don't handle

If you add the following code:

The copy code code is as follows:


  local $SIG{__WARN__} = sub {      # warning message can be obtained here   };

This effectively means that every time a warning message is generated somewhere in the program, do nothing. Basically, this hides all warnings.

Catch warnings -- and turn them into exceptions

You could also write:

The copy code code is as follows:


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


This will call die() every time a warning is generated, that is, convert each warning into an exception.

If you want to include warnings in exceptions, you can write:

The copy code code is as follows:


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

The actual warning message is passed to the anonymous function as the only argument.

Catch warnings -- and write to logs
You might want to do something else in between:

Filter noisy warning messages for later analysis:

The copy code code is as follows:


  local $SIG{__WARN__} = sub {
    my $message = shift;
    logger($message);
  };


Here we assume that logger() is the logging function you implemented.

write log

Suppose your application already has a logging mechanism. If not, it's best to add it. Even if you can't add it, you need the OS's built-in logging mechanism. For example, syslog of Linux, Event Logger of MS Windows, and other operating systems also have their internal log mechanisms.

In the examples in this article, we use a homemade logger() function to represent this idea.

Complete example of capturing and writing logs

The copy code code is as follows:


  #!/usr/bin/perl
  use strict;
  use warnings;
 
  local $SIG{__WARN__} = sub {
    my $message = shift;
    logger('warning', $message);
  };
 
  my $counter;
  count();
  print "$counter\n";
  sub count {
    $counter = $counter + 42;
  }
 
 
  sub logger {
    my ($level, $msg) = @_;
    if (open my $out, '>>', 'log.txt') {
        chomp $msg;
        print $out "$level - $msg\n";
    }
  }

The above code will add the following line to the log.txt file:

The copy code code is as follows:


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


The variable $counter and the function count() are just some of the examples that generate warnings.

Warning message in warning handler function

__WARN__ is automatically disabled during the execution of its handler. So the (new) warning message generated during the execution of the warning handler function will not cause an infinite loop.

You can read more details in the perlvar documentation.

Avoid multiple warnings

Note that repeated warning messages may flood the log file. I can use a simple caching-like feature to reduce the number of duplicate warning messages.

The copy code code is as follows:


#!/usr/bin/perl
  use strict;
  use warnings;
 
 
  my %WARNS;
  local $SIG{__WARN__} = sub {
      my $message = shift;
      return if $WARNS{$message}++;
      logger('warning', $message);
  };
 
  my $counter;
  count();
  print "$counter\n";
  $counter = undef;
  count();
 
  sub count {
    $counter = $counter + 42;
  }
 
  sub logger {
    my ($level, $msg) = @_;
    if (open my $out, '>>', 'log.txt') {
        chomp $msg;
        print $out "$level - $msg\n";
    }
  }

As you can see, we assign the $counter variable to undef and then call the count() function again to generate the same warning.

We also replace the __WARN__ handler with a slightly more complex version:

The copy code code is as follows:


  my %WARNS;
  local $SIG{__WARN__} = sub {
      my $message = shift;
      return if $WARNS{$message}++;
      logger('warning', $message);
  };


Before calling the logger, it will check whether the current string is already in the %WARNShash table. If not, it is added and logger() is called. If there is already, call return and not record the same event twice.

You may recall that we used the same idea with unique values ​​in an array.

What is local?
In all the examples above, I use the local function to localize (alert handling) effects. Strictly speaking, we don't need to do this in these examples, since this code is assumed to be the first part of the main script. In this case, it doesn't matter, after all, it is in the global scope.

However, it is best to use it this way.

local is important for limiting (for warnings) changes within a module. Especially modules to be released. If not localized, it affects the entire application. limit will limit the impact to the enclosing code block where it is located.

Avoid using global %WARNS

If you are using Perl 5.10 or newer, you can rewrite the code to replace the global variable %WARNS. To do this, use use v5.10; at the beginning of the script, and then use the state keyword inside the anonymous function to declare the variable.

The copy code code is as follows:


  #!/usr/bin/perl
  use strict;
  use warnings;
 
  use v5.10;
 
  local $SIG{__WARN__} = sub {
      state %WARNS;
      my $message = shift;
      return if $WARNS{$message}++;
      logger('warning', $message);
  };

Guess you like

Origin blog.csdn.net/jh035512/article/details/128141758