1 /*
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)
6 */
7 /*!
8 * \file text_file_backend.cpp
9 * \author Andrey Semashev
10 * \date 09.06.2009
11 *
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.
14 */
15
16 #include <ctime>
17 #include <cctype>
18 #include <cwctype>
19 #include <ctime>
20 #include <cstdio>
21 #include <cstdlib>
22 #include <cstddef>
23 #include <list>
24 #include <string>
25 #include <locale>
26 #include <ostream>
27 #include <sstream>
28 #include <iterator>
29 #include <algorithm>
30 #include <stdexcept>
31 #include <boost/ref.hpp>
32 #include <boost/bind.hpp>
33 #include <boost/cstdint.hpp>
34 #include <boost/smart_ptr/make_shared_object.hpp>
35 #include <boost/enable_shared_from_this.hpp>
36 #include <boost/throw_exception.hpp>
37 #include <boost/mpl/if.hpp>
38 #include <boost/type_traits/is_same.hpp>
39 #include <boost/system/error_code.hpp>
40 #include <boost/system/system_error.hpp>
41 #include <boost/filesystem/path.hpp>
42 #include <boost/filesystem/fstream.hpp>
43 #include <boost/filesystem/operations.hpp>
44 #include <boost/filesystem/convenience.hpp>
45 #include <boost/intrusive/list.hpp>
46 #include <boost/intrusive/list_hook.hpp>
47 #include <boost/intrusive/options.hpp>
48 #include <boost/date_time/posix_time/posix_time.hpp>
49 #include <boost/date_time/gregorian/gregorian_types.hpp>
50 #include <boost/spirit/home/qi/numeric/numeric_utils.hpp>
51 #include <boost/log/detail/singleton.hpp>
52 #include <boost/log/detail/light_function.hpp>
53 #include <boost/log/exceptions.hpp>
54 #include <boost/log/attributes/time_traits.hpp>
55 #include <boost/log/sinks/text_file_backend.hpp>
56 #include "unique_ptr.hpp"
57
58 #if !defined(BOOST_LOG_NO_THREADS)
59 #include <boost/thread/locks.hpp>
60 #include <boost/thread/mutex.hpp>
61 #endif // !defined(BOOST_LOG_NO_THREADS)
62
63 #include <boost/log/detail/header.hpp>
64
65 namespace qi = boost::spirit::qi;
66
67 namespace boost {
68
69 BOOST_LOG_OPEN_NAMESPACE
70
71 namespace sinks {
72
73 BOOST_LOG_ANONYMOUS_NAMESPACE {
74
75 typedef filesystem::filesystem_error filesystem_error;
76
77 //! A possible Boost.Filesystem extension - renames or moves the file to the target storage
78 inline void move_file(
79 filesystem::path const& from,
80 filesystem::path const& to)
81 {
82 #if defined(BOOST_WINDOWS_API)
83 // On Windows MoveFile already does what we need
84 filesystem::rename(from, to);
85 #else
86 // On POSIX rename fails if the target points to a different device
87 system::error_code ec;
88 filesystem::rename(from, to, ec);
89 if (ec)
90 {
91 if (ec.value() == system::errc::cross_device_link)
92 {
93 // Attempt to manually move the file instead
94 filesystem::copy_file(from, to);
95 filesystem::remove(from);
96 }
97 else
98 {
99 BOOST_THROW_EXCEPTION(filesystem_error("failed to move file to another location", from, to, ec));
100 }
101 }
102 #endif
103 }
104
105 typedef filesystem::path::string_type path_string_type;
106 typedef path_string_type::value_type path_char_type;
107
108 //! An auxiliary traits that contain various constants and functions regarding string and character operations
109 template< typename CharT >
110 struct file_char_traits;
111
112 template< >
113 struct file_char_traits< char >
114 {
115 typedef char char_type;
116
117 static const char_type percent = '%';
118 static const char_type number_placeholder = 'N';
119 static const char_type day_placeholder = 'd';
120 static const char_type month_placeholder = 'm';
121 static const char_type year_placeholder = 'y';
122 static const char_type full_year_placeholder = 'Y';
123 static const char_type frac_sec_placeholder = 'f';
124 static const char_type seconds_placeholder = 'S';
125 static const char_type minutes_placeholder = 'M';
126 static const char_type hours_placeholder = 'H';
127 static const char_type space = ' ';
128 static const char_type plus = '+';
129 static const char_type minus = '-';
130 static const char_type zero = '0';
131 static const char_type dot = '.';
132 static const char_type newline = '\n';
133
134 static bool is_digit(char c)
135 {
136 using namespace std;
137 return (isdigit(c) != 0);
138 }
139 static std::string default_file_name_pattern() { return "%5N.log"; }
140 };
141
142 #ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
143 const file_char_traits< char >::char_type file_char_traits< char >::percent;
144 const file_char_traits< char >::char_type file_char_traits< char >::number_placeholder;
145 const file_char_traits< char >::char_type file_char_traits< char >::day_placeholder;
146 const file_char_traits< char >::char_type file_char_traits< char >::month_placeholder;
147 const file_char_traits< char >::char_type file_char_traits< char >::year_placeholder;
148 const file_char_traits< char >::char_type file_char_traits< char >::full_year_placeholder;
149 const file_char_traits< char >::char_type file_char_traits< char >::frac_sec_placeholder;
150 const file_char_traits< char >::char_type file_char_traits< char >::seconds_placeholder;
151 const file_char_traits< char >::char_type file_char_traits< char >::minutes_placeholder;
152 const file_char_traits< char >::char_type file_char_traits< char >::hours_placeholder;
153 const file_char_traits< char >::char_type file_char_traits< char >::space;
154 const file_char_traits< char >::char_type file_char_traits< char >::plus;
155 const file_char_traits< char >::char_type file_char_traits< char >::minus;
156 const file_char_traits< char >::char_type file_char_traits< char >::zero;
157 const file_char_traits< char >::char_type file_char_traits< char >::dot;
158 const file_char_traits< char >::char_type file_char_traits< char >::newline;
159 #endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
160
161 template< >
162 struct file_char_traits< wchar_t >
163 {
164 typedef wchar_t char_type;
165
166 static const char_type percent = L'%';
167 static const char_type number_placeholder = L'N';
168 static const char_type day_placeholder = L'd';
169 static const char_type month_placeholder = L'm';
170 static const char_type year_placeholder = L'y';
171 static const char_type full_year_placeholder = L'Y';
172 static const char_type frac_sec_placeholder = L'f';
173 static const char_type seconds_placeholder = L'S';
174 static const char_type minutes_placeholder = L'M';
175 static const char_type hours_placeholder = L'H';
176 static const char_type space = L' ';
177 static const char_type plus = L'+';
178 static const char_type minus = L'-';
179 static const char_type zero = L'0';
180 static const char_type dot = L'.';
181 static const char_type newline = L'\n';
182
183 static bool is_digit(wchar_t c)
184 {
185 using namespace std;
186 return (iswdigit(c) != 0);
187 }
188 static std::wstring default_file_name_pattern() { return L"%5N.log"; }
189 };
190
191 #ifndef BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
192 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::percent;
193 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::number_placeholder;
194 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::day_placeholder;
195 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::month_placeholder;
196 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::year_placeholder;
197 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::full_year_placeholder;
198 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::frac_sec_placeholder;
199 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::seconds_placeholder;
200 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::minutes_placeholder;
201 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::hours_placeholder;
202 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::space;
203 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::plus;
204 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::minus;
205 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::zero;
206 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::dot;
207 const file_char_traits< wchar_t >::char_type file_char_traits< wchar_t >::newline;
208 #endif // BOOST_LOG_BROKEN_STATIC_CONSTANTS_LINKAGE
209
210 //! Date and time formatter
211 class date_and_time_formatter
212 {
213 public:
214 typedef path_string_type result_type;
215
216 private:
217 typedef date_time::time_facet< posix_time::ptime, path_char_type > time_facet_type;
218
219 private:
220 time_facet_type* m_pFacet;
221 mutable std::basic_ostringstream< path_char_type > m_Stream;
222
223 public:
224 //! Constructor
225 date_and_time_formatter() : m_pFacet(NULL)
226 {
227 log::aux::unique_ptr< time_facet_type > pFacet(new time_facet_type());
228 m_pFacet = pFacet.get();
229 std::locale loc(m_Stream.getloc(), m_pFacet);
230 pFacet.release();
231 m_Stream.imbue(loc);
232 }
233 //! Copy constructor
234 date_and_time_formatter(date_and_time_formatter const& that) :
235 m_pFacet(that.m_pFacet)
236 {
237 m_Stream.imbue(that.m_Stream.getloc());
238 }
239 //! The method formats the current date and time according to the format string str and writes the result into it
240 path_string_type operator()(path_string_type const& pattern, unsigned int counter) const
241 {
242 m_pFacet->format(pattern.c_str());
243 m_Stream.str(path_string_type());
244 m_Stream << boost::log::attributes::local_time_traits::get_clock();
245 if (m_Stream.good())
246 {
247 return m_Stream.str();
248 }
249 else
250 {
251 m_Stream.clear();
252 return pattern;
253 }
254 }
255 };
256
257 //! The functor formats the file counter into the file name
258 class file_counter_formatter
259 {
260 public:
261 typedef path_string_type result_type;
262
263 private:
264 //! The position in the pattern where the file counter placeholder is
265 path_string_type::size_type m_FileCounterPosition;
266 //! File counter width
267 std::streamsize m_Width;
268 //! The file counter formatting stream
269 mutable std::basic_ostringstream< path_char_type > m_Stream;
270
271 public:
272 //! Initializing constructor
273 file_counter_formatter(path_string_type::size_type pos, unsigned int width) :
274 m_FileCounterPosition(pos),
275 m_Width(width)
276 {
277 typedef file_char_traits< path_char_type > traits_t;
278 m_Stream.fill(traits_t::zero);
279 }
280 //! Copy constructor
281 file_counter_formatter(file_counter_formatter const& that) :
282 m_FileCounterPosition(that.m_FileCounterPosition),
283 m_Width(that.m_Width)
284 {
285 m_Stream.fill(that.m_Stream.fill());
286 }
287
288 //! The function formats the file counter into the file name
289 path_string_type operator()(path_string_type const& pattern, unsigned int counter) const
290 {
291 path_string_type file_name = pattern;
292
293 m_Stream.str(path_string_type());
294 m_Stream.width(m_Width);
295 m_Stream << counter;
296 file_name.insert(m_FileCounterPosition, m_Stream.str());
297
298 return file_name;
299 }
300 };
301
302 //! The function returns the pattern as the file name
303 class empty_formatter
304 {
305 public:
306 typedef path_string_type result_type;
307
308 private:
309 path_string_type m_Pattern;
310
311 public:
312 //! Initializing constructor
313 explicit empty_formatter(path_string_type const& pattern) : m_Pattern(pattern)
314 {
315 }
316 //! Copy constructor
317 empty_formatter(empty_formatter const& that) : m_Pattern(that.m_Pattern)
318 {
319 }
320
321 //! The function returns the pattern as the file name
322 path_string_type const& operator() (unsigned int) const
323 {
324 return m_Pattern;
325 }
326 };
327
328 //! The function parses the format placeholder for file counter
329 bool parse_counter_placeholder(path_string_type::const_iterator& it, path_string_type::const_iterator end, unsigned int& width)
330 {
331 typedef qi::extract_uint< unsigned int, 10, 1, -1 > width_extract;
332 typedef file_char_traits< path_char_type > traits_t;
333 if (it == end)
334 return false;
335
336 path_char_type c = *it;
337 if (c == traits_t::zero || c == traits_t::space || c == traits_t::plus || c == traits_t::minus)
338 {
339 // Skip filler and alignment specification
340 ++it;
341 if (it == end)
342 return false;
343 c = *it;
344 }
345
346 if (traits_t::is_digit(c))
347 {
348 // Parse width
349 if (!width_extract::call(it, end, width))
350 return false;
351 if (it == end)
352 return false;
353 c = *it;
354 }
355
356 if (c == traits_t::dot)
357 {
358 // Skip precision
359 ++it;
360 while (it != end && traits_t::is_digit(*it))
361 ++it;
362 if (it == end)
363 return false;
364 c = *it;
365 }
366
367 if (c == traits_t::number_placeholder)
368 {
369 ++it;
370 return true;
371 }
372
373 return false;
374 }
375
376 //! The function matches the file name and the pattern
377 bool match_pattern(path_string_type const& file_name, path_string_type const& pattern, unsigned int& file_counter)
378 {
379 typedef qi::extract_uint< unsigned int, 10, 1, -1 > file_counter_extract;
380 typedef file_char_traits< path_char_type > traits_t;
381
382 struct local
383 {
384 // Verifies that the string contains exactly n digits
385 static bool scan_digits(path_string_type::const_iterator& it, path_string_type::const_iterator end, std::ptrdiff_t n)
386 {
387 for (; n > 0; --n)
388 {
389 path_char_type c = *it++;
390 if (!traits_t::is_digit(c) || it == end)
391 return false;
392 }
393 return true;
394 }
395 };
396
397 path_string_type::const_iterator
398 f_it = file_name.begin(),
399 f_end = file_name.end(),
400 p_it = pattern.begin(),
401 p_end = pattern.end();
402 bool placeholder_expected = false;
403 while (f_it != f_end && p_it != p_end)
404 {
405 path_char_type p_c = *p_it, f_c = *f_it;
406 if (!placeholder_expected)
407 {
408 if (p_c == traits_t::percent)
409 {
410 placeholder_expected = true;
411 ++p_it;
412 }
413 else if (p_c == f_c)
414 {
415 ++p_it;
416 ++f_it;
417 }
418 else
419 return false;
420 }
421 else
422 {
423 switch (p_c)
424 {
425 case traits_t::percent: // An escaped '%'
426 if (p_c == f_c)
427 {
428 ++p_it;
429 ++f_it;
430 break;
431 }
432 else
433 return false;
434
435 case traits_t::seconds_placeholder: // Date/time components with 2-digits width
436 case traits_t::minutes_placeholder:
437 case traits_t::hours_placeholder:
438 case traits_t::day_placeholder:
439 case traits_t::month_placeholder:
440 case traits_t::year_placeholder:
441 if (!local::scan_digits(f_it, f_end, 2))
442 return false;
443 ++p_it;
444 break;
445
446 case traits_t::full_year_placeholder: // Date/time components with 4-digits width
447 if (!local::scan_digits(f_it, f_end, 4))
448 return false;
449 ++p_it;
450 break;
451
452 case traits_t::frac_sec_placeholder: // Fraction seconds width is configuration-dependent
453 typedef posix_time::time_res_traits posix_resolution_traits;
454 if (!local::scan_digits(f_it, f_end, posix_resolution_traits::num_fractional_digits()))
455 {
456 return false;
457 }
458 ++p_it;
459 break;
460
461 default: // This should be the file counter placeholder or some unsupported placeholder
462 {
463 path_string_type::const_iterator p = p_it;
464 unsigned int width = 0;
465 if (!parse_counter_placeholder(p, p_end, width))
466 {
467 BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported placeholder used in pattern for file scanning"));
468 }
469
470 // Find where the file number ends
471 path_string_type::const_iterator f = f_it;
472 if (!local::scan_digits(f, f_end, width))
473 return false;
474 while (f != f_end && traits_t::is_digit(*f))
475 ++f;
476
477 if (!file_counter_extract::call(f_it, f, file_counter))
478 return false;
479
480 p_it = p;
481 }
482 break;
483 }
484
485 placeholder_expected = false;
486 }
487 }
488
489 if (p_it == p_end)
490 {
491 if (f_it != f_end)
492 {
493 // The actual file name may end with an additional counter
494 // that is added by the collector in case if file name clash
495 return local::scan_digits(f_it, f_end, std::distance(f_it, f_end));
496 }
497 else
498 return true;
499 }
500 else
501 return false;
502 }
503
504
505 class file_collector_repository;
506
507 //! Type of the hook used for sequencing file collectors
508 typedef intrusive::list_base_hook<
509 intrusive::link_mode< intrusive::safe_link >
510 > file_collector_hook;
511
512 //! Log file collector implementation
513 class file_collector :
514 public file::collector,
515 public file_collector_hook,
516 public enable_shared_from_this< file_collector >
517 {
518 private:
519 //! Information about a single stored file
520 struct file_info
521 {
522 uintmax_t m_Size;
523 std::time_t m_TimeStamp;
524 filesystem::path m_Path;
525 };
526 //! A list of the stored files
527 typedef std::list< file_info > file_list;
528 //! The string type compatible with the universal path type
529 typedef filesystem::path::string_type path_string_type;
530
531 private:
532 //! A reference to the repository this collector belongs to
533 shared_ptr< file_collector_repository > m_pRepository;
534
535 #if !defined(BOOST_LOG_NO_THREADS)
536 //! Synchronization mutex
537 mutex m_Mutex;
538 #endif // !defined(BOOST_LOG_NO_THREADS)
539
540 //! Total file size upper limit
541 uintmax_t m_MaxSize;
542 //! Free space lower limit
543 uintmax_t m_MinFreeSpace;
544 //! The current path at the point when the collector is created
545 /*
546 * The special member is required to calculate absolute paths with no
547 * dependency on the current path for the application, which may change
548 */
549 const filesystem::path m_BasePath;
550 //! Target directory to store files to
551 filesystem::path m_StorageDir;
552
553 //! The list of stored files
554 file_list m_Files;
555 //! Total size of the stored files
556 uintmax_t m_TotalSize;
557
558 public:
559 //! Constructor
560 file_collector(
561 shared_ptr< file_collector_repository > const& repo,
562 filesystem::path const& target_dir,
563 uintmax_t max_size,
564 uintmax_t min_free_space);
565
566 //! Destructor
567 ~file_collector();
568
569 //! The function stores the specified file in the storage
570 void store_file(filesystem::path const& file_name);
571
572 //! Scans the target directory for the files that have already been stored
573 uintmax_t scan_for_files(
574 file::scan_method method, filesystem::path const& pattern, unsigned int* counter);
575
576 //! The function updates storage restrictions
577 void update(uintmax_t max_size, uintmax_t min_free_space);
578
579 //! The function checks if the directory is governed by this collector
580 bool is_governed(filesystem::path const& dir) const
581 {
582 return filesystem::equivalent(m_StorageDir, dir);
583 }
584
585 private:
586 //! Makes relative path absolute with respect to the base path
587 filesystem::path make_absolute(filesystem::path const& p)
588 {
589 return filesystem::absolute(p, m_BasePath);
590 }
591 //! Acquires file name string from the path
592 static path_string_type filename_string(filesystem::path const& p)
593 {
594 return p.filename().string< path_string_type >();
595 }
596 };
597
598
599 //! The singleton of the list of file collectors
600 class file_collector_repository :
601 public log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > >
602 {
603 private:
604 //! Base type
605 typedef log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > > base_type;
606
607 #if !defined(BOOST_LOG_BROKEN_FRIEND_TEMPLATE_SPECIALIZATIONS)
608 friend class log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > >;
609 #else
610 friend class base_type;
611 #endif
612
613 //! The type of the list of collectors
614 typedef intrusive::list<
615 file_collector,
616 intrusive::base_hook< file_collector_hook >
617 > file_collectors;
618
619 private:
620 #if !defined(BOOST_LOG_NO_THREADS)
621 //! Synchronization mutex
622 mutex m_Mutex;
623 #endif // !defined(BOOST_LOG_NO_THREADS)
624 //! The list of file collectors
625 file_collectors m_Collectors;
626
627 public:
628 //! Finds or creates a file collector
629 shared_ptr< file::collector > get_collector(
630 filesystem::path const& target_dir, uintmax_t max_size, uintmax_t min_free_space);
631
632 //! Removes the file collector from the list
633 void remove_collector(file_collector* p);
634
635 private:
636 //! Initializes the singleton instance
637 static void init_instance()
638 {
639 base_type::get_instance() = boost::make_shared< file_collector_repository >();
640 }
641 };
642
643 //! Constructor
644 file_collector::file_collector(
645 shared_ptr< file_collector_repository > const& repo,
646 filesystem::path const& target_dir,
647 uintmax_t max_size,
648 uintmax_t min_free_space
649 ) :
650 m_pRepository(repo),
651 m_MaxSize(max_size),
652 m_MinFreeSpace(min_free_space),
653 m_BasePath(filesystem::current_path()),
654 m_TotalSize(0)
655 {
656 m_StorageDir = make_absolute(target_dir);
657 filesystem::create_directories(m_StorageDir);
658 }
659
660 //! Destructor
661 file_collector::~file_collector()
662 {
663 m_pRepository->remove_collector(this);
664 }
665
666 //! The function stores the specified file in the storage
667 void file_collector::store_file(filesystem::path const& src_path)
668 {
669 // NOTE FOR THE FOLLOWING CODE:
670 // Avoid using Boost.Filesystem functions that would call path::codecvt(). store_file() can be called
671 // at process termination, and the global codecvt facet can already be destroyed at this point.
672 // https://svn.boost.org/trac/boost/ticket/8642
673
674 // Let's construct the new file name
675 file_info info;
676 info.m_TimeStamp = filesystem::last_write_time(src_path);
677 info.m_Size = filesystem::file_size(src_path);
678
679 filesystem::path file_name_path = src_path.filename();
680 path_string_type file_name = file_name_path.native();
681 info.m_Path = m_StorageDir / file_name_path;
682
683 // Check if the file is already in the target directory
684 filesystem::path src_dir = src_path.has_parent_path() ?
685 filesystem::system_complete(src_path.parent_path()) :
686 m_BasePath;
687 const bool is_in_target_dir = filesystem::equivalent(src_dir, m_StorageDir);
688 if (!is_in_target_dir)
689 {
690 if (filesystem::exists(info.m_Path))
691 {
692 // If the file already exists, try to mangle the file name
693 // to ensure there's no conflict. I'll need to make this customizable some day.
694 file_counter_formatter formatter(file_name.size(), 5);
695 unsigned int n = 0;
696 do
697 {
698 path_string_type alt_file_name = formatter(file_name, n++);
699 info.m_Path = m_StorageDir / filesystem::path(alt_file_name);
700 }
701 while (filesystem::exists(info.m_Path) && n < (std::numeric_limits< unsigned int >::max)());
702 }
703
704 // The directory should have been created in constructor, but just in case it got deleted since then...
705 filesystem::create_directories(m_StorageDir);
706 }
707
708 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
709
710 // Check if an old file should be erased
711 uintmax_t free_space = m_MinFreeSpace ? filesystem::space(m_StorageDir).available : static_cast< uintmax_t >(0);
712 file_list::iterator it = m_Files.begin(), end = m_Files.end();
713 while (it != end &&
714 (m_TotalSize + info.m_Size > m_MaxSize || (m_MinFreeSpace && m_MinFreeSpace > free_space)))
715 {
716 file_info& old_info = *it;
717 if (filesystem::exists(old_info.m_Path) && filesystem::is_regular_file(old_info.m_Path))
718 {
719 try
720 {
721 filesystem::remove(old_info.m_Path);
722 // Free space has to be queried as it may not increase equally
723 // to the erased file size on compressed filesystems
724 if (m_MinFreeSpace)
725 free_space = filesystem::space(m_StorageDir).available;
726 m_TotalSize -= old_info.m_Size;
727 m_Files.erase(it++);
728 }
729 catch (system::system_error&)
730 {
731 // Can't erase the file. Maybe it's locked? Never mind...
732 ++it;
733 }
734 }
735 else
736 {
737 // If it's not a file or is absent, just remove it from the list
738 m_TotalSize -= old_info.m_Size;
739 m_Files.erase(it++);
740 }
741 }
742
743 if (!is_in_target_dir)
744 {
745 // Move/rename the file to the target storage
746 move_file(src_path, info.m_Path);
747 }
748
749 m_Files.push_back(info);
750 m_TotalSize += info.m_Size;
751 }
752
753 //! Scans the target directory for the files that have already been stored
754 uintmax_t file_collector::scan_for_files(
755 file::scan_method method, filesystem::path const& pattern, unsigned int* counter)
756 {
757 uintmax_t file_count = 0;
758 if (method != file::no_scan)
759 {
760 filesystem::path dir = m_StorageDir;
761 path_string_type mask;
762 if (method == file::scan_matching)
763 {
764 mask = filename_string(pattern);
765 if (pattern.has_parent_path())
766 dir = make_absolute(pattern.parent_path());
767 }
768 else
769 {
770 counter = NULL;
771 }
772
773 if (filesystem::exists(dir) && filesystem::is_directory(dir))
774 {
775 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
776
777 if (counter)
778 *counter = 0;
779
780 file_list files;
781 filesystem::directory_iterator it(dir), end;
782 uintmax_t total_size = 0;
783 for (; it != end; ++it)
784 {
785 file_info info;
786 info.m_Path = *it;
787 if (filesystem::is_regular_file(info.m_Path))
788 {
789 // Check that there are no duplicates in the resulting list
790 struct local
791 {
792 static bool equivalent(filesystem::path const& left, file_info const& right)
793 {
794 return filesystem::equivalent(left, right.m_Path);
795 }
796 };
797 if (std::find_if(m_Files.begin(), m_Files.end(),
798 boost::bind(&local::equivalent, boost::cref(info.m_Path), _1)) == m_Files.end())
799 {
800 // Check that the file name matches the pattern
801 unsigned int file_number = 0;
802 if (method != file::scan_matching ||
803 match_pattern(filename_string(info.m_Path), mask, file_number))
804 {
805 info.m_Size = filesystem::file_size(info.m_Path);
806 total_size += info.m_Size;
807 info.m_TimeStamp = filesystem::last_write_time(info.m_Path);
808 files.push_back(info);
809 ++file_count;
810
811 if (counter && file_number >= *counter)
812 *counter = file_number + 1;
813 }
814 }
815 }
816 }
817
818 // Sort files chronologically
819 m_Files.splice(m_Files.end(), files);
820 m_TotalSize += total_size;
821 m_Files.sort(boost::bind(&file_info::m_TimeStamp, _1) < boost::bind(&file_info::m_TimeStamp, _2));
822 }
823 }
824
825 return file_count;
826 }
827
828 //! The function updates storage restrictions
829 void file_collector::update(uintmax_t max_size, uintmax_t min_free_space)
830 {
831 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
832
833 m_MaxSize = (std::min)(m_MaxSize, max_size);
834 m_MinFreeSpace = (std::max)(m_MinFreeSpace, min_free_space);
835 }
836
837
838 //! Finds or creates a file collector
839 shared_ptr< file::collector > file_collector_repository::get_collector(
840 filesystem::path const& target_dir, uintmax_t max_size, uintmax_t min_free_space)
841 {
842 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
843
844 file_collectors::iterator it = std::find_if(m_Collectors.begin(), m_Collectors.end(),
845 boost::bind(&file_collector::is_governed, _1, boost::cref(target_dir)));
846 shared_ptr< file_collector > p;
847 if (it != m_Collectors.end()) try
848 {
849 // This may throw if the collector is being currently destroyed
850 p = it->shared_from_this();
851 p->update(max_size, min_free_space);
852 }
853 catch (bad_weak_ptr&)
854 {
855 }
856
857 if (!p)
858 {
859 p = boost::make_shared< file_collector >(
860 file_collector_repository::get(), target_dir, max_size, min_free_space);
861 m_Collectors.push_back(*p);
862 }
863
864 return p;
865 }
866
867 //! Removes the file collector from the list
868 void file_collector_repository::remove_collector(file_collector* p)
869 {
870 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
871 m_Collectors.erase(m_Collectors.iterator_to(*p));
872 }
873
874 //! Checks if the time point is valid
875 void check_time_point_validity(unsigned char hour, unsigned char minute, unsigned char second)
876 {
877 if (hour >= 24)
878 {
879 std::ostringstream strm;
880 strm << "Time point hours value is out of range: " << static_cast< unsigned int >(hour);
881 BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
882 }
883 if (minute >= 60)
884 {
885 std::ostringstream strm;
886 strm << "Time point minutes value is out of range: " << static_cast< unsigned int >(minute);
887 BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
888 }
889 if (second >= 60)
890 {
891 std::ostringstream strm;
892 strm << "Time point seconds value is out of range: " << static_cast< unsigned int >(second);
893 BOOST_THROW_EXCEPTION(std::out_of_range(strm.str()));
894 }
895 }
896
897 } // namespace
898
899 namespace file {
900
901 namespace aux {
902
903 //! Creates and returns a file collector with the specified parameters
make_collector(filesystem::path const & target_dir,uintmax_t max_size,uintmax_t min_free_space)904 BOOST_LOG_API shared_ptr< collector > make_collector(
905 filesystem::path const& target_dir,
906 uintmax_t max_size,
907 uintmax_t min_free_space)
908 {
909 return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space);
910 }
911
912 } // namespace aux
913
914 //! Creates a rotation time point of every day at the specified time
rotation_at_time_point(unsigned char hour,unsigned char minute,unsigned char second)915 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
916 unsigned char hour,
917 unsigned char minute,
918 unsigned char second
919 ) :
920 m_DayKind(not_specified),
921 m_Day(0),
922 m_Hour(hour),
923 m_Minute(minute),
924 m_Second(second),
925 m_Previous(date_time::not_a_date_time)
926 {
927 check_time_point_validity(hour, minute, second);
928 }
929
930 //! Creates a rotation time point of each specified weekday at the specified time
rotation_at_time_point(date_time::weekdays wday,unsigned char hour,unsigned char minute,unsigned char second)931 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
932 date_time::weekdays wday,
933 unsigned char hour,
934 unsigned char minute,
935 unsigned char second
936 ) :
937 m_DayKind(weekday),
938 m_Day(static_cast< unsigned char >(wday)),
939 m_Hour(hour),
940 m_Minute(minute),
941 m_Second(second),
942 m_Previous(date_time::not_a_date_time)
943 {
944 check_time_point_validity(hour, minute, second);
945 }
946
947 //! Creates a rotation time point of each specified day of month at the specified time
rotation_at_time_point(gregorian::greg_day mday,unsigned char hour,unsigned char minute,unsigned char second)948 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
949 gregorian::greg_day mday,
950 unsigned char hour,
951 unsigned char minute,
952 unsigned char second
953 ) :
954 m_DayKind(monthday),
955 m_Day(static_cast< unsigned char >(mday.as_number())),
956 m_Hour(hour),
957 m_Minute(minute),
958 m_Second(second),
959 m_Previous(date_time::not_a_date_time)
960 {
961 check_time_point_validity(hour, minute, second);
962 }
963
964 //! Checks if it's time to rotate the file
operator ()() const965 BOOST_LOG_API bool rotation_at_time_point::operator()() const
966 {
967 bool result = false;
968 posix_time::time_duration rotation_time(
969 static_cast< posix_time::time_duration::hour_type >(m_Hour),
970 static_cast< posix_time::time_duration::min_type >(m_Minute),
971 static_cast< posix_time::time_duration::sec_type >(m_Second));
972 posix_time::ptime now = posix_time::second_clock::local_time();
973
974 if (m_Previous.is_special())
975 {
976 m_Previous = now;
977 return false;
978 }
979
980 const bool time_of_day_passed = rotation_time.total_seconds() <= m_Previous.time_of_day().total_seconds();
981 switch (m_DayKind)
982 {
983 case not_specified:
984 {
985 // The rotation takes place every day at the specified time
986 gregorian::date previous_date = m_Previous.date();
987 if (time_of_day_passed)
988 previous_date += gregorian::days(1);
989 posix_time::ptime next(previous_date, rotation_time);
990 result = (now >= next);
991 }
992 break;
993
994 case weekday:
995 {
996 // The rotation takes place on the specified week day at the specified time
997 gregorian::date previous_date = m_Previous.date(), next_date = previous_date;
998 int weekday = m_Day, previous_weekday = static_cast< int >(previous_date.day_of_week().as_number());
999 next_date += gregorian::days(weekday - previous_weekday);
1000 if (weekday < previous_weekday || (weekday == previous_weekday && time_of_day_passed))
1001 {
1002 next_date += gregorian::weeks(1);
1003 }
1004
1005 posix_time::ptime next(next_date, rotation_time);
1006 result = (now >= next);
1007 }
1008 break;
1009
1010 case monthday:
1011 {
1012 // The rotation takes place on the specified day of month at the specified time
1013 gregorian::date previous_date = m_Previous.date();
1014 gregorian::date::day_type monthday = static_cast< gregorian::date::day_type >(m_Day),
1015 previous_monthday = previous_date.day();
1016 gregorian::date next_date(previous_date.year(), previous_date.month(), monthday);
1017 if (monthday < previous_monthday || (monthday == previous_monthday && time_of_day_passed))
1018 {
1019 next_date += gregorian::months(1);
1020 }
1021
1022 posix_time::ptime next(next_date, rotation_time);
1023 result = (now >= next);
1024 }
1025 break;
1026
1027 default:
1028 break;
1029 }
1030
1031 if (result)
1032 m_Previous = now;
1033
1034 return result;
1035 }
1036
1037 //! Checks if it's time to rotate the file
operator ()() const1038 BOOST_LOG_API bool rotation_at_time_interval::operator()() const
1039 {
1040 bool result = false;
1041 posix_time::ptime now = posix_time::second_clock::universal_time();
1042 if (m_Previous.is_special())
1043 {
1044 m_Previous = now;
1045 return false;
1046 }
1047
1048 result = (now - m_Previous) >= m_Interval;
1049
1050 if (result)
1051 m_Previous = now;
1052
1053 return result;
1054 }
1055
1056 } // namespace file
1057
1058 ////////////////////////////////////////////////////////////////////////////////
1059 // File sink backend implementation
1060 ////////////////////////////////////////////////////////////////////////////////
1061 //! Sink implementation data
1062 struct text_file_backend::implementation
1063 {
1064 //! File open mode
1065 std::ios_base::openmode m_FileOpenMode;
1066
1067 //! File name pattern
1068 filesystem::path m_FileNamePattern;
1069 //! Directory to store files in
1070 filesystem::path m_StorageDir;
1071 //! File name generator (according to m_FileNamePattern)
1072 boost::log::aux::light_function< path_string_type (unsigned int) > m_FileNameGenerator;
1073
1074 //! Stored files counter
1075 unsigned int m_FileCounter;
1076
1077 //! Current file name
1078 filesystem::path m_FileName;
1079 //! File stream
1080 filesystem::ofstream m_File;
1081 //! Characters written
1082 uintmax_t m_CharactersWritten;
1083
1084 //! File collector functional object
1085 shared_ptr< file::collector > m_pFileCollector;
1086 //! File open handler
1087 open_handler_type m_OpenHandler;
1088 //! File close handler
1089 close_handler_type m_CloseHandler;
1090
1091 //! The maximum temp file size, in characters written to the stream
1092 uintmax_t m_FileRotationSize;
1093 //! Time-based rotation predicate
1094 time_based_rotation_predicate m_TimeBasedRotation;
1095 //! The flag shows if every written record should be flushed
1096 bool m_AutoFlush;
1097
implementationboost::sinks::text_file_backend::implementation1098 implementation(uintmax_t rotation_size, bool auto_flush) :
1099 m_FileOpenMode(std::ios_base::trunc | std::ios_base::out),
1100 m_FileCounter(0),
1101 m_CharactersWritten(0),
1102 m_FileRotationSize(rotation_size),
1103 m_AutoFlush(auto_flush)
1104 {
1105 }
1106 };
1107
1108 //! Constructor. No streams attached to the constructed backend, auto flush feature disabled.
text_file_backend()1109 BOOST_LOG_API text_file_backend::text_file_backend()
1110 {
1111 construct(log::aux::empty_arg_list());
1112 }
1113
1114 //! Destructor
~text_file_backend()1115 BOOST_LOG_API text_file_backend::~text_file_backend()
1116 {
1117 try
1118 {
1119 // Attempt to put the temporary file into storage
1120 if (m_pImpl->m_File.is_open() && m_pImpl->m_CharactersWritten > 0)
1121 rotate_file();
1122 }
1123 catch (...)
1124 {
1125 }
1126
1127 delete m_pImpl;
1128 }
1129
1130 //! Constructor implementation
construct(filesystem::path const & pattern,std::ios_base::openmode mode,uintmax_t rotation_size,time_based_rotation_predicate const & time_based_rotation,bool auto_flush)1131 BOOST_LOG_API void text_file_backend::construct(
1132 filesystem::path const& pattern,
1133 std::ios_base::openmode mode,
1134 uintmax_t rotation_size,
1135 time_based_rotation_predicate const& time_based_rotation,
1136 bool auto_flush)
1137 {
1138 m_pImpl = new implementation(rotation_size, auto_flush);
1139 set_file_name_pattern_internal(pattern);
1140 set_time_based_rotation(time_based_rotation);
1141 set_open_mode(mode);
1142 }
1143
1144 //! The method sets maximum file size.
set_rotation_size(uintmax_t size)1145 BOOST_LOG_API void text_file_backend::set_rotation_size(uintmax_t size)
1146 {
1147 m_pImpl->m_FileRotationSize = size;
1148 }
1149
1150 //! The method sets the maximum time interval between file rotations.
set_time_based_rotation(time_based_rotation_predicate const & predicate)1151 BOOST_LOG_API void text_file_backend::set_time_based_rotation(time_based_rotation_predicate const& predicate)
1152 {
1153 m_pImpl->m_TimeBasedRotation = predicate;
1154 }
1155
1156 //! Sets the flag to automatically flush buffers of all attached streams after each log record
auto_flush(bool f)1157 BOOST_LOG_API void text_file_backend::auto_flush(bool f)
1158 {
1159 m_pImpl->m_AutoFlush = f;
1160 }
1161
1162 //! The method writes the message to the sink
consume(record_view const & rec,string_type const & formatted_message)1163 BOOST_LOG_API void text_file_backend::consume(record_view const& rec, string_type const& formatted_message)
1164 {
1165 typedef file_char_traits< string_type::value_type > traits_t;
1166
1167 bool no_new_filename = false;
1168 if (!m_pImpl->m_File.good())
1169 {
1170 // The file stream is not operational. One possible reason is that there is no more free space
1171 // on the file system. In this case it is possible that this log record will fail to be written as well,
1172 // leaving the newly creted file empty. Eventually this results in lots of empty log files.
1173 // We should take precautions to avoid this. https://svn.boost.org/trac/boost/ticket/11016
1174 close_file();
1175
1176 system::error_code ec;
1177 uintmax_t size = filesystem::file_size(m_pImpl->m_FileName, ec);
1178 if (!!ec || size == 0)
1179 {
1180 // To reuse the empty file avoid re-generating the new file name later
1181 no_new_filename = true;
1182 }
1183 else if (!!m_pImpl->m_pFileCollector)
1184 {
1185 // Complete file rotation
1186 m_pImpl->m_pFileCollector->store_file(m_pImpl->m_FileName);
1187 }
1188 }
1189 else if
1190 (
1191 m_pImpl->m_File.is_open() &&
1192 (
1193 m_pImpl->m_CharactersWritten + formatted_message.size() >= m_pImpl->m_FileRotationSize ||
1194 (!m_pImpl->m_TimeBasedRotation.empty() && m_pImpl->m_TimeBasedRotation())
1195 )
1196 )
1197 {
1198 rotate_file();
1199 }
1200
1201 if (!m_pImpl->m_File.is_open())
1202 {
1203 if (!no_new_filename)
1204 m_pImpl->m_FileName = m_pImpl->m_StorageDir / m_pImpl->m_FileNameGenerator(m_pImpl->m_FileCounter++);
1205
1206 filesystem::create_directories(m_pImpl->m_FileName.parent_path());
1207
1208 m_pImpl->m_File.open(m_pImpl->m_FileName, m_pImpl->m_FileOpenMode);
1209 if (!m_pImpl->m_File.is_open())
1210 {
1211 filesystem_error err(
1212 "Failed to open file for writing",
1213 m_pImpl->m_FileName,
1214 system::error_code(system::errc::io_error, system::generic_category()));
1215 BOOST_THROW_EXCEPTION(err);
1216 }
1217
1218 if (!m_pImpl->m_OpenHandler.empty())
1219 m_pImpl->m_OpenHandler(m_pImpl->m_File);
1220
1221 m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
1222 }
1223
1224 m_pImpl->m_File.write(formatted_message.data(), static_cast< std::streamsize >(formatted_message.size()));
1225 m_pImpl->m_File.put(traits_t::newline);
1226
1227 m_pImpl->m_CharactersWritten += formatted_message.size() + 1;
1228
1229 if (m_pImpl->m_AutoFlush)
1230 m_pImpl->m_File.flush();
1231 }
1232
1233 //! The method flushes the currently open log file
flush()1234 BOOST_LOG_API void text_file_backend::flush()
1235 {
1236 if (m_pImpl->m_File.is_open())
1237 m_pImpl->m_File.flush();
1238 }
1239
1240 //! The method sets file name mask
set_file_name_pattern_internal(filesystem::path const & pattern)1241 BOOST_LOG_API void text_file_backend::set_file_name_pattern_internal(filesystem::path const& pattern)
1242 {
1243 // Note: avoid calling Boost.Filesystem functions that involve path::codecvt()
1244 // https://svn.boost.org/trac/boost/ticket/9119
1245
1246 typedef file_char_traits< path_char_type > traits_t;
1247 filesystem::path p = pattern;
1248 if (p.empty())
1249 p = filesystem::path(traits_t::default_file_name_pattern());
1250
1251 m_pImpl->m_FileNamePattern = p.filename();
1252 path_string_type name_pattern = m_pImpl->m_FileNamePattern.native();
1253 m_pImpl->m_StorageDir = filesystem::absolute(p.parent_path());
1254
1255 // Let's try to find the file counter placeholder
1256 unsigned int placeholder_count = 0;
1257 unsigned int width = 0;
1258 bool counter_found = false;
1259 path_string_type::size_type counter_pos = 0;
1260 path_string_type::const_iterator end = name_pattern.end();
1261 path_string_type::const_iterator it = name_pattern.begin();
1262
1263 do
1264 {
1265 it = std::find(it, end, traits_t::percent);
1266 if (it == end)
1267 break;
1268 path_string_type::const_iterator placeholder_begin = it++;
1269 if (it == end)
1270 break;
1271 if (*it == traits_t::percent)
1272 {
1273 // An escaped percent detected
1274 ++it;
1275 continue;
1276 }
1277
1278 ++placeholder_count;
1279
1280 if (!counter_found)
1281 {
1282 path_string_type::const_iterator it2 = it;
1283 if (parse_counter_placeholder(it2, end, width))
1284 {
1285 // We've found the file counter placeholder in the pattern
1286 counter_found = true;
1287 counter_pos = placeholder_begin - name_pattern.begin();
1288 name_pattern.erase(counter_pos, it2 - placeholder_begin);
1289 --placeholder_count;
1290 it = name_pattern.begin() + counter_pos;
1291 end = name_pattern.end();
1292 }
1293 }
1294 }
1295 while (it != end);
1296
1297 // Construct the formatter functor
1298 if (placeholder_count > 0)
1299 {
1300 if (counter_found)
1301 {
1302 // Both counter and date/time placeholder in the pattern
1303 m_pImpl->m_FileNameGenerator = boost::bind(date_and_time_formatter(),
1304 boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1), _1);
1305 }
1306 else
1307 {
1308 // Only date/time placeholders in the pattern
1309 m_pImpl->m_FileNameGenerator =
1310 boost::bind(date_and_time_formatter(), name_pattern, _1);
1311 }
1312 }
1313 else if (counter_found)
1314 {
1315 // Only counter placeholder in the pattern
1316 m_pImpl->m_FileNameGenerator =
1317 boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1);
1318 }
1319 else
1320 {
1321 // No placeholders detected
1322 m_pImpl->m_FileNameGenerator = empty_formatter(name_pattern);
1323 }
1324 }
1325
1326 //! Closes the currently open file
close_file()1327 void text_file_backend::close_file()
1328 {
1329 if (!m_pImpl->m_CloseHandler.empty())
1330 {
1331 // Rationale: We should call the close handler even if the stream is !good() because
1332 // writing the footer may not be the only thing the handler does. However, there is
1333 // a chance that the file had become writable since the last failure (e.g. there was
1334 // no space left to write the last record, but it got freed since then), so if the handler
1335 // attempts to write a footer it may succeed now. For this reason we clear the stream state
1336 // and let the handler have a try.
1337 m_pImpl->m_File.clear();
1338 m_pImpl->m_CloseHandler(m_pImpl->m_File);
1339 }
1340 m_pImpl->m_File.close();
1341 m_pImpl->m_File.clear();
1342 m_pImpl->m_CharactersWritten = 0;
1343 }
1344
1345 //! The method rotates the file
rotate_file()1346 BOOST_LOG_API void text_file_backend::rotate_file()
1347 {
1348 close_file();
1349
1350 if (!!m_pImpl->m_pFileCollector)
1351 m_pImpl->m_pFileCollector->store_file(m_pImpl->m_FileName);
1352 }
1353
1354 //! The method sets the file open mode
set_open_mode(std::ios_base::openmode mode)1355 BOOST_LOG_API void text_file_backend::set_open_mode(std::ios_base::openmode mode)
1356 {
1357 mode |= std::ios_base::out;
1358 mode &= ~std::ios_base::in;
1359 if ((mode & (std::ios_base::trunc | std::ios_base::app)) == 0)
1360 mode |= std::ios_base::trunc;
1361 m_pImpl->m_FileOpenMode = mode;
1362 }
1363
1364 //! The method sets file collector
set_file_collector(shared_ptr<file::collector> const & collector)1365 BOOST_LOG_API void text_file_backend::set_file_collector(shared_ptr< file::collector > const& collector)
1366 {
1367 m_pImpl->m_pFileCollector = collector;
1368 }
1369
1370 //! The method sets file open handler
set_open_handler(open_handler_type const & handler)1371 BOOST_LOG_API void text_file_backend::set_open_handler(open_handler_type const& handler)
1372 {
1373 m_pImpl->m_OpenHandler = handler;
1374 }
1375
1376 //! The method sets file close handler
set_close_handler(close_handler_type const & handler)1377 BOOST_LOG_API void text_file_backend::set_close_handler(close_handler_type const& handler)
1378 {
1379 m_pImpl->m_CloseHandler = handler;
1380 }
1381
1382 //! Performs scanning of the target directory for log files
scan_for_files(file::scan_method method,bool update_counter)1383 BOOST_LOG_API uintmax_t text_file_backend::scan_for_files(file::scan_method method, bool update_counter)
1384 {
1385 if (m_pImpl->m_pFileCollector)
1386 {
1387 unsigned int* counter = update_counter ? &m_pImpl->m_FileCounter : static_cast< unsigned int* >(NULL);
1388 return m_pImpl->m_pFileCollector->scan_for_files(method, m_pImpl->m_FileNamePattern, counter);
1389 }
1390 else
1391 {
1392 BOOST_LOG_THROW_DESCR(setup_error, "File collector is not set");
1393 }
1394 }
1395
1396 } // namespace sinks
1397
1398 BOOST_LOG_CLOSE_NAMESPACE // namespace log
1399
1400 } // namespace boost
1401
1402 #include <boost/log/detail/footer.hpp>
1403