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