#include <boost/log/detail/light_function.hpp>
#include <boost/log/exceptions.hpp>
#include <boost/log/attributes/time_traits.hpp>
+#include <boost/log/sinks/auto_newline_mode.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include "unique_ptr.hpp"
filesystem::rename(from, to, ec);
if (ec)
{
- if (ec.value() == system::errc::cross_device_link)
+ if (BOOST_LIKELY(ec.value() == system::errc::cross_device_link))
{
// Attempt to manually move the file instead
filesystem::copy_file(from, to);
{
for (; n > 0; --n)
{
+ if (it == end)
+ return false;
path_char_type c = *it++;
- if (!traits_t::is_digit(c) || it == end)
+ if (!traits_t::is_digit(c))
return false;
}
return true;
return false;
}
+ //! The function parses file name pattern and splits it into path and filename and creates a function object that will generate the actual filename from the pattern
+ void parse_file_name_pattern(filesystem::path const& pattern, filesystem::path& storage_dir, filesystem::path& file_name_pattern, boost::log::aux::light_function< path_string_type (unsigned int) >& file_name_generator)
+ {
+ // Note: avoid calling Boost.Filesystem functions that involve path::codecvt()
+ // https://svn.boost.org/trac/boost/ticket/9119
+
+ typedef file_char_traits< path_char_type > traits_t;
+
+ file_name_pattern = pattern.filename();
+ path_string_type name_pattern = file_name_pattern.native();
+ storage_dir = filesystem::absolute(pattern.parent_path());
+
+ // Let's try to find the file counter placeholder
+ unsigned int placeholder_count = 0;
+ unsigned int width = 0;
+ bool counter_found = false;
+ path_string_type::size_type counter_pos = 0;
+ path_string_type::const_iterator end = name_pattern.end();
+ path_string_type::const_iterator it = name_pattern.begin();
+
+ do
+ {
+ it = std::find(it, end, traits_t::percent);
+ if (it == end)
+ break;
+ path_string_type::const_iterator placeholder_begin = it++;
+ if (it == end)
+ break;
+ if (*it == traits_t::percent)
+ {
+ // An escaped percent detected
+ ++it;
+ continue;
+ }
+
+ ++placeholder_count;
+
+ if (!counter_found)
+ {
+ path_string_type::const_iterator it2 = it;
+ if (parse_counter_placeholder(it2, end, width))
+ {
+ // We've found the file counter placeholder in the pattern
+ counter_found = true;
+ counter_pos = placeholder_begin - name_pattern.begin();
+ name_pattern.erase(counter_pos, it2 - placeholder_begin);
+ --placeholder_count;
+ it = name_pattern.begin() + counter_pos;
+ end = name_pattern.end();
+ }
+ }
+ }
+ while (it != end);
+
+ // Construct the formatter functor
+ if (placeholder_count > 0)
+ {
+ if (counter_found)
+ {
+ // Both counter and date/time placeholder in the pattern
+ file_name_generator = boost::bind(date_and_time_formatter(),
+ boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1), _1);
+ }
+ else
+ {
+ // Only date/time placeholders in the pattern
+ file_name_generator = boost::bind(date_and_time_formatter(), name_pattern, _1);
+ }
+ }
+ else if (counter_found)
+ {
+ // Only counter placeholder in the pattern
+ file_name_generator = boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1);
+ }
+ else
+ {
+ // No placeholders detected
+ file_name_generator = empty_formatter(name_pattern);
+ }
+ }
+
class file_collector_repository;
// to ensure there's no conflict. I'll need to make this customizable some day.
file_counter_formatter formatter(file_name.size(), 5);
unsigned int n = 0;
- do
+ while (true)
{
- path_string_type alt_file_name = formatter(file_name, n++);
+ path_string_type alt_file_name = formatter(file_name, n);
info.m_Path = m_StorageDir / filesystem::path(alt_file_name);
+ if (!filesystem::exists(info.m_Path))
+ break;
+
+ if (BOOST_UNLIKELY(n == (std::numeric_limits< unsigned int >::max)()))
+ {
+ BOOST_THROW_EXCEPTION(filesystem_error(
+ "Target file exists and an unused fallback file name could not be found",
+ info.m_Path,
+ system::error_code(system::errc::io_error, system::generic_category())));
+ }
+
+ ++n;
}
- while (filesystem::exists(info.m_Path) && n < (std::numeric_limits< unsigned int >::max)());
}
// The directory should have been created in constructor, but just in case it got deleted since then...
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
+ file_list::iterator it = m_Files.begin();
+ const file_list::iterator end = m_Files.end();
+ if (is_in_target_dir)
+ {
+ // If the sink writes log file into the target dir (is_in_target_dir == true), it is possible that after scanning
+ // an old file entry refers to the file that is picked up by the sink for writing. Later on, the sink attempts
+ // to store the file in the storage. At best, this would result in duplicate file entries. At worst, if the storage
+ // limits trigger a deletion and this file get deleted, we may have an entry that refers to no actual file. In any case,
+ // the total size of files in the storage will be incorrect. Here we work around this problem and simply remove
+ // the old file entry without removing the file. The entry will be re-added to the list later.
+ while (it != end)
+ {
+ system::error_code ec;
+ if (filesystem::equivalent(it->m_Path, info.m_Path, ec))
+ {
+ m_TotalSize -= it->m_Size;
+ m_Files.erase(it);
+ break;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ it = m_Files.begin();
+ }
+
// Check if an old file should be erased
uintmax_t free_space = m_MinFreeSpace ? filesystem::space(m_StorageDir).available : static_cast< uintmax_t >(0);
- file_list::iterator it = m_Files.begin(), end = m_Files.end();
while (it != end &&
(m_TotalSize + info.m_Size > m_MaxSize || (m_MinFreeSpace && m_MinFreeSpace > free_space) || m_MaxFiles <= m_Files.size()))
{
file_info& old_info = *it;
- if (filesystem::exists(old_info.m_Path) && filesystem::is_regular_file(old_info.m_Path))
+ system::error_code ec;
+ filesystem::file_status status = filesystem::status(old_info.m_Path, ec);
+
+ if (status.type() == filesystem::regular_file)
{
try
{
counter = NULL;
}
- if (filesystem::exists(dir) && filesystem::is_directory(dir))
+ system::error_code ec;
+ filesystem::file_status status = filesystem::status(dir, ec);
+ if (status.type() == filesystem::directory_file)
{
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
uintmax_t total_size = 0;
for (; it != end; ++it)
{
+ filesystem::directory_entry const& dir_entry = *it;
file_info info;
- info.m_Path = *it;
- if (filesystem::is_regular_file(info.m_Path))
+ info.m_Path = dir_entry.path();
+ status = dir_entry.status(ec);
+ if (status.type() == filesystem::regular_file)
{
// Check that there are no duplicates in the resulting list
struct local
//! Checks if the time point is valid
void check_time_point_validity(unsigned char hour, unsigned char minute, unsigned char second)
{
- if (hour >= 24)
+ if (BOOST_UNLIKELY(hour >= 24))
{
std::ostringstream strm;
strm << "Time point hours value is out of range: " << static_cast< unsigned int >(hour);
BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
}
- if (minute >= 60)
+ if (BOOST_UNLIKELY(minute >= 60))
{
std::ostringstream strm;
strm << "Time point minutes value is out of range: " << static_cast< unsigned int >(minute);
BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
}
- if (second >= 60)
+ if (BOOST_UNLIKELY(second >= 60))
{
std::ostringstream strm;
strm << "Time point seconds value is out of range: " << static_cast< unsigned int >(second);
//! File name generator (according to m_FileNamePattern)
boost::log::aux::light_function< path_string_type (unsigned int) > m_FileNameGenerator;
+ //! Target file name pattern
+ filesystem::path m_TargetFileNamePattern;
+ //! Target directory to store files in
+ filesystem::path m_TargetStorageDir;
+ //! Target file name generator (according to m_TargetFileNamePattern)
+ boost::log::aux::light_function< path_string_type (unsigned int) > m_TargetFileNameGenerator;
+
//! Stored files counter
unsigned int m_FileCounter;
uintmax_t m_FileRotationSize;
//! Time-based rotation predicate
time_based_rotation_predicate m_TimeBasedRotation;
+ //! Indicates whether to append a trailing newline after every log record
+ auto_newline_mode m_AutoNewlineMode;
//! The flag shows if every written record should be flushed
bool m_AutoFlush;
//! The flag indicates whether the final rotation should be performed
bool m_FinalRotationEnabled;
- implementation(uintmax_t rotation_size, bool auto_flush, bool enable_final_rotation) :
+ implementation(uintmax_t rotation_size, auto_newline_mode auto_newline, bool auto_flush, bool enable_final_rotation) :
m_FileOpenMode(std::ios_base::trunc | std::ios_base::out),
m_FileCounter(0),
m_CharactersWritten(0),
m_FileRotationSize(rotation_size),
+ m_AutoNewlineMode(auto_newline),
m_AutoFlush(auto_flush),
m_FinalRotationEnabled(enable_final_rotation)
{
//! Constructor implementation
BOOST_LOG_API void text_file_backend::construct(
filesystem::path const& pattern,
+ filesystem::path const& target_file_name,
std::ios_base::openmode mode,
uintmax_t rotation_size,
time_based_rotation_predicate const& time_based_rotation,
+ auto_newline_mode auto_newline,
bool auto_flush,
bool enable_final_rotation)
{
- m_pImpl = new implementation(rotation_size, auto_flush, enable_final_rotation);
+ m_pImpl = new implementation(rotation_size, auto_newline, auto_flush, enable_final_rotation);
set_file_name_pattern_internal(pattern);
+ set_target_file_name_pattern_internal(target_file_name);
set_time_based_rotation(time_based_rotation);
set_open_mode(mode);
}
m_pImpl->m_AutoFlush = enable;
}
+//! Selects whether a trailing newline should be automatically inserted after every log record.
+BOOST_LOG_API void text_file_backend::set_auto_newline_mode(auto_newline_mode mode)
+{
+ m_pImpl->m_AutoNewlineMode = mode;
+}
+
//! The method writes the message to the sink
BOOST_LOG_API void text_file_backend::consume(record_view const& rec, string_type const& formatted_message)
{
}
m_pImpl->m_File.write(formatted_message.data(), static_cast< std::streamsize >(formatted_message.size()));
- m_pImpl->m_File.put(traits_t::newline);
+ m_pImpl->m_CharactersWritten += formatted_message.size();
- m_pImpl->m_CharactersWritten += formatted_message.size() + 1;
+ if (m_pImpl->m_AutoNewlineMode != disabled_auto_newline)
+ {
+ if (m_pImpl->m_AutoNewlineMode == always_insert || formatted_message.empty() || *formatted_message.rbegin() != traits_t::newline)
+ {
+ m_pImpl->m_File.put(traits_t::newline);
+ ++m_pImpl->m_CharactersWritten;
+ }
+ }
if (m_pImpl->m_AutoFlush)
m_pImpl->m_File.flush();
m_pImpl->m_File.flush();
}
-//! The method sets file name mask
+//! The method sets file name pattern
BOOST_LOG_API void text_file_backend::set_file_name_pattern_internal(filesystem::path const& pattern)
{
- // Note: avoid calling Boost.Filesystem functions that involve path::codecvt()
- // https://svn.boost.org/trac/boost/ticket/9119
-
typedef file_char_traits< path_char_type > traits_t;
- filesystem::path p = pattern;
- if (p.empty())
- p = filesystem::path(traits_t::default_file_name_pattern());
-
- m_pImpl->m_FileNamePattern = p.filename();
- path_string_type name_pattern = m_pImpl->m_FileNamePattern.native();
- m_pImpl->m_StorageDir = filesystem::absolute(p.parent_path());
-
- // Let's try to find the file counter placeholder
- unsigned int placeholder_count = 0;
- unsigned int width = 0;
- bool counter_found = false;
- path_string_type::size_type counter_pos = 0;
- path_string_type::const_iterator end = name_pattern.end();
- path_string_type::const_iterator it = name_pattern.begin();
-
- do
- {
- it = std::find(it, end, traits_t::percent);
- if (it == end)
- break;
- path_string_type::const_iterator placeholder_begin = it++;
- if (it == end)
- break;
- if (*it == traits_t::percent)
- {
- // An escaped percent detected
- ++it;
- continue;
- }
- ++placeholder_count;
-
- if (!counter_found)
- {
- path_string_type::const_iterator it2 = it;
- if (parse_counter_placeholder(it2, end, width))
- {
- // We've found the file counter placeholder in the pattern
- counter_found = true;
- counter_pos = placeholder_begin - name_pattern.begin();
- name_pattern.erase(counter_pos, it2 - placeholder_begin);
- --placeholder_count;
- it = name_pattern.begin() + counter_pos;
- end = name_pattern.end();
- }
- }
- }
- while (it != end);
+ parse_file_name_pattern
+ (
+ !pattern.empty() ? pattern : filesystem::path(traits_t::default_file_name_pattern()),
+ m_pImpl->m_StorageDir,
+ m_pImpl->m_FileNamePattern,
+ m_pImpl->m_FileNameGenerator
+ );
+}
- // Construct the formatter functor
- if (placeholder_count > 0)
- {
- if (counter_found)
- {
- // Both counter and date/time placeholder in the pattern
- m_pImpl->m_FileNameGenerator = boost::bind(date_and_time_formatter(),
- boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1), _1);
- }
- else
- {
- // Only date/time placeholders in the pattern
- m_pImpl->m_FileNameGenerator =
- boost::bind(date_and_time_formatter(), name_pattern, _1);
- }
- }
- else if (counter_found)
+//! The method sets target file name pattern
+BOOST_LOG_API void text_file_backend::set_target_file_name_pattern_internal(filesystem::path const& pattern)
+{
+ if (!pattern.empty())
{
- // Only counter placeholder in the pattern
- m_pImpl->m_FileNameGenerator =
- boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1);
+ parse_file_name_pattern(pattern, m_pImpl->m_TargetStorageDir, m_pImpl->m_TargetFileNamePattern, m_pImpl->m_TargetFileNameGenerator);
}
else
{
- // No placeholders detected
- m_pImpl->m_FileNameGenerator = empty_formatter(name_pattern);
+ m_pImpl->m_TargetStorageDir.clear();
+ m_pImpl->m_TargetFileNamePattern.clear();
+ m_pImpl->m_TargetFileNameGenerator.clear();
}
}
filesystem::path prev_file_name = m_pImpl->m_FileName;
close_file();
+ if (!!m_pImpl->m_TargetFileNameGenerator)
+ {
+ filesystem::path new_file_name;
+ new_file_name = m_pImpl->m_TargetStorageDir / m_pImpl->m_TargetFileNameGenerator(m_pImpl->m_FileCounter);
+
+ if (new_file_name != prev_file_name)
+ {
+ filesystem::create_directories(new_file_name.parent_path());
+ move_file(prev_file_name, new_file_name);
+
+ prev_file_name.swap(new_file_name);
+ }
+ }
+
if (!!m_pImpl->m_pFileCollector)
- m_pImpl->m_pFileCollector->store_file(prev_file_name);
+ {
+ // Check if the file has not been deleted by another process
+ system::error_code ec;
+ filesystem::file_status status = filesystem::status(prev_file_name, ec);
+ if (status.type() == filesystem::regular_file)
+ m_pImpl->m_pFileCollector->store_file(prev_file_name);
+ }
}
//! The method sets the file open mode
//! Performs scanning of the target directory for log files
BOOST_LOG_API uintmax_t text_file_backend::scan_for_files(file::scan_method method, bool update_counter)
{
- if (m_pImpl->m_pFileCollector)
+ if (BOOST_LIKELY(!!m_pImpl->m_pFileCollector))
{
unsigned int* counter = update_counter ? &m_pImpl->m_FileCounter : static_cast< unsigned int* >(NULL);
- return m_pImpl->m_pFileCollector->scan_for_files(method, m_pImpl->m_FileNamePattern, counter);
+ return m_pImpl->m_pFileCollector->scan_for_files
+ (
+ method,
+ m_pImpl->m_TargetFileNamePattern.empty() ? m_pImpl->m_FileNamePattern : m_pImpl->m_TargetFileNamePattern,
+ counter
+ );
}
else
{