Index: boost/log/sinks/text_file_backend.hpp =================================================================== --- boost/log/sinks/text_file_backend.hpp (revision 858) +++ boost/log/sinks/text_file_backend.hpp (working copy) @@ -125,9 +125,24 @@ namespace aux { - //! Creates and returns a file collector with the specified parameters + /*! + * Creates and returns a file collector with the specified parameters + * + * \param target_dir Directory where log files to be collected and rotated. + * \param file_name File name or file mask, optional. + * Has the same meaning as in text_file_backend context. + * When specified, collector will perform files rotation in "sliding window" manner: + * file.log (latest), file1.log (previous), ..., fileN.log (earliest). + * When \a file_name does not contain any placeholders, + * placeholder".%N" will be appended. + * \param max_size Maximum size of all collected files. + * When exceeded, log file(s) deletion will take place. + * \param min_free_space Minimum device partition free space. + * When exceeded, log file(s) deletion will take place. + */ BOOST_LOG_API shared_ptr< collector > make_collector( filesystem::path const& target_dir, + filesystem::path const& file_name, uintmax_t max_size, uintmax_t min_free_space ); @@ -136,6 +151,7 @@ { return aux::make_collector( filesystem::path(args[keywords::target]), + filesystem::path(args[keywords::file_name | filesystem::path()]), args[keywords::max_size | (std::numeric_limits< uintmax_t >::max)()], args[keywords::min_free_space | static_cast< uintmax_t >(0)]); } @@ -159,6 +175,11 @@ { return aux::make_collector((a1, a2, a3)); } +template< typename T1, typename T2, typename T3, typename T4 > +inline shared_ptr< collector > make_collector(T1 const& a1, T2 const& a2, T3 const& a3, T4 const& a4) +{ + return aux::make_collector((a1, a2, a3, a4)); +} #else Index: libs/log/src/text_file_backend.cpp =================================================================== --- libs/log/src/text_file_backend.cpp (revision 858) +++ libs/log/src/text_file_backend.cpp (working copy) @@ -468,37 +468,149 @@ return false; } + class file_name_generator + { + public: + //generator types + enum formatter + { + empty = 0, + counter = 1, + date_time = 1<<1, + date_time_counter = date_time | counter + }; - class file_collector_repository; + typedef boost::log::aux::light_function< path_string_type (unsigned int) > type; - //! Type of the hook used for sequencing file collectors - typedef intrusive::list_base_hook< - intrusive::link_mode< intrusive::safe_link > - > file_collector_hook; + struct cookie + { + path_string_type pattern; + path_string_type::size_type counter_pos; + unsigned int placeholder_count; + unsigned int width; + bool counter_found; + enum formatter formatter; - //! Log file collector implementation - class file_collector : - public file::collector, - public file_collector_hook, - public enable_shared_from_this< file_collector > + explicit cookie(path_string_type const& name_pattern) + : pattern(name_pattern), + counter_pos(0), + placeholder_count(0), + width(0), + counter_found(false), + formatter(empty) + {} + + explicit cookie(filesystem::path const& file_name) + : pattern(file_name.filename().string< path_string_type >()), + counter_pos(0), + placeholder_count(0), + width(0), + counter_found(false), + formatter(empty) + {} + + void parse() + { + typedef file_char_traits< path_char_type > traits_t; + + path_string_type::const_iterator end = pattern.end(); + path_string_type::const_iterator it = 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 && parse_counter_placeholder(it, end, width)) + { + // We've found the file counter placeholder in the pattern + counter_found = true; + counter_pos = placeholder_begin - pattern.begin(); + pattern.erase(counter_pos, it - placeholder_begin); + --placeholder_count; + it = pattern.begin() + counter_pos; + end = pattern.end(); + } + } + while (it != end); + + unsigned int choice = (static_cast< unsigned int >(placeholder_count > 0) << 1) | + static_cast< unsigned int >(counter_found); + + formatter = choice == 0 || choice > date_time_counter ? + empty : static_cast(choice) + ; + } + }; + + static type create(path_string_type const& name_pattern) + { + typedef file_char_traits< path_char_type > traits_t; + + cookie cook(name_pattern); + cook.parse(); + + return create(cook); + } + + static type create(const cookie& c) + { + switch (c.formatter) + { + case counter: // Only counter placeholder in the pattern + return + boost::bind(file_counter_formatter(c.counter_pos, c.width), c.pattern, _1); + case date_time: // Only date/time placeholders in the pattern + return + boost::bind(date_and_time_formatter(), c.pattern, _1); + case date_time_counter: // Counter and date/time placeholder in the pattern + return boost::bind(date_and_time_formatter(), + boost::bind(file_counter_formatter(c.counter_pos, c.width), c.pattern, _1), _1); + default: // No placeholders detected + return empty_formatter(c.pattern); + } + } + }; + + + //! Base implementation class for log file collectors + class file_collector_base : public file::collector { - private: + protected: //! Information about a single stored file struct file_info { uintmax_t m_Size; std::time_t m_TimeStamp; filesystem::path m_Path; + + file_info() + : m_Size(0), + m_TimeStamp(0) + {} + + explicit file_info(filesystem::path const& path) + : m_Size(filesystem::file_size(path)), + m_TimeStamp(filesystem::last_write_time(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; @@ -522,17 +634,27 @@ //! Total size of the stored files uintmax_t m_TotalSize; + virtual void do_rollover(file_list& files) + {} + protected: + filesystem::path storage_dir() const + { + return m_StorageDir; + } public: - //! Constructor - file_collector( - shared_ptr< file_collector_repository > const& repo, + file_collector_base( filesystem::path const& target_dir, uintmax_t max_size, - uintmax_t min_free_space); + uintmax_t min_free_space + ) : 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(); - //! The function stores the specified file in the storage void store_file(filesystem::path const& file_name); @@ -562,81 +684,11 @@ } }; - - //! 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_INSTANTIATIONS) - 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) + void file_collector_base::store_file(filesystem::path const& src_path) { // 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); + file_info info(src_path); path_string_type file_name = filename_string(src_path); info.m_Path = m_StorageDir / file_name; @@ -709,10 +761,13 @@ m_Files.push_back(info); m_TotalSize += info.m_Size; + + //TODO: asserts on m_Files size change? + do_rollover(m_Files); } //! Scans the target directory for the files that have already been stored - uintmax_t file_collector::scan_for_files( + uintmax_t file_collector_base::scan_for_files( file::scan_method method, filesystem::path const& pattern, unsigned int* counter) { uintmax_t file_count = 0; @@ -779,6 +834,7 @@ // Sort files chronologically m_Files.splice(m_Files.end(), files); m_TotalSize += total_size; + //TODO: this sorting could fail when several files were generated within 1 second m_Files.sort(boost::bind(&file_info::m_TimeStamp, _1) < boost::bind(&file_info::m_TimeStamp, _2)); } } @@ -787,7 +843,7 @@ } //! The function updates storage restrictions - void file_collector::update(uintmax_t max_size, uintmax_t min_free_space) + void file_collector_base::update(uintmax_t max_size, uintmax_t min_free_space) { BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);) @@ -796,6 +852,231 @@ } + 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_base, + public file_collector_hook, + public enable_shared_from_this< file_collector > + { + //! A reference to the repository this collector belongs to + shared_ptr< file_collector_repository > m_pRepository; + 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(); + }; + + + //! Rotating log file collector, collects only files that are base on 1 file pattern + class rotating_file_collector : public file_collector_base + { + class rollover_policy + { + filesystem::path m_StorageDir; + protected: + file_name_generator::type m_FileNameGenerator; + + filesystem::path storage_path(filesystem::path const& name) const + { + return m_StorageDir / name; + } + public: + rollover_policy(filesystem::path const& storage_dir, file_name_generator::type const& g) + : m_StorageDir(storage_dir), m_FileNameGenerator(g) + {} + + virtual ~rollover_policy() {} + + virtual void invoke(file_list& files) const = 0; + }; + + class date_rollover_policy : public rollover_policy + { + public: + explicit date_rollover_policy(filesystem::path const& d, file_name_generator::type const& g) + : rollover_policy(d, g) + {} + + void invoke(file_list& files) const + { + file_info last = files.back(); + filesystem::path path = storage_path(m_FileNameGenerator(files.size())); + filesystem::rename(last.m_Path, path); + last.m_Path = path; + } + }; + + class counter_rollover_policy : public rollover_policy + { + struct rename + { + filesystem::path& path; + + explicit rename(filesystem::path& p) + : path(p) + {} + + void operator () (file_info& fi) const + { + filesystem::rename(fi.m_Path, path); + using std::swap; + swap(fi.m_Path, path); + } + }; + public: + explicit counter_rollover_policy(filesystem::path const& d, file_name_generator::type const& g) + : rollover_policy(d, g) + {} + + void invoke(file_list& files) const + { + filesystem::path last = storage_path(m_FileNameGenerator(files.size())); + + std::for_each(files.begin(), files.end(), rename(last)); + } + }; + + //! File name pattern + filesystem::path m_FileNamePattern; + shared_ptr< rollover_policy > m_Rollover; + //! File name generator + file_name_generator::type m_FileNameGenerator; + + void do_rollover(file_list& files) + { + if (files.empty()) return ; + + m_Rollover->invoke(files); + } + + void init(filesystem::path const& file_name) + { + file_name_generator::cookie cookie(file_name); + cookie.parse(); + + if (cookie.formatter == file_name_generator::empty) + { + init(file_name.string< path_string_type >() + ".%N");//TODO: use file_char_traits + return ; + } + + m_FileNamePattern = file_name; + + file_name_generator::type g = file_name_generator::create(cookie); + if (cookie.formatter == file_name_generator::counter) + { + m_Rollover.reset(new counter_rollover_policy(storage_dir(), g)); + } + else + { + m_Rollover.reset(new date_rollover_policy(storage_dir(), g)); + } + } + public: + rotating_file_collector( + filesystem::path const& target_dir, + filesystem::path const& file_name, + uintmax_t max_size, + uintmax_t min_free_space + ) : file_collector_base(target_dir, max_size, min_free_space) + { + init(file_name); + } + + uintmax_t scan_for_files( + file::scan_method method, filesystem::path const& pattern, unsigned int* counter) + { + //ensure that pattern does not have any placeholders - + //in such case rotating collector will conflict with file sink + file_name_generator::cookie cookie(pattern); + cookie.parse(); + if (cookie.formatter != file_name_generator::empty) + { + //TODO: + //BOOST_ASSERT_MSG + } + + return file_collector_base::scan_for_files(method, m_FileNamePattern, counter); + } + }; + + + //! 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_INSTANTIATIONS) + 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 + ) : + file_collector_base(target_dir, max_size, min_free_space), + m_pRepository(repo) + {} + + //! Destructor + file_collector::~file_collector() + { + m_pRepository->remove_collector(this); + } + + //! 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) @@ -864,10 +1145,20 @@ //! Creates and returns a file collector with the specified parameters BOOST_LOG_API shared_ptr< collector > make_collector( filesystem::path const& target_dir, + filesystem::path const& file_name, uintmax_t max_size, uintmax_t min_free_space) { - return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space); + if (file_name.empty()) + { + return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space); + } + else + { + return shared_ptr< collector >( + new rotating_file_collector(target_dir, file_name, max_size, min_free_space) + ); + } } } // namespace aux @@ -1030,7 +1321,7 @@ //! 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; + file_name_generator::type m_FileNameGenerator; //! Stored files counter unsigned int m_FileCounter; @@ -1188,65 +1479,7 @@ m_pImpl->m_FileNamePattern = name_pattern; 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 && parse_counter_placeholder(it, 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, it - placeholder_begin); - --placeholder_count; - it = name_pattern.begin() + counter_pos; - end = name_pattern.end(); - } - } - while (it != end); - - // Construct the formatter functor - unsigned int choice = (static_cast< unsigned int >(placeholder_count > 0) << 1) | - static_cast< unsigned int >(counter_found); - switch (choice) - { - case 1: // Only counter placeholder in the pattern - m_pImpl->m_FileNameGenerator = - boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1); - break; - case 2: // Only date/time placeholders in the pattern - m_pImpl->m_FileNameGenerator = - boost::bind(date_and_time_formatter(), name_pattern, _1); - break; - case 3: // 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); - break; - default: // No placeholders detected - m_pImpl->m_FileNameGenerator = empty_formatter(name_pattern); - break; - } + m_pImpl->m_FileNameGenerator = file_name_generator::create(name_pattern); } //! The method rotates the file Index: libs/log/example/rotating_file/main.cpp =================================================================== --- libs/log/example/rotating_file/main.cpp (revision 858) +++ libs/log/example/rotating_file/main.cpp (working copy) @@ -41,24 +41,47 @@ enum { LOG_RECORDS_TO_WRITE = 10000 }; -int main(int argc, char* argv[]) + +class sink_builder { - try +public: + typedef sinks::synchronous_sink< sinks::text_file_backend > file_sink; + typedef shared_ptr< file_sink > file_sink_ptr; + + typedef shared_ptr< sinks::file::collector > collector_ptr; + + file_sink_ptr make(std::string const& file_name_pattern) const { - // Create a text file sink - typedef sinks::synchronous_sink< sinks::text_file_backend > file_sink; - shared_ptr< file_sink > sink(new file_sink( - keywords::file_name = "%Y%m%d_%H%M%S_%5N.log", // file name pattern - keywords::rotation_size = 16384 // rotation size, in characters - )); + return file_sink_ptr(new file_sink( + keywords::file_name = file_name_pattern, + keywords::rotation_size = 16384 + ) + ); + } + collector_ptr make_coll_impl_(std::string const& target_dir, std::string const& file_name_pattern) const + { // Set up where the rotated files will be stored - sink->locked_backend()->set_file_collector(sinks::file::make_collector( - keywords::target = "logs", // where to store rotated files - keywords::max_size = 16 * 1024 * 1024, // maximum total size of the stored files, in bytes - keywords::min_free_space = 100 * 1024 * 1024 // minimum free space on the drive, in bytes - )); + return sinks::file::make_collector( + keywords::target = target_dir, + keywords::file_name = file_name_pattern, + keywords::max_size = 16 * 1024 * 1024, + keywords::min_free_space = 100 * 1024 * 1024 + ); + } + collector_ptr make_collector(std::string const& target_dir) const + { + return make_coll_impl_(target_dir, ""); + } + + collector_ptr make_rotating_collector(std::string const& target_dir, std::string const& file_name_pattern) const + { + return make_coll_impl_(target_dir, file_name_pattern); + } + + void setup(file_sink_ptr const& sink) const + { // Upon restart, scan the target directory for files matching the file_name pattern sink->locked_backend()->scan_for_files(); @@ -69,9 +92,34 @@ % expr::attr< boost::posix_time::ptime >("TimeStamp") % expr::smessage ); + } +}; - // Add it to the core + +int main(int argc, char* argv[]) +{ + try + { + sink_builder builder; + + // Let's setup 2 sinks: + // 1st will be rotating sink with regular file collector, + // 2nd - regular sink with rotating file collector + + sink_builder::file_sink_ptr sink = builder.make("%Y%m%d_%H%M%S_%5N.log"); + sink->locked_backend()->set_file_collector(builder.make_collector("logs/chrono")); + + sink_builder::file_sink_ptr rsink = builder.make("logs/app.log"); + rsink->locked_backend()->set_file_collector( + builder.make_rotating_collector("logs", "app.log") + ); + + builder.setup(sink); + builder.setup(rsink); + + // Add them to the core logging::core::get()->add_sink(sink); + logging::core::get()->add_sink(rsink); // Add some attributes too logging::core::get()->add_global_attribute("TimeStamp", attrs::local_clock());