File Coverage

blib/lib/No/Worries/Syslog.pm
Criterion Covered Total %
statement 58 96 60.4
branch 10 34 29.4
condition 0 3 0.0
subroutine 13 22 59.0
pod 8 8 100.0
total 89 163 54.6


line stmt bran cond sub pod time code
1             #+##############################################################################
2             # #
3             # File: No/Worries/Syslog.pm #
4             # #
5             # Description: syslog handling without worries #
6             # #
7             #-##############################################################################
8              
9             #
10             # module definition
11             #
12              
13             package No::Worries::Syslog;
14 1     1   70482 use strict;
  1         11  
  1         24  
15 1     1   4 use warnings;
  1         2  
  1         18  
16 1     1   16 use 5.005; # need the four-argument form of substr()
  1         3  
17             our $VERSION = "1.6";
18             our $REVISION = sprintf("%d.%02d", q$Revision: 1.24 $ =~ /(\d+)\.(\d+)/);
19              
20             #
21             # used modules
22             #
23              
24 1     1   6 use Encode qw();
  1         2  
  1         25  
25 1     1   340 use No::Worries::Die qw(dief);
  1         3  
  1         6  
26 1     1   7 use No::Worries::Export qw(export_control);
  1         2  
  1         5  
27 1     1   436 use No::Worries::Log qw();
  1         3  
  1         31  
28 1     1   6 use No::Worries::String qw(string_trim);
  1         3  
  1         7  
29 1     1   6 use Params::Validate qw(validate :types);
  1         2  
  1         122  
30 1     1   549 use Sys::Syslog qw(openlog closelog syslog);
  1         10255  
  1         78  
31 1     1   459 use URI::Escape qw(uri_escape);
  1         1257  
  1         1059  
