File Coverage

blib/lib/Parse/Syslog/Line.pm
Criterion Covered Total %
statement 129 145 88.9
branch 72 90 80.0
condition 14 27 51.8
subroutine 15 17 88.2
pod 6 6 100.0
total 236 285 82.8


line stmt bran cond sub pod time code
1             # ABSTRACT: Simple syslog line parser
2              
3             package Parse::Syslog::Line;
4              
5 4     4   856886 use warnings;
  4         13  
  4         154  
6 4     4   31 use strict;
  4         12  
  4         93  
7              
8 4     4   22 use Carp;
  4         16  
  4         269  
9 4     4   1834 use Const::Fast;
  4         9492  
  4         28  
10 4     4   2652 use English qw(-no_match_vars);
  4         14600  
  4         31  
11 4     4   1506 use Exporter;
  4         12  
  4         153  
12 4     4   2090 use HTTP::Date qw( str2time );
  4         13942  
  4         331  
13 4     4   2242 use Module::Load qw( load );
  4         3896  
  4         34  
14 4     4   2003 use Module::Loaded qw( is_loaded );
  4         2559  
  4         256  
15 4     4   1568 use POSIX qw( strftime tzset );
  4         18700  
  4         41  
16              
17             our $VERSION = '3.8';
18              
19             # Default for Handling Parsing
20             our $DateParsing = 1;
21             our $DateTimeCreate = 0;
22             our $EpochCreate = 1;
23             our $NormalizeToUTC = 0;
24             our $OutputTimeZone = 0;
25             our $IgnoreTimeZones = 0;
26              
27             our $ExtractProgram = 1;
28             our $PruneRaw = 0;
29             our $PruneEmpty = 0;
30             our @PruneFields = ();
31             our $FmtDate;
32              
33              
34             my %INT_PRIORITY = (
35             'emerg' => 0,
36             'alert' => 1,
37             'crit' => 2,
38             'err' => 3,
39             'warn' => 4,
40             'notice' => 5,
41             'info' => 6,
42             'debug' => 7,
43             );
44              
45             my %INT_FACILITY = (
46             #
47             # POSIX Facilities
48             'kern' => 0 << 3,
49             'user' => 1 << 3,
50             'mail' => 2 << 3,
51             'daemon' => 3 << 3,
52             'auth' => 4 << 3,
53             'syslog' => 5 << 3,
54             'lpr' => 6 << 3,
55             'news' => 7 << 3,
56             'uucp' => 8 << 3,
57             'cron' => 9 << 3,
58             'authpriv' => 10 << 3,
59             'ftp' => 11 << 3,
60             #
61             # Local Reserved
62             'local0' => 16 << 3,
63             'local1' => 17 << 3,
64             'local2' => 18 << 3,
65             'local3' => 19 << 3,
66             'local4' => 20 << 3,
67             'local5' => 21 << 3,
68             'local6' => 22 << 3,
69             'local7' => 23 << 3,
70             #
71             # Apple Additions
72             'netinfo' => 12 << 3,
73             'remoteauth' => 13 << 3,
74             'install' => 14 << 3,
75             'ras' => 15 << 3,
76             'launchd' => 24 << 3,
77             );
78              
79             const our %LOG_PRIORITY => (
80             %INT_PRIORITY,
81             reverse(%INT_PRIORITY),
82             );
83              
84             const our %LOG_FACILITY => (
85             %INT_FACILITY,
86             reverse(%INT_FACILITY),
87             );
88              
89             const our %CONV_MASK => (
90             priority => 0x07,
91             facility => 0x03f8,
92             );
93              
94              
95             our @ISA = qw(Exporter);
96             our @EXPORT = qw(parse_syslog_line);
97             our @EXPORT_OK = qw(
98             parse_syslog_line
99             preamble_priority preamble_facility
100             %LOG_FACILITY %LOG_PRIORITY
101             get_syslog_timezone set_syslog_timezone
102             use_utc_syslog
103             );
104             our %EXPORT_TAGS = (
105             constants => [ qw( %LOG_FACILITY %LOG_PRIORITY ) ],
106             preamble => [ qw(preamble_priority preamble_facility) ],
107             with_timezones => [ qw(parse_syslog_line set_syslog_timezone get_syslog_timezone use_utc_syslog) ],
108             );
109              
110             # Regex to Extract Data
111             const my %RE => (
112             IPv4 => qr/(?>(?:[0-9]{1,3}\.){3}[0-9]{1,3})/,
113             preamble => qr/(?>^\<(\d+)\>)/,
114             year => qr/(?>^(\d{4}) )/,
115             date => qr/(?>^([A-Za-z]{3}\s+[0-9]+\s+[0-9]{1,2}(?:\:[0-9]{2}){1,2}))/,
116             date_long => qr/^(?>
117             (?:[0-9]{4}\s+)? # Year: Because, Cisco
118             ([.*])? # Cisco adds a * for no ntp, and a . for configured but out of sync
119             [a-zA-Z]{3}\s+[0-9]+ # Date: Jan 1
120             (?:\s+[0-9]{4})? # Year: Because, Cisco
121             \s+ # Date Separator: spaces
122             [0-9]{1,2}(?:\:[0-9]{2}){1,2} # Time: HH:MM or HH:MM:SS
123             (?:\.[0-9]{3,6})? # Time: .DDD(DDD) ms resolution
124             (?:\s+[A-Z]{3,4})? # Timezone, ZZZ or ZZZZ
125             (?:\:?) # Cisco adds a : after the second timestamp
126             )/x,
127             date_iso8601 => qr/(?>^(
128             [0-9]{4}(?:\-[0-9]{2}){2} # Date YYYY-MM-DD
129             (?:\s|T) # Date Separator T or ' '
130             [0-9]{2}(?:\:[0-9]{2}){1,2} # Time HH:MM:SS
131             (?:\.(?:[0-9]{3}){1,2})? # Time: .DDD millisecond or .DDDDDD microsecond resolution
132             (?:[Zz]|[+\-][0-9]{2}\:[0-9]{2}) # UTC Offset +DD:MM or 'Z' indicating UTC-0
133             ))/x,
134             host => qr/^\s*([^:\s]+)\s+/,
135             cisco_hates_you => qr/^\s*[0-9]*:\s+/,
136             program_raw => qr/^\s*([^\[][^:]+):\s*/,
137             program_name => qr/^([^\[\(\ ]+)/,
138             program_sub => qr/(?>\(([^\)]+)\))/,
139             program_pid => qr/(?>\[([^\]]+)\])/,
140             program_netapp => qr/(?>\[([^\]]+)\]:\s*)/,
141             );
142              
143              
144             my %_empty_msg = map { $_ => undef } qw(
145             preamble priority priority_int facility facility_int
146             datetime_raw date_raw date time datetime_str datetime_obj epoch
147             host_raw host domain
148             program_raw program_name program_pid program_sub
149             );
150              
151              
152             my $SYSLOG_TIMEZONE = '';
153             my $DateTimeTried = 0;
154              
155             sub parse_syslog_line {
156 83     83 1 128749 my ($raw_string) = @_;
157              
158             # Initialize everything to undef
159 83 100       950 my %msg = $PruneEmpty ? () : %_empty_msg;
160 83 100       441 $msg{message_raw} = $raw_string unless $PruneRaw;
161              
162             #
163             # grab the preamble:
164 83 100       653 if( $raw_string =~ s/$RE{preamble}//o ) {
165             # Cast to integer
166 54         247 $msg{preamble} = int $1;
167              
168             # Extract Integers
169 54         157 $msg{priority_int} = $msg{preamble} & $CONV_MASK{priority};
170 54         117 $msg{facility_int} = $msg{preamble} & $CONV_MASK{facility};
171              
172             # Lookups
173 54         201 $msg{priority} = $LOG_PRIORITY{ $msg{priority_int} };
174 54         171 $msg{facility} = $LOG_FACILITY{ $msg{facility_int} };
175             }
176              
177             #
178             # Handle Date/Time
179 83         180 my $year;
180 83 100       541 if( $raw_string =~ s/$RE{year}//o ) {
181 3         14 $year = $1;
182             }
183 83 100       834 if( $raw_string =~ s/$RE{date}//o) {
    50          
184 65         218 $msg{datetime_raw} = $1;
185 65 100       192 $msg{datetime_raw} .= " $year"
186             if $year;
187             }
188             elsif( $raw_string =~ s/$RE{date_iso8601}//o) {
189 18         83 $msg{datetime_raw} = $1;
190             }
191 83 50 33     532 if( exists $msg{datetime_raw} && length $msg{datetime_raw} ) {
192 83         189 $msg{date_raw} = $msg{datetime_raw};
193              
194 83 50       245 if ( $DateParsing ) {
195             # if User wants to fight with dates himself, let him :)
196 83 100 66     317 if( $FmtDate && ref $FmtDate eq 'CODE' ) {
197 20         79 @msg{qw(date time epoch datetime_str)} = $FmtDate->($msg{datetime_raw});
198             }
199             else {
200             # Parse the Epoch
201 63         284 $msg{epoch} = HTTP::Date::str2time($msg{datetime_raw});
202              
203             # Format accordingly, keep highest resolution we can
204 63         10020 my $diff = ($msg{epoch} - int($msg{epoch}));
205 63 100       328 my $hires = $diff > 0 ? substr(sprintf('%0.6f', $diff),1) : '';
206 63 100       294 my $tm_fmt = '%FT%T' . $hires . ( $OutputTimeZone ? ($SYSLOG_TIMEZONE eq 'UTC' ? 'Z' : '%z') : '' );
    100          
207              
208             # Set the Date Strings
209             $msg{datetime_str} = $NormalizeToUTC ? strftime($tm_fmt, gmtime $msg{epoch})
210 63 100       869 : strftime($tm_fmt, localtime $msg{epoch});
211             # Split this up into parts
212 63         1611 my @parts = split /[ T]/, $msg{datetime_str};
213 63         176 $msg{date} = $parts[0];
214 63         240 $msg{time} = (split /[+\-Z]/, $parts[1])[0];
215 63 100       417 $msg{offset} = $NormalizeToUTC ? 'Z'
    100          
216             : $parts[1] =~ /([Z+\-].*)/ ? $1
217             : $SYSLOG_TIMEZONE;
218              
219             # Debugging for my sanity
220             printf("TZ=%s Parsed: %s to [%s] %s D:%s T:%s O:%s\n",
221             $SYSLOG_TIMEZONE,
222             @msg{qw(datetime_raw epoch datetime_str date time offset)},
223 63 50       270 ) if $ENV{DEBUG_PARSE_SYSLOG_LINE};
224             }
225             # Use Module::Load to runtime load the DateTime libraries *if* necessary
226 83 100       547 if( $DateTimeCreate ) {
227 6 100 66     315 unless( $DateTimeTried && is_loaded('DateTime') ) {
228 1         85 warn "DateTime seriously degrades performance, please start using 'epoch' and/or 'datetime_str' instead.";
229             eval {
230 1         15 load DateTime;
231 1         99 1;
232 1 50       6 } or do {
233 0         0 my $err = $@;
234 0         0 warn "DateTime unavailable, disabling it: $err";
235 0         0 $DateTimeCreate = 0;
236             };
237 1         3 $DateTimeTried++;
238             }
239 6 50       140 eval {
240 6         177 my %args = (epoch => $msg{epoch});
241 6 50       26 $args{time_zone} = $SYSLOG_TIMEZONE if $SYSLOG_TIMEZONE;
242 6         47 $msg{datetime_obj} = DateTime->from_epoch(%args);
243             } if $DateTimeCreate;
244             }
245             }
246             }
247              
248             #
249             # Host Information:
250 83 100       21698 if( $raw_string =~ s/$RE{host}//o ) {
251 80         249 my $hostStr = $1;
252 80         482 my($ip) = ($hostStr =~ /($RE{IPv4})/o);
253 80 100 66     459 if( defined $ip && length $ip ) {
    50          
254 21         70 $msg{host_raw} = $hostStr;
255 21         65 $msg{host} = $ip;
256             }
257             elsif( length $hostStr ) {
258 59         240 my ($host,$domain) = split /\./, $hostStr, 2;
259 59         159 $msg{host_raw} = $hostStr;
260 59         128 $msg{host} = $host;
261 59         151 $msg{domain} = $domain;
262             }
263             }
264 83 100       485 if( $raw_string =~ s/$RE{cisco_hates_you}//o ) {
265             # Yes, Cisco adds a second timestamp to it's messages, because it hates you.
266 23 100       213 if( $raw_string =~ s/$RE{date_long}//o ) {
267             # Cisco encodes the status of NTP in the second datestamp, so let's pass it back
268 17 100       67 if ( my $ntp = $1 ) {
269 6 50       28 $msg{ntp} = $ntp eq '.' ? 'out of sync'
    100          
270             : $ntp eq '*' ? 'not configured'
271             : 'unknown';
272             }
273             else {
274 11         35 $msg{ntp} = 'ok';
275             }
276             }
277             }
278              
279             #
280             # Parse the Program portion
281 83 100       232 if( $ExtractProgram ) {
282 63 100       443 if( $raw_string =~ s/$RE{program_raw}//o ) {
    50          
283 57         192 $msg{program_raw} = $1;
284 57         265 my $progStr = join ' ', grep {!exists $INT_PRIORITY{$_}} split /\s+/, $msg{program_raw};
  68         331  
285 57 50 33     349 if( defined $progStr && length $progStr) {
286 57 50       372 if( ($msg{program_name}) = ($progStr =~ /$RE{program_name}/o) ) {
287 57 100       216 if (length $msg{program_name} != length $msg{program_raw} ) {
288             (($msg{program_pid}) = ($progStr =~ /$RE{program_pid}/o))
289 14 100       103 || (($msg{program_sub}) = ($progStr =~ /$RE{program_sub}/o))
290             }
291             }
292             }
293             }
294             elsif( $raw_string =~ s/$RE{program_netapp}//o ) {
295             # Check for a [host thing.subthing:level]: tag
296             # or [host:thing.subthing:level]: tag, Thanks NetApp.
297 6         19 my $subStr = $1;
298 6         17 $msg{program_raw} = qq{[$subStr]};
299 6         36 my ($host,$program,$level) = split /[: ]+/, $subStr;
300 6         13 $msg{program_name} = $program;
301 6 0 33     20 if(!exists $msg{priority} && exists $LOG_PRIORITY{$level}) {
302 0         0 $msg{priority} = $level;
303 0         0 $msg{priority_int} = $LOG_PRIORITY{$level};
304             }
305 6         19 $raw_string =~ s/^[ :]+//;
306             }
307             }
308             else {
309 20         58 $raw_string =~ s/^\s+//;
310             }
311              
312             # The left overs should be the message
313 83         232 $msg{content} = $raw_string;
314 83         212 chomp $msg{content};
315 83 100       344 $msg{message} = defined $msg{program_raw} ? "$msg{program_raw}: $msg{content}" : $msg{content};
316              
317 83 100       254 if( $PruneRaw ) {
318 9         49 delete $msg{$_} for grep { $_ =~ /_raw$/ } keys %msg;
  158         480  
319             }
320 83 100       250 if( $PruneEmpty ) {
321 9         34 delete $msg{$_} for grep { !defined $msg{$_} } keys %msg;
  122         234  
322             }
323 83 50       246 if( @PruneFields ) {
324 4     4   11806 no warnings;
  4         10  
  4         2375  
325 0         0 delete $msg{$_} for @PruneFields;
326             }
327 83 50 33     465 delete $msg{epoch} if exists $msg{epoch} and !$EpochCreate;
328              
329             #
330             # Return our hash reference!
331 83         309 return \%msg;
332             }
333              
334              
335             sub preamble_priority {
336 0     0 1 0 my $preamble = int shift;
337              
338 0         0 my %hash = (
339             preamble => $preamble,
340             );
341              
342 0         0 $hash{as_int} = $preamble & $CONV_MASK{priority};
343 0         0 $hash{as_text} = $LOG_PRIORITY{ $hash{as_int} };
344              
345 0         0 return \%hash;
346             }
347              
348              
349             sub preamble_facility {
350 0     0 1 0 my $preamble = int shift;
351              
352 0         0 my %hash = (
353             preamble => $preamble,
354             );
355              
356 0         0 $hash{as_int} = $preamble & $CONV_MASK{facility};
357 0         0 $hash{as_text} = $LOG_FACILITY{ $hash{as_int} };
358              
359 0         0 return \%hash;
360              
361             }
362              
363             sub set_syslog_timezone {
364 7     7 1 24276 my ( $tz_name ) = @_;
365              
366 7 50 66     85 if( defined $tz_name && (!exists $ENV{TZ} || $tz_name ne $ENV{TZ}) ) {
      66        
367 7         73 $ENV{TZ} = $SYSLOG_TIMEZONE = $tz_name;
368 7         771 tzset();
369 7         27 $OutputTimeZone = 1;
370             # Output some useful debug information
371             printf("set_syslog_timezone('%s') results in a timezone of '%s' with offset: %s\n",
372             $tz_name,
373             strftime('%Z', localtime),
374             strftime('%z', localtime),
375 7 50       32 ) if $ENV{DEBUG_PARSE_SYSLOG_LINE};
376             }
377              
378 7         24 return $SYSLOG_TIMEZONE;
379             }
380              
381             sub get_syslog_timezone {
382 1     1 1 1209 return $SYSLOG_TIMEZONE;
383             }
384              
385             # If you have a syslog which logs dates in UTC, then processing will be much, much faster
386             sub use_utc_syslog {
387 1     1 1 4910 set_syslog_timezone('UTC');
388 1         3 $NormalizeToUTC = 1;
389 1         3 return;
390             }
391              
392             1; # End of Parse::Syslog::Line
393              
394             __END__
395              
396             =pod
397              
398             =encoding UTF-8
399              
400             =head1 NAME
401              
402             Parse::Syslog::Line - Simple syslog line parser
403              
404             =head1 VERSION
405              
406             version 3.8
407              
408             =head1 SYNOPSIS
409              
410             I wanted a very simple log parser for network based syslog input.
411             Nothing existed that simply took a line and returned a hash ref all
412             parsed out.
413              
414             use Parse::Syslog::Line qw(parse_syslog_line);
415              
416             $Parse::Syslog::Line::DateTimeCreate = 1;
417              
418             my $href = parse_syslog_line( $msg );
419             #
420             # $href = {
421             # preamble => '13',
422             # priority => 'notice',
423             # priority_int => 5,
424             # facility => 'user',
425             # facility_int => 8,
426             # date => 'YYYY-MM-DD',
427             # time => 'HH::MM:SS',
428             # epoch => 1361095933,
429             # datetime_str => ISO 8601 datetime, $NormalizeToUTC = 1 then UTC, else local
430             # datetime_obj => undef, # If $DateTimeCreate = 1, else undef
431             # datetime_raw => 'Feb 17 11:12:13'
432             # date_raw => 'Feb 17 11:12:13'
433             # host_raw => 'hostname', # Hostname as it appeared in the message
434             # host => 'hostname', # Hostname without domain
435             # domain => 'blah.com', # if provided
436             # program_raw => 'sshd(blah)[pid]',
437             # program_name => 'sshd',
438             # program_sub => 'pam_unix',
439             # program_pid => 20345,
440             # content => 'the rest of the message'
441             # message => 'program[pid]: the rest of the message',
442             # message_raw => 'The message as it was passed',
443             # ntp => 'ok', # Only set for Cisco messages
444             # };
445             ...
446              
447             =head1 EXPORT
448              
449             Exported by default:
450             parse_syslog_line( $one_line_of_syslog_message );
451              
452             Optional Exports:
453             :preamble
454             preamble_priority
455             preamble_facility
456              
457             :constants
458             %LOG_FACILITY
459             %LOG_PRIORITY
460              
461             :with_timezones
462             set_syslog_timezone
463             get_syslog_timezone
464             use_utc_syslog
465              
466             =head1 VARIABLES
467              
468             =head2 ExtractProgram
469              
470             If this variable is set to 1 (the default), parse_syslog_line() will try it's
471             best to extract a "program" field from the input. This is the most expensive
472             set of regex in the module, so if you don't need that pre-parsed, you can speed
473             the module up significantly by setting this variable.
474              
475             Vendors who do proprietary non-sense with their syslog formats are to blame for
476             this setting.
477              
478             Usage:
479              
480             $Parse::Syslog::Line::ExtractProgram = 0;
481              
482             =head2 DateParsing
483              
484             If this variable is set to 0 raw date will not be parsed further into components (datetime_str date time epoch).
485             Default is 1 (parsing enabled).
486              
487             Usage:
488              
489             $Parse::Syslog::Line::DateParsing = 0;
490              
491             =head2 DateTimeCreate
492              
493             If this variable is set to 1 (the default), a DateTime object will be returned in the
494             $m->{datetime_obj} field. Otherwise, this will be skipped.
495              
496             NOTE: DateTime timezone calculation is fairly slow. Unless you really need to
497             take timezones into account, you're better off using other modes (below).
498              
499             Usage:
500              
501             $Parse::Syslog::Line::DateTimeCreate = 0;
502              
503             =head2 EpochCreate
504              
505             If this variable is set to 1, the default, the number of seconds from UNIX
506             epoch will be returned in the $m->{epoch} field. Setting this to false will
507             only delete the epoch before returning the hash reference.
508              
509             =head2 NormalizeToUTC
510              
511             When set, the datetime_str will be ISO8601 UTC.
512              
513             =head2 OutputTimeZones
514              
515             Default is false, but is enabled if you call set_syslog_timezone() or
516             use_utc_syslog(). If enabled, this will append the timezone offset to the
517             datetime_str.
518              
519             =head2 FmtDate
520              
521             You can pass your own formatter/parser here. Given a raw datetime string it
522             should output a list containing date, time, epoch, datetime_str,
523             in your wanted format.
524              
525             use Parse::Syslog::Line;
526              
527             local $Parse::Syslog::Line::FmtDate = sub {
528             my ($raw_datestr) = @_;
529             my @elements = (
530             #date
531             #time
532             #epoch
533             #datetime_str
534             );
535             return @elements;
536             };
537              
538             B<NOTE>: No further date processing will be done, you're on your own here.
539              
540             =head2 PruneRaw
541              
542             This variable defaults to 0, set to 1 to delete all keys in the return hash ending in "_raw"
543              
544             Usage:
545              
546             $Parse::Syslog::Line::PruneRaw = 1;
547              
548             =head2 PruneEmpty
549              
550             This variable defaults to 0, set to 1 to delete all keys in the return hash which are undefined.
551              
552             Usage:
553              
554             $Parse::Syslog::Line::PruneEmpty = 1;
555              
556             =head2 PruneFields
557              
558             This should be an array of fields you'd like to be removed from the hash reference.
559              
560             Usage:
561              
562             @Parse::Syslog::Line::PruneFields = qw(date_raw facility_int priority_int);
563              
564             =head1 FUNCTIONS
565              
566             =head2 parse_syslog_line
567              
568             Returns a hash reference of syslog message parsed data.
569              
570             B<NOTE>: Date/time parsing is hard. This module has been optimized to balance
571             common sense and processing speed. Care is taken to ensure that any data input
572             into the system isn't lost, but with the varieties of vendor and admin crafted
573             date formats, we don't always get it right. Feel free to override date
574             processing using by setting the $FmtDate variable or completely disable it with
575             $DateParsing set to 0.
576              
577             =head2 set_syslog_timezone($timezone_name)
578              
579             Sets a timezone $timezone_name for parsed messages. This timezone will be used
580             to calculate offset from UTC if a timezone designation is not present in the
581             message being parsed. This timezone will also serve as the source timezone for
582             the datetime_str field.
583              
584             =head2 get_syslog_timezone
585              
586             Returns the name of the timezone currently set by set_syslog_timezone.
587              
588             =head2 use_utc_syslog
589              
590             A convenient function which sets the syslog timezone to UTC and sets the config
591             variables accordingly. Automatically sets $NormaizeToUTC and datetime_str will
592             be set to the UTC equivalent.
593              
594             =head2 preamble_priority
595              
596             Takes the Integer portion of the syslog messsage and returns
597             a hash reference as such:
598              
599             $prioRef = {
600             'preamble' => 13
601             'as_text' => 'notice',
602             'as_int' => 5,
603             };
604              
605             =head2 preamble_facility
606              
607             Takes the Integer portion of the syslog messsage and returns
608             a hash reference as such:
609              
610             $facRef = {
611             'preamble' => 13
612             'as_text' => 'user',
613             'as_int' => 8,
614             };
615              
616             =head1 DEVELOPMENT
617              
618             This module is developed with Dist::Zilla. To build from the repository, use Dist::Zilla:
619              
620             dzil authordeps --missing |cpanm
621             dzil listdeps --missing |cpanm
622             dzil build
623             dzil test
624              
625             =head1 AUTHOR
626              
627             Brad Lhotsky <brad@divisionbyzero.net>
628              
629             =head1 COPYRIGHT AND LICENSE
630              
631             This software is Copyright (c) 2015 by Brad Lhotsky.
632              
633             This is free software, licensed under:
634              
635             The (three-clause) BSD License
636              
637             =head1 CONTRIBUTORS
638              
639             =for stopwords BartÅ‚omiej Fulanty Csillag Tamas Keedi Kim Mateu X Hunter Neil Bowers Shawn Wilson Tomohiro Hosaka
640              
641             =over 4
642              
643             =item *
644              
645             BartÅ‚omiej Fulanty <starlight@cpan.org>
646              
647             =item *
648              
649             Csillag Tamas <cstamas@digitus.itk.ppke.hu>
650              
651             =item *
652              
653             Keedi Kim <keedi.k@gmail.com>
654              
655             =item *
656              
657             Mateu X Hunter <mhunter@maxmind.com>
658              
659             =item *
660              
661             Neil Bowers <neil@bowers.com>
662              
663             =item *
664              
665             Shawn Wilson <swilson@korelogic.com>
666              
667             =item *
668              
669             Tomohiro Hosaka <bokutin@bokut.in>
670              
671             =back
672              
673             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
674              
675             =head1 SUPPORT
676              
677             =head2 Websites
678              
679             The following websites have more information about this module, and may be of help to you. As always,
680             in addition to those websites please use your favorite search engine to discover more resources.
681              
682             =over 4
683              
684             =item *
685              
686             MetaCPAN
687              
688             A modern, open-source CPAN search engine, useful to view POD in HTML format.
689              
690             L<http://metacpan.org/release/Parse-Syslog-Line>
691              
692             =item *
693              
694             RT: CPAN's Bug Tracker
695              
696             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
697              
698             L<https://rt.cpan.org/Public/Dist/Display.html?Name=Parse-Syslog-Line>
699              
700             =back
701              
702             =head2 Source Code
703              
704             This module's source code is available by visiting:
705             L<https://github.com/reyjrar/Parse-Syslog-Line>
706              
707             =cut