1/*
2 * Copyright Andrey Semashev 2007 - 2020.
3 * Distributed under the Boost Software License, Version 1.0.
4 * (See accompanying file LICENSE_1_0.txt or copy at
5 * http://www.boost.org/LICENSE_1_0.txt)
6 */
7/*!
8 * \file syslog_backend.cpp
9 * \author Andrey Semashev
10 * \date 08.01.2008
11 *
12 * \brief This header is the Boost.Log library implementation, see the library documentation
13 * at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
14 */
15
16#include <boost/log/detail/config.hpp>
17
18#ifndef BOOST_LOG_WITHOUT_SYSLOG
19
20#include <ctime>
21#include <algorithm>
22#include <stdexcept>
23#include <boost/limits.hpp>
24#include <boost/assert.hpp>
25#include <boost/core/snprintf.hpp>
26#include <boost/smart_ptr/weak_ptr.hpp>
27#include <boost/smart_ptr/shared_ptr.hpp>
28#include <boost/smart_ptr/make_shared_object.hpp>
29#include <boost/throw_exception.hpp>
30#if !defined(BOOST_LOG_NO_ASIO)
31#include <boost/asio/buffer.hpp>
32#include <boost/asio/socket_base.hpp>
33#include <boost/asio/io_context.hpp>
34#include <boost/asio/ip/udp.hpp>
35#include <boost/asio/ip/address.hpp>
36#include <boost/asio/ip/host_name.hpp>
37#include <boost/asio/ip/resolver_base.hpp>
38#endif
39#include <boost/system/error_code.hpp>
40#include <boost/date_time/c_time.hpp>
41#include <boost/log/sinks/syslog_backend.hpp>
42#include <boost/log/sinks/syslog_constants.hpp>
43#include <boost/log/detail/singleton.hpp>
44#include <boost/log/exceptions.hpp>
45#if !defined(BOOST_LOG_NO_THREADS)
46#include <boost/thread/locks.hpp>
47#include <boost/thread/mutex.hpp>
48#endif
49#include "unique_ptr.hpp"
50
51#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
52#include <syslog.h>
53#endif // BOOST_LOG_USE_NATIVE_SYSLOG
54
55#include <boost/log/detail/header.hpp>
56
57namespace boost {
58
59BOOST_LOG_OPEN_NAMESPACE
60
61namespace sinks {
62
63namespace syslog {
64
65 //! The function constructs log record level from an integer
66 BOOST_LOG_API level make_level(int lev)
67 {
68 if (BOOST_UNLIKELY(static_cast< unsigned int >(lev) >= 8u))
69 BOOST_THROW_EXCEPTION(std::out_of_range("syslog level value is out of range"));
70 return static_cast< level >(lev);
71 }
72
73 //! The function constructs log source facility from an integer
74 BOOST_LOG_API facility make_facility(int fac)
75 {
76 if (BOOST_UNLIKELY((static_cast< unsigned int >(fac) & 7u) != 0u
77 || static_cast< unsigned int >(fac) > (23u * 8u)))
78 {
79 BOOST_THROW_EXCEPTION(std::out_of_range("syslog facility code value is out of range"));
80 }
81 return static_cast< facility >(fac);
82 }
83
84} // namespace syslog
85
86////////////////////////////////////////////////////////////////////////////////
87//! Syslog sink backend implementation
88////////////////////////////////////////////////////////////////////////////////
89struct syslog_backend::implementation
90{
91#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
92 struct native;
93#endif // BOOST_LOG_USE_NATIVE_SYSLOG
94#if !defined(BOOST_LOG_NO_ASIO)
95 struct udp_socket_based;
96#endif
97
98 //! Level mapper
99 severity_mapper_type m_LevelMapper;
100
101 //! Logging facility (portable or native, depending on the backend implementation)
102 const int m_Facility;
103
104 //! Constructor
105 explicit implementation(int facility) :
106 m_Facility(facility)
107 {
108 }
109 //! Virtual destructor
110 virtual ~implementation() {}
111
112 //! The method sends the formatted message to the syslog host
113 virtual void send(syslog::level lev, string_type const& formatted_message) = 0;
114};
115
116
117////////////////////////////////////////////////////////////////////////////////
118// Native syslog API support
119////////////////////////////////////////////////////////////////////////////////
120
121#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
122
123BOOST_LOG_ANONYMOUS_NAMESPACE {
124
125 //! Syslog service initializer (implemented as a weak singleton)
126#if !defined(BOOST_LOG_NO_THREADS)
127 class native_syslog_initializer :
128 private log::aux::lazy_singleton< native_syslog_initializer, mutex >
129#else
130 class native_syslog_initializer
131#endif
132 {
133#if !defined(BOOST_LOG_NO_THREADS)
134 friend class log::aux::lazy_singleton< native_syslog_initializer, mutex >;
135 typedef log::aux::lazy_singleton< native_syslog_initializer, mutex > mutex_holder;
136#endif
137
138 private:
139 /*!
140 * \brief Application identification string
141 *
142 * \note We have to keep it as an immutable member because some syslog implementations (e.g. glibc)
143 * do not deep-copy the ident string to internal storage when \c openlog is called
144 * and instead save a pointer to the user-provided string. This means the user-provided
145 * string needs to remain accessible for the whole duration of logging.
146 *
147 * https://github.com/boostorg/log/issues/97
148 * https://sourceware.org/bugzilla/show_bug.cgi?id=25442
149 */
150 const std::string m_Ident;
151
152 public:
153 native_syslog_initializer(std::string const& ident, int facility) :
154 m_Ident(ident)
155 {
156 ::openlog(ident: (m_Ident.empty() ? static_cast< const char* >(NULL) : m_Ident.c_str()), option: 0, facility: facility);
157 }
158 ~native_syslog_initializer()
159 {
160 ::closelog();
161 }
162
163 static shared_ptr< native_syslog_initializer > get_instance(std::string const& ident, int facility)
164 {
165#if !defined(BOOST_LOG_NO_THREADS)
166 lock_guard< mutex > lock(mutex_holder::get());
167#endif
168 static weak_ptr< native_syslog_initializer > instance;
169 shared_ptr< native_syslog_initializer > p(instance.lock());
170 if (!p)
171 {
172 p = boost::make_shared< native_syslog_initializer >(args: ident, args&: facility);
173 instance = p;
174 }
175 return p;
176 }
177
178 BOOST_DELETED_FUNCTION(native_syslog_initializer(native_syslog_initializer const&))
179 BOOST_DELETED_FUNCTION(native_syslog_initializer& operator= (native_syslog_initializer const&))
180 };
181
182} // namespace
183
184struct syslog_backend::implementation::native :
185 public implementation
186{
187 //! Reference to the syslog service initializer
188 const shared_ptr< native_syslog_initializer > m_pSyslogInitializer;
189
190 //! Constructor
191 native(syslog::facility const& fac, std::string const& ident) :
192 implementation(convert_facility(fac)),
193 m_pSyslogInitializer(native_syslog_initializer::get_instance(ident, facility: this->m_Facility))
194 {
195 }
196
197 //! The method sends the formatted message to the syslog host
198 void send(syslog::level lev, string_type const& formatted_message) BOOST_OVERRIDE
199 {
200 int native_level;
201 switch (lev)
202 {
203 case syslog::emergency:
204 native_level = LOG_EMERG; break;
205 case syslog::alert:
206 native_level = LOG_ALERT; break;
207 case syslog::critical:
208 native_level = LOG_CRIT; break;
209 case syslog::error:
210 native_level = LOG_ERR; break;
211 case syslog::warning:
212 native_level = LOG_WARNING; break;
213 case syslog::notice:
214 native_level = LOG_NOTICE; break;
215 case syslog::debug:
216 native_level = LOG_DEBUG; break;
217 default:
218 native_level = LOG_INFO; break;
219 }
220
221 ::syslog(pri: this->m_Facility | native_level, fmt: "%s", formatted_message.c_str());
222 }
223
224private:
225 //! The function converts portable facility codes to the native codes
226 static int convert_facility(syslog::facility const& fac)
227 {
228 // POSIX does not specify anything except for LOG_USER and LOG_LOCAL*
229 #ifndef LOG_KERN
230 #define LOG_KERN LOG_USER
231 #endif
232 #ifndef LOG_DAEMON
233 #define LOG_DAEMON LOG_KERN
234 #endif
235 #ifndef LOG_MAIL
236 #define LOG_MAIL LOG_USER
237 #endif
238 #ifndef LOG_AUTH
239 #define LOG_AUTH LOG_DAEMON
240 #endif
241 #ifndef LOG_SYSLOG
242 #define LOG_SYSLOG LOG_DAEMON
243 #endif
244 #ifndef LOG_LPR
245 #define LOG_LPR LOG_DAEMON
246 #endif
247 #ifndef LOG_NEWS
248 #define LOG_NEWS LOG_USER
249 #endif
250 #ifndef LOG_UUCP
251 #define LOG_UUCP LOG_USER
252 #endif
253 #ifndef LOG_CRON
254 #define LOG_CRON LOG_DAEMON
255 #endif
256 #ifndef LOG_AUTHPRIV
257 #define LOG_AUTHPRIV LOG_AUTH
258 #endif
259 #ifndef LOG_FTP
260 #define LOG_FTP LOG_DAEMON
261 #endif
262
263 static const int native_facilities[24] =
264 {
265 LOG_KERN,
266 LOG_USER,
267 LOG_MAIL,
268 LOG_DAEMON,
269 LOG_AUTH,
270 LOG_SYSLOG,
271 LOG_LPR,
272 LOG_NEWS,
273 LOG_UUCP,
274 LOG_CRON,
275 LOG_AUTHPRIV,
276 LOG_FTP,
277
278 // reserved values
279 LOG_USER,
280 LOG_USER,
281 LOG_USER,
282 LOG_USER,
283
284 LOG_LOCAL0,
285 LOG_LOCAL1,
286 LOG_LOCAL2,
287 LOG_LOCAL3,
288 LOG_LOCAL4,
289 LOG_LOCAL5,
290 LOG_LOCAL6,
291 LOG_LOCAL7
292 };
293
294 std::size_t n = static_cast< unsigned int >(fac) / 8u;
295 BOOST_ASSERT(n < sizeof(native_facilities) / sizeof(*native_facilities));
296 return native_facilities[n];
297 }
298};
299
300#endif // BOOST_LOG_USE_NATIVE_SYSLOG
301
302
303////////////////////////////////////////////////////////////////////////////////
304// Socket-based implementation
305////////////////////////////////////////////////////////////////////////////////
306
307#if !defined(BOOST_LOG_NO_ASIO)
308
309BOOST_LOG_ANONYMOUS_NAMESPACE {
310
311 //! The shared UDP socket
312 struct syslog_udp_socket
313 {
314 private:
315 //! The socket primitive
316 asio::ip::udp::socket m_Socket;
317
318 public:
319 //! The constructor creates a socket bound to the specified local address and port
320 explicit syslog_udp_socket(asio::io_context& io_ctx, asio::ip::udp const& protocol, asio::ip::udp::endpoint const& local_address) :
321 m_Socket(io_ctx)
322 {
323 m_Socket.open(protocol);
324 m_Socket.set_option(asio::socket_base::reuse_address(true));
325 m_Socket.bind(endpoint: local_address);
326 }
327 //! The destructor closes the socket
328 ~syslog_udp_socket()
329 {
330 boost::system::error_code ec;
331 m_Socket.shutdown(what: asio::socket_base::shutdown_both, ec);
332 m_Socket.close(ec);
333 }
334
335 //! The method sends the syslog message to the specified endpoint
336 void send_message(int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message);
337
338 BOOST_DELETED_FUNCTION(syslog_udp_socket(syslog_udp_socket const&))
339 BOOST_DELETED_FUNCTION(syslog_udp_socket& operator= (syslog_udp_socket const&))
340 };
341
342 //! The class contains the UDP service for syslog sockets to function
343 class syslog_udp_service :
344 public log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >
345 {
346 friend class log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > >;
347 typedef log::aux::lazy_singleton< syslog_udp_service, shared_ptr< syslog_udp_service > > base_type;
348
349 public:
350 //! The IO context instance
351 asio::io_context m_IOContext;
352 //! The local host name to put into log message
353 std::string m_LocalHostName;
354
355#if !defined(BOOST_LOG_NO_THREADS)
356 //! A synchronization primitive to protect the host name resolver
357 mutex m_Mutex;
358 //! The resolver is used to acquire connection endpoints
359 asio::ip::udp::resolver m_HostNameResolver;
360#endif // !defined(BOOST_LOG_NO_THREADS)
361
362 private:
363 //! Default constructor
364 syslog_udp_service()
365#if !defined(BOOST_LOG_NO_THREADS)
366 : m_HostNameResolver(m_IOContext)
367#endif // !defined(BOOST_LOG_NO_THREADS)
368 {
369 boost::system::error_code err;
370 m_LocalHostName = asio::ip::host_name(ec&: err);
371 }
372 //! Initializes the singleton instance
373 static void init_instance()
374 {
375 base_type::get_instance().reset(p: new syslog_udp_service());
376 }
377 };
378
379 //! The method sends the syslog message to the specified endpoint
380 void syslog_udp_socket::send_message(
381 int pri, const char* local_host_name, asio::ip::udp::endpoint const& target, const char* message)
382 {
383 std::time_t t = std::time(NULL);
384 std::tm ts;
385 std::tm* time_stamp = boost::date_time::c_time::localtime(t: &t, result: &ts);
386
387 // Month will have to be injected separately, as involving locale won't do here
388 static const char months[12][4] =
389 {
390 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
391 };
392
393 // The packet size is mandated in RFC3164, plus one for the terminating zero
394 char packet[1025];
395 int n = boost::core::snprintf
396 (
397 s: packet,
398 maxlen: sizeof(packet),
399 format: "<%d>%s %2d %02d:%02d:%02d %s %s",
400 pri,
401 months[time_stamp->tm_mon],
402 time_stamp->tm_mday,
403 time_stamp->tm_hour,
404 time_stamp->tm_min,
405 time_stamp->tm_sec,
406 local_host_name,
407 message
408 );
409 if (BOOST_LIKELY(n > 0))
410 {
411 std::size_t packet_size = static_cast< std::size_t >(n) >= sizeof(packet) ? sizeof(packet) - 1u : static_cast< std::size_t >(n);
412 m_Socket.send_to(buffers: asio::buffer(data&: packet, max_size_in_bytes: packet_size), destination: target);
413 }
414 }
415
416} // namespace
417
418struct syslog_backend::implementation::udp_socket_based :
419 public implementation
420{
421 //! Protocol to be used
422 asio::ip::udp m_Protocol;
423 //! Pointer to the list of sockets
424 shared_ptr< syslog_udp_service > m_pService;
425 //! Pointer to the socket being used
426 log::aux::unique_ptr< syslog_udp_socket > m_pSocket;
427 //! The target host to send packets to
428 asio::ip::udp::endpoint m_TargetHost;
429
430 //! Constructor
431 explicit udp_socket_based(syslog::facility const& fac, asio::ip::udp const& protocol) :
432 implementation(fac),
433 m_Protocol(protocol),
434 m_pService(syslog_udp_service::get())
435 {
436 if (m_Protocol == asio::ip::udp::v4())
437 {
438 m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v4(0x7F000001), 514); // 127.0.0.1:514
439 }
440 else
441 {
442 // ::1, port 514
443 asio::ip::address_v6::bytes_type addr;
444 std::fill_n(first: addr.data(), n: addr.size() - 1u, value: static_cast< unsigned char >(0u));
445 addr[addr.size() - 1u] = 1u;
446 m_TargetHost = asio::ip::udp::endpoint(asio::ip::address_v6(addr), 514);
447 }
448 }
449
450 //! The method sends the formatted message to the syslog host
451 void send(syslog::level lev, string_type const& formatted_message) BOOST_OVERRIDE
452 {
453 if (!m_pSocket.get())
454 {
455 asio::ip::udp::endpoint any_local_address(m_Protocol, 0u);
456 m_pSocket.reset(p: new syslog_udp_socket(m_pService->m_IOContext, m_Protocol, any_local_address));
457 }
458
459 m_pSocket->send_message(
460 pri: this->m_Facility | static_cast< int >(lev),
461 local_host_name: m_pService->m_LocalHostName.c_str(),
462 target: m_TargetHost,
463 message: formatted_message.c_str());
464 }
465};
466
467#endif // !defined(BOOST_LOG_NO_ASIO)
468
469////////////////////////////////////////////////////////////////////////////////
470// Sink backend implementation
471////////////////////////////////////////////////////////////////////////////////
472BOOST_LOG_API syslog_backend::syslog_backend()
473{
474 construct(args: log::aux::empty_arg_list());
475}
476
477//! Destructor
478BOOST_LOG_API syslog_backend::~syslog_backend()
479{
480 delete m_pImpl;
481}
482
483//! The method installs the function object that maps application severity levels to Syslog levels
484BOOST_LOG_API void syslog_backend::set_severity_mapper(severity_mapper_type const& mapper)
485{
486 m_pImpl->m_LevelMapper = mapper;
487}
488
489//! The method writes the message to the sink
490BOOST_LOG_API void syslog_backend::consume(record_view const& rec, string_type const& formatted_message)
491{
492 m_pImpl->send(
493 lev: m_pImpl->m_LevelMapper.empty() ? syslog::info : m_pImpl->m_LevelMapper(rec),
494 formatted_message);
495}
496
497
498//! The method creates the backend implementation
499BOOST_LOG_API void syslog_backend::construct(syslog::facility fac, syslog::impl_types use_impl, ip_versions ip_version, std::string const& ident)
500{
501#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
502 if (use_impl == syslog::native)
503 {
504 typedef implementation::native native_impl;
505 m_pImpl = new native_impl(fac, ident);
506 return;
507 }
508#endif // BOOST_LOG_USE_NATIVE_SYSLOG
509
510#if !defined(BOOST_LOG_NO_ASIO)
511 typedef implementation::udp_socket_based udp_socket_based_impl;
512 asio::ip::udp protocol = asio::ip::udp::v4();
513 switch (ip_version)
514 {
515 case v4:
516 break;
517 case v6:
518 protocol = asio::ip::udp::v6();
519 break;
520 default:
521 BOOST_LOG_THROW_DESCR(setup_error, "Incorrect IP version specified");
522 }
523
524 m_pImpl = new udp_socket_based_impl(fac, protocol);
525#endif
526}
527
528#if !defined(BOOST_LOG_NO_ASIO)
529
530//! The method sets the local address which log records will be sent from.
531BOOST_LOG_API void syslog_backend::set_local_address(std::string const& addr, unsigned short port)
532{
533#if !defined(BOOST_LOG_NO_THREADS)
534 typedef implementation::udp_socket_based udp_socket_based_impl;
535 if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
536 {
537 char service_name[std::numeric_limits< unsigned int >::digits10 + 3];
538 boost::core::snprintf(s: service_name, maxlen: sizeof(service_name), format: "%u", static_cast< unsigned int >(port));
539
540 asio::ip::udp::endpoint local_address;
541 {
542 lock_guard< mutex > lock(impl->m_pService->m_Mutex);
543 asio::ip::udp::resolver::results_type results = impl->m_pService->m_HostNameResolver.resolve
544 (
545 protocol: impl->m_Protocol,
546 host: addr,
547 service: service_name,
548 resolve_flags: asio::ip::resolver_base::address_configured | asio::ip::resolver_base::passive
549 );
550
551 local_address = *results.cbegin();
552 }
553
554 impl->m_pSocket.reset(p: new syslog_udp_socket(impl->m_pService->m_IOContext, impl->m_Protocol, local_address));
555 }
556#else
557 // Boost.ASIO requires threads for the host name resolver,
558 // so without threads we simply assume the string already contains IP address
559 set_local_address(boost::asio::ip::address::from_string(addr), port);
560#endif // !defined(BOOST_LOG_NO_THREADS)
561}
562//! The method sets the local address which log records will be sent from.
563BOOST_LOG_API void syslog_backend::set_local_address(boost::asio::ip::address const& addr, unsigned short port)
564{
565 typedef implementation::udp_socket_based udp_socket_based_impl;
566 if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
567 {
568 if ((impl->m_Protocol == asio::ip::udp::v4() && !addr.is_v4()) || (impl->m_Protocol == asio::ip::udp::v6() && !addr.is_v6()))
569 BOOST_LOG_THROW_DESCR(setup_error, "Incorrect IP version specified in the local address");
570
571 impl->m_pSocket.reset(p: new syslog_udp_socket(
572 impl->m_pService->m_IOContext, impl->m_Protocol, asio::ip::udp::endpoint(addr, port)));
573 }
574}
575
576//! The method sets the address of the remote host where log records will be sent to.
577BOOST_LOG_API void syslog_backend::set_target_address(std::string const& addr, unsigned short port)
578{
579#if !defined(BOOST_LOG_NO_THREADS)
580 typedef implementation::udp_socket_based udp_socket_based_impl;
581 if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
582 {
583 char service_name[std::numeric_limits< unsigned int >::digits10 + 3];
584 boost::core::snprintf(s: service_name, maxlen: sizeof(service_name), format: "%u", static_cast< unsigned int >(port));
585
586 asio::ip::udp::endpoint remote_address;
587 {
588 lock_guard< mutex > lock(impl->m_pService->m_Mutex);
589 asio::ip::udp::resolver::results_type results = impl->m_pService->m_HostNameResolver.resolve
590 (
591 protocol: impl->m_Protocol,
592 host: addr,
593 service: service_name,
594 resolve_flags: asio::ip::resolver_query_base::address_configured
595 );
596
597 remote_address = *results.cbegin();
598 }
599
600 impl->m_TargetHost = remote_address;
601 }
602#else
603 // Boost.ASIO requires threads for the host name resolver,
604 // so without threads we simply assume the string already contains IP address
605 set_target_address(boost::asio::ip::address::from_string(addr), port);
606#endif // !defined(BOOST_LOG_NO_THREADS)
607}
608//! The method sets the address of the remote host where log records will be sent to.
609BOOST_LOG_API void syslog_backend::set_target_address(boost::asio::ip::address const& addr, unsigned short port)
610{
611 typedef implementation::udp_socket_based udp_socket_based_impl;
612 if (udp_socket_based_impl* impl = dynamic_cast< udp_socket_based_impl* >(m_pImpl))
613 {
614 if ((impl->m_Protocol == asio::ip::udp::v4() && !addr.is_v4()) || (impl->m_Protocol == asio::ip::udp::v6() && !addr.is_v6()))
615 BOOST_LOG_THROW_DESCR(setup_error, "Incorrect IP version specified in the target address");
616
617 impl->m_TargetHost = asio::ip::udp::endpoint(addr, port);
618 }
619}
620
621#endif // !defined(BOOST_LOG_NO_ASIO)
622
623} // namespace sinks
624
625BOOST_LOG_CLOSE_NAMESPACE // namespace log
626
627} // namespace boost
628
629#include <boost/log/detail/footer.hpp>
630
631#endif // !defined(BOOST_LOG_WITHOUT_SYSLOG)
632

source code of boost/libs/log/src/syslog_backend.cpp