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