mirror of
https://github.com/boostorg/log.git
synced 2026-02-17 13:52:15 +00:00
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.
This commit is contained in:
@@ -32,10 +32,10 @@
|
||||
#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/mpl/if.hpp>
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
@@ -79,9 +79,7 @@ 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)
|
||||
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
|
||||
@@ -610,6 +608,38 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
//! 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;
|
||||
@@ -664,9 +694,11 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
//! 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
|
||||
uintmax_t scan_for_files(
|
||||
file::scan_method method, filesystem::path const& pattern, unsigned int* counter) BOOST_OVERRIDE;
|
||||
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);
|
||||
@@ -679,7 +711,7 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
|
||||
private:
|
||||
//! Makes relative path absolute with respect to the base path
|
||||
filesystem::path make_absolute(filesystem::path const& p)
|
||||
filesystem::path make_absolute(filesystem::path const& p) const
|
||||
{
|
||||
return filesystem::absolute(p, m_BasePath);
|
||||
}
|
||||
@@ -779,8 +811,8 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
@@ -888,11 +920,34 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
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)
|
||||
//! 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
|
||||
{
|
||||
uintmax_t file_count = 0u;
|
||||
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;
|
||||
@@ -903,20 +958,11 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
if (pattern.has_parent_path())
|
||||
dir = make_absolute(pattern.parent_path());
|
||||
}
|
||||
else
|
||||
{
|
||||
counter = NULL;
|
||||
}
|
||||
|
||||
system::error_code ec;
|
||||
filesystem::file_status status = filesystem::status(dir, ec);
|
||||
if (status.type() == filesystem::directory_file)
|
||||
{
|
||||
unsigned int new_counter = 0u;
|
||||
if (counter)
|
||||
new_counter = *counter;
|
||||
bool found_file_number = false;
|
||||
|
||||
BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
|
||||
|
||||
file_list files;
|
||||
@@ -931,18 +977,10 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
if (status.type() == filesystem::regular_file)
|
||||
{
|
||||
// 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), boost::placeholders::_1)) == m_Files.end())
|
||||
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 = 0;
|
||||
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))
|
||||
@@ -951,14 +989,11 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
total_size += info.m_Size;
|
||||
info.m_TimeStamp = filesystem::last_write_time(info.m_Path);
|
||||
files.push_back(info);
|
||||
++file_count;
|
||||
++result.found_count;
|
||||
|
||||
// Test that the file_number >= new_counter accounting for the integer overflow
|
||||
if (file_number_parsed && (file_number - new_counter) < ((~0u) ^ ((~0u) >> 1u)))
|
||||
{
|
||||
found_file_number = true;
|
||||
new_counter = file_number + 1u;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -967,14 +1002,11 @@ BOOST_LOG_ANONYMOUS_NAMESPACE {
|
||||
// Sort files chronologically
|
||||
m_Files.splice(m_Files.end(), files);
|
||||
m_TotalSize += total_size;
|
||||
m_Files.sort(boost::bind(&file_info::m_TimeStamp, boost::placeholders::_1) < boost::bind(&file_info::m_TimeStamp, boost::placeholders::_2));
|
||||
|
||||
if (counter && found_file_number)
|
||||
*counter = new_counter;
|
||||
m_Files.sort(file_info::order_by_timestamp());
|
||||
}
|
||||
}
|
||||
|
||||
return file_count;
|
||||
return result;
|
||||
}
|
||||
|
||||
//! The function updates storage restrictions
|
||||
@@ -1229,7 +1261,7 @@ struct text_file_backend::implementation
|
||||
//! Target file name generator (according to m_TargetFileNamePattern)
|
||||
boost::log::aux::light_function< path_string_type (unsigned int) > m_TargetFileNameGenerator;
|
||||
|
||||
//! Stored files counter
|
||||
//! Counter to use in file names
|
||||
unsigned int m_FileCounter;
|
||||
|
||||
//! File open mode
|
||||
@@ -1260,6 +1292,11 @@ struct text_file_backend::implementation
|
||||
//! 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),
|
||||
@@ -1267,7 +1304,9 @@ struct text_file_backend::implementation
|
||||
m_FileRotationSize(rotation_size),
|
||||
m_AutoNewlineMode(auto_newline),
|
||||
m_AutoFlush(auto_flush),
|
||||
m_FinalRotationEnabled(enable_final_rotation)
|
||||
m_FinalRotationEnabled(enable_final_rotation),
|
||||
m_FileCounterIsLastUsed(false),
|
||||
m_IsFirstFile(true)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -1383,13 +1422,49 @@ BOOST_LOG_API void text_file_backend::consume(record_view const& rec, string_typ
|
||||
rotate_file();
|
||||
}
|
||||
|
||||
if (!m_pImpl->m_File.is_open())
|
||||
while (!m_pImpl->m_File.is_open())
|
||||
{
|
||||
filesystem::path new_file_name;
|
||||
if (!use_prev_file_name)
|
||||
new_file_name = m_pImpl->m_StorageDir / m_pImpl->m_FileNameGenerator(m_pImpl->m_FileCounter++);
|
||||
{
|
||||
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());
|
||||
|
||||
@@ -1402,11 +1477,47 @@ BOOST_LOG_API void text_file_backend::consume(record_view const& rec, string_typ
|
||||
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);
|
||||
|
||||
m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
|
||||
// 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()));
|
||||
@@ -1499,8 +1610,7 @@ BOOST_LOG_API void text_file_backend::rotate_file()
|
||||
{
|
||||
if (!!m_pImpl->m_TargetFileNameGenerator)
|
||||
{
|
||||
// File counter was incremented when the file was opened, we have to use the same counter value we used to generate the original filename
|
||||
filesystem::path new_file_name = m_pImpl->m_TargetStorageDir / m_pImpl->m_TargetFileNameGenerator(m_pImpl->m_FileCounter - 1u);
|
||||
filesystem::path new_file_name = m_pImpl->m_TargetStorageDir / m_pImpl->m_TargetFileNameGenerator(m_pImpl->m_FileCounter);
|
||||
|
||||
if (new_file_name != prev_file_name)
|
||||
{
|
||||
@@ -1555,20 +1665,27 @@ BOOST_LOG_API filesystem::path text_file_backend::get_current_file_name() const
|
||||
//! 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_LIKELY(!!m_pImpl->m_pFileCollector))
|
||||
{
|
||||
unsigned int* counter = update_counter ? &m_pImpl->m_FileCounter : static_cast< unsigned int* >(NULL);
|
||||
return m_pImpl->m_pFileCollector->scan_for_files
|
||||
(
|
||||
method,
|
||||
m_pImpl->m_TargetFileNamePattern.empty() ? m_pImpl->m_FileNamePattern : m_pImpl->m_TargetFileNamePattern,
|
||||
counter
|
||||
);
|
||||
}
|
||||
else
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user