2
0
mirror of https://github.com/boostorg/log.git synced 2026-01-19 04:22:09 +00:00
Files
log/src/core.cpp
Andrey Semashev f01996cfd3 Removed dependency on Boost.Random.
Boost.Random imposes excessive compiler requirements that prevents
Boost.Log from being built and tested with 32-bit gcc 8 on MinGW-w64.
We only need a fast RNG to quickly shuffle sinks in case of thread
contention in the core.

Use a local implementation of xorshift64 for such an RNG, which should
be even faster than taus88 and have sufficiently good quality. Also,
use std::random_device to initialize the PRNG. Keep the clock/thread_id
based initialization as a backup, in case if std::random_device fails.

Refs https://github.com/boostorg/random/issues/131.
2025-06-14 20:19:55 +03:00

783 lines
24 KiB
C++

/*
* Copyright Andrey Semashev 2007 - 2025.
* 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 core.cpp
* \author Andrey Semashev
* \date 19.04.2007
*
* \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.
*/
#include <boost/log/detail/config.hpp>
#include <cstddef>
#include <new>
#include <chrono>
#include <memory>
#include <vector>
#include <random>
#include <algorithm>
#include <boost/cstdint.hpp>
#include <boost/assert.hpp>
#include <boost/core/invoke_swap.hpp>
#include <boost/filesystem/path.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/range/iterator_range_core.hpp>
#include <boost/move/core.hpp>
#include <boost/move/utility_core.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/core/record.hpp>
#include <boost/log/core/record_view.hpp>
#include <boost/log/sinks/sink.hpp>
#include <boost/log/attributes/attribute_value_set.hpp>
#include <boost/log/detail/singleton.hpp>
#if !defined(BOOST_LOG_NO_THREADS)
#include <boost/memory_order.hpp>
#include <boost/atomic/atomic.hpp>
#include <boost/thread/tss.hpp>
#include <boost/log/detail/locks.hpp>
#include <boost/log/detail/light_rw_mutex.hpp>
#include <boost/log/detail/thread_id.hpp>
#endif
#include "xorshift.hpp"
#include "default_sink.hpp"
#include "stateless_allocator.hpp"
#include "alignment_gap_between.hpp"
#include <boost/log/detail/header.hpp>
namespace boost {
BOOST_LOG_OPEN_NAMESPACE
namespace aux {
BOOST_LOG_ANONYMOUS_NAMESPACE {
//! Sequence shuffling algorithm. Very similar to std::random_shuffle, used for forward portability with compilers that removed it from the standard library (C++17).
template< typename Iterator, typename RandomNumberGenerator >
inline void random_shuffle(Iterator begin, Iterator end, RandomNumberGenerator& rng)
{
Iterator it = begin;
++it;
while (it != end)
{
Iterator where = begin + rng() % (it - begin + 1u);
if (where != it)
boost::core::invoke_swap(*where, *it);
++it;
}
}
} // namespace
} // namespace aux
//! Private record data information, with core-specific structures
struct record_view::private_data :
public public_data
{
//! Underlying memory allocator
typedef boost::log::aux::stateless_allocator< char > stateless_allocator;
//! Sink pointer type
typedef weak_ptr< sinks::sink > sink_ptr;
//! Iterator range with pointers to the accepting sinks
typedef iterator_range< sink_ptr* > sink_list;
private:
//! Number of sinks accepting the record
uint32_t m_accepting_sink_count;
//! Maximum number of sinks accepting the record
const uint32_t m_accepting_sink_capacity;
//! The flag indicates that the record has to be detached from the current thread
bool m_detach_from_thread_needed;
private:
//! Initializing constructor
private_data(BOOST_RV_REF(attribute_value_set) values, uint32_t capacity) BOOST_NOEXCEPT :
public_data(boost::move(values)),
m_accepting_sink_count(0),
m_accepting_sink_capacity(capacity),
m_detach_from_thread_needed(false)
{
}
public:
//! Creates the object with the specified capacity
static private_data* create(BOOST_RV_REF(attribute_value_set) values, uint32_t capacity)
{
private_data* p = reinterpret_cast< private_data* >(stateless_allocator().allocate
(
sizeof(private_data) +
boost::log::aux::alignment_gap_between< private_data, sink_ptr >::value +
capacity * sizeof(sink_ptr)
));
new (p) private_data(boost::move(values), capacity);
return p;
}
//! Destroys the object and frees the underlying storage
void destroy() BOOST_NOEXCEPT
{
sink_ptr* psink = begin();
for (uint32_t i = 0u, n = m_accepting_sink_count; i < n; ++i)
{
psink[i].~sink_ptr();
}
const uint32_t capacity = m_accepting_sink_capacity;
this->~private_data();
stateless_allocator().deallocate
(
reinterpret_cast< stateless_allocator::pointer >(this),
sizeof(private_data) +
boost::log::aux::alignment_gap_between< private_data, sink_ptr >::value +
capacity * sizeof(sink_ptr)
);
}
//! Returns iterator range with the pointers to the accepting sinks
sink_list get_accepting_sinks() BOOST_NOEXCEPT
{
sink_ptr* p = begin();
return sink_list(p, p + m_accepting_sink_count);
}
//! Adds an accepting sink
void push_back_accepting_sink(shared_ptr< sinks::sink > const& sink)
{
BOOST_ASSERT(m_accepting_sink_count < m_accepting_sink_capacity);
sink_ptr* p = begin() + m_accepting_sink_count;
new (p) sink_ptr(sink);
++m_accepting_sink_count;
m_detach_from_thread_needed |= sink->is_cross_thread();
}
//! Returns the number of accepting sinks
uint32_t accepting_sink_count() const BOOST_NOEXCEPT { return m_accepting_sink_count; }
//! Returns the flag indicating whether it is needed to detach the record from the current thread
bool is_detach_from_thread_needed() const BOOST_NOEXCEPT { return m_detach_from_thread_needed; }
BOOST_DELETED_FUNCTION(private_data(private_data const&))
BOOST_DELETED_FUNCTION(private_data& operator= (private_data const&))
private:
//! Returns a pointer to the first accepting sink
sink_ptr* begin() BOOST_NOEXCEPT
{
return reinterpret_cast< sink_ptr* >
(
reinterpret_cast< char* >(this) +
sizeof(private_data) +
boost::log::aux::alignment_gap_between< private_data, sink_ptr >::value
);
}
};
//! Destructor
BOOST_LOG_API void record_view::public_data::destroy(const public_data* p) BOOST_NOEXCEPT
{
const_cast< private_data* >(static_cast< const private_data* >(p))->destroy();
}
//! The function ensures that the log record does not depend on any thread-specific data.
BOOST_LOG_API record_view record::lock()
{
BOOST_ASSERT(m_impl != NULL);
record_view::private_data* const impl = static_cast< record_view::private_data* >(m_impl);
if (impl->is_detach_from_thread_needed())
{
attribute_value_set::const_iterator
it = impl->m_attribute_values.begin(),
end = impl->m_attribute_values.end();
for (; it != end; ++it)
{
// Yep, a bit hackish. I'll need a better backdoor to do it gracefully.
const_cast< attribute_value_set::mapped_type& >(it->second).detach_from_thread();
}
}
// Move the implementation to the view
m_impl = NULL;
return record_view(impl);
}
//! Logging system implementation
struct core::implementation :
public log::aux::lazy_singleton<
implementation,
core_ptr
>
{
public:
//! Base type of singleton holder
typedef log::aux::lazy_singleton<
implementation,
core_ptr
> base_type;
#if !defined(BOOST_LOG_NO_THREADS)
//! Read lock type
typedef log::aux::shared_lock_guard< log::aux::light_rw_mutex > scoped_read_lock;
//! Write lock type
typedef log::aux::exclusive_lock_guard< log::aux::light_rw_mutex > scoped_write_lock;
#endif
//! Sinks container type
typedef std::vector< shared_ptr< sinks::sink > > sink_list;
//! Thread-specific data
struct thread_data
{
//! Thread-specific attribute set
attribute_set m_thread_attributes;
//! Random number generator for shuffling
log::aux::xorshift64 m_rng;
thread_data() : m_rng(get_random_seed())
{
}
private:
//! Creates a seed for RNG
static uint64_t get_random_seed()
{
try
{
std::random_device rng;
std::uniform_int_distribution< uint64_t > distrib(1u);
return distrib(rng);
}
catch (...)
{
uint64_t seed;
do
{
seed = static_cast< uint64_t >(std::chrono::system_clock::now().time_since_epoch().count());
#if !defined(BOOST_LOG_NO_THREADS)
seed += static_cast< uint64_t >(log::aux::this_thread::get_id().native_id()) * UINT64_C(0x2545F4914F6CDD1D);
#endif
}
while (seed == 0u);
return seed;
}
}
};
public:
#if !defined(BOOST_LOG_NO_THREADS)
//! Synchronization mutex
log::aux::light_rw_mutex m_mutex;
#endif
//! List of sinks involved into output
sink_list m_sinks;
//! Default sink
const shared_ptr< sinks::sink > m_default_sink;
//! Global attribute set
attribute_set m_global_attributes;
#if !defined(BOOST_LOG_NO_THREADS)
//! Thread-specific data
thread_specific_ptr< thread_data > m_thread_data;
#if defined(BOOST_LOG_USE_COMPILER_TLS)
//! Cached pointer to the thread-specific data
static BOOST_LOG_TLS thread_data* m_thread_data_cache;
#endif
#else
//! Thread-specific data
std::unique_ptr< thread_data > m_thread_data;
#endif
//! The global state of logging
#if !defined(BOOST_LOG_NO_THREADS)
boost::atomic< bool > m_enabled;
#else
bool m_enabled;
#endif
//! Global filter
filter m_filter;
//! Exception handler
exception_handler_type m_exception_handler;
public:
//! Constructor
implementation() :
m_default_sink(boost::make_shared< sinks::aux::default_sink >()),
m_enabled(true)
{
}
//! Opens a record
template< typename SourceAttributesT >
BOOST_FORCEINLINE record open_record(BOOST_FWD_REF(SourceAttributesT) source_attributes)
{
record_view::private_data* rec_impl = NULL;
bool invoke_exception_handler = true;
// Try a quick win first
#if !defined(BOOST_LOG_NO_THREADS)
if (BOOST_LIKELY(m_enabled.load(boost::memory_order_relaxed)))
#else
if (BOOST_LIKELY(m_enabled))
#endif
try
{
thread_data* tsd = get_thread_data();
#if !defined(BOOST_LOG_NO_THREADS)
// Lock the core to be safe against any attribute or sink set modifications
scoped_read_lock lock(m_mutex);
if (BOOST_LIKELY(m_enabled.load(boost::memory_order_relaxed)))
#endif
{
// Compose a view of attribute values (unfrozen, yet)
attribute_value_set attr_values(boost::forward< SourceAttributesT >(source_attributes), tsd->m_thread_attributes, m_global_attributes);
if (m_filter(attr_values))
{
// The global filter passed, trying the sinks
attribute_value_set* values = &attr_values;
// apply_sink_filter will invoke the exception handler if it has to
invoke_exception_handler = false;
if (!m_sinks.empty())
{
uint32_t remaining_capacity = static_cast< uint32_t >(m_sinks.size());
sink_list::iterator it = m_sinks.begin(), end = m_sinks.end();
for (; it != end; ++it, --remaining_capacity)
{
apply_sink_filter(*it, rec_impl, values, remaining_capacity);
}
}
else
{
// Use the default sink
apply_sink_filter(m_default_sink, rec_impl, values, 1);
}
invoke_exception_handler = true;
if (rec_impl && rec_impl->accepting_sink_count() == 0)
{
// No sinks accepted the record
rec_impl->destroy();
rec_impl = NULL;
goto done;
}
// Some sinks have accepted the record
values->freeze();
}
}
}
catch (...)
{
if (rec_impl)
{
rec_impl->destroy();
rec_impl = NULL;
}
if (invoke_exception_handler)
{
// Lock the core to be safe against any attribute or sink set modifications
BOOST_LOG_EXPR_IF_MT(scoped_read_lock lock(m_mutex);)
if (m_exception_handler.empty())
throw;
m_exception_handler();
}
else
throw;
}
done:
return record(rec_impl);
}
//! The method returns the current thread-specific data
thread_data* get_thread_data()
{
#if defined(BOOST_LOG_USE_COMPILER_TLS)
thread_data* p = m_thread_data_cache;
#else
thread_data* p = m_thread_data.get();
#endif
if (BOOST_UNLIKELY(!p))
{
init_thread_data();
#if defined(BOOST_LOG_USE_COMPILER_TLS)
p = m_thread_data_cache;
#else
p = m_thread_data.get();
#endif
}
return p;
}
//! The function initializes the logging system
static void init_instance()
{
base_type::get_instance().reset(new core());
}
private:
//! The method initializes thread-specific data
void init_thread_data()
{
BOOST_LOG_EXPR_IF_MT(scoped_write_lock lock(m_mutex);)
if (!m_thread_data.get())
{
std::unique_ptr< thread_data > p(new thread_data());
m_thread_data.reset(p.get());
#if defined(BOOST_LOG_USE_COMPILER_TLS)
m_thread_data_cache = p.release();
#else
p.release();
#endif
}
}
//! Invokes sink-specific filter and adds the sink to the record if the filter passes the log record
void apply_sink_filter(shared_ptr< sinks::sink > const& sink, record_view::private_data*& rec_impl, attribute_value_set*& attr_values, uint32_t remaining_capacity)
{
try
{
if (sink->will_consume(*attr_values))
{
// If at least one sink accepts the record, it's time to create it
record_view::private_data* impl = rec_impl;
if (!impl)
{
rec_impl = impl = record_view::private_data::create(boost::move(*attr_values), remaining_capacity);
attr_values = &impl->m_attribute_values;
}
impl->push_back_accepting_sink(sink);
}
}
catch (...)
{
if (m_exception_handler.empty())
throw;
m_exception_handler();
}
}
};
#if defined(BOOST_LOG_USE_COMPILER_TLS)
//! Cached pointer to the thread-specific data
BOOST_LOG_TLS core::implementation::thread_data* core::implementation::m_thread_data_cache = NULL;
#endif // defined(BOOST_LOG_USE_COMPILER_TLS)
//! Logging system constructor
core::core() :
m_impl(new implementation())
{
}
//! Logging system destructor
core::~core()
{
delete m_impl;
m_impl = NULL;
}
//! The method returns a pointer to the logging system instance
BOOST_LOG_API core_ptr core::get()
{
return implementation::get();
}
//! The method enables or disables logging and returns the previous state of logging flag
BOOST_LOG_API bool core::set_logging_enabled(bool enabled)
{
#if !defined(BOOST_LOG_NO_THREADS)
return m_impl->m_enabled.exchange(enabled, boost::memory_order_relaxed);
#else
const bool old_value = m_impl->m_enabled;
m_impl->m_enabled = enabled;
return old_value;
#endif
}
//! The method allows to detect if logging is enabled
BOOST_LOG_API bool core::get_logging_enabled() const
{
#if !defined(BOOST_LOG_NO_THREADS)
return m_impl->m_enabled.load(boost::memory_order_relaxed);
#else
return m_impl->m_enabled;
#endif
}
//! The method adds a new sink
BOOST_LOG_API void core::add_sink(shared_ptr< sinks::sink > const& s)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
implementation::sink_list::iterator it =
std::find(m_impl->m_sinks.begin(), m_impl->m_sinks.end(), s);
if (it == m_impl->m_sinks.end())
m_impl->m_sinks.push_back(s);
}
//! The method removes the sink from the output
BOOST_LOG_API void core::remove_sink(shared_ptr< sinks::sink > const& s)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
implementation::sink_list::iterator it =
std::find(m_impl->m_sinks.begin(), m_impl->m_sinks.end(), s);
if (it != m_impl->m_sinks.end())
m_impl->m_sinks.erase(it);
}
//! The method removes all registered sinks from the output
BOOST_LOG_API void core::remove_all_sinks()
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_sinks.clear();
}
//! The method adds an attribute to the global attribute set
BOOST_LOG_API std::pair< attribute_set::iterator, bool >
core::add_global_attribute(attribute_name const& name, attribute const& attr)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
return m_impl->m_global_attributes.insert(name, attr);
}
//! The method removes an attribute from the global attribute set
BOOST_LOG_API void core::remove_global_attribute(attribute_set::iterator it)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_global_attributes.erase(it);
}
//! The method returns the complete set of currently registered global attributes
BOOST_LOG_API attribute_set core::get_global_attributes() const
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_read_lock lock(m_impl->m_mutex);)
return m_impl->m_global_attributes;
}
//! The method replaces the complete set of currently registered global attributes with the provided set
BOOST_LOG_API void core::set_global_attributes(attribute_set const& attrs)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_global_attributes = attrs;
}
//! The method adds an attribute to the thread-specific attribute set
BOOST_LOG_API std::pair< attribute_set::iterator, bool >
core::add_thread_attribute(attribute_name const& name, attribute const& attr)
{
implementation::thread_data* p = m_impl->get_thread_data();
return p->m_thread_attributes.insert(name, attr);
}
//! The method removes an attribute from the thread-specific attribute set
BOOST_LOG_API void core::remove_thread_attribute(attribute_set::iterator it)
{
implementation::thread_data* p = m_impl->get_thread_data();
p->m_thread_attributes.erase(it);
}
//! The method returns the complete set of currently registered thread-specific attributes
BOOST_LOG_API attribute_set core::get_thread_attributes() const
{
implementation::thread_data* p = m_impl->get_thread_data();
return p->m_thread_attributes;
}
//! The method replaces the complete set of currently registered thread-specific attributes with the provided set
BOOST_LOG_API void core::set_thread_attributes(attribute_set const& attrs)
{
implementation::thread_data* p = m_impl->get_thread_data();
p->m_thread_attributes = attrs;
}
//! An internal method to set the global filter
BOOST_LOG_API void core::set_filter(filter const& filter)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_filter = filter;
}
//! The method removes the global logging filter
BOOST_LOG_API void core::reset_filter()
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_filter.reset();
}
//! The method sets exception handler function
BOOST_LOG_API void core::set_exception_handler(exception_handler_type const& handler)
{
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
m_impl->m_exception_handler = handler;
}
//! The method performs flush on all registered sinks.
BOOST_LOG_API void core::flush()
{
// Acquire exclusive lock to prevent any logging attempts while flushing
BOOST_LOG_EXPR_IF_MT(implementation::scoped_write_lock lock(m_impl->m_mutex);)
if (BOOST_LIKELY(!m_impl->m_sinks.empty()))
{
implementation::sink_list::iterator it = m_impl->m_sinks.begin(), end = m_impl->m_sinks.end();
for (; it != end; ++it)
{
try
{
it->get()->flush();
}
catch (...)
{
if (m_impl->m_exception_handler.empty())
throw;
m_impl->m_exception_handler();
}
}
}
else
{
try
{
m_impl->m_default_sink->flush();
}
catch (...)
{
if (m_impl->m_exception_handler.empty())
throw;
m_impl->m_exception_handler();
}
}
}
//! The method attempts to open a new record to be written
BOOST_LOG_API record core::open_record(attribute_set const& source_attributes)
{
return m_impl->open_record(source_attributes);
}
//! The method attempts to open a new record to be written
BOOST_LOG_API record core::open_record(attribute_value_set const& source_attributes)
{
return m_impl->open_record(source_attributes);
}
//! The method attempts to open a new record to be written.
BOOST_LOG_API record core::open_record_move(attribute_value_set& source_attributes)
{
return m_impl->open_record(boost::move(source_attributes));
}
//! The method pushes the record
BOOST_LOG_API void core::push_record_move(record& rec)
{
try
{
record_view rec_view(rec.lock());
record_view::private_data* data = static_cast< record_view::private_data* >(rec_view.m_impl.get());
typedef std::vector< shared_ptr< sinks::sink > > accepting_sinks_t;
accepting_sinks_t accepting_sinks(data->accepting_sink_count());
shared_ptr< sinks::sink >* const begin = &*accepting_sinks.begin();
shared_ptr< sinks::sink >* end = begin;
// Lock sinks that are willing to consume the record
record_view::private_data::sink_list weak_sinks = data->get_accepting_sinks();
record_view::private_data::sink_list::iterator
weak_it = weak_sinks.begin(),
weak_end = weak_sinks.end();
for (; weak_it != weak_end; ++weak_it)
{
shared_ptr< sinks::sink >& last = *end;
weak_it->lock().swap(last);
if (last.get())
++end;
}
bool shuffled = (end - begin) <= 1;
shared_ptr< sinks::sink >* it = begin;
while (true) try
{
// First try to distribute load between different sinks
bool all_locked = true;
while (it != end)
{
if (it->get()->try_consume(rec_view))
{
--end;
end->swap(*it);
all_locked = false;
}
else
++it;
}
it = begin;
if (begin != end)
{
if (all_locked)
{
// If all sinks are busy then block on any
if (!shuffled)
{
implementation::thread_data* tsd = m_impl->get_thread_data();
log::aux::random_shuffle(begin, end, tsd->m_rng);
shuffled = true;
}
it->get()->consume(rec_view);
--end;
end->swap(*it);
}
}
else
break;
}
catch (...)
{
// Lock the core to be safe against any attribute or sink set modifications
BOOST_LOG_EXPR_IF_MT(implementation::scoped_read_lock lock(m_impl->m_mutex);)
if (m_impl->m_exception_handler.empty())
throw;
m_impl->m_exception_handler();
// Skip the sink that failed to consume the record
--end;
end->swap(*it);
}
}
catch (...)
{
// Lock the core to be safe against any attribute or sink set modifications
BOOST_LOG_EXPR_IF_MT(implementation::scoped_read_lock lock(m_impl->m_mutex);)
if (m_impl->m_exception_handler.empty())
throw;
m_impl->m_exception_handler();
}
}
BOOST_LOG_CLOSE_NAMESPACE // namespace log
} // namespace boost
#include <boost/log/detail/footer.hpp>