2 * Copyright Andrey Semashev 2007 - 2015.
3 * Distributed under the Boost Software License, Version 1.0.
4 * (See accompanying file LICENSE_1_0.txt or copy at
5 * http://www.boost.org/LICENSE_1_0.txt)
8 * \file text_file_backend.cpp
9 * \author Andrey Semashev
12 * \brief This header is the Boost.Log library implementation, see the library documentation
13 * at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
16 #include <boost/log/detail/config.hpp>
32 #include <boost/ref.hpp>
33 #include <boost/bind.hpp>
34 #include <boost/cstdint.hpp>
35 #include <boost/smart_ptr/make_shared_object.hpp>
36 #include <boost/enable_shared_from_this.hpp>
37 #include <boost/throw_exception.hpp>
38 #include <boost/mpl/if.hpp>
39 #include <boost/type_traits/is_same.hpp>
40 #include <boost/system/error_code.hpp>
41 #include <boost/system/system_error.hpp>
42 #include <boost/filesystem/path.hpp>
43 #include <boost/filesystem/fstream.hpp>
44 #include <boost/filesystem/operations.hpp>
45 #include <boost/filesystem/convenience.hpp>
46 #include <boost/intrusive/list.hpp>
47 #include <boost/intrusive/list_hook.hpp>
48 #include <boost/intrusive/options.hpp>
49 #include <boost/date_time/posix_time/posix_time.hpp>
50 #include <boost/date_time/gregorian/gregorian_types.hpp>
51 #include <boost/spirit/home/qi/numeric/numeric_utils.hpp>
52 #include <boost/log/detail/singleton.hpp>
53 #include <boost/log/detail/light_function.hpp>
54 #include <boost/log/exceptions.hpp>
55 #include <boost/log/attributes/time_traits.hpp>
56 #include <boost/log/sinks/auto_newline_mode.hpp>
57 #include <boost/log/sinks/text_file_backend.hpp>
58 #include "unique_ptr.hpp"
60 #if !defined(BOOST_LOG_NO_THREADS)
61 #include <boost/thread/locks.hpp>
62 #include <boost/thread/mutex.hpp>
63 #endif // !defined(BOOST_LOG_NO_THREADS)
65 #include <boost/log/detail/header.hpp>
67 namespace qi
= boost::spirit::qi
;
71 BOOST_LOG_OPEN_NAMESPACE
75 BOOST_LOG_ANONYMOUS_NAMESPACE
{
77 typedef filesystem::filesystem_error filesystem_error
;
79 //! A possible Boost.Filesystem extension - renames or moves the file to the target storage
80 inline void move_file(
81 filesystem::path
const& from
,
82 filesystem::path
const& to
)
84 #if defined(BOOST_WINDOWS_API)
85 // On Windows MoveFile already does what we need
86 filesystem::rename(from
, to
);
88 // On POSIX rename fails if the target points to a different device
89 system::error_code ec
;
90 filesystem::rename(from
, to
, ec
);
93 if (BOOST_LIKELY(ec
.value() == system::errc::cross_device_link
))
95 // Attempt to manually move the file instead
96 filesystem::copy_file(from
, to
);
97 filesystem::remove(from
);
101 BOOST_THROW_EXCEPTION(filesystem_error("failed to move file to another location", from
, to
, ec
));
107 typedef filesystem::path::string_type path_string_type
;
108 typedef path_string_type::value_type path_char_type
;
110 //! An auxiliary traits that contain various constants and functions regarding string and character operations
111 template< typename CharT
>
112 struct file_char_traits
;
115 struct file_char_traits
< char >
117 typedef char char_type
;
119 static const char_type percent
= '%';
120 static const char_type number_placeholder
= 'N';
121 static const char_type day_placeholder
= 'd';
122 static const char_type month_placeholder
= 'm';
123 static const char_type year_placeholder
= 'y';
124 static const char_type full_year_placeholder
= 'Y';
125 static const char_type frac_sec_placeholder
= 'f';
126 static const char_type seconds_placeholder
= 'S';
127 static const char_type minutes_placeholder
= 'M';
128 static const char_type hours_placeholder
= 'H';
129 static const char_type space
= ' ';
130 static const char_type plus
= '+';
131 static const char_type minus
= '-';
132 static const char_type zero
= '0';
133 static const char_type dot
= '.';
134 static const char_type newline
= '\n';
136 static bool is_digit(char c
)
139 return (isdigit(c
) != 0);
141 static std::string
default_file_name_pattern() { return "%5N.log"; }
144 #ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
145 const file_char_traits
< char >::char_type file_char_traits
< char >::percent
;
146 const file_char_traits
< char >::char_type file_char_traits
< char >::number_placeholder
;
147 const file_char_traits
< char >::char_type file_char_traits
< char >::day_placeholder
;
148 const file_char_traits
< char >::char_type file_char_traits
< char >::month_placeholder
;
149 const file_char_traits
< char >::char_type file_char_traits
< char >::year_placeholder
;
150 const file_char_traits
< char >::char_type file_char_traits
< char >::full_year_placeholder
;
151 const file_char_traits
< char >::char_type file_char_traits
< char >::frac_sec_placeholder
;
152 const file_char_traits
< char >::char_type file_char_traits
< char >::seconds_placeholder
;
153 const file_char_traits
< char >::char_type file_char_traits
< char >::minutes_placeholder
;
154 const file_char_traits
< char >::char_type file_char_traits
< char >::hours_placeholder
;
155 const file_char_traits
< char >::char_type file_char_traits
< char >::space
;
156 const file_char_traits
< char >::char_type file_char_traits
< char >::plus
;
157 const file_char_traits
< char >::char_type file_char_traits
< char >::minus
;
158 const file_char_traits
< char >::char_type file_char_traits
< char >::zero
;
159 const file_char_traits
< char >::char_type file_char_traits
< char >::dot
;
160 const file_char_traits
< char >::char_type file_char_traits
< char >::newline
;
161 #endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
164 struct file_char_traits
< wchar_t >
166 typedef wchar_t char_type
;
168 static const char_type percent
= L
'%';
169 static const char_type number_placeholder
= L
'N';
170 static const char_type day_placeholder
= L
'd';
171 static const char_type month_placeholder
= L
'm';
172 static const char_type year_placeholder
= L
'y';
173 static const char_type full_year_placeholder
= L
'Y';
174 static const char_type frac_sec_placeholder
= L
'f';
175 static const char_type seconds_placeholder
= L
'S';
176 static const char_type minutes_placeholder
= L
'M';
177 static const char_type hours_placeholder
= L
'H';
178 static const char_type space
= L
' ';
179 static const char_type plus
= L
'+';
180 static const char_type minus
= L
'-';
181 static const char_type zero
= L
'0';
182 static const char_type dot
= L
'.';
183 static const char_type newline
= L
'\n';
185 static bool is_digit(wchar_t c
)
188 return (iswdigit(c
) != 0);
190 static std::wstring
default_file_name_pattern() { return L
"%5N.log"; }
193 #ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
194 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::percent
;
195 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::number_placeholder
;
196 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::day_placeholder
;
197 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::month_placeholder
;
198 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::year_placeholder
;
199 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::full_year_placeholder
;
200 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::frac_sec_placeholder
;
201 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::seconds_placeholder
;
202 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::minutes_placeholder
;
203 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::hours_placeholder
;
204 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::space
;
205 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::plus
;
206 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::minus
;
207 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::zero
;
208 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::dot
;
209 const file_char_traits
< wchar_t >::char_type file_char_traits
< wchar_t >::newline
;
210 #endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
212 //! Date and time formatter
213 class date_and_time_formatter
216 typedef path_string_type result_type
;
219 typedef date_time::time_facet
< posix_time::ptime
, path_char_type
> time_facet_type
;
222 mutable time_facet_type m_Facet
;
223 mutable std::basic_ostringstream
< path_char_type
> m_Stream
;
227 date_and_time_formatter() : m_Facet(1u)
231 date_and_time_formatter(date_and_time_formatter
const& that
) : m_Facet(1u)
234 //! The method formats the current date and time according to the format string str and writes the result into it
235 path_string_type
operator()(path_string_type
const& pattern
, unsigned int counter
) const
237 m_Facet
.format(pattern
.c_str());
238 m_Stream
.str(path_string_type());
239 // Note: the regular operator<< fails because std::use_facet fails to find the facet in the locale because
240 // the facet type in Boost.DateTime has hidden visibility. See this ticket:
241 // https://svn.boost.org/trac/boost/ticket/11707
242 std::ostreambuf_iterator
< path_char_type
> sbuf_it(m_Stream
);
243 m_Facet
.put(sbuf_it
, m_Stream
, m_Stream
.fill(), boost::log::attributes::local_time_traits::get_clock());
246 return m_Stream
.str();
255 BOOST_DELETED_FUNCTION(date_and_time_formatter
& operator= (date_and_time_formatter
const&))
258 //! The functor formats the file counter into the file name
259 class file_counter_formatter
262 typedef path_string_type result_type
;
265 //! The position in the pattern where the file counter placeholder is
266 path_string_type::size_type m_FileCounterPosition
;
267 //! File counter width
268 std::streamsize m_Width
;
269 //! The file counter formatting stream
270 mutable std::basic_ostringstream
< path_char_type
> m_Stream
;
273 //! Initializing constructor
274 file_counter_formatter(path_string_type::size_type pos
, unsigned int width
) :
275 m_FileCounterPosition(pos
),
278 typedef file_char_traits
< path_char_type
> traits_t
;
279 m_Stream
.fill(traits_t::zero
);
282 file_counter_formatter(file_counter_formatter
const& that
) :
283 m_FileCounterPosition(that
.m_FileCounterPosition
),
284 m_Width(that
.m_Width
)
286 m_Stream
.fill(that
.m_Stream
.fill());
289 //! The function formats the file counter into the file name
290 path_string_type
operator()(path_string_type
const& pattern
, unsigned int counter
) const
292 path_string_type file_name
= pattern
;
294 m_Stream
.str(path_string_type());
295 m_Stream
.width(m_Width
);
297 file_name
.insert(m_FileCounterPosition
, m_Stream
.str());
302 BOOST_DELETED_FUNCTION(file_counter_formatter
& operator= (file_counter_formatter
const&))
305 //! The function returns the pattern as the file name
306 class empty_formatter
309 typedef path_string_type result_type
;
312 path_string_type m_Pattern
;
315 //! Initializing constructor
316 explicit empty_formatter(path_string_type
const& pattern
) : m_Pattern(pattern
)
320 empty_formatter(empty_formatter
const& that
) : m_Pattern(that
.m_Pattern
)
324 //! The function returns the pattern as the file name
325 path_string_type
const& operator() (unsigned int) const
330 BOOST_DELETED_FUNCTION(empty_formatter
& operator= (empty_formatter
const&))
333 //! The function parses the format placeholder for file counter
334 bool parse_counter_placeholder(path_string_type::const_iterator
& it
, path_string_type::const_iterator end
, unsigned int& width
)
336 typedef qi::extract_uint
< unsigned int, 10, 1, -1 > width_extract
;
337 typedef file_char_traits
< path_char_type
> traits_t
;
341 path_char_type c
= *it
;
342 if (c
== traits_t::zero
|| c
== traits_t::space
|| c
== traits_t::plus
|| c
== traits_t::minus
)
344 // Skip filler and alignment specification
351 if (traits_t::is_digit(c
))
354 if (!width_extract::call(it
, end
, width
))
361 if (c
== traits_t::dot
)
365 while (it
!= end
&& traits_t::is_digit(*it
))
372 if (c
== traits_t::number_placeholder
)
381 //! The function matches the file name and the pattern
382 bool match_pattern(path_string_type
const& file_name
, path_string_type
const& pattern
, unsigned int& file_counter
, bool& file_counter_parsed
)
384 typedef qi::extract_uint
< unsigned int, 10, 1, -1 > file_counter_extract
;
385 typedef file_char_traits
< path_char_type
> traits_t
;
389 // Verifies that the string contains exactly n digits
390 static bool scan_digits(path_string_type::const_iterator
& it
, path_string_type::const_iterator end
, std::ptrdiff_t n
)
396 path_char_type c
= *it
++;
397 if (!traits_t::is_digit(c
))
404 path_string_type::const_iterator
405 f_it
= file_name
.begin(),
406 f_end
= file_name
.end(),
407 p_it
= pattern
.begin(),
408 p_end
= pattern
.end();
409 bool placeholder_expected
= false;
410 while (f_it
!= f_end
&& p_it
!= p_end
)
412 path_char_type p_c
= *p_it
, f_c
= *f_it
;
413 if (!placeholder_expected
)
415 if (p_c
== traits_t::percent
)
417 placeholder_expected
= true;
432 case traits_t::percent
: // An escaped '%'
442 case traits_t::seconds_placeholder
: // Date/time components with 2-digits width
443 case traits_t::minutes_placeholder
:
444 case traits_t::hours_placeholder
:
445 case traits_t::day_placeholder
:
446 case traits_t::month_placeholder
:
447 case traits_t::year_placeholder
:
448 if (!local::scan_digits(f_it
, f_end
, 2))
453 case traits_t::full_year_placeholder
: // Date/time components with 4-digits width
454 if (!local::scan_digits(f_it
, f_end
, 4))
459 case traits_t::frac_sec_placeholder
: // Fraction seconds width is configuration-dependent
460 typedef posix_time::time_res_traits posix_resolution_traits
;
461 if (!local::scan_digits(f_it
, f_end
, posix_resolution_traits::num_fractional_digits()))
468 default: // This should be the file counter placeholder or some unsupported placeholder
470 path_string_type::const_iterator p
= p_it
;
471 unsigned int width
= 0;
472 if (!parse_counter_placeholder(p
, p_end
, width
))
474 BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported placeholder used in pattern for file scanning"));
477 // Find where the file number ends
478 path_string_type::const_iterator f
= f_it
;
479 if (!local::scan_digits(f
, f_end
, width
))
481 while (f
!= f_end
&& traits_t::is_digit(*f
))
484 if (!file_counter_extract::call(f_it
, f
, file_counter
))
487 file_counter_parsed
= true;
493 placeholder_expected
= false;
501 // The actual file name may end with an additional counter
502 // that is added by the collector in case if file name clash
503 return local::scan_digits(f_it
, f_end
, std::distance(f_it
, f_end
));
512 //! 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
513 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
)
515 // Note: avoid calling Boost.Filesystem functions that involve path::codecvt()
516 // https://svn.boost.org/trac/boost/ticket/9119
518 typedef file_char_traits
< path_char_type
> traits_t
;
520 file_name_pattern
= pattern
.filename();
521 path_string_type name_pattern
= file_name_pattern
.native();
522 storage_dir
= filesystem::absolute(pattern
.parent_path());
524 // Let's try to find the file counter placeholder
525 unsigned int placeholder_count
= 0;
526 unsigned int width
= 0;
527 bool counter_found
= false;
528 path_string_type::size_type counter_pos
= 0;
529 path_string_type::const_iterator end
= name_pattern
.end();
530 path_string_type::const_iterator it
= name_pattern
.begin();
534 it
= std::find(it
, end
, traits_t::percent
);
537 path_string_type::const_iterator placeholder_begin
= it
++;
540 if (*it
== traits_t::percent
)
542 // An escaped percent detected
551 path_string_type::const_iterator it2
= it
;
552 if (parse_counter_placeholder(it2
, end
, width
))
554 // We've found the file counter placeholder in the pattern
555 counter_found
= true;
556 counter_pos
= placeholder_begin
- name_pattern
.begin();
557 name_pattern
.erase(counter_pos
, it2
- placeholder_begin
);
559 it
= name_pattern
.begin() + counter_pos
;
560 end
= name_pattern
.end();
566 // Construct the formatter functor
567 if (placeholder_count
> 0)
571 // Both counter and date/time placeholder in the pattern
572 file_name_generator
= boost::bind(date_and_time_formatter(),
573 boost::bind(file_counter_formatter(counter_pos
, width
), name_pattern
, _1
), _1
);
577 // Only date/time placeholders in the pattern
578 file_name_generator
= boost::bind(date_and_time_formatter(), name_pattern
, _1
);
581 else if (counter_found
)
583 // Only counter placeholder in the pattern
584 file_name_generator
= boost::bind(file_counter_formatter(counter_pos
, width
), name_pattern
, _1
);
588 // No placeholders detected
589 file_name_generator
= empty_formatter(name_pattern
);
594 class file_collector_repository
;
596 //! Type of the hook used for sequencing file collectors
597 typedef intrusive::list_base_hook
<
598 intrusive::link_mode
< intrusive::safe_link
>
599 > file_collector_hook
;
601 //! Log file collector implementation
602 class file_collector
:
603 public file::collector
,
604 public file_collector_hook
,
605 public enable_shared_from_this
< file_collector
>
608 //! Information about a single stored file
612 std::time_t m_TimeStamp
;
613 filesystem::path m_Path
;
615 //! A list of the stored files
616 typedef std::list
< file_info
> file_list
;
617 //! The string type compatible with the universal path type
618 typedef filesystem::path::string_type path_string_type
;
621 //! A reference to the repository this collector belongs to
622 shared_ptr
< file_collector_repository
> m_pRepository
;
624 #if !defined(BOOST_LOG_NO_THREADS)
625 //! Synchronization mutex
627 #endif // !defined(BOOST_LOG_NO_THREADS)
629 //! Total file size upper limit
631 //! Free space lower limit
632 uintmax_t m_MinFreeSpace
;
633 //! File count upper limit
634 uintmax_t m_MaxFiles
;
636 //! The current path at the point when the collector is created
638 * The special member is required to calculate absolute paths with no
639 * dependency on the current path for the application, which may change
641 const filesystem::path m_BasePath
;
642 //! Target directory to store files to
643 filesystem::path m_StorageDir
;
645 //! The list of stored files
647 //! Total size of the stored files
648 uintmax_t m_TotalSize
;
653 shared_ptr
< file_collector_repository
> const& repo
,
654 filesystem::path
const& target_dir
,
656 uintmax_t min_free_space
,
657 uintmax_t max_files
);
662 //! The function stores the specified file in the storage
663 void store_file(filesystem::path
const& file_name
);
665 //! Scans the target directory for the files that have already been stored
666 uintmax_t scan_for_files(
667 file::scan_method method
, filesystem::path
const& pattern
, unsigned int* counter
);
669 //! The function updates storage restrictions
670 void update(uintmax_t max_size
, uintmax_t min_free_space
, uintmax_t max_files
);
672 //! The function checks if the directory is governed by this collector
673 bool is_governed(filesystem::path
const& dir
) const
675 return filesystem::equivalent(m_StorageDir
, dir
);
679 //! Makes relative path absolute with respect to the base path
680 filesystem::path
make_absolute(filesystem::path
const& p
)
682 return filesystem::absolute(p
, m_BasePath
);
684 //! Acquires file name string from the path
685 static path_string_type
filename_string(filesystem::path
const& p
)
687 return p
.filename().string
< path_string_type
>();
692 //! The singleton of the list of file collectors
693 class file_collector_repository
:
694 public log::aux::lazy_singleton
< file_collector_repository
, shared_ptr
< file_collector_repository
> >
698 typedef log::aux::lazy_singleton
< file_collector_repository
, shared_ptr
< file_collector_repository
> > base_type
;
700 #if !defined(BOOST_LOG_BROKEN_FRIEND_TEMPLATE_SPECIALIZATIONS)
701 friend class log::aux::lazy_singleton
< file_collector_repository
, shared_ptr
< file_collector_repository
> >;
703 friend class base_type
;
706 //! The type of the list of collectors
707 typedef intrusive::list
<
709 intrusive::base_hook
< file_collector_hook
>
713 #if !defined(BOOST_LOG_NO_THREADS)
714 //! Synchronization mutex
716 #endif // !defined(BOOST_LOG_NO_THREADS)
717 //! The list of file collectors
718 file_collectors m_Collectors
;
721 //! Finds or creates a file collector
722 shared_ptr
< file::collector
> get_collector(
723 filesystem::path
const& target_dir
, uintmax_t max_size
, uintmax_t min_free_space
, uintmax_t max_files
);
725 //! Removes the file collector from the list
726 void remove_collector(file_collector
* p
);
729 //! Initializes the singleton instance
730 static void init_instance()
732 base_type::get_instance() = boost::make_shared
< file_collector_repository
>();
737 file_collector::file_collector(
738 shared_ptr
< file_collector_repository
> const& repo
,
739 filesystem::path
const& target_dir
,
741 uintmax_t min_free_space
,
746 m_MinFreeSpace(min_free_space
),
747 m_MaxFiles(max_files
),
748 m_BasePath(filesystem::current_path()),
751 m_StorageDir
= make_absolute(target_dir
);
752 filesystem::create_directories(m_StorageDir
);
756 file_collector::~file_collector()
758 m_pRepository
->remove_collector(this);
761 //! The function stores the specified file in the storage
762 void file_collector::store_file(filesystem::path
const& src_path
)
764 // NOTE FOR THE FOLLOWING CODE:
765 // Avoid using Boost.Filesystem functions that would call path::codecvt(). store_file() can be called
766 // at process termination, and the global codecvt facet can already be destroyed at this point.
767 // https://svn.boost.org/trac/boost/ticket/8642
769 // Let's construct the new file name
771 info
.m_TimeStamp
= filesystem::last_write_time(src_path
);
772 info
.m_Size
= filesystem::file_size(src_path
);
774 filesystem::path file_name_path
= src_path
.filename();
775 path_string_type file_name
= file_name_path
.native();
776 info
.m_Path
= m_StorageDir
/ file_name_path
;
778 // Check if the file is already in the target directory
779 filesystem::path src_dir
= src_path
.has_parent_path() ?
780 filesystem::system_complete(src_path
.parent_path()) :
782 const bool is_in_target_dir
= filesystem::equivalent(src_dir
, m_StorageDir
);
783 if (!is_in_target_dir
)
785 if (filesystem::exists(info
.m_Path
))
787 // If the file already exists, try to mangle the file name
788 // to ensure there's no conflict. I'll need to make this customizable some day.
789 file_counter_formatter
formatter(file_name
.size(), 5);
793 path_string_type alt_file_name
= formatter(file_name
, n
);
794 info
.m_Path
= m_StorageDir
/ filesystem::path(alt_file_name
);
795 if (!filesystem::exists(info
.m_Path
))
798 if (BOOST_UNLIKELY(n
== (std::numeric_limits
< unsigned int >::max
)()))
800 BOOST_THROW_EXCEPTION(filesystem_error(
801 "Target file exists and an unused fallback file name could not be found",
803 system::error_code(system::errc::io_error
, system::generic_category())));
810 // The directory should have been created in constructor, but just in case it got deleted since then...
811 filesystem::create_directories(m_StorageDir
);
814 BOOST_LOG_EXPR_IF_MT(lock_guard
< mutex
> lock(m_Mutex
);)
816 file_list::iterator it
= m_Files
.begin();
817 const file_list::iterator end
= m_Files
.end();
818 if (is_in_target_dir
)
820 // If the sink writes log file into the target dir (is_in_target_dir == true), it is possible that after scanning
821 // an old file entry refers to the file that is picked up by the sink for writing. Later on, the sink attempts
822 // to store the file in the storage. At best, this would result in duplicate file entries. At worst, if the storage
823 // limits trigger a deletion and this file get deleted, we may have an entry that refers to no actual file. In any case,
824 // the total size of files in the storage will be incorrect. Here we work around this problem and simply remove
825 // the old file entry without removing the file. The entry will be re-added to the list later.
828 system::error_code ec
;
829 if (filesystem::equivalent(it
->m_Path
, info
.m_Path
, ec
))
831 m_TotalSize
-= it
->m_Size
;
841 it
= m_Files
.begin();
844 // Check if an old file should be erased
845 uintmax_t free_space
= m_MinFreeSpace
? filesystem::space(m_StorageDir
).available
: static_cast< uintmax_t >(0);
847 (m_TotalSize
+ info
.m_Size
> m_MaxSize
|| (m_MinFreeSpace
&& m_MinFreeSpace
> free_space
) || m_MaxFiles
<= m_Files
.size()))
849 file_info
& old_info
= *it
;
850 system::error_code ec
;
851 filesystem::file_status status
= filesystem::status(old_info
.m_Path
, ec
);
853 if (status
.type() == filesystem::regular_file
)
857 filesystem::remove(old_info
.m_Path
);
858 // Free space has to be queried as it may not increase equally
859 // to the erased file size on compressed filesystems
861 free_space
= filesystem::space(m_StorageDir
).available
;
862 m_TotalSize
-= old_info
.m_Size
;
865 catch (system::system_error
&)
867 // Can't erase the file. Maybe it's locked? Never mind...
873 // If it's not a file or is absent, just remove it from the list
874 m_TotalSize
-= old_info
.m_Size
;
879 if (!is_in_target_dir
)
881 // Move/rename the file to the target storage
882 move_file(src_path
, info
.m_Path
);
885 m_Files
.push_back(info
);
886 m_TotalSize
+= info
.m_Size
;
889 //! Scans the target directory for the files that have already been stored
890 uintmax_t file_collector::scan_for_files(
891 file::scan_method method
, filesystem::path
const& pattern
, unsigned int* counter
)
893 uintmax_t file_count
= 0;
894 if (method
!= file::no_scan
)
896 filesystem::path dir
= m_StorageDir
;
897 path_string_type mask
;
898 if (method
== file::scan_matching
)
900 mask
= filename_string(pattern
);
901 if (pattern
.has_parent_path())
902 dir
= make_absolute(pattern
.parent_path());
909 system::error_code ec
;
910 filesystem::file_status status
= filesystem::status(dir
, ec
);
911 if (status
.type() == filesystem::directory_file
)
913 BOOST_LOG_EXPR_IF_MT(lock_guard
< mutex
> lock(m_Mutex
);)
919 filesystem::directory_iterator
it(dir
), end
;
920 uintmax_t total_size
= 0;
921 for (; it
!= end
; ++it
)
923 filesystem::directory_entry
const& dir_entry
= *it
;
925 info
.m_Path
= dir_entry
.path();
926 status
= dir_entry
.status(ec
);
927 if (status
.type() == filesystem::regular_file
)
929 // Check that there are no duplicates in the resulting list
932 static bool equivalent(filesystem::path
const& left
, file_info
const& right
)
934 return filesystem::equivalent(left
, right
.m_Path
);
937 if (std::find_if(m_Files
.begin(), m_Files
.end(),
938 boost::bind(&local::equivalent
, boost::cref(info
.m_Path
), _1
)) == m_Files
.end())
940 // Check that the file name matches the pattern
941 unsigned int file_number
= 0;
942 bool file_number_parsed
= false;
943 if (method
!= file::scan_matching
||
944 match_pattern(filename_string(info
.m_Path
), mask
, file_number
, file_number_parsed
))
946 info
.m_Size
= filesystem::file_size(info
.m_Path
);
947 total_size
+= info
.m_Size
;
948 info
.m_TimeStamp
= filesystem::last_write_time(info
.m_Path
);
949 files
.push_back(info
);
952 // Test that the file_number >= *counter accounting for the integer overflow
953 if (file_number_parsed
&& counter
!= NULL
&& (file_number
- *counter
) < ((~0u) ^ ((~0u) >> 1)))
954 *counter
= file_number
+ 1u;
960 // Sort files chronologically
961 m_Files
.splice(m_Files
.end(), files
);
962 m_TotalSize
+= total_size
;
963 m_Files
.sort(boost::bind(&file_info::m_TimeStamp
, _1
) < boost::bind(&file_info::m_TimeStamp
, _2
));
970 //! The function updates storage restrictions
971 void file_collector::update(uintmax_t max_size
, uintmax_t min_free_space
, uintmax_t max_files
)
973 BOOST_LOG_EXPR_IF_MT(lock_guard
< mutex
> lock(m_Mutex
);)
975 m_MaxSize
= (std::min
)(m_MaxSize
, max_size
);
976 m_MinFreeSpace
= (std::max
)(m_MinFreeSpace
, min_free_space
);
977 m_MaxFiles
= (std::min
)(m_MaxFiles
, max_files
);
981 //! Finds or creates a file collector
982 shared_ptr
< file::collector
> file_collector_repository::get_collector(
983 filesystem::path
const& target_dir
, uintmax_t max_size
, uintmax_t min_free_space
, uintmax_t max_files
)
985 BOOST_LOG_EXPR_IF_MT(lock_guard
< mutex
> lock(m_Mutex
);)
987 file_collectors::iterator it
= std::find_if(m_Collectors
.begin(), m_Collectors
.end(),
988 boost::bind(&file_collector::is_governed
, _1
, boost::cref(target_dir
)));
989 shared_ptr
< file_collector
> p
;
990 if (it
!= m_Collectors
.end()) try
992 // This may throw if the collector is being currently destroyed
993 p
= it
->shared_from_this();
994 p
->update(max_size
, min_free_space
, max_files
);
996 catch (bad_weak_ptr
&)
1002 p
= boost::make_shared
< file_collector
>(
1003 file_collector_repository::get(), target_dir
, max_size
, min_free_space
, max_files
);
1004 m_Collectors
.push_back(*p
);
1010 //! Removes the file collector from the list
1011 void file_collector_repository::remove_collector(file_collector
* p
)
1013 BOOST_LOG_EXPR_IF_MT(lock_guard
< mutex
> lock(m_Mutex
);)
1014 m_Collectors
.erase(m_Collectors
.iterator_to(*p
));
1017 //! Checks if the time point is valid
1018 void check_time_point_validity(unsigned char hour
, unsigned char minute
, unsigned char second
)
1020 if (BOOST_UNLIKELY(hour
>= 24))
1022 std::ostringstream strm
;
1023 strm
<< "Time point hours value is out of range: " << static_cast< unsigned int >(hour
);
1024 BOOST_THROW_EXCEPTION(std::out_of_range(strm
.str()));
1026 if (BOOST_UNLIKELY(minute
>= 60))
1028 std::ostringstream strm
;
1029 strm
<< "Time point minutes value is out of range: " << static_cast< unsigned int >(minute
);
1030 BOOST_THROW_EXCEPTION(std::out_of_range(strm
.str()));
1032 if (BOOST_UNLIKELY(second
>= 60))
1034 std::ostringstream strm
;
1035 strm
<< "Time point seconds value is out of range: " << static_cast< unsigned int >(second
);
1036 BOOST_THROW_EXCEPTION(std::out_of_range(strm
.str()));
1046 //! Creates and returns a file collector with the specified parameters
1047 BOOST_LOG_API shared_ptr
< collector
> make_collector(
1048 filesystem::path
const& target_dir
,
1050 uintmax_t min_free_space
,
1051 uintmax_t max_files
)
1053 return file_collector_repository::get()->get_collector(target_dir
, max_size
, min_free_space
, max_files
);
1058 //! Creates a rotation time point of every day at the specified time
1059 BOOST_LOG_API
rotation_at_time_point::rotation_at_time_point(
1061 unsigned char minute
,
1062 unsigned char second
1064 m_DayKind(not_specified
),
1069 m_Previous(date_time::not_a_date_time
)
1071 check_time_point_validity(hour
, minute
, second
);
1074 //! Creates a rotation time point of each specified weekday at the specified time
1075 BOOST_LOG_API
rotation_at_time_point::rotation_at_time_point(
1076 date_time::weekdays wday
,
1078 unsigned char minute
,
1079 unsigned char second
1082 m_Day(static_cast< unsigned char >(wday
)),
1086 m_Previous(date_time::not_a_date_time
)
1088 check_time_point_validity(hour
, minute
, second
);
1091 //! Creates a rotation time point of each specified day of month at the specified time
1092 BOOST_LOG_API
rotation_at_time_point::rotation_at_time_point(
1093 gregorian::greg_day mday
,
1095 unsigned char minute
,
1096 unsigned char second
1098 m_DayKind(monthday
),
1099 m_Day(static_cast< unsigned char >(mday
.as_number())),
1103 m_Previous(date_time::not_a_date_time
)
1105 check_time_point_validity(hour
, minute
, second
);
1108 //! Checks if it's time to rotate the file
1109 BOOST_LOG_API
bool rotation_at_time_point::operator()() const
1111 bool result
= false;
1112 posix_time::time_duration
rotation_time(
1113 static_cast< posix_time::time_duration::hour_type
>(m_Hour
),
1114 static_cast< posix_time::time_duration::min_type
>(m_Minute
),
1115 static_cast< posix_time::time_duration::sec_type
>(m_Second
));
1116 posix_time::ptime now
= posix_time::second_clock::local_time();
1118 if (m_Previous
.is_special())
1124 const bool time_of_day_passed
= rotation_time
.total_seconds() <= m_Previous
.time_of_day().total_seconds();
1129 // The rotation takes place every day at the specified time
1130 gregorian::date previous_date
= m_Previous
.date();
1131 if (time_of_day_passed
)
1132 previous_date
+= gregorian::days(1);
1133 posix_time::ptime
next(previous_date
, rotation_time
);
1134 result
= (now
>= next
);
1140 // The rotation takes place on the specified week day at the specified time
1141 gregorian::date previous_date
= m_Previous
.date(), next_date
= previous_date
;
1142 int weekday
= m_Day
, previous_weekday
= static_cast< int >(previous_date
.day_of_week().as_number());
1143 next_date
+= gregorian::days(weekday
- previous_weekday
);
1144 if (weekday
< previous_weekday
|| (weekday
== previous_weekday
&& time_of_day_passed
))
1146 next_date
+= gregorian::weeks(1);
1149 posix_time::ptime
next(next_date
, rotation_time
);
1150 result
= (now
>= next
);
1156 // The rotation takes place on the specified day of month at the specified time
1157 gregorian::date previous_date
= m_Previous
.date();
1158 gregorian::date::day_type monthday
= static_cast< gregorian::date::day_type
>(m_Day
),
1159 previous_monthday
= previous_date
.day();
1160 gregorian::date
next_date(previous_date
.year(), previous_date
.month(), monthday
);
1161 if (monthday
< previous_monthday
|| (monthday
== previous_monthday
&& time_of_day_passed
))
1163 next_date
+= gregorian::months(1);
1166 posix_time::ptime
next(next_date
, rotation_time
);
1167 result
= (now
>= next
);
1181 //! Checks if it's time to rotate the file
1182 BOOST_LOG_API
bool rotation_at_time_interval::operator()() const
1184 bool result
= false;
1185 posix_time::ptime now
= posix_time::second_clock::universal_time();
1186 if (m_Previous
.is_special())
1192 result
= (now
- m_Previous
) >= m_Interval
;
1202 ////////////////////////////////////////////////////////////////////////////////
1203 // File sink backend implementation
1204 ////////////////////////////////////////////////////////////////////////////////
1205 //! Sink implementation data
1206 struct text_file_backend::implementation
1209 std::ios_base::openmode m_FileOpenMode
;
1211 //! File name pattern
1212 filesystem::path m_FileNamePattern
;
1213 //! Directory to store files in
1214 filesystem::path m_StorageDir
;
1215 //! File name generator (according to m_FileNamePattern)
1216 boost::log::aux::light_function
< path_string_type (unsigned int) > m_FileNameGenerator
;
1218 //! Target file name pattern
1219 filesystem::path m_TargetFileNamePattern
;
1220 //! Target directory to store files in
1221 filesystem::path m_TargetStorageDir
;
1222 //! Target file name generator (according to m_TargetFileNamePattern)
1223 boost::log::aux::light_function
< path_string_type (unsigned int) > m_TargetFileNameGenerator
;
1225 //! Stored files counter
1226 unsigned int m_FileCounter
;
1228 //! Current file name
1229 filesystem::path m_FileName
;
1231 filesystem::ofstream m_File
;
1232 //! Characters written
1233 uintmax_t m_CharactersWritten
;
1235 //! File collector functional object
1236 shared_ptr
< file::collector
> m_pFileCollector
;
1237 //! File open handler
1238 open_handler_type m_OpenHandler
;
1239 //! File close handler
1240 close_handler_type m_CloseHandler
;
1242 //! The maximum temp file size, in characters written to the stream
1243 uintmax_t m_FileRotationSize
;
1244 //! Time-based rotation predicate
1245 time_based_rotation_predicate m_TimeBasedRotation
;
1246 //! Indicates whether to append a trailing newline after every log record
1247 auto_newline_mode m_AutoNewlineMode
;
1248 //! The flag shows if every written record should be flushed
1250 //! The flag indicates whether the final rotation should be performed
1251 bool m_FinalRotationEnabled
;
1253 implementation(uintmax_t rotation_size
, auto_newline_mode auto_newline
, bool auto_flush
, bool enable_final_rotation
) :
1254 m_FileOpenMode(std::ios_base::trunc
| std::ios_base::out
),
1256 m_CharactersWritten(0),
1257 m_FileRotationSize(rotation_size
),
1258 m_AutoNewlineMode(auto_newline
),
1259 m_AutoFlush(auto_flush
),
1260 m_FinalRotationEnabled(enable_final_rotation
)
1265 //! Constructor. No streams attached to the constructed backend, auto flush feature disabled.
1266 BOOST_LOG_API
text_file_backend::text_file_backend()
1268 construct(log::aux::empty_arg_list());
1272 BOOST_LOG_API
text_file_backend::~text_file_backend()
1276 // Attempt to put the temporary file into storage
1277 if (m_pImpl
->m_FinalRotationEnabled
&& m_pImpl
->m_File
.is_open() && m_pImpl
->m_CharactersWritten
> 0)
1287 //! Constructor implementation
1288 BOOST_LOG_API
void text_file_backend::construct(
1289 filesystem::path
const& pattern
,
1290 filesystem::path
const& target_file_name
,
1291 std::ios_base::openmode mode
,
1292 uintmax_t rotation_size
,
1293 time_based_rotation_predicate
const& time_based_rotation
,
1294 auto_newline_mode auto_newline
,
1296 bool enable_final_rotation
)
1298 m_pImpl
= new implementation(rotation_size
, auto_newline
, auto_flush
, enable_final_rotation
);
1299 set_file_name_pattern_internal(pattern
);
1300 set_target_file_name_pattern_internal(target_file_name
);
1301 set_time_based_rotation(time_based_rotation
);
1302 set_open_mode(mode
);
1305 //! The method sets maximum file size.
1306 BOOST_LOG_API
void text_file_backend::set_rotation_size(uintmax_t size
)
1308 m_pImpl
->m_FileRotationSize
= size
;
1311 //! The method sets the maximum time interval between file rotations.
1312 BOOST_LOG_API
void text_file_backend::set_time_based_rotation(time_based_rotation_predicate
const& predicate
)
1314 m_pImpl
->m_TimeBasedRotation
= predicate
;
1317 //! The method allows to enable or disable log file rotation on sink destruction.
1318 BOOST_LOG_API
void text_file_backend::enable_final_rotation(bool enable
)
1320 m_pImpl
->m_FinalRotationEnabled
= enable
;
1323 //! Sets the flag to automatically flush write buffers of the file being written after each log record.
1324 BOOST_LOG_API
void text_file_backend::auto_flush(bool enable
)
1326 m_pImpl
->m_AutoFlush
= enable
;
1329 //! Selects whether a trailing newline should be automatically inserted after every log record.
1330 BOOST_LOG_API
void text_file_backend::set_auto_newline_mode(auto_newline_mode mode
)
1332 m_pImpl
->m_AutoNewlineMode
= mode
;
1335 //! The method writes the message to the sink
1336 BOOST_LOG_API
void text_file_backend::consume(record_view
const& rec
, string_type
const& formatted_message
)
1338 typedef file_char_traits
< string_type::value_type
> traits_t
;
1340 filesystem::path prev_file_name
;
1341 bool use_prev_file_name
= false;
1342 if (BOOST_UNLIKELY(!m_pImpl
->m_File
.good()))
1344 // The file stream is not operational. One possible reason is that there is no more free space
1345 // on the file system. In this case it is possible that this log record will fail to be written as well,
1346 // leaving the newly creted file empty. Eventually this results in lots of empty log files.
1347 // We should take precautions to avoid this. https://svn.boost.org/trac/boost/ticket/11016
1348 prev_file_name
= m_pImpl
->m_FileName
;
1351 system::error_code ec
;
1352 uintmax_t size
= filesystem::file_size(prev_file_name
, ec
);
1353 if (!!ec
|| size
== 0)
1355 // To reuse the empty file avoid re-generating the new file name later
1356 use_prev_file_name
= true;
1358 else if (!!m_pImpl
->m_pFileCollector
)
1360 // Complete file rotation
1361 m_pImpl
->m_pFileCollector
->store_file(prev_file_name
);
1366 m_pImpl
->m_File
.is_open() &&
1368 m_pImpl
->m_CharactersWritten
+ formatted_message
.size() >= m_pImpl
->m_FileRotationSize
||
1369 (!m_pImpl
->m_TimeBasedRotation
.empty() && m_pImpl
->m_TimeBasedRotation())
1376 if (!m_pImpl
->m_File
.is_open())
1378 filesystem::path new_file_name
;
1379 if (!use_prev_file_name
)
1380 new_file_name
= m_pImpl
->m_StorageDir
/ m_pImpl
->m_FileNameGenerator(m_pImpl
->m_FileCounter
++);
1382 prev_file_name
.swap(new_file_name
);
1384 filesystem::create_directories(new_file_name
.parent_path());
1386 m_pImpl
->m_File
.open(new_file_name
, m_pImpl
->m_FileOpenMode
);
1387 if (BOOST_UNLIKELY(!m_pImpl
->m_File
.is_open()))
1389 BOOST_THROW_EXCEPTION(filesystem_error(
1390 "Failed to open file for writing",
1392 system::error_code(system::errc::io_error
, system::generic_category())));
1394 m_pImpl
->m_FileName
.swap(new_file_name
);
1396 if (!m_pImpl
->m_OpenHandler
.empty())
1397 m_pImpl
->m_OpenHandler(m_pImpl
->m_File
);
1399 m_pImpl
->m_CharactersWritten
= static_cast< std::streamoff
>(m_pImpl
->m_File
.tellp());
1402 m_pImpl
->m_File
.write(formatted_message
.data(), static_cast< std::streamsize
>(formatted_message
.size()));
1403 m_pImpl
->m_CharactersWritten
+= formatted_message
.size();
1405 if (m_pImpl
->m_AutoNewlineMode
!= disabled_auto_newline
)
1407 if (m_pImpl
->m_AutoNewlineMode
== always_insert
|| formatted_message
.empty() || *formatted_message
.rbegin() != traits_t::newline
)
1409 m_pImpl
->m_File
.put(traits_t::newline
);
1410 ++m_pImpl
->m_CharactersWritten
;
1414 if (m_pImpl
->m_AutoFlush
)
1415 m_pImpl
->m_File
.flush();
1418 //! The method flushes the currently open log file
1419 BOOST_LOG_API
void text_file_backend::flush()
1421 if (m_pImpl
->m_File
.is_open())
1422 m_pImpl
->m_File
.flush();
1425 //! The method sets file name pattern
1426 BOOST_LOG_API
void text_file_backend::set_file_name_pattern_internal(filesystem::path
const& pattern
)
1428 typedef file_char_traits
< path_char_type
> traits_t
;
1430 parse_file_name_pattern
1432 !pattern
.empty() ? pattern
: filesystem::path(traits_t::default_file_name_pattern()),
1433 m_pImpl
->m_StorageDir
,
1434 m_pImpl
->m_FileNamePattern
,
1435 m_pImpl
->m_FileNameGenerator
1439 //! The method sets target file name pattern
1440 BOOST_LOG_API
void text_file_backend::set_target_file_name_pattern_internal(filesystem::path
const& pattern
)
1442 if (!pattern
.empty())
1444 parse_file_name_pattern(pattern
, m_pImpl
->m_TargetStorageDir
, m_pImpl
->m_TargetFileNamePattern
, m_pImpl
->m_TargetFileNameGenerator
);
1448 m_pImpl
->m_TargetStorageDir
.clear();
1449 m_pImpl
->m_TargetFileNamePattern
.clear();
1450 m_pImpl
->m_TargetFileNameGenerator
.clear();
1454 //! Closes the currently open file
1455 void text_file_backend::close_file()
1457 if (m_pImpl
->m_File
.is_open())
1459 if (!m_pImpl
->m_CloseHandler
.empty())
1461 // Rationale: We should call the close handler even if the stream is !good() because
1462 // writing the footer may not be the only thing the handler does. However, there is
1463 // a chance that the file had become writable since the last failure (e.g. there was
1464 // no space left to write the last record, but it got freed since then), so if the handler
1465 // attempts to write a footer it may succeed now. For this reason we clear the stream state
1466 // and let the handler have a try.
1467 m_pImpl
->m_File
.clear();
1468 m_pImpl
->m_CloseHandler(m_pImpl
->m_File
);
1471 m_pImpl
->m_File
.close();
1474 m_pImpl
->m_File
.clear();
1475 m_pImpl
->m_CharactersWritten
= 0;
1476 m_pImpl
->m_FileName
.clear();
1479 //! The method rotates the file
1480 BOOST_LOG_API
void text_file_backend::rotate_file()
1482 filesystem::path prev_file_name
= m_pImpl
->m_FileName
;
1485 if (!!m_pImpl
->m_TargetFileNameGenerator
)
1487 filesystem::path new_file_name
;
1488 new_file_name
= m_pImpl
->m_TargetStorageDir
/ m_pImpl
->m_TargetFileNameGenerator(m_pImpl
->m_FileCounter
);
1490 if (new_file_name
!= prev_file_name
)
1492 filesystem::create_directories(new_file_name
.parent_path());
1493 move_file(prev_file_name
, new_file_name
);
1495 prev_file_name
.swap(new_file_name
);
1499 if (!!m_pImpl
->m_pFileCollector
)
1501 // Check if the file has not been deleted by another process
1502 system::error_code ec
;
1503 filesystem::file_status status
= filesystem::status(prev_file_name
, ec
);
1504 if (status
.type() == filesystem::regular_file
)
1505 m_pImpl
->m_pFileCollector
->store_file(prev_file_name
);
1509 //! The method sets the file open mode
1510 BOOST_LOG_API
void text_file_backend::set_open_mode(std::ios_base::openmode mode
)
1512 mode
|= std::ios_base::out
;
1513 mode
&= ~std::ios_base::in
;
1514 if ((mode
& (std::ios_base::trunc
| std::ios_base::app
)) == 0)
1515 mode
|= std::ios_base::trunc
;
1516 m_pImpl
->m_FileOpenMode
= mode
;
1519 //! The method sets file collector
1520 BOOST_LOG_API
void text_file_backend::set_file_collector(shared_ptr
< file::collector
> const& collector
)
1522 m_pImpl
->m_pFileCollector
= collector
;
1525 //! The method sets file open handler
1526 BOOST_LOG_API
void text_file_backend::set_open_handler(open_handler_type
const& handler
)
1528 m_pImpl
->m_OpenHandler
= handler
;
1531 //! The method sets file close handler
1532 BOOST_LOG_API
void text_file_backend::set_close_handler(close_handler_type
const& handler
)
1534 m_pImpl
->m_CloseHandler
= handler
;
1537 //! The method returns name of the currently open log file. If no file is open, returns an empty path.
1538 BOOST_LOG_API
filesystem::path
text_file_backend::get_current_file_name() const
1540 return m_pImpl
->m_FileName
;
1543 //! Performs scanning of the target directory for log files
1544 BOOST_LOG_API
uintmax_t text_file_backend::scan_for_files(file::scan_method method
, bool update_counter
)
1546 if (BOOST_LIKELY(!!m_pImpl
->m_pFileCollector
))
1548 unsigned int* counter
= update_counter
? &m_pImpl
->m_FileCounter
: static_cast< unsigned int* >(NULL
);
1549 return m_pImpl
->m_pFileCollector
->scan_for_files
1552 m_pImpl
->m_TargetFileNamePattern
.empty() ? m_pImpl
->m_FileNamePattern
: m_pImpl
->m_TargetFileNamePattern
,
1558 BOOST_LOG_THROW_DESCR(setup_error
, "File collector is not set");
1562 } // namespace sinks
1564 BOOST_LOG_CLOSE_NAMESPACE
// namespace log
1566 } // namespace boost
1568 #include <boost/log/detail/footer.hpp>