mirror of
https://github.com/boostorg/log.git
synced 2026-02-09 11:12:38 +00:00
1406 lines
49 KiB
C++
1406 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 <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;
|
|
//! 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);
|
|
|
|
//! 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);
|
|
|
|
//! 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);
|
|
|
|
//! 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
|
|
) :
|
|
m_pRepository(repo),
|
|
m_MaxSize(max_size),
|
|
m_MinFreeSpace(min_free_space),
|
|
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)))
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
|
|
//! 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)
|
|
{
|
|
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);
|
|
}
|
|
catch (bad_weak_ptr&)
|
|
{
|
|
}
|
|
|
|
if (!p)
|
|
{
|
|
p = boost::make_shared< file_collector >(
|
|
file_collector_repository::get(), target_dir, max_size, min_free_space);
|
|
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)
|
|
{
|
|
return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space);
|
|
}
|
|
|
|
} // 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>
|