Matthew
Matthew

Reputation: 6496

What's the best way to send an email if any error occurs in a Perl script?

I am working on a script that does many things, and if at any point during its execution a 'die' gets executed or an error occurs, I'd like to send an email message to myself.

I'm currently using the END block to check the exit status, and if it is greater than 0 call a error email subroutine, but I am wondering if there is a better way. The flaw with this current implementation is that if an error occurs in the email subroutine, an infinite loop will occur.

Upvotes: 2

Views: 3357

Answers (4)

Dave Sherohman
Dave Sherohman

Reputation: 46207

We've really got two separate questions here:

  1. How do you (reliably) execute code when the program dies without creating an infinite loop if this code also hits a fatal error?

  2. What's the best way to send email from Perl code?

So, to answer each separately:

  1. Rather than an END block, I would use a $SIG{__DIE__} handler (untested code, but should be right):

    $SIG{__DIE__} = \&error_handler;
    
    sub error_handler {
      die @_ if $^S;          # Do nothing if we died in an eval{}
      delete $SIG{__DIE__};   # Clear handler in case following code also dies
    
      print STDERR "Blue Wizard is about to die!\n";
    }
    
  2. There are, of course, many excellent mail-sending modules on CPAN. My personal preference from among them is MIME::Lite, which will send mail using sendmail by default, but, if you're on Windows or some other system with no sendmail command, you can use MIME::Lite->send("smtp"); to send via SMTP directly without going through a local MTA. There is also support for routing mail through an outgoing mail server (handy if your upstream provider blocks port 25!) and handling common authentication methods on that server if needed.

Upvotes: 4

caw
caw

Reputation: 421

My personal preference is the Net::SMTP perl module. Somewhat verbose but very full-featured.

 my $mail = Net::SMTP->new($smtp_host);
  $mail->mail("$login\@foo.com");
  $mail->to("$login\@foo.com");

  $mail->data();

  #Generate email headers
  $mail->datasend("From: Root <root\@foo.com>");
  $mail->datasend("Subject: Status");
  $mail->datasend("To: root\@foo.com");

  $mail->datasend("\n") # Need one newline between headers and body

  # Report data
  $mail->datasend("Mail body here");
  $mail->datasend("More mail body, repeat $mail->datasend() as needed");

  # Then close connection
  $mail->quit();

Upvotes: 1

mattexx
mattexx

Reputation: 6606

I use eval with sendmail (since it's almost always installed):

eval
{
    # do something
};
if ( $@ )
{

    my $ip = `ifconfig`
        or die "Can't ifconfig: $!\n";

    my @now = localtime;
    my $now = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $now[5]+1900, $now[4]+1, $now[3], $now[2], $now[1], $now[0] );

    open ( SENDMAIL, "|/usr/sbin/sendmail -oi -t -fyou\@yourdomain.com" )
        or die "Can't fork for sendmail: $!\n";

    print SENDMAIL << "EOF";
To: Your Name <you\@yourdomain.com>
Subject: ERROR: Exception

$now

$@

$ip
EOF

    close ( SENDMAIL )
        or warn "Sendmail didn't close nicely";

};

This will fix your infinite loop, but won't send an email in the unlikely event that the email block dies first. I think @jcomeau_ictx gets the bulletproof award.

I also have no idea how to do this in Windows. Hope this helps!

Upvotes: 3

jcomeau_ictx
jcomeau_ictx

Reputation: 38472

if you're on a Unix system, including (probably) Mac OS/X: ./myscript.pl 2>/tmp/myscript.pl.log || mail -s 'myscript.pl failed' [email protected] < /tmp/myscript.pl.log

on Windows I don't know.

Upvotes: 2

Related Questions