/* * Copyright Andrey Semashev 2007 - 2014. * 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 #include #include #include #include #include #include #include #include #include #if !defined(BOOST_LOG_NO_ASIO) #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #if !defined(BOOST_LOG_NO_THREADS) #include #include #endif #ifdef BOOST_LOG_USE_NATIVE_SYSLOG #include #endif // BOOST_LOG_USE_NATIVE_SYSLOG #include 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]; std::size_t packet_size = 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 ); 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 #endif // !defined(BOOST_LOG_WITHOUT_SYSLOG)