mirror of
https://github.com/boostorg/log.git
synced 2026-02-09 11:12:38 +00:00
As requested here: https://svn.boost.org/trac/boost/ticket/8746 this commit introduces the boost::log::keywords::max_files keyword and implements this functionality in the file collector. Additionally, the non-keyword make_collector call has max_files added as a uintmax_t parameter, which has a default value as to not break existing code. The purpose of this is to limit the total number of files in the collected logs. If not specified, the limit will be std::numeric_limits<uintmax_t>::max(), which is likely greater than the capacity of the filesystem.
1415 lines
49 KiB
C++
1415 lines
49 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/ref.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/cstdint.hpp>
|
|
#include <boost/smart_ptr/make_shared_object.hpp>
|
|
#include <boost/enable_shared_from_this.hpp>
|
|
#include <boost/throw_exception.hpp>
|
|
#include <boost/mpl/if.hpp>
|
|
#include <boost/type_traits/is_same.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
#include <boost/system/system_error.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/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 (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)
|
|
{
|
|
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)
|
|
{
|
|
path_char_type c = *it++;
|
|
if (!traits_t::is_digit(c) || it == end)
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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
|
|
{
|
|
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();
|
|
|
|
//! The function stores the specified file in the storage
|
|
void store_file(filesystem::path const& file_name);
|
|
|
|
//! Scans the target directory for the files that have already been stored
|
|
uintmax_t scan_for_files(
|
|
file::scan_method method, filesystem::path const& pattern, unsigned int* counter);
|
|
|
|
//! 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)
|
|
{
|
|
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);
|
|
|
|
filesystem::path file_name_path = src_path.filename();
|
|
path_string_type 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;
|
|
do
|
|
{
|
|
path_string_type alt_file_name = formatter(file_name, n++);
|
|
info.m_Path = m_StorageDir / filesystem::path(alt_file_name);
|
|
}
|
|
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...
|
|
filesystem::create_directories(m_StorageDir);
|
|
}
|
|
|
|
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
|
|
|
|
// 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))
|
|
{
|
|
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;
|
|
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;
|
|
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;
|
|
}
|
|
|
|
//! Scans the target directory for the files that have already been stored
|
|
uintmax_t file_collector::scan_for_files(
|
|
file::scan_method method, filesystem::path const& pattern, unsigned int* counter)
|
|
{
|
|
uintmax_t file_count = 0;
|
|
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());
|
|
}
|
|
else
|
|
{
|
|
counter = NULL;
|
|
}
|
|
|
|
if (filesystem::exists(dir) && filesystem::is_directory(dir))
|
|
{
|
|
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
|
|
|
|
if (counter)
|
|
*counter = 0;
|
|
|
|
file_list files;
|
|
filesystem::directory_iterator it(dir), end;
|
|
uintmax_t total_size = 0;
|
|
for (; it != end; ++it)
|
|
{
|
|
file_info info;
|
|
info.m_Path = *it;
|
|
if (filesystem::is_regular_file(info.m_Path))
|
|
{
|
|
// Check that there are no duplicates in the resulting list
|
|
struct local
|
|
{
|
|
static bool equivalent(filesystem::path const& left, file_info const& right)
|
|
{
|
|
return filesystem::equivalent(left, right.m_Path);
|
|
}
|
|
};
|
|
if (std::find_if(m_Files.begin(), m_Files.end(),
|
|
boost::bind(&local::equivalent, boost::cref(info.m_Path), _1)) == m_Files.end())
|
|
{
|
|
// Check that the file name matches the pattern
|
|
unsigned int file_number = 0;
|
|
if (method != file::scan_matching ||
|
|
match_pattern(filename_string(info.m_Path), mask, file_number))
|
|
{
|
|
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);
|
|
++file_count;
|
|
|
|
if (counter && file_number >= *counter)
|
|
*counter = file_number + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort files chronologically
|
|
m_Files.splice(m_Files.end(), files);
|
|
m_TotalSize += total_size;
|
|
m_Files.sort(boost::bind(&file_info::m_TimeStamp, _1) < boost::bind(&file_info::m_TimeStamp, _2));
|
|
}
|
|
}
|
|
|
|
return file_count;
|
|
}
|
|
|
|
//! 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, _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 (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)
|
|
{
|
|
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)
|
|
{
|
|
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_DayKind(not_specified),
|
|
m_Day(0),
|
|
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_DayKind(weekday),
|
|
m_Day(static_cast< unsigned char >(wday)),
|
|
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_DayKind(monthday),
|
|
m_Day(static_cast< unsigned char >(mday.as_number())),
|
|
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 (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 open mode
|
|
std::ios_base::openmode m_FileOpenMode;
|
|
|
|
//! 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;
|
|
|
|
//! Stored files counter
|
|
unsigned int m_FileCounter;
|
|
|
|
//! 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;
|
|
//! The flag shows if every written record should be flushed
|
|
bool m_AutoFlush;
|
|
|
|
implementation(uintmax_t rotation_size, bool auto_flush) :
|
|
m_FileOpenMode(std::ios_base::trunc | std::ios_base::out),
|
|
m_FileCounter(0),
|
|
m_CharactersWritten(0),
|
|
m_FileRotationSize(rotation_size),
|
|
m_AutoFlush(auto_flush)
|
|
{
|
|
}
|
|
};
|
|
|
|
//! 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_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,
|
|
std::ios_base::openmode mode,
|
|
uintmax_t rotation_size,
|
|
time_based_rotation_predicate const& time_based_rotation,
|
|
bool auto_flush)
|
|
{
|
|
m_pImpl = new implementation(rotation_size, auto_flush);
|
|
set_file_name_pattern_internal(pattern);
|
|
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;
|
|
}
|
|
|
|
//! Sets the flag to automatically flush buffers of all attached streams after each log record
|
|
BOOST_LOG_API void text_file_backend::auto_flush(bool f)
|
|
{
|
|
m_pImpl->m_AutoFlush = f;
|
|
}
|
|
|
|
//! 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;
|
|
|
|
bool no_new_filename = false;
|
|
if (!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 creted 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
|
|
close_file();
|
|
|
|
system::error_code ec;
|
|
uintmax_t size = filesystem::file_size(m_pImpl->m_FileName, ec);
|
|
if (!!ec || size == 0)
|
|
{
|
|
// To reuse the empty file avoid re-generating the new file name later
|
|
no_new_filename = true;
|
|
}
|
|
else if (!!m_pImpl->m_pFileCollector)
|
|
{
|
|
// Complete file rotation
|
|
m_pImpl->m_pFileCollector->store_file(m_pImpl->m_FileName);
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|
|
if (!m_pImpl->m_File.is_open())
|
|
{
|
|
if (!no_new_filename)
|
|
m_pImpl->m_FileName = m_pImpl->m_StorageDir / m_pImpl->m_FileNameGenerator(m_pImpl->m_FileCounter++);
|
|
|
|
filesystem::create_directories(m_pImpl->m_FileName.parent_path());
|
|
|
|
m_pImpl->m_File.open(m_pImpl->m_FileName, m_pImpl->m_FileOpenMode);
|
|
if (!m_pImpl->m_File.is_open())
|
|
{
|
|
filesystem_error err(
|
|
"Failed to open file for writing",
|
|
m_pImpl->m_FileName,
|
|
system::error_code(system::errc::io_error, system::generic_category()));
|
|
BOOST_THROW_EXCEPTION(err);
|
|
}
|
|
|
|
if (!m_pImpl->m_OpenHandler.empty())
|
|
m_pImpl->m_OpenHandler(m_pImpl->m_File);
|
|
|
|
m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
|
|
}
|
|
|
|
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() + 1;
|
|
|
|
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 mask
|
|
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);
|
|
|
|
// 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)
|
|
{
|
|
// Only counter placeholder in the pattern
|
|
m_pImpl->m_FileNameGenerator =
|
|
boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1);
|
|
}
|
|
else
|
|
{
|
|
// No placeholders detected
|
|
m_pImpl->m_FileNameGenerator = empty_formatter(name_pattern);
|
|
}
|
|
}
|
|
|
|
//! Closes the currently open file
|
|
void text_file_backend::close_file()
|
|
{
|
|
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;
|
|
}
|
|
|
|
//! The method rotates the file
|
|
BOOST_LOG_API void text_file_backend::rotate_file()
|
|
{
|
|
close_file();
|
|
|
|
if (!!m_pImpl->m_pFileCollector)
|
|
m_pImpl->m_pFileCollector->store_file(m_pImpl->m_FileName);
|
|
}
|
|
|
|
//! 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::trunc | std::ios_base::app)) == 0)
|
|
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;
|
|
}
|
|
|
|
//! 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)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
BOOST_LOG_THROW_DESCR(setup_error, "File collector is not set");
|
|
}
|
|
}
|
|
|
|
} // namespace sinks
|
|
|
|
BOOST_LOG_CLOSE_NAMESPACE // namespace log
|
|
|
|
} // namespace boost
|
|
|
|
#include <boost/log/detail/footer.hpp>
|