File Coverage

File:blib/lib/Log/Abstraction/Backend/Syslog.pm
Coverage:98.6%

linestmtbrancondsubtimecode
1package 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
13our $VERSION = '0.40';
14
15# Default syslog connection parameters
16Readonly::Scalar my $DEFAULT_FACILITY => 'local0';
17Readonly::Scalar my $DEFAULT_OPTIONS  => 'cons,pid';
18Readonly::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
22Readonly::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
38my $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
73sub 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
95sub 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
127sub 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
141sub 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
1651;