2
0
mirror of https://github.com/boostorg/log.git synced 2026-02-08 10:52:17 +00:00
Files
log/src/text_file_backend.cpp
Andrey Semashev 69ec057e7c Added support for appending to files with file counter placeholders in names.
Previously, text_file_backend would generate a new file name when the file
name pattern contained a file counter placeholder, which prevented appending
to the file. Appending was only possible when the active file name was
stable across process restarts (effectively, this prohibited the file counter
to be used in the active file name).

This commit adds support for appending in such configuration. The file
collector has been updated to return the last found file counter during
filesystem scanning, and the sink backend doesn't increment it when the initial
file is opened, when appending is enabled, and when the file to be opened
exists and, if file collector is used, is in the target storage of the file
collector. In all other cases the file counter is incremented, which leaves
the behavior unchanged.

Closes https://github.com/boostorg/log/issues/179.
2022-02-27 00:03:37 +03:00

1698 lines
62 KiB
C++

/*
* Copyright Andrey Semashev 2007 - 2015.
* 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 text_file_backend.cpp
* \author Andrey Semashev
* \date 09.06.2009
*
* \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 <ctime>
#include <cctype>
#include <cwctype>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <list>
#include <string>
#include <locale>
#include <ostream>
#include <sstream>
#include <iterator>
#include <algorithm>
#include <stdexcept>
#include <boost/core/ref.hpp>
#include <boost/bind/bind.hpp>
#include <boost/cstdint.hpp>
#include <boost/optional/optional.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/throw_exception.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/system/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/exception.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/list_hook.hpp>
#include <boost/intrusive/options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/gregorian/gregorian_types.hpp>
#include <boost/spirit/home/qi/numeric/numeric_utils.hpp>
#include <boost/log/detail/singleton.hpp>
#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"
#if !defined(BOOST_LOG_NO_THREADS)
#include <boost/thread/locks.hpp>
#include <boost/thread/mutex.hpp>
#endif // !defined(BOOST_LOG_NO_THREADS)
#include <boost/log/detail/header.hpp>
namespace qi = boost::spirit::qi;
namespace boost {
BOOST_LOG_OPEN_NAMESPACE
namespace sinks {
BOOST_LOG_ANONYMOUS_NAMESPACE {
typedef filesystem::filesystem_error filesystem_error;
//! A possible Boost.Filesystem extension - renames or moves the file to the target storage
inline void move_file(filesystem::path const& from, filesystem::path const& to)
{
#if defined(BOOST_WINDOWS_API)
// On Windows MoveFile already does what we need
filesystem::rename(from, to);
#else
// On POSIX rename fails if the target points to a different device
system::error_code ec;
filesystem::rename(from, to, ec);
if (ec)
{
if (BOOST_LIKELY(ec.value() == system::errc::cross_device_link))
{
// Attempt to manually move the file instead
filesystem::copy_file(from, to);
filesystem::remove(from);
}
else
{
BOOST_THROW_EXCEPTION(filesystem_error("failed to move file to another location", from, to, ec));
}
}
#endif
}
typedef filesystem::path::string_type path_string_type;
typedef path_string_type::value_type path_char_type;
//! An auxiliary traits that contain various constants and functions regarding string and character operations
template< typename CharT >
struct file_char_traits;
template< >
struct file_char_traits< char >
{
typedef char char_type;
static const char_type percent = '%';
static const char_type number_placeholder = 'N';
static const char_type day_placeholder = 'd';
static const char_type month_placeholder = 'm';
static const char_type year_placeholder = 'y';
static const char_type full_year_placeholder = 'Y';
static const char_type frac_sec_placeholder = 'f';
static const char_type seconds_placeholder = 'S';
static const char_type minutes_placeholder = 'M';
static const char_type hours_placeholder = 'H';
static const char_type space = ' ';
static const char_type plus = '+';
static const char_type minus = '-';
static const char_type zero = '0';
static const char_type dot = '.';
static const char_type newline = '\n';
static bool is_digit(char c)
{
using namespace std;
return (isdigit(c) != 0);
}
static std::string default_file_name_pattern() { return "%5N.log"; }
};
#ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
const file_char_traits< char >::char_type file_char_traits< char >::percent;
const file_char_traits< char >::char_type file_char_traits< char >::number_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::day_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::month_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::year_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::full_year_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::frac_sec_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::seconds_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::minutes_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::hours_placeholder;
const file_char_traits< char >::char_type file_char_traits< char >::space;
const file_char_traits< char >::char_type file_char_traits< char >::plus;
const file_char_traits< char >::char_type file_char_traits< char >::minus;
const file_char_traits< char >::char_type file_char_traits< char >::zero;
const file_char_traits< char >::char_type file_char_traits< char >::dot;
const file_char_traits< char >::char_type file_char_traits< char >::newline;
#endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
template< >
struct file_char_traits< wchar_t >
{
typedef wchar_t char_type;
static const char_type percent = L'%';
static const char_type number_placeholder = L'N';
static const char_type day_placeholder = L'd';
static const char_type month_placeholder = L'm';
static const char_type year_placeholder = L'y';
static const char_type full_year_placeholder = L'Y';
static const char_type frac_sec_placeholder = L'f';
static const char_type seconds_placeholder = L'S';
static const char_type minutes_placeholder = L'M';
static const char_type hours_placeholder = L'H';
static const char_type space = L' ';
static const char_type plus = L'+';
static const char_type minus = L'-';
static const char_type zero = L'0';
static const char_type dot = L'.';
static const char_type newline = L'\n';
static bool is_digit(wchar_t c)
{
using namespace std;
return (iswdigit(c) != 0);
}
static std::wstring default_file_name_pattern() { return L"%5N.log"; }
};
#ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::percent;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::number_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::day_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::month_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::year_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::full_year_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::frac_sec_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::seconds_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::minutes_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::hours_placeholder;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::space;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::plus;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::minus;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::zero;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::dot;
const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::newline;
#endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
//! Date and time formatter
class date_and_time_formatter
{
public:
typedef path_string_type result_type;
private:
typedef date_time::time_facet< posix_time::ptime, path_char_type > time_facet_type;
private:
mutable time_facet_type m_Facet;
mutable std::basic_ostringstream< path_char_type > m_Stream;
public:
//! Constructor
date_and_time_formatter() : m_Facet(1u)
{
}
//! Copy constructor
date_and_time_formatter(date_and_time_formatter const& that) : m_Facet(1u)
{
}
//! The method formats the current date and time according to the format string str and writes the result into it
path_string_type operator()(path_string_type const& pattern, unsigned int counter) const
{
m_Facet.format(pattern.c_str());
m_Stream.str(path_string_type());
// Note: the regular operator<< fails because std::use_facet fails to find the facet in the locale because
// the facet type in Boost.DateTime has hidden visibility. See this ticket:
// https://svn.boost.org/trac/boost/ticket/11707
std::ostreambuf_iterator< path_char_type > sbuf_it(m_Stream);
m_Facet.put(sbuf_it, m_Stream, m_Stream.fill(), boost::log::attributes::local_time_traits::get_clock());
if (m_Stream.good())
{
return m_Stream.str();
}
else
{
m_Stream.clear();
return pattern;
}
}
BOOST_DELETED_FUNCTION(date_and_time_formatter& operator= (date_and_time_formatter const&))
};
//! The functor formats the file counter into the file name
class file_counter_formatter
{
public:
typedef path_string_type result_type;
private:
//! The position in the pattern where the file counter placeholder is
path_string_type::size_type m_FileCounterPosition;
//! File counter width
std::streamsize m_Width;
//! The file counter formatting stream
mutable std::basic_ostringstream< path_char_type > m_Stream;
public:
//! Initializing constructor
file_counter_formatter(path_string_type::size_type pos, unsigned int width) :
m_FileCounterPosition(pos),
m_Width(width)
{
typedef file_char_traits< path_char_type > traits_t;
m_Stream.fill(traits_t::zero);
}
//! Copy constructor
file_counter_formatter(file_counter_formatter const& that) :
m_FileCounterPosition(that.m_FileCounterPosition),
m_Width(that.m_Width)
{
m_Stream.fill(that.m_Stream.fill());
}
//! The function formats the file counter into the file name
path_string_type operator()(path_string_type const& pattern, unsigned int counter) const
{
path_string_type file_name = pattern;
m_Stream.str(path_string_type());
m_Stream.width(m_Width);
m_Stream << counter;
file_name.insert(m_FileCounterPosition, m_Stream.str());
return file_name;
}
BOOST_DELETED_FUNCTION(file_counter_formatter& operator= (file_counter_formatter const&))
};
//! The function returns the pattern as the file name
class empty_formatter
{
public:
typedef path_string_type result_type;
private:
path_string_type m_Pattern;
public:
//! Initializing constructor
explicit empty_formatter(path_string_type const& pattern) : m_Pattern(pattern)
{
}
//! Copy constructor
empty_formatter(empty_formatter const& that) : m_Pattern(that.m_Pattern)
{
}
//! The function returns the pattern as the file name
path_string_type const& operator() (unsigned int) const
{
return m_Pattern;
}
BOOST_DELETED_FUNCTION(empty_formatter& operator= (empty_formatter const&))
};
//! The function parses the format placeholder for file counter
bool parse_counter_placeholder(path_string_type::const_iterator& it, path_string_type::const_iterator end, unsigned int& width)
{
typedef qi::extract_uint< unsigned int, 10, 1, -1 > width_extract;
typedef file_char_traits< path_char_type > traits_t;
if (it == end)
return false;
path_char_type c = *it;
if (c == traits_t::zero || c == traits_t::space || c == traits_t::plus || c == traits_t::minus)
{
// Skip filler and alignment specification
++it;
if (it == end)
return false;
c = *it;
}
if (traits_t::is_digit(c))
{
// Parse width
if (!width_extract::call(it, end, width))
return false;
if (it == end)
return false;
c = *it;
}
if (c == traits_t::dot)
{
// Skip precision
++it;
while (it != end && traits_t::is_digit(*it))
++it;
if (it == end)
return false;
c = *it;
}
if (c == traits_t::number_placeholder)
{
++it;
return true;
}
return false;
}
//! The function matches the file name and the pattern
bool match_pattern(path_string_type const& file_name, path_string_type const& pattern, unsigned int& file_counter, bool& file_counter_parsed)
{
typedef qi::extract_uint< unsigned int, 10, 1, -1 > file_counter_extract;
typedef file_char_traits< path_char_type > traits_t;
struct local
{
// Verifies that the string contains exactly n digits
static bool scan_digits(path_string_type::const_iterator& it, path_string_type::const_iterator end, std::ptrdiff_t n)
{
for (; n > 0; --n)
{
if (it == end)
return false;
path_char_type c = *it++;
if (!traits_t::is_digit(c))
return false;
}
return true;
}
};
path_string_type::const_iterator
f_it = file_name.begin(),
f_end = file_name.end(),
p_it = pattern.begin(),
p_end = pattern.end();
bool placeholder_expected = false;
while (f_it != f_end && p_it != p_end)
{
path_char_type p_c = *p_it, f_c = *f_it;
if (!placeholder_expected)
{
if (p_c == traits_t::percent)
{
placeholder_expected = true;
++p_it;
}
else if (p_c == f_c)
{
++p_it;
++f_it;
}
else
return false;
}
else
{
switch (p_c)
{
case traits_t::percent: // An escaped '%'
if (p_c == f_c)
{
++p_it;
++f_it;
break;
}
else
return false;
case traits_t::seconds_placeholder: // Date/time components with 2-digits width
case traits_t::minutes_placeholder:
case traits_t::hours_placeholder:
case traits_t::day_placeholder:
case traits_t::month_placeholder:
case traits_t::year_placeholder:
if (!local::scan_digits(f_it, f_end, 2))
return false;
++p_it;
break;
case traits_t::full_year_placeholder: // Date/time components with 4-digits width
if (!local::scan_digits(f_it, f_end, 4))
return false;
++p_it;
break;
case traits_t::frac_sec_placeholder: // Fraction seconds width is configuration-dependent
typedef posix_time::time_res_traits posix_resolution_traits;
if (!local::scan_digits(f_it, f_end, posix_resolution_traits::num_fractional_digits()))
{
return false;
}
++p_it;
break;
default: // This should be the file counter placeholder or some unsupported placeholder
{
path_string_type::const_iterator p = p_it;
unsigned int width = 0;
if (!parse_counter_placeholder(p, p_end, width))
{
BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported placeholder used in pattern for file scanning"));
}
// Find where the file number ends
path_string_type::const_iterator f = f_it;
if (!local::scan_digits(f, f_end, width))
return false;
while (f != f_end && traits_t::is_digit(*f))
++f;
if (!file_counter_extract::call(f_it, f, file_counter))
return false;
file_counter_parsed = true;
p_it = p;
}
break;
}
placeholder_expected = false;
}
}
if (p_it == p_end)
{
if (f_it != f_end)
{
// The actual file name may end with an additional counter
// that is added by the collector in case if file name clash
return local::scan_digits(f_it, f_end, std::distance(f_it, f_end));
}
else
return true;
}
else
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, boost::placeholders::_1), boost::placeholders::_1);
}
else
{
// Only date/time placeholders in the pattern
file_name_generator = boost::bind(date_and_time_formatter(), name_pattern, boost::placeholders::_1);
}
}
else if (counter_found)
{
// Only counter placeholder in the pattern
file_name_generator = boost::bind(file_counter_formatter(counter_pos, width), name_pattern, boost::placeholders::_1);
}
else
{
// No placeholders detected
file_name_generator = empty_formatter(name_pattern);
}
}
class file_collector_repository;
//! Type of the hook used for sequencing file collectors
typedef intrusive::list_base_hook<
intrusive::link_mode< intrusive::safe_link >
> file_collector_hook;
//! Log file collector implementation
class file_collector :
public file::collector,
public file_collector_hook,
public enable_shared_from_this< file_collector >
{
private:
//! Information about a single stored file
struct file_info
{
//! Ordering predicate by timestamp
struct order_by_timestamp
{
typedef bool result_type;
result_type operator()(file_info const& left, file_info const& right) const BOOST_NOEXCEPT
{
return left.m_TimeStamp < right.m_TimeStamp;
}
};
//! Predicate for testing if a file_info refers to a file equivalent to another path
class equivalent_file
{
public:
typedef bool result_type;
private:
filesystem::path const& m_Path;
public:
explicit equivalent_file(filesystem::path const& path) BOOST_NOEXCEPT :
m_Path(path)
{
}
result_type operator()(file_info const& info) const
{
return filesystem::equivalent(info.m_Path, m_Path);
}
};
uintmax_t m_Size;
std::time_t m_TimeStamp;
filesystem::path m_Path;
};
//! A list of the stored files
typedef std::list< file_info > file_list;
//! The string type compatible with the universal path type
typedef filesystem::path::string_type path_string_type;
private:
//! A reference to the repository this collector belongs to
shared_ptr< file_collector_repository > m_pRepository;
#if !defined(BOOST_LOG_NO_THREADS)
//! Synchronization mutex
mutex m_Mutex;
#endif // !defined(BOOST_LOG_NO_THREADS)
//! Total file size upper limit
uintmax_t m_MaxSize;
//! Free space lower limit
uintmax_t m_MinFreeSpace;
//! File count upper limit
uintmax_t m_MaxFiles;
//! The current path at the point when the collector is created
/*
* The special member is required to calculate absolute paths with no
* dependency on the current path for the application, which may change
*/
const filesystem::path m_BasePath;
//! Target directory to store files to
filesystem::path m_StorageDir;
//! The list of stored files
file_list m_Files;
//! Total size of the stored files
uintmax_t m_TotalSize;
public:
//! Constructor
file_collector(
shared_ptr< file_collector_repository > const& repo,
filesystem::path const& target_dir,
uintmax_t max_size,
uintmax_t min_free_space,
uintmax_t max_files);
//! Destructor
~file_collector() BOOST_OVERRIDE;
//! The function stores the specified file in the storage
void store_file(filesystem::path const& file_name) BOOST_OVERRIDE;
//! The function checks if the specified path refers to an existing file in the storage
bool is_in_storage(filesystem::path const& src_path) const BOOST_OVERRIDE;
//! Scans the target directory for the files that have already been stored
file::scan_result scan_for_files(file::scan_method method, filesystem::path const& pattern) BOOST_OVERRIDE;
//! The function updates storage restrictions
void update(uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files);
//! The function checks if the directory is governed by this collector
bool is_governed(filesystem::path const& dir) const
{
return filesystem::equivalent(m_StorageDir, dir);
}
private:
//! Makes relative path absolute with respect to the base path
filesystem::path make_absolute(filesystem::path const& p) const
{
return filesystem::absolute(p, m_BasePath);
}
//! Acquires file name string from the path
static path_string_type filename_string(filesystem::path const& p)
{
return p.filename().string< path_string_type >();
}
};
//! The singleton of the list of file collectors
class file_collector_repository :
public log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > >
{
private:
//! Base type
typedef log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > > base_type;
#if !defined(BOOST_LOG_BROKEN_FRIEND_TEMPLATE_SPECIALIZATIONS)
friend class log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > >;
#else
friend class base_type;
#endif
//! The type of the list of collectors
typedef intrusive::list<
file_collector,
intrusive::base_hook< file_collector_hook >
> file_collectors;
private:
#if !defined(BOOST_LOG_NO_THREADS)
//! Synchronization mutex
mutex m_Mutex;
#endif // !defined(BOOST_LOG_NO_THREADS)
//! The list of file collectors
file_collectors m_Collectors;
public:
//! Finds or creates a file collector
shared_ptr< file::collector > get_collector(
filesystem::path const& target_dir, uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files);
//! Removes the file collector from the list
void remove_collector(file_collector* p);
private:
//! Initializes the singleton instance
static void init_instance()
{
base_type::get_instance() = boost::make_shared< file_collector_repository >();
}
};
//! Constructor
file_collector::file_collector(
shared_ptr< file_collector_repository > const& repo,
filesystem::path const& target_dir,
uintmax_t max_size,
uintmax_t min_free_space,
uintmax_t max_files
) :
m_pRepository(repo),
m_MaxSize(max_size),
m_MinFreeSpace(min_free_space),
m_MaxFiles(max_files),
m_BasePath(filesystem::current_path()),
m_TotalSize(0)
{
m_StorageDir = make_absolute(target_dir);
filesystem::create_directories(m_StorageDir);
}
//! Destructor
file_collector::~file_collector()
{
m_pRepository->remove_collector(this);
}
//! The function stores the specified file in the storage
void file_collector::store_file(filesystem::path const& src_path)
{
// NOTE FOR THE FOLLOWING CODE:
// Avoid using Boost.Filesystem functions that would call path::codecvt(). store_file() can be called
// at process termination, and the global codecvt facet can already be destroyed at this point.
// https://svn.boost.org/trac/boost/ticket/8642
// Let's construct the new file name
file_info info;
info.m_TimeStamp = filesystem::last_write_time(src_path);
info.m_Size = filesystem::file_size(src_path);
const filesystem::path file_name_path = src_path.filename();
path_string_type const& file_name = file_name_path.native();
info.m_Path = m_StorageDir / file_name_path;
// Check if the file is already in the target directory
filesystem::path src_dir = src_path.has_parent_path() ?
filesystem::system_complete(src_path.parent_path()) :
m_BasePath;
const bool is_in_target_dir = filesystem::equivalent(src_dir, m_StorageDir);
if (!is_in_target_dir)
{
if (filesystem::exists(info.m_Path))
{
// If the file already exists, try to mangle the file name
// 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;
while (true)
{
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;
}
}
// The directory should have been created in constructor, but just in case it got deleted since then...
filesystem::create_directories(m_StorageDir);
}
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);
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;
system::error_code ec;
filesystem::file_status status = filesystem::status(old_info.m_Path, ec);
if (status.type() == filesystem::regular_file)
{
try
{
filesystem::remove(old_info.m_Path);
// Free space has to be queried as it may not increase equally
// to the erased file size on compressed filesystems
if (m_MinFreeSpace)
free_space = filesystem::space(m_StorageDir).available;
m_TotalSize -= old_info.m_Size;
it = m_Files.erase(it);
}
catch (system::system_error&)
{
// Can't erase the file. Maybe it's locked? Never mind...
++it;
}
}
else
{
// If it's not a file or is absent, just remove it from the list
m_TotalSize -= old_info.m_Size;
it = m_Files.erase(it);
}
}
if (!is_in_target_dir)
{
// Move/rename the file to the target storage
move_file(src_path, info.m_Path);
}
m_Files.push_back(info);
m_TotalSize += info.m_Size;
}
//! The function checks if the specified path refers to an existing file in the storage
bool file_collector::is_in_storage(filesystem::path const& src_path) const
{
const filesystem::path file_name_path = src_path.filename();
const filesystem::path trg_path = m_StorageDir / file_name_path;
// Check if the file is already in the target directory
system::error_code ec;
filesystem::path src_dir = src_path.has_parent_path() ?
filesystem::system_complete(src_path.parent_path(), ec) :
m_BasePath;
if (ec)
return false;
filesystem::file_status status = filesystem::status(trg_path, ec);
if (ec || status.type() != filesystem::regular_file)
return false;
bool equiv = filesystem::equivalent(src_dir / file_name_path, trg_path, ec);
if (ec)
return false;
return equiv;
}
//! Scans the target directory for the files that have already been stored
file::scan_result file_collector::scan_for_files(file::scan_method method, filesystem::path const& pattern)
{
file::scan_result result;
if (method != file::no_scan)
{
filesystem::path dir = m_StorageDir;
path_string_type mask;
if (method == file::scan_matching)
{
mask = filename_string(pattern);
if (pattern.has_parent_path())
dir = make_absolute(pattern.parent_path());
}
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);)
file_list files;
filesystem::directory_iterator it(dir), end;
uintmax_t total_size = 0u;
for (; it != end; ++it)
{
filesystem::directory_entry const& dir_entry = *it;
file_info info;
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
if (std::find_if(m_Files.begin(), m_Files.end(), file_info::equivalent_file(info.m_Path)) == m_Files.end())
{
// Check that the file name matches the pattern
unsigned int file_number = 0u;
bool file_number_parsed = false;
if (method != file::scan_matching ||
match_pattern(filename_string(info.m_Path), mask, file_number, file_number_parsed))
{
info.m_Size = filesystem::file_size(info.m_Path);
total_size += info.m_Size;
info.m_TimeStamp = filesystem::last_write_time(info.m_Path);
files.push_back(info);
++result.found_count;
// Test that the file_number >= result.last_file_counter accounting for the integer overflow
if (file_number_parsed && (!result.last_file_counter || (file_number - *result.last_file_counter) < ((~0u) ^ ((~0u) >> 1u))))
result.last_file_counter = file_number;
}
}
}
}
// Sort files chronologically
m_Files.splice(m_Files.end(), files);
m_TotalSize += total_size;
m_Files.sort(file_info::order_by_timestamp());
}
}
return result;
}
//! The function updates storage restrictions
void file_collector::update(uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files)
{
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
m_MaxSize = (std::min)(m_MaxSize, max_size);
m_MinFreeSpace = (std::max)(m_MinFreeSpace, min_free_space);
m_MaxFiles = (std::min)(m_MaxFiles, max_files);
}
//! Finds or creates a file collector
shared_ptr< file::collector > file_collector_repository::get_collector(
filesystem::path const& target_dir, uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files)
{
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
file_collectors::iterator it = std::find_if(m_Collectors.begin(), m_Collectors.end(),
boost::bind(&file_collector::is_governed, boost::placeholders::_1, boost::cref(target_dir)));
shared_ptr< file_collector > p;
if (it != m_Collectors.end()) try
{
// This may throw if the collector is being currently destroyed
p = it->shared_from_this();
p->update(max_size, min_free_space, max_files);
}
catch (bad_weak_ptr&)
{
}
if (!p)
{
p = boost::make_shared< file_collector >(
file_collector_repository::get(), target_dir, max_size, min_free_space, max_files);
m_Collectors.push_back(*p);
}
return p;
}
//! Removes the file collector from the list
void file_collector_repository::remove_collector(file_collector* p)
{
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
m_Collectors.erase(m_Collectors.iterator_to(*p));
}
//! Checks if the time point is valid
void check_time_point_validity(unsigned char hour, unsigned char minute, unsigned char second)
{
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 (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 (BOOST_UNLIKELY(second >= 60))
{
std::ostringstream strm;
strm << "Time point seconds value is out of range: " << static_cast< unsigned int >(second);
BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
}
}
} // namespace
namespace file {
namespace aux {
//! Creates and returns a file collector with the specified parameters
BOOST_LOG_API shared_ptr< collector > make_collector(
filesystem::path const& target_dir,
uintmax_t max_size,
uintmax_t min_free_space,
uintmax_t max_files)
{
return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space, max_files);
}
} // namespace aux
//! Creates a rotation time point of every day at the specified time
BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
unsigned char hour,
unsigned char minute,
unsigned char second
) :
m_Day(0),
m_DayKind(not_specified),
m_Hour(hour),
m_Minute(minute),
m_Second(second),
m_Previous(date_time::not_a_date_time)
{
check_time_point_validity(hour, minute, second);
}
//! Creates a rotation time point of each specified weekday at the specified time
BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
date_time::weekdays wday,
unsigned char hour,
unsigned char minute,
unsigned char second
) :
m_Day(static_cast< unsigned char >(wday)),
m_DayKind(weekday),
m_Hour(hour),
m_Minute(minute),
m_Second(second),
m_Previous(date_time::not_a_date_time)
{
check_time_point_validity(hour, minute, second);
}
//! Creates a rotation time point of each specified day of month at the specified time
BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
gregorian::greg_day mday,
unsigned char hour,
unsigned char minute,
unsigned char second
) :
m_Day(static_cast< unsigned char >(mday.as_number())),
m_DayKind(monthday),
m_Hour(hour),
m_Minute(minute),
m_Second(second),
m_Previous(date_time::not_a_date_time)
{
check_time_point_validity(hour, minute, second);
}
//! Checks if it's time to rotate the file
BOOST_LOG_API bool rotation_at_time_point::operator()() const
{
bool result = false;
posix_time::time_duration rotation_time(
static_cast< posix_time::time_duration::hour_type >(m_Hour),
static_cast< posix_time::time_duration::min_type >(m_Minute),
static_cast< posix_time::time_duration::sec_type >(m_Second));
posix_time::ptime now = posix_time::second_clock::local_time();
if (m_Previous.is_special())
{
m_Previous = now;
return false;
}
const bool time_of_day_passed = rotation_time.total_seconds() <= m_Previous.time_of_day().total_seconds();
switch (static_cast< day_kind >(m_DayKind))
{
case not_specified:
{
// The rotation takes place every day at the specified time
gregorian::date previous_date = m_Previous.date();
if (time_of_day_passed)
previous_date += gregorian::days(1);
posix_time::ptime next(previous_date, rotation_time);
result = (now >= next);
}
break;
case weekday:
{
// The rotation takes place on the specified week day at the specified time
gregorian::date previous_date = m_Previous.date(), next_date = previous_date;
int weekday = m_Day, previous_weekday = static_cast< int >(previous_date.day_of_week().as_number());
next_date += gregorian::days(weekday - previous_weekday);
if (weekday < previous_weekday || (weekday == previous_weekday && time_of_day_passed))
{
next_date += gregorian::weeks(1);
}
posix_time::ptime next(next_date, rotation_time);
result = (now >= next);
}
break;
case monthday:
{
// The rotation takes place on the specified day of month at the specified time
gregorian::date previous_date = m_Previous.date();
gregorian::date::day_type monthday = static_cast< gregorian::date::day_type >(m_Day),
previous_monthday = previous_date.day();
gregorian::date next_date(previous_date.year(), previous_date.month(), monthday);
if (monthday < previous_monthday || (monthday == previous_monthday && time_of_day_passed))
{
next_date += gregorian::months(1);
}
posix_time::ptime next(next_date, rotation_time);
result = (now >= next);
}
break;
default:
break;
}
if (result)
m_Previous = now;
return result;
}
//! Checks if it's time to rotate the file
BOOST_LOG_API bool rotation_at_time_interval::operator()() const
{
bool result = false;
posix_time::ptime now = posix_time::second_clock::universal_time();
if (m_Previous.is_special())
{
m_Previous = now;
return false;
}
result = (now - m_Previous) >= m_Interval;
if (result)
m_Previous = now;
return result;
}
} // namespace file
////////////////////////////////////////////////////////////////////////////////
// File sink backend implementation
////////////////////////////////////////////////////////////////////////////////
//! Sink implementation data
struct text_file_backend::implementation
{
//! File name pattern
filesystem::path m_FileNamePattern;
//! Directory to store files in
filesystem::path m_StorageDir;
//! 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;
//! Counter to use in file names
unsigned int m_FileCounter;
//! File open mode
std::ios_base::openmode m_FileOpenMode;
//! Current file name
filesystem::path m_FileName;
//! File stream
filesystem::ofstream m_File;
//! Characters written
uintmax_t m_CharactersWritten;
//! File collector functional object
shared_ptr< file::collector > m_pFileCollector;
//! File open handler
open_handler_type m_OpenHandler;
//! File close handler
close_handler_type m_CloseHandler;
//! The maximum temp file size, in characters written to the stream
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;
//! The flag indicates that \c m_FileCounter is set to the last used counter value
bool m_FileCounterIsLastUsed;
//! The flag indicates whether the next opened file will be the first file opened by this backend
bool m_IsFirstFile;
implementation(uintmax_t rotation_size, auto_newline_mode auto_newline, bool auto_flush, bool enable_final_rotation) :
m_FileCounter(0u),
m_FileOpenMode(std::ios_base::trunc | std::ios_base::out),
m_CharactersWritten(0u),
m_FileRotationSize(rotation_size),
m_AutoNewlineMode(auto_newline),
m_AutoFlush(auto_flush),
m_FinalRotationEnabled(enable_final_rotation),
m_FileCounterIsLastUsed(false),
m_IsFirstFile(true)
{
}
};
//! Constructor. No streams attached to the constructed backend, auto flush feature disabled.
BOOST_LOG_API text_file_backend::text_file_backend()
{
construct(log::aux::empty_arg_list());
}
//! Destructor
BOOST_LOG_API text_file_backend::~text_file_backend()
{
try
{
// Attempt to put the temporary file into storage
if (m_pImpl->m_FinalRotationEnabled && m_pImpl->m_File.is_open() && m_pImpl->m_CharactersWritten > 0)
rotate_file();
}
catch (...)
{
}
delete m_pImpl;
}
//! 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_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);
}
//! The method sets maximum file size.
BOOST_LOG_API void text_file_backend::set_rotation_size(uintmax_t size)
{
m_pImpl->m_FileRotationSize = size;
}
//! The method sets the maximum time interval between file rotations.
BOOST_LOG_API void text_file_backend::set_time_based_rotation(time_based_rotation_predicate const& predicate)
{
m_pImpl->m_TimeBasedRotation = predicate;
}
//! The method allows to enable or disable log file rotation on sink destruction.
BOOST_LOG_API void text_file_backend::enable_final_rotation(bool enable)
{
m_pImpl->m_FinalRotationEnabled = enable;
}
//! Sets the flag to automatically flush write buffers of the file being written after each log record.
BOOST_LOG_API void text_file_backend::auto_flush(bool enable)
{
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)
{
typedef file_char_traits< string_type::value_type > traits_t;
filesystem::path prev_file_name;
bool use_prev_file_name = false;
if (BOOST_UNLIKELY(!m_pImpl->m_File.good()))
{
// The file stream is not operational. One possible reason is that there is no more free space
// on the file system. In this case it is possible that this log record will fail to be written as well,
// leaving the newly created file empty. Eventually this results in lots of empty log files.
// We should take precautions to avoid this. https://svn.boost.org/trac/boost/ticket/11016
prev_file_name = m_pImpl->m_FileName;
close_file();
system::error_code ec;
uintmax_t size = filesystem::file_size(prev_file_name, ec);
if (!!ec || size == 0)
{
// To reuse the empty file avoid re-generating the new file name later
use_prev_file_name = true;
}
else if (!!m_pImpl->m_pFileCollector)
{
// Complete file rotation
m_pImpl->m_pFileCollector->store_file(prev_file_name);
}
}
else if
(
m_pImpl->m_File.is_open() &&
(
m_pImpl->m_CharactersWritten + formatted_message.size() >= m_pImpl->m_FileRotationSize ||
(!m_pImpl->m_TimeBasedRotation.empty() && m_pImpl->m_TimeBasedRotation())
)
)
{
rotate_file();
}
while (!m_pImpl->m_File.is_open())
{
filesystem::path new_file_name;
if (!use_prev_file_name)
{
unsigned int file_counter = m_pImpl->m_FileCounter;
if (BOOST_LIKELY(m_pImpl->m_FileCounterIsLastUsed))
{
// If the sink backend is configured to append to a previously written file, don't
// increment the file counter and try to open the existing file. Only do this if the
// file is not moved to a different storage location by the file collector.
bool increment_file_counter = true;
if (BOOST_UNLIKELY(m_pImpl->m_IsFirstFile && (m_pImpl->m_FileOpenMode & std::ios_base::app) != 0))
{
filesystem::path last_file_name = m_pImpl->m_StorageDir / m_pImpl->m_FileNameGenerator(file_counter);
if (!!m_pImpl->m_pFileCollector)
{
increment_file_counter = !m_pImpl->m_pFileCollector->is_in_storage(last_file_name);
}
else
{
system::error_code ec;
increment_file_counter = filesystem::status(last_file_name, ec).type() != filesystem::regular_file;
}
}
if (BOOST_LIKELY(increment_file_counter))
{
++file_counter;
m_pImpl->m_FileCounter = file_counter;
}
}
else
{
m_pImpl->m_FileCounterIsLastUsed = true;
}
new_file_name = m_pImpl->m_StorageDir / m_pImpl->m_FileNameGenerator(file_counter);
}
else
{
prev_file_name.swap(new_file_name);
}
filesystem::create_directories(new_file_name.parent_path());
m_pImpl->m_File.open(new_file_name, m_pImpl->m_FileOpenMode);
if (BOOST_UNLIKELY(!m_pImpl->m_File.is_open()))
{
BOOST_THROW_EXCEPTION(filesystem_error(
"Failed to open file for writing",
new_file_name,
system::error_code(system::errc::io_error, system::generic_category())));
}
m_pImpl->m_FileName.swap(new_file_name);
m_pImpl->m_IsFirstFile = false;
// Check the file size before invoking the open handler, as it may write more data to the file
m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
if (m_pImpl->m_CharactersWritten + formatted_message.size() >= m_pImpl->m_FileRotationSize)
{
// Avoid running the close handler, as we haven't run the open handler yet
struct close_handler_backup_guard
{
explicit close_handler_backup_guard(close_handler_type& orig_close_handler) BOOST_NOEXCEPT :
m_orig_close_handler(orig_close_handler)
{
orig_close_handler.swap(m_backup_close_handler);
}
~close_handler_backup_guard() BOOST_NOEXCEPT
{
m_orig_close_handler.swap(m_backup_close_handler);
}
private:
close_handler_type& m_orig_close_handler;
close_handler_type m_backup_close_handler;
}
close_handler_guard(m_pImpl->m_CloseHandler);
rotate_file();
continue;
}
if (!m_pImpl->m_OpenHandler.empty())
{
m_pImpl->m_OpenHandler(m_pImpl->m_File);
// Update the size of the written data, but don't rotate the file. If the open handler
// exceeds the file size limit we could end up in an infinite loop, as we are constantly
// rotating the file and immediately exceeding its size limit after the open handler is run.
// Write the log record and then rotate the file upon the next log record.
m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
}
break;
}
m_pImpl->m_File.write(formatted_message.data(), static_cast< std::streamsize >(formatted_message.size()));
m_pImpl->m_CharactersWritten += formatted_message.size();
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();
}
//! The method flushes the currently open log file
BOOST_LOG_API void text_file_backend::flush()
{
if (m_pImpl->m_File.is_open())
m_pImpl->m_File.flush();
}
//! The method sets file name pattern
BOOST_LOG_API void text_file_backend::set_file_name_pattern_internal(filesystem::path const& pattern)
{
typedef file_char_traits< path_char_type > traits_t;
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
);
}
//! 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())
{
parse_file_name_pattern(pattern, m_pImpl->m_TargetStorageDir, m_pImpl->m_TargetFileNamePattern, m_pImpl->m_TargetFileNameGenerator);
}
else
{
m_pImpl->m_TargetStorageDir.clear();
m_pImpl->m_TargetFileNamePattern.clear();
m_pImpl->m_TargetFileNameGenerator.clear();
}
}
//! Closes the currently open file
void text_file_backend::close_file()
{
if (m_pImpl->m_File.is_open())
{
if (!m_pImpl->m_CloseHandler.empty())
{
// Rationale: We should call the close handler even if the stream is !good() because
// writing the footer may not be the only thing the handler does. However, there is
// a chance that the file had become writable since the last failure (e.g. there was
// no space left to write the last record, but it got freed since then), so if the handler
// attempts to write a footer it may succeed now. For this reason we clear the stream state
// and let the handler have a try.
m_pImpl->m_File.clear();
m_pImpl->m_CloseHandler(m_pImpl->m_File);
}
m_pImpl->m_File.close();
}
m_pImpl->m_File.clear();
m_pImpl->m_CharactersWritten = 0;
m_pImpl->m_FileName.clear();
}
//! The method rotates the file
BOOST_LOG_API void text_file_backend::rotate_file()
{
filesystem::path prev_file_name = m_pImpl->m_FileName;
close_file();
// Check if the file has been created in the first place
system::error_code ec;
filesystem::file_status status = filesystem::status(prev_file_name, ec);
if (status.type() == filesystem::regular_file)
{
if (!!m_pImpl->m_TargetFileNameGenerator)
{
filesystem::path 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);
}
}
//! The method sets the file open mode
BOOST_LOG_API void text_file_backend::set_open_mode(std::ios_base::openmode mode)
{
mode |= std::ios_base::out;
mode &= ~std::ios_base::in;
if ((mode & std::ios_base::app) != 0)
mode |= std::ios_base::ate; // we need to seek to end after opening the file to obtain its size
else
mode |= std::ios_base::trunc;
m_pImpl->m_FileOpenMode = mode;
}
//! The method sets file collector
BOOST_LOG_API void text_file_backend::set_file_collector(shared_ptr< file::collector > const& collector)
{
m_pImpl->m_pFileCollector = collector;
}
//! The method sets file open handler
BOOST_LOG_API void text_file_backend::set_open_handler(open_handler_type const& handler)
{
m_pImpl->m_OpenHandler = handler;
}
//! The method sets file close handler
BOOST_LOG_API void text_file_backend::set_close_handler(close_handler_type const& handler)
{
m_pImpl->m_CloseHandler = handler;
}
//! The method returns name of the currently open log file. If no file is open, returns an empty path.
BOOST_LOG_API filesystem::path text_file_backend::get_current_file_name() const
{
return m_pImpl->m_FileName;
}
//! 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 (BOOST_UNLIKELY(!m_pImpl->m_pFileCollector))
{
BOOST_LOG_THROW_DESCR(setup_error, "File collector is not set");
}
file::scan_result result = m_pImpl->m_pFileCollector->scan_for_files
(
method,
m_pImpl->m_TargetFileNamePattern.empty() ? m_pImpl->m_FileNamePattern : m_pImpl->m_TargetFileNamePattern
);
if (update_counter && !!result.last_file_counter)
{
if (!m_pImpl->m_FileCounterIsLastUsed || (*result.last_file_counter - m_pImpl->m_FileCounter) < ((~0u) ^ ((~0u) >> 1u)))
{
m_pImpl->m_FileCounter = *result.last_file_counter;
m_pImpl->m_FileCounterIsLastUsed = true;
}
}
return result.found_count;
}
} // namespace sinks
BOOST_LOG_CLOSE_NAMESPACE // namespace log
} // namespace boost
#include <boost/log/detail/footer.hpp>