32              
33             #
34             # global variables
35             #
36              
37             our($MaximumLength, $SplitLines);
38              
39             #
40             # open a "connection" to syslog via openlog()
41             #
42              
43             my %syslog_open_options = (
44             ident => { optional => 1, type => SCALAR, regex => qr/^[\w\-\.\/]+$/ },
45             option => { optional => 1, type => SCALAR, regex => qr/^\w+(,\w+)*$/ },
46             facility => { optional => 1, type => SCALAR, regex => qr/^\w+$/ },
47             );
48              
49             sub syslog_open (@) {
50 0     0 1 0 my(%option);
51              
52 0 0       0 %option = validate(@_, \%syslog_open_options) if @_;
53             # defaults which may differ from Sys::Syslog
54 0 0       0 $option{option} = "ndelay,pid" unless $option{option};
55             # we do not allow "nofatal" as this clashes with our error handling
56 0 0       0 $option{option} =~ s/\bnofatal\b//g if $option{option};
57             # simply call openlog() now...
58 0         0 eval { openlog($option{ident}, $option{option}, $option{facility}) };
  0         0  
59 0 0       0 dief("cannot openlog(): %s", $@) if $@;
60             }
61              
62             #
63             # close a "connection" to syslog via closelog()
64             #
65              
66             sub syslog_close () {
67             # simply call closelog()
68 0     0 1 0 eval { closelog() };
  0         0  
69 0 0       0 dief("cannot closelog(): %s", $@) if $@;
70             }
71              
72             #
73             # sanitize a string so that it can safely be given to syslog
74             #
75              
76             sub syslog_sanitize ($) {
77 10     10 1 7466 my($string) = @_;
78 10         18 my($flags, $tmp);
79              
80 10         13 $flags = "";
81             # 1: try to UTF-8 encode it if it has the UTF-8 flag set
82 10 100       33 if (Encode::is_utf8($string)) {
83             # we use Encode::FB_DEFAULT to replace invalid characters
84 3         4 local $@ = ""; # preserve $@!
85 3         10 $tmp = Encode::encode("UTF-8", $string, Encode::FB_DEFAULT);
86 3 100       237 unless ($tmp eq $string) {
87             # encoded is indeed different, use it
88 2         5 $string = $tmp;
89 2         3 $flags .= "U";
90             }
91             }
92             # 2: silently trim trailing spaces and replace tabs
93 10         88 $string =~ s/\s+$//;
94 10         19 $string =~ s/\t/ /g;
95             # 3: try to URI escape non-printable characters plus # % \ `
96 10         26 $tmp = uri_escape($string, q{\x00-\x1f\x7f-\xff\x23\x25\x5c\x60});
97 10 100       855 unless ($tmp eq $string) {
98             # escaped is indeed different, use it
99 5         7 $string = $tmp;
100 5         9 $flags .= "E";
101             }
102             # 4: truncate if it is too long, taking into acount the possible flags
103 10         31 $tmp = length($string) - $MaximumLength + 4;
104 10 100       23 if ($tmp > 0) {
105 1         16 substr($string, $MaximumLength - 4, $tmp, "");
106 1         3 $flags .= "T";
107             }
108             # 5: append the flags to keep track of what happened to the string
109 10 100       21 $string .= "#$flags" if $flags;
110             # that should be enough...
111 10         25 return($string);
112             }
113              
114             #
115             # handy wrappers around syslog()
116             #
117              
118             sub _syslog_any ($$@) {
119 0     0   0 my($priority, $prefix, $message, @arguments) = @_;
120 0         0 my($separator, $string);
121              
122 0 0       0 $message = sprintf($message, @arguments) if @arguments;
123 0         0 $message = string_trim($message);
124 0         0 $separator = " ";
125 0 0 0     0 if ($SplitLines and $message =~ /\n/) {
126             # multiple syslog entries
127 0         0 foreach my $line (split(/\n/, $message)) {
128 0         0 $string = syslog_sanitize("[$prefix]$separator$line");
129 0         0 eval { syslog($priority, $string) };
  0         0  
130 0 0       0 dief("cannot syslog(): %s", $@) if $@;
131 0         0 $separator = "+";
132             }
133             } else {
134             # one syslog entry
135 0         0 $string = syslog_sanitize("[$prefix]$separator$message");
136 0         0 eval { syslog($priority, $string) };
  0         0  
137 0 0       0 dief("cannot syslog(): %s", $@) if $@;
138             }
139             }
140              
141 0     0 1 0 sub syslog_error ($@) { _syslog_any("err", "ERROR", @_) }
142 0     0 1 0 sub syslog_warning ($@) { _syslog_any("warning", "WARNING", @_) }
143 0     0 1 0 sub syslog_info ($@) { _syslog_any("info", "INFO", @_) }
144 0     0 1 0 sub syslog_debug ($@) { _syslog_any("debug", "DEBUG", @_) }
145              
146             #
147             # No::Worries::Log-compatible handler
148             #
149              
150             sub log2syslog ($) {
151 0     0 1 0 my($info) = @_;
152              
153 0 0       0 if ($info->{level} eq "error") {
    0          
    0          
154 0         0 syslog_error($info->{message});
155             } elsif ($info->{level} eq "warning") {
156 0         0 syslog_warning($info->{message});
157             } elsif ($info->{level} eq "info") {
158 0         0 syslog_info($info->{message});
159             } else {
160 0         0 syslog_debug($info->{message}); # for debug _and_ trace
161             }
162 0         0 return(1);
163             }
164              
165             #
166             # module initialization
167             #
168              
169             $MaximumLength = 1000;
170             $SplitLines = 1;
171              
172             #
173             # export control
174             #
175              
176             sub import : method {
177 1     1   9 my($pkg, %exported);
178              
179 1         3 $pkg = shift(@_);
180 1         9 grep($exported{$_}++,
181             map("syslog_$_", qw(open close sanitize error warning info debug)));
182 1     0   5 $exported{"log2syslog"} = sub { $No::Worries::Log::Handler = \&log2syslog };
  0         0  
183 1         6 export_control(scalar(caller()), $pkg, \%exported, @_);
184             }
185              
186             1;
187              
188             __DATA__