Parsing help in Perl - perl

My (existing) perl files creates a Log file using Log4Perl in the following format
[2011-11-21 08:50:22,406] States_Sync INFO Logger.pm:33 script starts
[2011-11-21 08:50:22,610] States_Sync ERROR Logger.pm:36 Error occurred ....
[2011-11-21 08:50:22,406] States_Sync INFO Logger.pm:33 ...
[2011-11-21 08:50:22,610] States_Sync ERROR Logger.pm:36 Error occurred ....
[2011-11-21 08:50:22,406] States_Sync INFO Logger.pm:33 ...
[2011-11-21 08:50:22,610] States_Sync ERROR Logger.pm:36 Error occurred ....
The above is only an example of my log file. I use the following formatter
$layout = Log::Log4perl::Layout::PatternLayout->new("[%d{ISO8601}] %c %p %F{1}:%L %m%n");
Currently I have to send an email in case of error.
Instead of modifying the existing script, I thought of parsing the generated log files only for Error and send all the messages related to "Error" from the log file as email
Is there any easy way of parsing the log file ?
Regards,
Karthik

use grep(1):
grep ERROR log.file
or use perl:
perl -ne 'print if /ERROR/' log.file

I might try this:
if ( /^[^:]+?\s+ERROR\s/ ) {
# pull fields
# send email
}
Try that one out and see if it gives you too many lines. More elaborate version might be:
if ( /^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}\]\s+([\w\s])?\s+ERROR\s+/ ) {
but only if the data was more complicated.

Since you're using log4perl, you could use the built-in methods there to check for an ERROR (or above) and send an e-mail based on that.
if ($layout->is_error()){
# Put e-mail logic here
}
And if you wanted just error information and not warn, info, debug or trace you can do the following:
if($layout->is_error() && ! $layout->is_warn()){
# Put e-mail logic here
}

You did not specify how often do you have to check the log file and send the e-mails. If it once a day then the solution provided by tadmc would be your best bet.
However, if you would like to automate it and send an e-mail after a specific interval of time (see maxinterval) for each new ERROR entry encountered, you may want to check the following.
NOTE #1 Adjust the interval and maxinterval as per your requirements to not flood people with e-mails
NOTE #2 Run it in accordance to your log rotate intervals
#!/usr/bin/perl
use strict;
use warnings;
use File::Tail;
my #logs_to_email;
my $log_file = "file.log";
my $error_pattern = qr(^\[.*?\]\s*States_Sync\s*ERROR);
my $tail = File::Tail->new(
name => $log_file,
maxinterval => 60,
interval => 10,
adjustafter => 10,
);
while (defined (my $line = $tail->read)) {
chomp $line;
next if $line =~ /^\s*$/;
next unless $line =~ $error_pattern;
push #logs_to_email, $line;
##
## put e-mail logic to send
## #logs_to_email here
##
}

Related

Trying to print part of an array and replace another string in the same array in Perl

I am new to Perl and am trying to write a script to do the following.
I have an array, basically its an output of a command.
BIP: Message flow 'Message Flow name' on execution group 'EG' is running.(There are bunch of similar lines)
So I am using foreach $_(#array name) to read each line.
Now I just want the Message Flow name from the array and want to change running to started. and get that in the OP too.
So, I want to get:
Message Flow Name,started as my OP.
Can you please help?
I tried splice, split but no use.
Thanks in advance.
Try this
#!/usr/bin/perl
use strict;
use warnings;
my #messages = ("BIP: Message flow 'Name1' on execution group 'EG' is running", "BIP: Message flow 'Name2' on execution group 'EG' is running");
for (#messages) {
if (/BIP: Message flow '(.*?)' .* running/) {
print("$1 started as my OP\n");
}
}

Can't call method "id" on an undefined value

recently my hosting company updated their servers, and i believe my code's inefficiency is being brought to light.
i have this code:
#!/usr/bin/perl
use CGI qw(:standard);
use CGI::Session;
$cgi = new CGI;
$sid = $cgi->cookie("CGISESSID") || undef;
$session = new CGI::Session(undef, $sid, {Directory=>'/tmp'});
$cookie = $cgi->cookie(CGISESSID => $session->id);
$sSid = $session->id();
print $cgi->header(-cookie=>$cookie);
print << "EOF";
HTML CODE
EOF
this was fine until (it seems) the update - now i'm getting this error in my logs:
Can't call method "id" on an undefined value at /home/users/web/XXXX/my-file.cgi line 10.
i then edited this into line 10:
$cookie = $cgi->cookie(CGISESSID => $session->id) || undef;
figuring that it would take care of the issue, but it didn't.
it seems to be causing a brief 500 server error, and then the site comes back online, with the session id persisting through the error.
i'm completely lost as to what is happening. what's worse is it isn't happening every time the page loads - it seems so random but i'm sure i'm just not encountering the specific situation to bring that error.
i've been using the same setup for all my cgi's for the past 7 years. not sure why it's causing a brief error now, but that's besides the point.
$session is undef, so that means new returned undef. The docs have the following to say about that:
Returns new session object, or undef on failure. Error message is accessible through errstr() - class method.
So call CGI::Session->errstr to find out what the problem is.

perl log db query errors into a log file

So I started to get familiar with Perl and I wrote my first Db script.
Now I am trying to select data from atable which is huge and trying to insert into a summary table based on some criteria.
Now there are chances , that select query may fail or the insert query may fail due to timeout or other database issues that is beyond my control.
Eventually my script is going to be cron script.
Can I log just the errors that i encounter for the connection,inserts and selects into a file generated in the script?
$logfile = $path.'logs/$currdate.log';
here is my code:
my $SQL_handled="SELECT division_id,region_id, NVL(COUNT(*),0) FROM super_tab GROUP BY division_id,region_id;";
my $result_handled = $dbh->prepare($SQL_handled);
$result_handled->execute();
while (my ($division_id,$region_id,$count ) = $result_handled->fetchrow_array()){
my $InsertHandled="INSERT INTO summary_tab (date_hour, division_id, region_id,volume) VALUES ('$current',$division_id,$region_id,$market_id,'$service_type','$handled',$count);";
my $result_insert_handled = $dbh->prepare($InsertHandled);
$result_insert_handled->execute();
}
something like
if(DBI-query failed ) {
// log the error onto the above logpath
}
Its usually done like this
my $SQL_handled="SELECT division_id,region_id, NVL(COUNT(*),0) FROM super_tab GROUP BY division_id,region_id;";
my $result_handled = $dbh->prepare($SQL_handled);
my $retval = $result_handled->execute();
if(!$retval){
#open a log file and write errors
writelog();
die "Error executing SQL SELECT - $dbh->errstr";
}
while(my ($division_id,$region_id,$count ) = $result_handled->fetchrow_array()){....
}
---------------------------------
sub writelog{
my $path = "/path/to/logfile";
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$year += 1900;
$mon++;
my $currdate = "$mon$mday$year";
$logfile = $path . "/$currdate.log";
open (OUT, ">>$logfile");
print OUT "There was an error encountered while executing SQL- $dbh->errstr \n";
close(OUT);
}
You can also use $dbh->err; which returns the native Oracle error code to trap the error and exit accordingly.
The above, basic exception handling can be performed for every execute() method call in your script. Remember, DBI will have AutoCommit set to 1 (enabled) by default, unless explicitly disabled. So your transactions would be auto committed per insert, in order to handle the ATOMICITY of the entire transaction, you can disable autocommit and use $dbh->commit and $dbh->rollback to handle when you want to commit, or may be use some custom commit point (for larger sets of data).
Or the below can be used while connecting to the DB
$dbh = DBI->connect( "dbi:Oracle:abcdef", "username", "password" , {
PrintError => 0, ### Don't report errors via warn( )
RaiseError => 1 ### Do report errors via die( )
} );
this would automatically report all errors via die. The RaiseError is usually turned off by default.
Also if I understand you correctly then, by cron you mean you would be calling it from a shell cron job. In that case, call to your perl script from the cron itself can be redirected to log files something like below
perl your_perl.pl >> out.log 2>> err.log
out.log will contain regular logs and err.log will contain errors (specifically thrown by DBI prepare() or execute() methods too). In this case, you also need to make sure you use proper verbiage in print or die so that the logs look meaningful.
First, bear in mind that if you put an email address at the top of your crontab file any output from the cron job will be emailed to you:
MAILTO=me#mydomain.com
Second, if you set DBI's RaiseError to 1 when you connect you do not need to check every call, DBI will raise an error whenever one happens.
Third, DBI has an error handler callback. You register a handler and it is called whenever an error occurs with the handle in error and error text etc. If you return false from the error handler, DBI works as it would without the handler and goes on to die or warn. As a result, it is easier to set RaiseError and create an error handler than as Annjawn suggested.
Lastly, if you don't want to do this yourself, you can use something like DBIx::Log4perl and simply ask for it to log errors and nothing else. Any errors will be written to your Log4perl file and they include the SQL being executed, parameters etc.

Can a Log4perl filter access the Mapped Diagnostic Context?

I'm writting some Perl CGI scripts, and am using Log4perl to log to various files. I thought it may be useful to be able to trace a particular (or various) user's activities to a separate log file. I can put a hook into my session routine to stuff the userid into the MDC, but I don't know of any way to access the MDC from a filter. As far as I know, the MDC is only used for pattern creation.
Is this doable with Log4perl?
Per suggestion below I added a filter in my config file, but it's still not working:
log4perl.appender.User = Log::Log4perl::Appender::File
log4perl.appender.User.filename = /data/wwwwii/logs/appUser.log
log4perl.appender.User.syswrite = 1
log4perl.appender.User.Filter = User
log4perl.appender.User.layout = PatternLayout
log4perl.appender.User.layout.ConversionPattern=%p{1} %d{ISO8601}Z [%03r] %15X{remoteAddr}/%05P %M %L --> %m%n
log4perl.filter.User = sub { Log::Log4perl::MDC->get('userId') == 12; }
I stuff the userid as soon as I do the session lookup, but I never do get an appUser.log file created. No errors show up that I can see.
Can't you just do:
my $value = Log::Log4perl::MDC->get($key); # for 1 value, or
my $hashref = Log::Log4perl::MDC->get_context; # for the whole context

Can't get Extended SNMP output in Perl

I have written a Perl script to put back some SNMP values, which works fine. I have now written a script on the remote server and used the extend function in SNMP to put the value from the script into SNMP.
If I run:
snmpget -v2c -c public 10.0.0.10 'NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."cc_power"'
I get the result:
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."cc_power" = STRING: 544
But when I try to use my script to get the information back it doesn't get it. Here is the script:
#!/usr/bin/perl
use strict;
use SNMP;
use RRDs;
my $rrd_db = "/storage/db/rrd/cc_power.rrd";
my $sess;
my $val;
my $error;
$sess = new SNMP::Session(DestHost => "10.0.0.10", Community => "public", Version => 2);
my $power = $sess->get('NET-SNMP-EXTEND-MIB::nsExtendOutput1Line.\"cc_power\"');
$error=RRDs::error;
die "ERROR while updating RRD: $error\n" if $error;
my $date=time;
print "Data Script has been run - Output: ${date}:${power}\n";
but nothing is returned, and I have no idea why... no errors or anything, have I missed something stupid?
Hope someone can help as this is driving me nuts :)
I assume that you used netsnmp snmpget. Well, it hides too many details from you, as it loads MIB documents in background and nicely translate OIDs and SNMP values to all kinds of user friendly formats.
So next time pay attention to what decoration it performs and simulate that in your own code to achieve the same effects.