blob: a866a1e35517c6d41c93e5064802d4c0acd52879 [file] [log] [blame]
/*
* Copyright Andrey Semashev 2007 - 2015.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
/*!
* \file syslog_backend.cpp
* \author Andrey Semashev
* \date 08.01.2008
*
* \brief This header is the Boost.Log library implementation, see the library documentation
* at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
*/
#ifndef BOOST_LOG_WITHOUT_SYSLOG
#include "windows_version.hpp"
#include <boost/log/detail/config.hpp>
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <boost/limits.hpp>
#include <boost/assert.hpp>
#include <boost/smart_ptr/weak_ptr.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/throw_exception.hpp>
#if !defined(BOOST_LOG_NO_ASIO)
#include <boost/asio/buffer.hpp>
#include <boost/asio/socket_base.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/host_name.hpp>
#endif
#include <boost/system/error_code.hpp>
#include <boost/date_time/c_time.hpp>
#include <ctime>
#include <boost/log/sinks/syslog_backend.hpp>
#include <boost/log/detail/singleton.hpp>
#include <boost/log/detail/snprintf.hpp>
#include <boost/log/exceptions.hpp>
#if !defined(BOOST_LOG_NO_THREADS)
#include <boost/thread/locks.hpp>
#include <boost/thread/mutex.hpp>
#endif
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
#include <syslog.h>
#endif // BOOST_LOG_USE_NATIVE_SYSLOG
#include <boost/log/detail/header.hpp>
namespace boost {
BOOST_LOG_OPEN_NAMESPACE
namespace sinks {
namespace syslog {
//! The function constructs log record level from an integer
BOOST_LOG_API level make_level(int lev)
{
if (static_cast< unsigned int >(lev) >= 8)
BOOST_THROW_EXCEPTION(std::out_of_range("syslog level value is out of range"));
return static_cast< level >(lev);
}
//! The function constructs log source facility from an integer
BOOST_LOG_API facility make_facility(int fac)
{
if ((static_cast< unsigned int >(fac) & 7U) != 0
|| static_cast< unsigned int >(fac) > (23U * 8U))
{
BOOST_THROW_EXCEPTION(std::out_of_range("syslog facility code value is out of range"));
}
return static_cast< facility >(fac);
}
} // namespace syslog
////////////////////////////////////////////////////////////////////////////////
//! Syslog sink backend implementation
////////////////////////////////////////////////////////////////////////////////
struct syslog_backend::implementation
{
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
struct native;
#endif // BOOST_LOG_USE_NATIVE_SYSLOG
#if !defined(BOOST_LOG_NO_ASIO)
struct udp_socket_based;
#endif
//! Level mapper
severity_mapper_type m_LevelMapper;
//! Logging facility (portable or native, depending on the backend implementation)
const int m_Facility;
//! Constructor
explicit implementation(int facility) :
m_Facility(facility)
{
}
//! Virtual destructor
virtual ~implementation() {}
//! The method sends the formatted message to the syslog host
virtual void send(syslog::level lev, string_type const& formatted_message) = 0;
};
////////////////////////////////////////////////////////////////////////////////
// Native syslog API support
////////////////////////////////////////////////////////////////////////////////
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
BOOST_LOG_ANONYMOUS_NAMESPACE {
//! Syslog service initializer (implemented as a weak singleton)
#if !defined(BOOST_LOG_NO_THREADS)
class native_syslog_initializer :
private log::aux::lazy_singleton< native_syslog_initializer, mutex >
#else
class native_syslog_initializer
#endif
{
#if !defined(BOOST_LOG_NO_THREADS)
friend class log::aux::lazy_singleton< native_syslog_initializer, mutex >;
typedef log::aux::lazy_singleton< native_syslog_initializer, mutex > mutex_holder;
#endif
public:
native_syslog_initializer(std::string const& ident, int facility)
{
::openlog((ident.empty() ? static_cast< const char* >(NULL) : ident.c_str()), 0, facility);
}
~native_syslog_initializer()
{
::closelog();
}
static shared_ptr< native_syslog_initializer > get_instance(std::string const& ident, int facility)
{
#if !defined(BOOST_LOG_NO_THREADS)
lock_guard< mutex > lock(mutex_holder::get());
#endif
static weak_ptr< native_syslog_initializer > instance;
shared_ptr< native_syslog_initializer > p(instance.lock());
if (!p)
{
p = boost::make_shared< native_syslog_initializer >(ident, facility);
instance = p;
}
return p;
}
};
} // namespace
struct syslog_backend::implementation::native :
public implementation
{
//! Reference to the syslog service initializer
const shared_ptr< native_syslog_initializer > m_pSyslogInitializer;
//! Constructor
native(syslog::facility const& fac, std::string const& ident) :
implementation(convert_facility(fac)),
m_pSyslogInitializer(native_syslog_initializer::get_instance(ident, this->m_Facility))
{
}
//! The method sends the formatted message to the syslog host
void send(syslog::level lev, string_type const& formatted_message)
{
int native_level;
switch (lev)
{
case syslog::emergency:
native_level = LOG_EMERG; break;
case syslog::alert:
native_level = LOG_ALERT; break;
case syslog::critical:
native_level = LOG_CRIT; break;
case syslog::error:
native_level = LOG_ERR; break;
case syslog::warning:
native_level = LOG_WARNING; break;
case syslog::notice:
native_level = LOG_NOTICE; break;
case syslog::debug:
native_level = LOG_DEBUG; break;
default:
native_level = LOG_INFO; break;
}
::syslog(this->m_Facility | native_level, "%s", formatted_message.c_str());
}
private:
//! The function converts portable facility codes to the native codes
static int convert_facility(syslog::facility const& fac)
{
// POSIX does not specify anything except for LOG_USER and LOG_LOCAL*
#ifndef LOG_KERN
#define LOG_KERN LOG_USER
#endif
#ifndef LOG_DAEMON
#define LOG_DAEMON LOG_KERN
#endif
#ifndef LOG_MAIL
#define LOG_MAIL LOG_USER
#endif
#ifndef LOG_AUTH
#define LOG_AUTH LOG_DAEMON
#endif
#ifndef LOG_SYSLOG
#define LOG_SYSLOG LOG_DAEMON
#endif
#ifndef LOG_LPR
#define LOG_LPR LOG_DAEMON
#endif
#ifndef LOG_NEWS
#define LOG_NEWS LOG_USER
#endif
#ifndef LOG_UUCP
#define LOG_UUCP LOG_USER
#endif
#ifndef LOG_CRON
#define LOG_CRON LOG_DAEMON
#endif
#ifndef LOG_AUTHPRIV
#define LOG_AUTHPRIV LOG_AUTH
#endif
#ifndef LOG_FTP
#define LOG_FTP LOG_DAEMON
#endif
static const int native_facilities[24] =
{
LOG_KERN,
LOG_USER,
LOG_MAIL,
LOG_DAEMON,
LOG_AUTH,
LOG_SYSLOG,
LOG_LPR,
LOG_NEWS,
LOG_UUCP,
LOG_CRON,
LOG_AUTHPRIV,
LOG_FTP,
// reserved values
LOG_USER,
LOG_USER,
LOG_USER,
LOG_USER,
LOG_LOCAL0,
LOG_LOCAL1,
LOG_LOCAL2,
LOG_LOCAL3,
LOG_LOCAL4,
LOG_LOCAL5,
LOG_LOCAL6,
LOG_LOCAL7
};
std::size_t n = static_cast< unsigned int >(fac) / 8U;
BOOST_ASSERT(n < sizeof(native_facilities) / sizeof(*native_facilities));
return native_facilities[n];
}
};
#endif // BOOST_LOG_USE_NATIVE_SYSLOG
////////////////////////////////////////////////////////////////////////////////
// Socket-based implementation
////////////////////////////////////////////////////////////////////////////////
#if !defined(BOOST_LOG_NO_ASIO)
BOOST_LOG_ANONYMOUS_NAMESPACE {
//! The shared UDP socket
struct syslog_udp_socket
{
private:
//! The socket primitive
asio::ip::udp::socket m_Socket;
public:
//! The constructor creates a socket bound to the specified local address and port
explicit syslog_udp_socket(asio::io_service& service, asio::ip::udp const& protocol, asio::ip::udp::endpoint const& local_address) :
m_Socket(service)
{
m_Socket.open(protocol);
m_Socket.set_option(asio::socket_base::reuse_address(true));
m_Socket.bind(local_address);
}
//! The destructor closes the socket
~syslog_udp_socket()
{
boost::system::error_code ec;
m_Socket.shutdown(asio::socket_base::shutdown_both, ec);
m_Socket.close(ec);
}
//! The method sends the syslog message to the specified endpoint
void send_message(int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message);
private:
syslog_udp_socket(syslog_udp_socket const&);
syslog_udp_socket& operator= (syslog_udp_socket const&);
};
//! The class contains the UDP service for syslog sockets to function
class syslog_udp_service :
public log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >
{
friend class log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >;
typedef log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > > base_type;
public:
//! The core IO service instance
asio::io_service m_IOService;
//! The local host name to put into log message
std::string m_LocalHostName;
#if !defined(BOOST_LOG_NO_THREADS)
//! A synchronization primitive to protect the host name resolver
mutex m_Mutex;
//! The resolver is used to acquire connection endpoints
asio::ip::udp::resolver m_HostNameResolver;
#endif // !defined(BOOST_LOG_NO_THREADS)
private:
//! Default constructor
syslog_udp_service()
#if !defined(BOOST_LOG_NO_THREADS)
: m_HostNameResolver(m_IOService)
#endif // !defined(BOOST_LOG_NO_THREADS)
{
boost::system::error_code err;
m_LocalHostName = asio::ip::host_name(err);
}
//! Initializes the singleton instance
static void init_instance()
{
base_type::get_instance().reset(new syslog_udp_service());
}
};
//! The method sends the syslog message to the specified endpoint
void syslog_udp_socket::send_message(
int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message)
{
std::time_t t = std::time(NULL);
std::tm ts;
std::tm* time_stamp = boost::date_time::c_time::localtime(&t, &ts);
// Month will have to be injected separately, as involving locale won't do here
static const char months[12][4] =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
// The packet size is mandated in RFC3164, plus one for the terminating zero
char packet[1025];
int n = boost::log::aux::snprintf
(
packet,
sizeof(packet),
"<%d> %s % 2d %02d:%02d:%02d %s %s",
pri,
months[time_stamp->tm_mon],
time_stamp->tm_mday,
time_stamp->tm_hour,
time_stamp->tm_min,
time_stamp->tm_sec,
local_host_name,
message
);
if (n > 0)
{
std::size_t packet_size = static_cast< std::size_t >(n) >= sizeof(packet) ? sizeof(packet) - 1u : static_cast< std::size_t >(n);
m_Socket.send_to(asio::buffer(packet, packet_size), target);
}
}
} // namespace
struct syslog_backend::implementation::udp_socket_based :
public implementation
{
//! Protocol to be used
asio::ip::udp m_Protocol;
//! Pointer to the list of sockets
shared_ptr< syslog_udp_service > m_pService;
//! Pointer to the socket being used
std::auto_ptr< syslog_udp_socket > m_pSocket;
//! The target host to send packets to
asio::ip::udp::endpoint m_TargetHost;
//! Constructor
explicit udp_socket_based(syslog::facility const& fac, asio::ip::udp const& protocol) :
implementation(fac),
m_Protocol(protocol),
m_pService(syslog_udp_service::get())
{
if (m_Protocol == asio::ip::udp::v4())
{
m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v4(0x7F000001), 514); // 127.0.0.1:514
}
else
{
// ::1, port 514
asio::ip::address_v6::bytes_type addr;
std::fill_n(addr.data(), addr.size() - 1, static_cast< unsigned char >(0));
addr[addr.size() - 1] = 1;
m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v6(addr), 514);
}
}
//! The method sends the formatted message to the syslog host
void send(syslog::level lev, string_type const& formatted_message)
{
if (!m_pSocket.get())
{
asio::ip::udp::endpoint any_local_address;
m_pSocket.reset(new syslog_udp_socket(m_pService->m_IOService, m_Protocol, any_local_address));
}
m_pSocket->send_message(
this->m_Facility | static_cast< int >(lev),
m_pService->m_LocalHostName.c_str(),
m_TargetHost,
formatted_message.c_str());
}
};
#endif // !defined(BOOST_LOG_NO_ASIO)
////////////////////////////////////////////////////////////////////////////////
// Sink backend implementation
////////////////////////////////////////////////////////////////////////////////
BOOST_LOG_API syslog_backend::syslog_backend()
{
construct(log::aux::empty_arg_list());
}
//! Destructor
BOOST_LOG_API syslog_backend::~syslog_backend()
{
delete m_pImpl;
}
//! The method installs the function object that maps application severity levels to Syslog levels
BOOST_LOG_API void syslog_backend::set_severity_mapper(severity_mapper_type const& mapper)
{
m_pImpl->m_LevelMapper = mapper;
}
//! The method writes the message to the sink
BOOST_LOG_API void syslog_backend::consume(record_view const& rec, string_type const& formatted_message)
{
m_pImpl->send(
m_pImpl->m_LevelMapper.empty() ? syslog::info : m_pImpl->m_LevelMapper(rec),
formatted_message);
}
//! The method creates the backend implementation
BOOST_LOG_API void syslog_backend::construct(syslog::facility fac, syslog::impl_types use_impl, ip_versions ip_version, std::string const& ident)
{
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
if (use_impl == syslog::native)
{
typedef implementation::native native_impl;
m_pImpl = new native_impl(fac, ident);
return;
}
#endif // BOOST_LOG_USE_NATIVE_SYSLOG
#if !defined(BOOST_LOG_NO_ASIO)
typedef implementation::udp_socket_based udp_socket_based_impl;
switch (ip_version)
{
case v4:
m_pImpl = new udp_socket_based_impl(fac, asio::ip::udp::v4());
break;
case v6:
m_pImpl = new udp_socket_based_impl(fac, asio::ip::udp::v6());
break;
default:
BOOST_LOG_THROW_DESCR(setup_error, "Incorrect IP version specified");
}
#endif
}
#if !defined(BOOST_LOG_NO_ASIO)
//! The method sets the local address which log records will be sent from.
BOOST_LOG_API void syslog_backend::set_local_address(std::string const& addr, unsigned short port)
{
#if !defined(BOOST_LOG_NO_THREADS)
typedef implementation::udp_socket_based udp_socket_based_impl;
if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
{
char service_name[std::numeric_limits< int >::digits10 + 3];
boost::log::aux::snprintf(service_name, sizeof(service_name), "%d", static_cast< int >(port));
asio::ip::udp::resolver::query q(
impl->m_Protocol,
addr,
service_name,
asio::ip::resolver_query_base::address_configured | asio::ip::resolver_query_base::passive);
asio::ip::udp::endpoint local_address;
{
lock_guard< mutex > _(impl->m_pService->m_Mutex);
local_address = *impl->m_pService->m_HostNameResolver.resolve(q);
}
impl->m_pSocket.reset(new syslog_udp_socket(impl->m_pService->m_IOService, impl->m_Protocol, local_address));
}
#else
// Boost.ASIO requires threads for the host name resolver,
// so without threads we simply assume the string already contains IP address
set_local_address(boost::asio::ip::address::from_string(addr), port);
#endif // !defined(BOOST_LOG_NO_THREADS)
}
//! The method sets the local address which log records will be sent from.
BOOST_LOG_API void syslog_backend::set_local_address(boost::asio::ip::address const& addr, unsigned short port)
{
typedef implementation::udp_socket_based udp_socket_based_impl;
if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
{
impl->m_pSocket.reset(new syslog_udp_socket(
impl->m_pService->m_IOService, impl->m_Protocol, asio::ip::udp::endpoint(addr, port)));
}
}
//! The method sets the address of the remote host where log records will be sent to.
BOOST_LOG_API void syslog_backend::set_target_address(std::string const& addr, unsigned short port)
{
#if !defined(BOOST_LOG_NO_THREADS)
typedef implementation::udp_socket_based udp_socket_based_impl;
if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
{
char service_name[std::numeric_limits< int >::digits10 + 3];
boost::log::aux::snprintf(service_name, sizeof(service_name), "%d", static_cast< int >(port));
asio::ip::udp::resolver::query q(impl->m_Protocol, addr, service_name, asio::ip::resolver_query_base::address_configured);
asio::ip::udp::endpoint remote_address;
{
lock_guard< mutex > _(impl->m_pService->m_Mutex);
remote_address = *impl->m_pService->m_HostNameResolver.resolve(q);
}
impl->m_TargetHost = remote_address;
}
#else
// Boost.ASIO requires threads for the host name resolver,
// so without threads we simply assume the string already contains IP address
set_target_address(boost::asio::ip::address::from_string(addr), port);
#endif // !defined(BOOST_LOG_NO_THREADS)
}
//! The method sets the address of the remote host where log records will be sent to.
BOOST_LOG_API void syslog_backend::set_target_address(boost::asio::ip::address const& addr, unsigned short port)
{
typedef implementation::udp_socket_based udp_socket_based_impl;
if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
{
impl->m_TargetHost = asio::ip::udp::endpoint(addr, port);
}
}
#endif // !defined(BOOST_LOG_NO_ASIO)
} // namespace sinks
BOOST_LOG_CLOSE_NAMESPACE // namespace log
} // namespace boost
#include <boost/log/detail/footer.hpp>
#endif // !defined(BOOST_LOG_WITHOUT_SYSLOG)