package Log::Syslog::Abstract;
use warnings;
use strict;
use Carp;

use 5.006;
use vars qw( $VERSION );
$VERSION = '1.101';

use Sub::Exporter -setup => {
	exports => [
		qw( openlog syslog closelog ),
	],
	generator => \&_build_log_methods,
};

my %log_subs;
sub _build_log_methods
{
	my($args) = @_;

	if( ! exists $log_subs{$args->{name}} ) {
		# Try Unix::Syslog first, then Sys::Syslog
		eval qq{use Unix::Syslog qw( :macros ); }; ## no critic (StringyEval)
		if( ! $@ ) {  ## no critic (PunctuationVars)
			@log_subs{'openlog','syslog','closelog'} = _wrap_for_unix_syslog();
		} else {
			eval qq{use Sys::Syslog ();}; ## no critic (StringyEval)
			if( ! $@ ) {  ## no critic (PunctuationVars)
				@log_subs{'openlog','syslog','closelog'} = _wrap_for_sys_syslog();
			} else {
				croak q{Unable to detect either Unix::Syslog or Sys::Syslog};
			}
		}
	}

	return $log_subs{$args->{name}};
}

sub _wrap_for_unix_syslog
{

	my $openlog = sub {
		my ($id, $flags, $facility) = @_;

		## no critic (ProhibitPostfixControls)
		croak q{first argument must be an identifier string} unless defined $id;
		croak q{second argument must be flag string} unless defined $flags;
		croak q{third argument must be a facility string} unless defined $facility;

		my $numeric_flags    = _convert_flags( $flags );
		my $numeric_facility = _convert_facility( $facility );

		return Unix::Syslog::openlog( $id, $numeric_flags, $numeric_facility);
	};

	my $syslog = sub {
		my $facility = shift;
		my $numeric_facility = _convert_facility( $facility );
		return Unix::Syslog::syslog( $numeric_facility, @_);
	};

	my $closelog = \&Unix::Syslog::closelog;

	return ($openlog, $syslog, $closelog);
}

sub _wrap_for_sys_syslog
{

	my $openlog  = sub {
		if( $Sys::Syslog::VERSION < 0.16 ) {
			# Older Sys::Syslog versions still need
			# setlogsock().  RHEL5 still ships with 0.13 :(
			Sys::Syslog::setlogsock([ 'unix', 'tcp', 'udp' ]);
		}
		return Sys::Syslog::openlog(@_);
	};
	my $syslog   = sub {
		return Sys::Syslog::syslog(@_);
	};
	my $closelog = sub {
		return Sys::Syslog::closelog(@_);
	};

	return ($openlog, $syslog, $closelog);
}

{
	my $flag_map;

	sub _convert_flags
	{
		my($flags) = @_;

		if( ! defined $flag_map ) {
			$flag_map = _make_flag_map();
		}

		my $num = 0;
		foreach my $thing (split(/,/, $flags)) {
			if ( ! exists $flag_map->{$thing} ) {
				next;
			}
			$num |= $flag_map->{$thing};
		}
		return $num;
	}

	sub _make_flag_map
	{
		return {
			pid     => Unix::Syslog::LOG_PID(),
			ndelay  => Unix::Syslog::LOG_NDELAY(),
		};
	}
}

{
	my $fac_map;

	sub _convert_facility
	{
		my($facility) = @_;

		if( ! defined $fac_map ) {
			$fac_map = _make_fac_map();
		}

		my $num = 0;
		foreach my $thing (split(/\|/, $facility)) {
			if ( ! exists $fac_map->{$thing} ) {
				next;
			}
			$num |= $fac_map->{$thing};
		}
		return $num;

	}

	my %special = (
		error => 'err',
		panic => 'emerg',
	);

	# Some of the Unix::Syslog 'macros' tag exports aren't
	# constants, so we need to ignore them if found.
	my %blacklisted = map { $_ => 1 } qw(
		LOG_MASK
		LOG_UPTO
		LOG_PRI
		LOG_MAKEPRI
		LOG_FAC
	);

	sub _make_fac_map
	{
		my %map;

		# Ugh.  Make sure we map only the available constants
		# on this platform.  Some are not defined properly on
		# all platforms.
		foreach my $constant ( grep { /^LOG_/ && !exists $blacklisted{$_} } @{ $Unix::Syslog::EXPORT_TAGS{macros}} ) {
			my $name = lc $constant;
			$name =~ s/^log_//;

			my $value = eval "Unix::Syslog::$constant()";
			if( defined $value ) {
				$map{$name} = $value;
			}
		}

		# Some strings supported by Sys::Syslog don't
		# correspond to a Unix::Syslog LOG_XXXX constant.
		while( my($new_key, $existing_key) = each %special ) {
			$map{$new_key} = $map{$existing_key};
		}

		return \%map;
	}
}

1;
__END__

=head1 NAME

Log::Syslog::Abstract - Use any available syslog API

=head1 VERSION

Version 1.000

=head1 SYNOPSIS

    use Log::Syslog::Abstract qw(openlog syslog closelog);

    openlog( 'myapp', 'pid,ndelay', 'local0' );
    ...
    syslog('info', '%s: %s', 'Something bad happened', $!);
    ...
    closelog();

=head1 DESCRIPTION

This module provides the bare minimum common API to L<Unix::Syslog> and
L<Sys::Syslog>, using whichever one happens to be available.

=head1 FUNCTIONS

=head2 openlog ( $ident, $options, $facility )

Opens a connection to the system logger.

I<$ident> is an identifier string that syslog will include in every
message.  It is normally set to the process name.

I<$options> is a comma-separated list of options.  Valid options are:

=over 4

=item ndelay

Don't delay open until first syslog() call

=item pid

Log the process ID with each message

=back

I<$facility> is a string indicating the syslog facility to be used.  Valid values are:

=over 4

=item auth

=item authpriv

=item cron

=item daemon

=item ftp

=item kern

=item lpr

=item mail

=item mark

=item news

=item security

=item syslog

=item user

=item uucp

=item local0

=item local1

=item local2

=item local3

=item local4

=item local5

=item local6

=item local7

=back

=head2 syslog ( $priority, $format, @args )

Generates a log message and passes it to the appropriate syslog backend.

I<$priority> should be a string containing one of the valid priority names:

=over 4

=item alert

=item crit

=item debug

=item emerg

=item err

=item error

=item info

=item none

=item notice

=item panic

=item warn

=item warning

=back

I<$format> is a format string in the style of printf(3)

I<@args> is a list of values that will replace the placeholders in $format

=head2 closelog ( )

Closes the connection to syslog.

=head1 EXPORT

Nothing is exported by default.  Specify what you need on the use()
line, or call with package-qualified name.

=head1 DEPENDENCIES

L<Sub::Exporter>.

Also, at least one of L<Unix::Syslog> or L<Sys::Syslog> must be
present, or Log::Syslog::Abstract will die at use() time.

=head1 AUTHOR

Dave O'Neill, C<< <dmo at roaringpenguin.com> >>

=head1 BUGS

=over 4

=item *

Currently, no validation is performed on the strings provided for
options, facility names, or message priority.  Bogus data may give
bizzare results.

=back

Please report any bugs or feature requests to
C<bug-log-syslog-abstract at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Log-Syslog-Abstract>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Log::Syslog::Abstract

You can also look for information at:

=over 4

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Log-Syslog-Abstract>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Log-Syslog-Abstract>

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Log-Syslog-Abstract>

=item * Search CPAN

L<http://search.cpan.org/dist/Log-Syslog-Abstract>

=back

=head1 COPYRIGHT & LICENSE

Copyright 2007 Dave O'Neill

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
