| MQSeries documentation | Contained in the MQSeries distribution. |
MQSeries::ErrorLog::Parser -- Parse a portion of an MQSeries error log and return parsed Entry objects.
use MQSeries::ErrorLog::Parser;
my $qmgr = 'foo'; # Queue Manager we are processing
my $parser = MQSeries::ErrorLog::Parser->
new('defaults' => { 'QMgr' => $qmgr });
open (ERRORS, '<', "/var/mqm/qmgrs/$qmgr/errors/AMQERR01.LOG");
local $/;
my @entries = $parser->parse_data(<ERRORS>);
close ERRORS;
The MQSeries::ErrorLog::Parser class is typically not used directly, but invoked through the MQSeries::ErrorLog::Tail class. When used directly, it can be used to parse a (possibly archived) error-log file and return an array of neatly-parsed MQSeries::ErrorLog::Entry objects.
This class will try and deal with the vagaries of error-log processing, chief of which is that the MQSeries error-log is not written to in an atomic fashion, meaning that some error-log entries may be interleaved and hence un-parseable.
All error-log entries can be parsed to some extent (summary, action, timestamp); however, most common error messages are also parsed in detail to give access to embedded fields such as 'QMgr', 'Channel', 'Queue', etc.
Create a new MQSeries::ErrorLog::Parser object. The constructor take the following optional parameters:
A reference to an error-handling routine. This defaults to 'carp', but can be changed to your own error-handling routine.
In order to avoid overloading the system with error messages, message format errors are not logged and errors for specific error-log messages are generated only once for each message code.
A reference to a hash with default parameters that will be used to initialize the MQSeries::ErrorLog::Entry object created. A typical default parameter is 'QMgr', which should be specified whenever you are parsing error-log messages for a specific queue manager.
Parse a chunk of text with one or more error messages and return individual parsed entries.
The file 'descriptions.pl' contains a list of all error messages supported, in the form of regular expressions and a set of field names extracted from these expressions. Additions and corrections are welcome.
MQSeries(3), MQSeries::ErrorLog::Tail(3), MQSeries::ErrorLog::Entry(3)
| MQSeries documentation | Contained in the MQSeries distribution. |
# # MQSeries::ErrorLog::Parser.pm - Parse error-log files into error-log # entry objects # # (c) 2000-2010 Morgan Stanley & Co. Incorporated # See ..../src/LICENSE for terms of distribution. # # $Id: Parser.pm,v 33.7 2010/04/01 16:24:54 anbrown Exp $ # package MQSeries::ErrorLog::Parser; use strict; use Carp; use Time::Local; use MQSeries::ErrorLog::Entry; # # Load a file with error-descriptions, available as # the variable 'error_table'. # our $error_table; require "MQSeries/ErrorLog/descriptions.pl"; our $VERSION = '1.32'; # # Constructor # Parameters: # - Class name # - Hash with optional arguments: # - 'default' => default values for Entry object (e.g. QMgr => abc) # - 'Carp' => complaints routine (default: carp) # Returns: # - New MQSeries::ErrorLog::Parser object # sub new { my ($class, %args) = @_; my $dfts = $args{'defaults'} || {}; my $carp = $args{'Carp'} || \&carp; my $this = { 'defaults' => $dfts, 'Carp' => $carp, }; return bless $this, $class; } # # CLASS METHOD: break a chunk of text into logical entries # # Parameters: # - MQSeries::ErrorLog::Parser object # - Text from ErrorLog file (with one or more errors) # Returns: # - MQSeries::ErrorLog::Entry objects created # sub parse_data { my ($this, $text) = @_; confess "Illegal no of args" unless (@_ == 2 && defined $text); my @entries; my @chunks = split /\n----+(?: [\w.]+ : \d+ ----+)?\n/, $text; #print "Have [" . @chunks . "] chunks\n"; foreach (@chunks) { # # Illegally formatted events cause this guy to blow up... # eval { my $entry = $this->parse_one_chunk($_); push @entries, $entry if (defined $entry); }; # DEBUG CODE - this finds ill-formatted entries if ($@) { $this->{'Carp'}->("Parse error: $@\n"); }; } return @entries; } # DEBUG HELP - issue a warning only once per event type my %warnings; # # CLASS METHOD: Parse one ErrorLog chunk # # Parameters: # - MQSeries::ErrorLog::Parser object # - Chunk of ErrorLog data # Returns: # - MQSeries::ErrorLog::Entry object # sub parse_one_chunk { my ($this, $chunk) = @_; # # Deal with catastrophic errors - if MQSeries cannot # write the message due to missing message catalog files, # don't bother to try and parse it. # if ($chunk =~ /MQSeries was unable to open a message catalog to display an error message for/) { return; } # # Apparently, MQSeries doesn't always do proper atomic writes # of entire error-log entries. Try and look for the most # common errors and don't try and parse the chunk if there are # obvious errors. # if ($chunk =~ m!^\d\d/\d\d/\d\d\s+\d\d/\d\d/\d\d\s+\d\d:\d\d:\d\d\s!s || $chunk =~ m!\nACTION:?\n.*\nACTION:?\n!s || $chunk =~ m!\nEXPLANATION:?\n.*\nEXPLANATION:?\n!s || $chunk =~ m!\nAMQ\d\d\d\d.*\nAMQ\d\d\d\d!s || $chunk =~ m!^(.+\n)(?=\d\d/\d\d/\d\d\s+\d\d:\d\d:\d\d\s*AMQ)!s) { #carp "Invalid chunk: $chunk\n"; $this->{'invalid'}++; return; } # # Break the chunk into: # - timestamp # - event code # - summary # - explanation # - action # - set of parsed fields (initialized fron class defaults) # my $data = { }; # # Handle timestamp # # NOTE: Depending on the MQSeries release, the text of the # error code & summary may or may not start on the next line. # # Thanks to Mike Carr [mcarr@qualcomm.com] for pointing this out. # Added support for multiple time stamp formats - depending on LOCALE settings. # Also to handle WMQ6.0 errorlog format. # # 1 2 3 4 5 6 7 8 if ($chunk =~ m!^((\d\d)/(\d\d)/(\d{2,4})\s+(\d\d):(\d\d):(\d\d))\s+([AaPp][Mm]|)([\w\-\(\)\.\s]*)(?=\n|AMQ)!g) { $data->{'timestamp'} = $1; my $hour = $5; if ($8 ne '') { my $am_pm = $8; if ($am_pm =~ m!AM!i && $hour eq "12") { $hour = 0; } elsif ($am_pm =~ m!PM!i && $hour ne "12") { $hour += 12; } } $data->{'ctime'} = timelocal($7, $6, $hour, $3, $2-1, $4); } else { confess "Cannot parse timestamp in [$chunk]"; } # Handle error code and summary if ($chunk =~ m!\G\s*(AMQ\d+):\s+(.*?)\s*\n\s*\n(?=EXPLANATION:?\s+)!gs) { $data->{'error_code'} = $1; $data->{'summary'} = $2; $data->{'summary'} =~ s!\s+! !g; } else { confess "Cannot parse summary in [$chunk]"; } # # Get explanation # NOTE: Depending on the MQSeries release, the # explanation text may or may not start on the next line. # if ($chunk =~ m!\GEXPLANATION:?\s+(.*?)\n(?=ACTION:?\s+)!gs) { $data->{'explanation'} = $1; $data->{'explanation'} =~ s!\s+! !g; } else { confess "Cannot parse explanation in [$chunk]"; } # # Get action # NOTE: Depending on the MQSeries release, the # action text may or may not start on the next line. # if ($chunk =~ m!\GACTION:?\s+(.*)$!gs) { $data->{'action'} = $1; $data->{'action'} =~ s!\s+! !g; } else { confess "Cannot parse action in [$chunk]; next is [" . substr($chunk, pos($chunk), 50) . "]"; } # # Every description in the error-table is basically # a string with a regexp, plus a list of field-names # for the first, second, etc parenthesis-group in the regexp. # my $desc = $error_table->{ $data->{'error_code'} }; if ($desc) { $chunk =~ s!\s+! !g; # Normalize to undo line-wrapping my $match = 0; my $fields = $data->{'fields'} = { %{ $this->{'defaults'} } }; # # Thanks to Michael Fowler <michael@shoebox.net> for a comment # that the regular expression should be evualated in an array # context. Earlier code was using a for-loop and symbolic # references for $1, $2, $3, ... # if (my @entries = ($chunk =~ m!$desc->[0]!)) { foreach my $fld (@{$desc}[1..$#$desc]) { my $val = shift @entries; $fields->{$fld} = $val if (defined $val); } # # For TCP/IP errors, split 'Host' field into either: # - 'HostName', 'IPAddress' and 'IPPort' # - 'HostName' and 'IPAddress' # - 'IPAddress' # depending on how muich is available # if (defined $fields->{'Host'}) { if ($fields->{'Host'} =~ m!^(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)\s+\((\d+)\)$!) { $fields->{'HostName'} = $1; $fields->{'IPAddress'} = $2; $fields->{'IPPort'} = $3; } elsif ($fields->{'Host'} =~ m!^(\S+)\s+\((\d+\.\d+\.\d+\.\d+)\)$!) { $fields->{'HostName'} = $1; $fields->{'IPAddress'} = $2; } elsif ($fields->{'Host'} =~ m!^(\d+\.\d+\.\d+\.\d+)$!) { $fields->{'IPAddress'} = $1; } else { confess "Cannot parse field 'Host' ($fields->{'Host'})"; } delete $fields->{'Host'}; } } else { unless ($warnings{ $data->{'error_code'} }++) { $this->{'Carp'}->("Cannot match for event [$data->{'error_code'}] and text [$chunk] with available error description [$desc->[0]]"); } } } my $entry = MQSeries::ErrorLog::Entry->new(%$data); # # Issue a warning if the event code is not known (no detailed view) - # using the event class' own methods # unless (defined $desc) { unless ($warnings{ $data->{'error_code'} }++) { $this->{'Carp'}->("WARNING: Unknown error " . $entry->display_raw() . "\n"); } } return $entry; } 1; # End on a positive note __END__