| File: | blib/lib/Log/Abstraction/Backend/Syslog.pm |
| Coverage: | 98.6% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package Log::Abstraction::Backend::Syslog; | |||||
| 2 | ||||||
| 3 | # The syslog backend: send messages to syslog through Sys::Syslog | |||||
| 4 | 37 37 37 | 119 12 139 | use strict; | |||
| 5 | 37 37 37 | 26 20 265 | use warnings; | |||
| 6 | ||||||
| 7 | 37 37 37 | 30 18 45 | use parent 'Log::Abstraction::Backend'; | |||
| 8 | ||||||
| 9 | 37 37 37 | 402 15 121 | use Carp (); | |||
| 10 | 37 37 37 | 31 24 228 | use Readonly (); | |||
| 11 | 37 37 37 | 5845 108002 5199 | use Sys::Syslog 0.28 (); | |||
| 12 | ||||||
| 13 | our $VERSION = '0.40'; | |||||
| 14 | ||||||
| 15 | # Default syslog connection parameters | |||||
| 16 | Readonly::Scalar my $DEFAULT_FACILITY => 'local0'; | |||||
| 17 | Readonly::Scalar my $DEFAULT_OPTIONS => 'cons,pid'; | |||||
| 18 | Readonly::Scalar my $DEFAULT_IDENTITY => 'user'; | |||||
| 19 | ||||||
| 20 | # Map internal level names to POSIX syslog priority strings. syslog has no | |||||
| 21 | # priority below debug, so trace shares debug's threshold | |||||
| 22 | Readonly::Hash my %PRIORITY => ( | |||||
| 23 | trace => 'debug', | |||||
| 24 | debug => 'debug', | |||||
| 25 | info => 'info', | |||||
| 26 | notice => 'notice', | |||||
| 27 | warn => 'warning', | |||||
| 28 | warning => 'warning', | |||||
| 29 | error => 'err', | |||||
| 30 | critical => 'crit', | |||||
| 31 | alert => 'alert', | |||||
| 32 | emergency => 'emerg', | |||||
| 33 | ); | |||||
| 34 | ||||||
| 35 | # Number of live Log::Abstraction instances that have opened the | |||||
| 36 | # process-global syslog connection; closelog() is only called when the last | |||||
| 37 | # one is destroyed | |||||
| 38 | my $open_count = 0; | |||||
| 39 | ||||||
| 40 - 71 | =head1 NAME Log::Abstraction::Backend::Syslog - send messages to syslog =head1 VERSION 0.40 =head1 DESCRIPTION Built for the C<syslog> key of a C<logger> hash. The connection is opened (C<setlogsock()> with the hash's other keys, then C<openlog()>) for the first message, and the message sent with C<syslog()> through a C<%s> format, so C<%> sequences in it are logged literally. The caller's hash is not changed: C<facility>, C<level> and C<format> are taken out of a copy, and C<server> renamed C<host>. C<openlog()> and C<closelog()> are process-wide, so the instances that have opened the connection are counted, and C<closelog()> is called when the last one is destroyed. For use by L<Log::Abstraction> only; see L<Log::Abstraction::Backend>. =head1 METHODS =head2 new my $backend = Log::Abstraction::Backend::Syslog->new( syslog => \%syslog, level => $level, format => $format, ); =cut | |||||
| 72 | ||||||
| 73 | sub new { | |||||
| 74 | 26 | 118 | my ($class, %args) = @_; | |||
| 75 | ||||||
| 76 | # Work on a copy so that the caller's hash is not changed | |||||
| 77 | 26 26 | 20 36 | my %sock = %{$args{'syslog'}}; | |||
| 78 | 26 | 42 | my $facility = delete($sock{'facility'}) || $DEFAULT_FACILITY; | |||
| 79 | 26 | 28 | delete @sock{qw(level format)}; | |||
| 80 | ||||||
| 81 | # Accept 'server' as an alias for 'host' (CHI convention) | |||||
| 82 | 26 | 38 | if($sock{'server'}) { | |||
| 83 | 3 | 3 | $sock{'host'} = delete $sock{'server'}; | |||
| 84 | } | |||||
| 85 | ||||||
| 86 | 26 | 63 | return $class->SUPER::new(%args, facility => $facility, sock => \%sock); | |||
| 87 | } | |||||
| 88 | ||||||
| 89 - 93 | =head2 emit $backend->emit($logger, $event); =cut | |||||
| 94 | ||||||
| 95 | sub emit { | |||||
| 96 | 30 | 28 | my ($self, $logger, $event) = @_; | |||
| 97 | ||||||
| 98 | # Open the persistent syslog connection on first use | |||||
| 99 | 30 | 29 | if(!$logger->{'_syslog_opened'}) { | |||
| 100 | 23 | 18 | my $sock = $self->{'sock'}; | |||
| 101 | 23 5 23 | 18 9 25 | Sys::Syslog::setlogsock({ %{$sock} }) if(scalar keys %{$sock}); | |||
| 102 | 23 | 41 | Sys::Syslog::openlog($logger->{'script_name'}, $DEFAULT_OPTIONS, $DEFAULT_IDENTITY); | |||
| 103 | 23 | 43 | $logger->{'_syslog_opened'} = 1; | |||
| 104 | 23 | 13 | $open_count++; | |||
| 105 | } | |||||
| 106 | ||||||
| 107 | # The message is passed through '%s' so that a '%m' (or any other '%' | |||||
| 108 | # sequence) in it is logged literally | |||||
| 109 | 30 | 24 | eval { | |||
| 110 | 30 | 34 | my $priority = $PRIORITY{$event->{'level'}}; | |||
| 111 | 30 | 109 | Sys::Syslog::syslog("$priority|$self->{facility}", '%s', $self->text($event, $event->{'text'})); | |||
| 112 | }; | |||||
| 113 | 30 | 566 | if($@) { | |||
| 114 | 2 | 9 | Carp::carp(ref($logger), ": syslog failed: $@"); | |||
| 115 | } | |||||
| 116 | } | |||||
| 117 | ||||||
| 118 - 125 | =head2 retain_connection Log::Abstraction::Backend::Syslog->retain_connection(); Counts another user of the open connection: a clone of a logger that has opened it. =cut | |||||
| 126 | ||||||
| 127 | sub retain_connection { | |||||
| 128 | 1 | 0 | $open_count++; | |||
| 129 | 1 | 1 | return; | |||
| 130 | } | |||||
| 131 | ||||||
| 132 - 139 | =head2 release_connection Log::Abstraction::Backend::Syslog->release_connection(); A logger that opened the connection is going away; calls C<closelog()> if it was the last. =cut | |||||
| 140 | ||||||
| 141 | sub release_connection { | |||||
| 142 | 26 | 32 | $open_count-- if($open_count > 0); | |||
| 143 | 26 | 46 | Sys::Syslog::closelog() if($open_count == 0); | |||
| 144 | 26 | 82 | return; | |||
| 145 | } | |||||
| 146 | ||||||
| 147 - 163 | =head1 AUTHOR Nigel Horne C<njh@nigelhorne.com> =head1 SEE ALSO L<Log::Abstraction>, L<Log::Abstraction::Backend> =head1 COPYRIGHT AND LICENSE Copyright (C) 2025-2026 Nigel Horne Usage is subject to the GPL2 licence terms. If you use it, please let me know. =cut | |||||
| 164 | ||||||
| 165 | 1; | |||||