File Coverage

blib/lib/Readonly/Values/Syslog.pm
Criterion Covered Total %
statement 83 86 96.5
branch 32 36 88.8
condition 3 3 100.0
subroutine 18 18 100.0
pod 9 9 100.0
total 145 152 95.3


line stmt bran cond sub pod time code
1             package Readonly::Values::Syslog;
2              
3 3     3   585214 use strict;
  3         8  
  3         105  
4 3     3   11 use warnings;
  3         5  
  3         140  
5 3     3   31 use v5.14; # Enable modern Perl features
  3         9  
6              
7             # Core dependencies
8 3     3   17 use Carp qw(croak carp);
  3         4  
  3         222  
9 3     3   1963 use Readonly;
  3         12588  
  3         192  
10 3     3   1522 use Readonly::Enum;
  3         11996  
  3         168  
11 3     3   25 use Exporter qw(import);
  3         6  
  3         108  
12 3     3   25 use Scalar::Util qw(looks_like_number);
  3         8  
  3         5673  
13              
14             our $VERSION = '0.04';
15              
16             =head1 NAME
17              
18             Readonly::Values::Syslog - RFC 3164 compliant syslog severity level constants
19              
20             =head1 VERSION
21              
22             Version 0.04
23              
24             =head1 DESCRIPTION
25              
26             This module provides RFC 3164 compliant syslog severity level constants and
27             utility functions for syslog level validation and conversion. It offers both
28             numeric constants and string-based lookups with comprehensive validation.
29              
30             =head1 SYNOPSIS
31              
32             use Carp qw(croak);
33             use Readonly::Values::Syslog qw(:all);
34              
35             # Using numeric constants
36             my $level = $CRITICAL; # 2
37              
38             # Using string-to-numeric conversion
39             my $numeric_level = get_syslog_level('critical'); # 2
40             $numeric_level = get_syslog_level('crit'); # 2 (alias)
41              
42             # Using numeric-to-string conversion
43             my $level_name = get_syslog_name(2); # 'critical'
44              
45             # Validation
46             if (is_valid_syslog_level('warning')) {
47             print "Valid syslog level\n";
48             }
49              
50             if (is_valid_syslog_number(4)) {
51             print "Valid syslog number\n";
52             }
53              
54             # Get all available levels
55             my @levels = get_all_syslog_levels(); # String names
56             my @numbers = get_all_syslog_numbers(); # Numeric values
57              
58             # Comprehensive example
59             sub log_message {
60             my ($level, $message) = @_;
61              
62             my $numeric_level = eval { get_syslog_level($level) };
63             if ($@) {
64             croak "Invalid syslog level: $level";
65             }
66              
67             my $level_name = get_syslog_name($numeric_level);
68             printf "[%s:%d] %s\n", uc($level_name), $numeric_level, $message;
69             }
70              
71             =head1 RFC 3164 COMPLIANCE
72              
73             This module implements the syslog severity levels as defined in RFC 3164:
74              
75             0 Emergency - System is unusable
76             1 Alert - Action must be taken immediately
77             2 Critical - Critical conditions
78             3 Error - Error conditions
79             4 Warning - Warning conditions
80             5 Notice - Normal but significant condition
81             6 Info - Informational messages
82             7 Debug - Debug-level messages
83              
84             =cut
85              
86             Readonly::Enum our ($SYSLOG_EMERGENCY, $SYSLOG_ALERT, $SYSLOG_CRITICAL, $SYSLOG_ERROR, $SYSLOG_WARNING, $SYSLOG_NOTICE, $SYSLOG_INFO, $SYSLOG_DEBUG) => 0;
87              
88             # Export the constants with traditional names for backward compatibility
89             Readonly our $EMERGENCY => $SYSLOG_EMERGENCY;
90             Readonly our $ALERT => $SYSLOG_ALERT;
91             Readonly our $CRITICAL => $SYSLOG_CRITICAL;
92             Readonly our $ERROR => $SYSLOG_ERROR;
93             Readonly our $WARNING => $SYSLOG_WARNING;
94             Readonly our $NOTICE => $SYSLOG_NOTICE;
95             Readonly our $INFORMATIONAL => $SYSLOG_INFO;
96             Readonly our $DEBUG => $SYSLOG_DEBUG;
97              
98             # Comprehensive string to numeric mapping with common aliases
99             Readonly::Hash our %SYSLOG_LEVELS => (
100             # Primary names (RFC 3164)
101             'emergency' => $SYSLOG_EMERGENCY,
102             'alert' => $SYSLOG_ALERT,
103             'critical' => $SYSLOG_CRITICAL,
104             'error' => $SYSLOG_ERROR,
105             'warning' => $SYSLOG_WARNING,
106             'notice' => $SYSLOG_NOTICE,
107             'informational' => $SYSLOG_INFO,
108             'info' => $SYSLOG_INFO,
109             'debug' => $SYSLOG_DEBUG,
110              
111             # Common aliases
112             'emerg' => $SYSLOG_EMERGENCY,
113             'crit' => $SYSLOG_CRITICAL,
114             'err' => $SYSLOG_ERROR,
115             'warn' => $SYSLOG_WARNING,
116             'trace' => $SYSLOG_DEBUG, # Common in many logging systems
117              
118             # Alternative forms
119             'panic' => $SYSLOG_EMERGENCY, # Historical alias
120             'fatal' => $SYSLOG_CRITICAL, # Common in application logging
121             );
122              
123             # Reverse mapping for numeric to string conversion
124             Readonly::Hash our %SYSLOG_NAMES => (
125             $SYSLOG_EMERGENCY => 'emergency',
126             $SYSLOG_ALERT => 'alert',
127             $SYSLOG_CRITICAL => 'critical',
128             $SYSLOG_ERROR => 'error',
129             $SYSLOG_WARNING => 'warning',
130             $SYSLOG_NOTICE => 'notice',
131             $SYSLOG_INFO => 'info',
132             $SYSLOG_DEBUG => 'debug',
133             );
134              
135             # Enhanced descriptions for documentation and tooling
136             Readonly::Hash our %SYSLOG_DESCRIPTIONS => (
137             $SYSLOG_EMERGENCY => 'System is unusable',
138             $SYSLOG_ALERT => 'Action must be taken immediately',
139             $SYSLOG_CRITICAL => 'Critical conditions',
140             $SYSLOG_ERROR => 'Error conditions',
141             $SYSLOG_WARNING => 'Warning conditions',
142             $SYSLOG_NOTICE => 'Normal but significant condition',
143             $SYSLOG_INFO => 'Informational messages',
144             $SYSLOG_DEBUG => 'Debug-level messages',
145             );
146              
147             # Backward compatibility - maintain old hash name but mark as deprecated
148             our %syslog_values = %SYSLOG_LEVELS;
149              
150             =head1 FUNCTIONS
151              
152             =head2 get_syslog_level($level_name)
153              
154             Converts a syslog level name (string) to its numeric value.
155              
156             my $numeric = get_syslog_level('critical'); # Returns 2
157             my $numeric = get_syslog_level('crit'); # Returns 2 (alias)
158              
159             Dies with a descriptive error if the level name is invalid.
160              
161             =cut
162              
163             sub get_syslog_level {
164 1145     1145 1 305406 my $level_name = $_[0];
165              
166 1145 100       2710 unless (defined $level_name) {
167 1         14 croak 'get_syslog_level: level name is required';
168             }
169              
170             # Normalize to lowercase for lookup
171 1144         2105 $level_name = lc($level_name);
172 1144         5267 $level_name =~ s/^\s+|\s+$//g; # Trim whitespace
173              
174 1144 100       3736 unless (exists $SYSLOG_LEVELS{$level_name}) {
175 3         38 croak "get_syslog_level: invalid syslog level '$level_name'. " .
176             "Valid levels are: " . join(', ', sort keys %SYSLOG_LEVELS);
177             }
178              
179 1141         10684 return $SYSLOG_LEVELS{$level_name};
180             }
181              
182             =head2 get_syslog_name($level_number)
183              
184             Converts a numeric syslog level to its canonical string name.
185              
186             my $name = get_syslog_name(2); # Returns 'critical'
187              
188             Dies with a descriptive error if the level number is invalid.
189              
190             =cut
191              
192             sub get_syslog_name {
193 1022     1022 1 44387 my $level_number = $_[0];
194              
195 1022 100       2456 unless (defined $level_number) {
196 1         14 croak 'get_syslog_name: level number is required';
197             }
198              
199 1021 100       3022 unless (looks_like_number($level_number)) {
200 1         15 croak "get_syslog_name: level must be numeric, got '$level_number'";
201             }
202              
203             # Convert to integer for exact matching
204 1020         1911 $level_number = int($level_number);
205              
206 1020 100       3079 unless (exists $SYSLOG_NAMES{$level_number}) {
207             croak "get_syslog_name: invalid syslog level number '$level_number'. " .
208 2         28 "Valid levels are: " . join(', ', sort { $a <=> $b } keys %SYSLOG_NAMES);
  34         166  
209             }
210              
211 1018         8944 return $SYSLOG_NAMES{$level_number};
212             }
213              
214             =head2 is_valid_syslog_level($level_name)
215              
216             Returns true if the given string is a valid syslog level name (including aliases).
217              
218             if (is_valid_syslog_level('warning')) {
219             # Process the warning
220             }
221              
222             =cut
223              
224             sub is_valid_syslog_level {
225 1018     1018 1 45853 my $level_name = $_[0];
226              
227 1018 100       2466 return 0 unless defined $level_name;
228              
229 1017         2167 $level_name = lc($level_name);
230 1017         6774 $level_name =~ s/^\s+|\s+$//g;
231              
232 1017         3387 return exists $SYSLOG_LEVELS{$level_name};
233             }
234              
235             =head2 is_valid_syslog_number($level_number)
236              
237             Returns true if the given number is a valid syslog severity level.
238              
239             if (is_valid_syslog_number(4)) {
240             # Process the warning level (4)
241             }
242              
243             =cut
244              
245             sub is_valid_syslog_number {
246 14     14 1 5819 my $level_number = $_[0];
247              
248 14 100       59 return 0 unless defined $level_number;
249 13 100       69 return 0 unless looks_like_number($level_number);
250              
251 11         81 return exists $SYSLOG_NAMES{$level_number};
252             }
253              
254             =head2 get_syslog_description($level)
255              
256             Returns the RFC 3164 description for a syslog level. Accepts either numeric
257             level or string name.
258              
259             my $desc = get_syslog_description(2); # 'Critical conditions'
260             my $desc = get_syslog_description('critical'); # 'Critical conditions'
261              
262             =cut
263              
264             sub get_syslog_description {
265 6     6 1 21557 my $level = $_[0];
266              
267 6 100       24 unless (defined $level) {
268 1         59 croak 'get_syslog_description: level is required';
269             }
270              
271 5         10 my $numeric_level;
272              
273 5 100       25 if (looks_like_number($level)) {
274 3         10 $numeric_level = int($level);
275 3 100       20 unless (exists $SYSLOG_NAMES{$numeric_level}) {
276 1         25 croak "get_syslog_description: invalid numeric level '$level'";
277             }
278             } else {
279 2         5 $numeric_level = eval { get_syslog_level($level) };
  2         8  
280 2 100       775 if ($@) {
281 1         11 croak "get_syslog_description: invalid level name '$level'";
282             }
283             }
284              
285 3         35 return $SYSLOG_DESCRIPTIONS{$numeric_level};
286             }
287              
288             =head2 get_all_syslog_levels()
289              
290             Returns a list of all valid syslog level names (strings) in severity order.
291              
292             my @levels = get_all_syslog_levels();
293             # Returns: ('emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug')
294              
295             =cut
296              
297             sub get_all_syslog_levels {
298 1     1 1 2697 return map { $SYSLOG_NAMES{$_} } sort { $a <=> $b } keys %SYSLOG_NAMES;
  8         64  
  17         75  
299             }
300              
301             =head2 get_all_syslog_numbers()
302              
303             Returns a list of all valid syslog level numbers in order.
304              
305             my @numbers = get_all_syslog_numbers();
306             # Returns: (0, 1, 2, 3, 4, 5, 6, 7)
307              
308             =cut
309              
310             sub get_all_syslog_numbers {
311 1     1 1 10269 return sort { $a <=> $b } keys %SYSLOG_NAMES;
  17         80  
312             }
313              
314             =head2 get_all_syslog_aliases()
315              
316             Returns a hash reference mapping all aliases to their canonical names.
317              
318             my $aliases = get_all_syslog_aliases();
319             # $aliases = { 'crit' => 'critical', 'err' => 'error', ... }
320              
321             =cut
322              
323             sub get_all_syslog_aliases {
324 1     1 1 4824 my %aliases;
325              
326 1         8 for my $alias (keys %SYSLOG_LEVELS) {
327 16         120 my $numeric = $SYSLOG_LEVELS{$alias};
328 16         256 my $canonical = $SYSLOG_NAMES{$numeric};
329              
330             # Only include if it's not the canonical name
331 16 100       126 if ($alias ne $canonical) {
332 8         27 $aliases{$alias} = $canonical;
333             }
334             }
335              
336 1         10 return \%aliases;
337             }
338              
339             =head2 compare_syslog_levels($level1, $level2)
340              
341             Compares two syslog levels and returns -1, 0, or 1 similar to cmp.
342             Accepts both numeric and string levels.
343              
344             my $cmp = compare_syslog_levels('error', 'warning'); # Returns -1 (error < warning)
345             my $cmp = compare_syslog_levels(2, 4); # Returns -1 (critical < warning)
346              
347             =cut
348              
349             sub compare_syslog_levels {
350 14     14 1 12694 my ($level1, $level2) = @_;
351              
352 14 100 100     90 unless (defined $level1 && defined $level2) {
353 2         25 croak 'compare_syslog_levels: both levels are required';
354             }
355              
356             # Convert both to numeric for comparison
357 12 100       100 my $num1 = looks_like_number($level1) ? int($level1) : get_syslog_level($level1);
358 12 100       104 my $num2 = looks_like_number($level2) ? int($level2) : get_syslog_level($level2);
359              
360 12         113 return $num1 <=> $num2;
361             }
362              
363             # Export control
364             our @EXPORT = qw(
365             $EMERGENCY $ALERT $CRITICAL $ERROR $WARNING $NOTICE $INFORMATIONAL $DEBUG
366             %syslog_values %SYSLOG_LEVELS
367             );
368              
369             our @EXPORT_OK = qw(
370             get_syslog_level get_syslog_name is_valid_syslog_level is_valid_syslog_number
371             get_syslog_description get_all_syslog_levels get_all_syslog_numbers
372             get_all_syslog_aliases compare_syslog_levels
373             %SYSLOG_NAMES %SYSLOG_DESCRIPTIONS %SYSLOG_LEVELS
374             $SYSLOG_EMERGENCY $SYSLOG_ALERT $SYSLOG_CRITICAL $SYSLOG_ERROR
375             $SYSLOG_WARNING $SYSLOG_NOTICE $SYSLOG_INFO $SYSLOG_DEBUG
376             $EMERGENCY $ALERT $CRITICAL $ERROR $WARNING $NOTICE $INFORMATIONAL $DEBUG
377             %syslog_values
378             );
379              
380             our %EXPORT_TAGS = (
381             'all' => \@EXPORT_OK,
382             'constants' => [qw($EMERGENCY $ALERT $CRITICAL $ERROR $WARNING $NOTICE $INFORMATIONAL $DEBUG)],
383             'functions' => [qw(get_syslog_level get_syslog_name is_valid_syslog_level is_valid_syslog_number
384             get_syslog_description get_all_syslog_levels get_all_syslog_numbers
385             get_all_syslog_aliases compare_syslog_levels)],
386             'hashes' => [qw(%SYSLOG_LEVELS %SYSLOG_NAMES %SYSLOG_DESCRIPTIONS)],
387             'rfc3164' => [qw($SYSLOG_EMERGENCY $SYSLOG_ALERT $SYSLOG_CRITICAL $SYSLOG_ERROR
388             $SYSLOG_WARNING $SYSLOG_NOTICE $SYSLOG_INFO $SYSLOG_DEBUG)],
389             );
390              
391             # Validate module consistency at compile time
392             BEGIN {
393             # Ensure all level numbers have corresponding names
394 3     3   21 for my $num (keys %SYSLOG_NAMES) {
395 0 0       0 die "Missing description for level $num" unless exists $SYSLOG_DESCRIPTIONS{$num};
396             }
397              
398             # Ensure all level names resolve to valid numbers
399 3         248 for my $name (keys %SYSLOG_LEVELS) {
400 0         0 my $num = $SYSLOG_LEVELS{$name};
401 0 0       0 die "Level '$name' maps to invalid number $num" unless exists $SYSLOG_NAMES{$num};
402             }
403             }
404              
405             =encoding utf-8
406              
407             =head1 FORMAL SPECIFICATION
408              
409             [LEVEL_NAME, LEVEL_NUMBER, DESCRIPTION]
410              
411             SyslogLevel ::= 0..7
412              
413             ValidLevelNames == {
414             emergency, alert, critical, error, warning, notice, info, debug,
415             emerg, crit, err, warn, trace, panic, fatal, informational
416             }
417              
418             LevelMapping == ValidLevelNames ⤇ SyslogLevel
419             ReverseLevelMapping == SyslogLevel ⤇ LEVEL_NAME
420             DescriptionMapping == SyslogLevel ⤇ DESCRIPTION
421              
422             │ dom(LevelMapping) = ValidLevelNames
423             │ ran(LevelMapping) = 0..7
424             │ dom(ReverseLevelMapping) = 0..7
425             │ ran(ReverseLevelMapping) ⊆ ValidLevelNames
426             │ dom(DescriptionMapping) = 0..7
427              
428             get_syslog_level: LEVEL_NAME → SyslogLevel
429             get_syslog_name: SyslogLevel → LEVEL_NAME
430             is_valid_syslog_level: LEVEL_NAME → ℙ
431             is_valid_syslog_number: ℤ → ℙ
432              
433             ∀ name: LEVEL_NAME •
434             name ∈ ValidLevelNames ⇔ is_valid_syslog_level(name)
435              
436             ∀ num: ℤ •
437             num ∈ 0..7 ⇔ is_valid_syslog_number(num)
438              
439             ∀ name: ValidLevelNames •
440             get_syslog_name(get_syslog_level(name)) ∈ ValidLevelNames
441              
442             ∀ level: SyslogLevel •
443             get_syslog_level(get_syslog_name(level)) = level
444              
445             =head1 AUTHOR
446              
447             Nigel Horne, C<< >>
448              
449             =head1 BUGS
450              
451             =head1 SEE ALSO
452              
453             =over 4
454              
455             =item * L
456              
457             =back
458              
459             =head1 SUPPORT
460              
461             This module is provided as-is without any warranty.
462              
463             Please report any bugs or feature requests to C,
464             or through the web interface at
465             L.
466             I will be notified, and then you'll
467             automatically be notified of progress on your bug as I make changes.
468              
469             You can find documentation for this module with the perldoc command.
470              
471             perldoc Readonly::Values::Syslog
472              
473             You can also look for information at:
474              
475             =over 4
476              
477             =item * MetaCPAN
478              
479             L
480              
481             =item * RT: CPAN's request tracker
482              
483             L
484              
485             =item * CPAN Testers' Matrix
486              
487             L
488              
489             =item * CPAN Testers Dependencies
490              
491             L
492              
493             =back
494              
495             =head1 LICENSE AND COPYRIGHT
496              
497             Copyright 2025 Nigel Horne.
498              
499             Usage is subject to licence terms.
500              
501             The licence terms of this software are as follows:
502              
503             =over 4
504              
505             =item * Personal single user, single computer use: GPL2
506              
507             =item * All other users (including Commercial, Charity, Educational, Government)
508             must apply in writing for a licence for use from Nigel Horne at the
509             above e-mail.
510              
511             =back
512              
513             =cut
514              
515             1;