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 <boost/log/detail/config.hpp>
17 #include <ctime>
18 #include <cctype>
19 #include <cwctype>
20 #include <ctime>
21 #include <cstdio>
22 #include <cstdlib>
23 #include <cstddef>
24 #include <list>
25 #include <string>
26 #include <locale>
27 #include <ostream>
28 #include <sstream>
29 #include <iterator>
30 #include <algorithm>
31 #include <stdexcept>
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"
59 
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)
64 
65 #include <boost/log/detail/header.hpp>
66 
67 namespace qi = boost::spirit::qi;
68 
69 namespace boost {
70 
71 BOOST_LOG_OPEN_NAMESPACE
72 
73 namespace sinks {
74 
75 BOOST_LOG_ANONYMOUS_NAMESPACE {
76 
77     typedef filesystem::filesystem_error filesystem_error;
78 
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)
83     {
84 #if defined(BOOST_WINDOWS_API)
85         // On Windows MoveFile already does what we need
86         filesystem::rename(from, to);
87 #else
88         // On POSIX rename fails if the target points to a different device
89         system::error_code ec;
90         filesystem::rename(from, to, ec);
91         if (ec)
92         {
93             if (BOOST_LIKELY(ec.value() == system::errc::cross_device_link))
94             {
95                 // Attempt to manually move the file instead
96                 filesystem::copy_file(from, to);
97                 filesystem::remove(from);
98             }
99             else
100             {
101                 BOOST_THROW_EXCEPTION(filesystem_error("failed to move file to another location", from, to, ec));
102             }
103         }
104 #endif
105     }
106 
107     typedef filesystem::path::string_type path_string_type;
108     typedef path_string_type::value_type path_char_type;
109 
110     //! An auxiliary traits that contain various constants and functions regarding string and character operations
111     template< typename CharT >
112     struct file_char_traits;
113 
114     template< >
115     struct file_char_traits< char >
116     {
117         typedef char char_type;
118 
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';
135 
136         static bool is_digit(char c)
137         {
138             using namespace std;
139             return (isdigit(c) != 0);
140         }
141         static std::string default_file_name_pattern() { return "%5N.log"; }
142     };
143 
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
162 
163     template< >
164     struct file_char_traits< wchar_t >
165     {
166         typedef wchar_t char_type;
167 
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';
184 
185         static bool is_digit(wchar_t c)
186         {
187             using namespace std;
188             return (iswdigit(c) != 0);
189         }
190         static std::wstring default_file_name_pattern() { return L"%5N.log"; }
191     };
192 
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
211 
212     //! Date and time formatter
213     class date_and_time_formatter
214     {
215     public:
216         typedef path_string_type result_type;
217 
218     private:
219         typedef date_time::time_facet< posix_time::ptime, path_char_type > time_facet_type;
220 
221     private:
222         mutable time_facet_type m_Facet;
223         mutable std::basic_ostringstream< path_char_type > m_Stream;
224 
225     public:
226         //! Constructor
227         date_and_time_formatter() : m_Facet(1u)
228         {
229         }
230         //! Copy constructor
231         date_and_time_formatter(date_and_time_formatter const& that) : m_Facet(1u)
232         {
233         }
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
236         {
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());
244             if (m_Stream.good())
245             {
246                 return m_Stream.str();
247             }
248             else
249             {
250                 m_Stream.clear();
251                 return pattern;
252             }
253         }
254 
255         BOOST_DELETED_FUNCTION(date_and_time_formatter& operator= (date_and_time_formatter const&))
256     };
257 
258     //! The functor formats the file counter into the file name
259     class file_counter_formatter
260     {
261     public:
262         typedef path_string_type result_type;
263 
264     private:
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;
271 
272     public:
273         //! Initializing constructor
274         file_counter_formatter(path_string_type::size_type pos, unsigned int width) :
275             m_FileCounterPosition(pos),
276             m_Width(width)
277         {
278             typedef file_char_traits< path_char_type > traits_t;
279             m_Stream.fill(traits_t::zero);
280         }
281         //! Copy constructor
282         file_counter_formatter(file_counter_formatter const& that) :
283             m_FileCounterPosition(that.m_FileCounterPosition),
284             m_Width(that.m_Width)
285         {
286             m_Stream.fill(that.m_Stream.fill());
287         }
288 
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
291         {
292             path_string_type file_name = pattern;
293 
294             m_Stream.str(path_string_type());
295             m_Stream.width(m_Width);
296             m_Stream << counter;
297             file_name.insert(m_FileCounterPosition, m_Stream.str());
298 
299             return file_name;
300         }
301 
302         BOOST_DELETED_FUNCTION(file_counter_formatter& operator= (file_counter_formatter const&))
303     };
304 
305     //! The function returns the pattern as the file name
306     class empty_formatter
307     {
308     public:
309         typedef path_string_type result_type;
310 
311     private:
312         path_string_type m_Pattern;
313 
314     public:
315         //! Initializing constructor
316         explicit empty_formatter(path_string_type const& pattern) : m_Pattern(pattern)
317         {
318         }
319         //! Copy constructor
320         empty_formatter(empty_formatter const& that) : m_Pattern(that.m_Pattern)
321         {
322         }
323 
324         //! The function returns the pattern as the file name
325         path_string_type const& operator() (unsigned int) const
326         {
327             return m_Pattern;
328         }
329 
330         BOOST_DELETED_FUNCTION(empty_formatter& operator= (empty_formatter const&))
331     };
332 
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)
335     {
336         typedef qi::extract_uint< unsigned int, 10, 1, -1 > width_extract;
337         typedef file_char_traits< path_char_type > traits_t;
338         if (it == end)
339             return false;
340 
341         path_char_type c = *it;
342         if (c == traits_t::zero || c == traits_t::space || c == traits_t::plus || c == traits_t::minus)
343         {
344             // Skip filler and alignment specification
345             ++it;
346             if (it == end)
347                 return false;
348             c = *it;
349         }
350 
351         if (traits_t::is_digit(c))
352         {
353             // Parse width
354             if (!width_extract::call(it, end, width))
355                 return false;
356             if (it == end)
357                 return false;
358             c = *it;
359         }
360 
361         if (c == traits_t::dot)
362         {
363             // Skip precision
364             ++it;
365             while (it != end && traits_t::is_digit(*it))
366                 ++it;
367             if (it == end)
368                 return false;
369             c = *it;
370         }
371 
372         if (c == traits_t::number_placeholder)
373         {
374             ++it;
375             return true;
376         }
377 
378         return false;
379     }
380 
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)
383     {
384         typedef qi::extract_uint< unsigned int, 10, 1, -1 > file_counter_extract;
385         typedef file_char_traits< path_char_type > traits_t;
386 
387         struct local
388         {
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)
391             {
392                 for (; n > 0; --n)
393                 {
394                     if (it == end)
395                         return false;
396                     path_char_type c = *it++;
397                     if (!traits_t::is_digit(c))
398                         return false;
399                 }
400                 return true;
401             }
402         };
403 
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)
411         {
412             path_char_type p_c = *p_it, f_c = *f_it;
413             if (!placeholder_expected)
414             {
415                 if (p_c == traits_t::percent)
416                 {
417                     placeholder_expected = true;
418                     ++p_it;
419                 }
420                 else if (p_c == f_c)
421                 {
422                     ++p_it;
423                     ++f_it;
424                 }
425                 else
426                     return false;
427             }
428             else
429             {
430                 switch (p_c)
431                 {
432                 case traits_t::percent: // An escaped '%'
433                     if (p_c == f_c)
434                     {
435                         ++p_it;
436                         ++f_it;
437                         break;
438                     }
439                     else
440                         return false;
441 
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))
449                         return false;
450                     ++p_it;
451                     break;
452 
453                 case traits_t::full_year_placeholder: // Date/time components with 4-digits width
454                     if (!local::scan_digits(f_it, f_end, 4))
455                         return false;
456                     ++p_it;
457                     break;
458 
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()))
462                     {
463                         return false;
464                     }
465                     ++p_it;
466                     break;
467 
468                 default: // This should be the file counter placeholder or some unsupported placeholder
469                     {
470                         path_string_type::const_iterator p = p_it;
471                         unsigned int width = 0;
472                         if (!parse_counter_placeholder(p, p_end, width))
473                         {
474                             BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported placeholder used in pattern for file scanning"));
475                         }
476 
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))
480                             return false;
481                         while (f != f_end && traits_t::is_digit(*f))
482                             ++f;
483 
484                         if (!file_counter_extract::call(f_it, f, file_counter))
485                             return false;
486 
487                         file_counter_parsed = true;
488                         p_it = p;
489                     }
490                     break;
491                 }
492 
493                 placeholder_expected = false;
494             }
495         }
496 
497         if (p_it == p_end)
498         {
499             if (f_it != f_end)
500             {
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));
504             }
505             else
506                 return true;
507         }
508         else
509             return false;
510     }
511 
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)
514     {
515         // Note: avoid calling Boost.Filesystem functions that involve path::codecvt()
516         // https://svn.boost.org/trac/boost/ticket/9119
517 
518         typedef file_char_traits< path_char_type > traits_t;
519 
520         file_name_pattern = pattern.filename();
521         path_string_type name_pattern = file_name_pattern.native();
522         storage_dir = filesystem::absolute(pattern.parent_path());
523 
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();
531 
532         do
533         {
534             it = std::find(it, end, traits_t::percent);
535             if (it == end)
536                 break;
537             path_string_type::const_iterator placeholder_begin = it++;
538             if (it == end)
539                 break;
540             if (*it == traits_t::percent)
541             {
542                 // An escaped percent detected
543                 ++it;
544                 continue;
545             }
546 
547             ++placeholder_count;
548 
549             if (!counter_found)
550             {
551                 path_string_type::const_iterator it2 = it;
552                 if (parse_counter_placeholder(it2, end, width))
553                 {
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);
558                     --placeholder_count;
559                     it = name_pattern.begin() + counter_pos;
560                     end = name_pattern.end();
561                 }
562             }
563         }
564         while (it != end);
565 
566         // Construct the formatter functor
567         if (placeholder_count > 0)
568         {
569             if (counter_found)
570             {
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);
574             }
575             else
576             {
577                 // Only date/time placeholders in the pattern
578                 file_name_generator = boost::bind(date_and_time_formatter(), name_pattern, _1);
579             }
580         }
581         else if (counter_found)
582         {
583             // Only counter placeholder in the pattern
584             file_name_generator = boost::bind(file_counter_formatter(counter_pos, width), name_pattern, _1);
585         }
586         else
587         {
588             // No placeholders detected
589             file_name_generator = empty_formatter(name_pattern);
590         }
591     }
592 
593 
594     class file_collector_repository;
595 
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;
600 
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 >
606     {
607     private:
608         //! Information about a single stored file
609         struct file_info
610         {
611             uintmax_t m_Size;
612             std::time_t m_TimeStamp;
613             filesystem::path m_Path;
614         };
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;
619 
620     private:
621         //! A reference to the repository this collector belongs to
622         shared_ptr< file_collector_repository > m_pRepository;
623 
624 #if !defined(BOOST_LOG_NO_THREADS)
625         //! Synchronization mutex
626         mutex m_Mutex;
627 #endif // !defined(BOOST_LOG_NO_THREADS)
628 
629         //! Total file size upper limit
630         uintmax_t m_MaxSize;
631         //! Free space lower limit
632         uintmax_t m_MinFreeSpace;
633         //! File count upper limit
634         uintmax_t m_MaxFiles;
635 
636         //! The current path at the point when the collector is created
637         /*
638          * The special member is required to calculate absolute paths with no
639          * dependency on the current path for the application, which may change
640          */
641         const filesystem::path m_BasePath;
642         //! Target directory to store files to
643         filesystem::path m_StorageDir;
644 
645         //! The list of stored files
646         file_list m_Files;
647         //! Total size of the stored files
648         uintmax_t m_TotalSize;
649 
650     public:
651         //! Constructor
652         file_collector(
653             shared_ptr< file_collector_repository > const& repo,
654             filesystem::path const& target_dir,
655             uintmax_t max_size,
656             uintmax_t min_free_space,
657             uintmax_t max_files);
658 
659         //! Destructor
660         ~file_collector();
661 
662         //! The function stores the specified file in the storage
663         void store_file(filesystem::path const& file_name);
664 
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);
668 
669         //! The function updates storage restrictions
670         void update(uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files);
671 
672         //! The function checks if the directory is governed by this collector
673         bool is_governed(filesystem::path const& dir) const
674         {
675             return filesystem::equivalent(m_StorageDir, dir);
676         }
677 
678     private:
679         //! Makes relative path absolute with respect to the base path
680         filesystem::path make_absolute(filesystem::path const& p)
681         {
682             return filesystem::absolute(p, m_BasePath);
683         }
684         //! Acquires file name string from the path
685         static path_string_type filename_string(filesystem::path const& p)
686         {
687             return p.filename().string< path_string_type >();
688         }
689     };
690 
691 
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 > >
695     {
696     private:
697         //! Base type
698         typedef log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > > base_type;
699 
700 #if !defined(BOOST_LOG_BROKEN_FRIEND_TEMPLATE_SPECIALIZATIONS)
701         friend class log::aux::lazy_singleton< file_collector_repository, shared_ptr< file_collector_repository > >;
702 #else
703         friend class base_type;
704 #endif
705 
706         //! The type of the list of collectors
707         typedef intrusive::list<
708             file_collector,
709             intrusive::base_hook< file_collector_hook >
710         > file_collectors;
711 
712     private:
713 #if !defined(BOOST_LOG_NO_THREADS)
714         //! Synchronization mutex
715         mutex m_Mutex;
716 #endif // !defined(BOOST_LOG_NO_THREADS)
717         //! The list of file collectors
718         file_collectors m_Collectors;
719 
720     public:
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);
724 
725         //! Removes the file collector from the list
726         void remove_collector(file_collector* p);
727 
728     private:
729         //! Initializes the singleton instance
730         static void init_instance()
731         {
732             base_type::get_instance() = boost::make_shared< file_collector_repository >();
733         }
734     };
735 
736     //! Constructor
737     file_collector::file_collector(
738         shared_ptr< file_collector_repository > const& repo,
739         filesystem::path const& target_dir,
740         uintmax_t max_size,
741         uintmax_t min_free_space,
742         uintmax_t max_files
743     ) :
744         m_pRepository(repo),
745         m_MaxSize(max_size),
746         m_MinFreeSpace(min_free_space),
747         m_MaxFiles(max_files),
748         m_BasePath(filesystem::current_path()),
749         m_TotalSize(0)
750     {
751         m_StorageDir = make_absolute(target_dir);
752         filesystem::create_directories(m_StorageDir);
753     }
754 
755     //! Destructor
756     file_collector::~file_collector()
757     {
758         m_pRepository->remove_collector(this);
759     }
760 
761     //! The function stores the specified file in the storage
762     void file_collector::store_file(filesystem::path const& src_path)
763     {
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
768 
769         // Let's construct the new file name
770         file_info info;
771         info.m_TimeStamp = filesystem::last_write_time(src_path);
772         info.m_Size = filesystem::file_size(src_path);
773 
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;
777 
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()) :
781                             m_BasePath;
782         const bool is_in_target_dir = filesystem::equivalent(src_dir, m_StorageDir);
783         if (!is_in_target_dir)
784         {
785             if (filesystem::exists(info.m_Path))
786             {
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);
790                 unsigned int n = 0;
791                 while (true)
792                 {
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))
796                         break;
797 
798                     if (BOOST_UNLIKELY(n == (std::numeric_limits< unsigned int >::max)()))
799                     {
800                         BOOST_THROW_EXCEPTION(filesystem_error(
801                             "Target file exists and an unused fallback file name could not be found",
802                             info.m_Path,
803                             system::error_code(system::errc::io_error, system::generic_category())));
804                     }
805 
806                     ++n;
807                 }
808             }
809 
810             // The directory should have been created in constructor, but just in case it got deleted since then...
811             filesystem::create_directories(m_StorageDir);
812         }
813 
814         BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
815 
816         file_list::iterator it = m_Files.begin();
817         const file_list::iterator end = m_Files.end();
818         if (is_in_target_dir)
819         {
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.
826             while (it != end)
827             {
828                 system::error_code ec;
829                 if (filesystem::equivalent(it->m_Path, info.m_Path, ec))
830                 {
831                     m_TotalSize -= it->m_Size;
832                     m_Files.erase(it);
833                     break;
834                 }
835                 else
836                 {
837                     ++it;
838                 }
839             }
840 
841             it = m_Files.begin();
842         }
843 
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);
846         while (it != end &&
847             (m_TotalSize + info.m_Size > m_MaxSize || (m_MinFreeSpace && m_MinFreeSpace > free_space) || m_MaxFiles <= m_Files.size()))
848         {
849             file_info& old_info = *it;
850             system::error_code ec;
851             filesystem::file_status status = filesystem::status(old_info.m_Path, ec);
852 
853             if (status.type() == filesystem::regular_file)
854             {
855                 try
856                 {
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
860                     if (m_MinFreeSpace)
861                         free_space = filesystem::space(m_StorageDir).available;
862                     m_TotalSize -= old_info.m_Size;
863                     m_Files.erase(it++);
864                 }
865                 catch (system::system_error&)
866                 {
867                     // Can't erase the file. Maybe it's locked? Never mind...
868                     ++it;
869                 }
870             }
871             else
872             {
873                 // If it's not a file or is absent, just remove it from the list
874                 m_TotalSize -= old_info.m_Size;
875                 m_Files.erase(it++);
876             }
877         }
878 
879         if (!is_in_target_dir)
880         {
881             // Move/rename the file to the target storage
882             move_file(src_path, info.m_Path);
883         }
884 
885         m_Files.push_back(info);
886         m_TotalSize += info.m_Size;
887     }
888 
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)
892     {
893         uintmax_t file_count = 0;
894         if (method != file::no_scan)
895         {
896             filesystem::path dir = m_StorageDir;
897             path_string_type mask;
898             if (method == file::scan_matching)
899             {
900                 mask = filename_string(pattern);
901                 if (pattern.has_parent_path())
902                     dir = make_absolute(pattern.parent_path());
903             }
904             else
905             {
906                 counter = NULL;
907             }
908 
909             system::error_code ec;
910             filesystem::file_status status = filesystem::status(dir, ec);
911             if (status.type() == filesystem::directory_file)
912             {
913                 BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
914 
915                 if (counter)
916                     *counter = 0;
917 
918                 file_list files;
919                 filesystem::directory_iterator it(dir), end;
920                 uintmax_t total_size = 0;
921                 for (; it != end; ++it)
922                 {
923                     filesystem::directory_entry const& dir_entry = *it;
924                     file_info info;
925                     info.m_Path = dir_entry.path();
926                     status = dir_entry.status(ec);
927                     if (status.type() == filesystem::regular_file)
928                     {
929                         // Check that there are no duplicates in the resulting list
930                         struct local
931                         {
932                             static bool equivalent(filesystem::path const& left, file_info const& right)
933                             {
934                                 return filesystem::equivalent(left, right.m_Path);
935                             }
936                         };
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())
939                         {
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))
945                             {
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);
950                                 ++file_count;
951 
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;
955                             }
956                         }
957                     }
958                 }
959 
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));
964             }
965         }
966 
967         return file_count;
968     }
969 
970     //! The function updates storage restrictions
971     void file_collector::update(uintmax_t max_size, uintmax_t min_free_space, uintmax_t max_files)
972     {
973         BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
974 
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);
978     }
979 
980 
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)
984     {
985         BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
986 
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
991         {
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);
995         }
996         catch (bad_weak_ptr&)
997         {
998         }
999 
1000         if (!p)
1001         {
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);
1005         }
1006 
1007         return p;
1008     }
1009 
1010     //! Removes the file collector from the list
1011     void file_collector_repository::remove_collector(file_collector* p)
1012     {
1013         BOOST_LOG_EXPR_IF_MT(lock_guard< mutex > lock(m_Mutex);)
1014         m_Collectors.erase(m_Collectors.iterator_to(*p));
1015     }
1016 
1017     //! Checks if the time point is valid
1018     void check_time_point_validity(unsigned char hour, unsigned char minute, unsigned char second)
1019     {
1020         if (BOOST_UNLIKELY(hour >= 24))
1021         {
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()));
1025         }
1026         if (BOOST_UNLIKELY(minute >= 60))
1027         {
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()));
1031         }
1032         if (BOOST_UNLIKELY(second >= 60))
1033         {
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()));
1037         }
1038     }
1039 
1040 } // namespace
1041 
1042 namespace file {
1043 
1044 namespace aux {
1045 
1046     //! 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,uintmax_t max_files)1047     BOOST_LOG_API shared_ptr< collector > make_collector(
1048         filesystem::path const& target_dir,
1049         uintmax_t max_size,
1050         uintmax_t min_free_space,
1051         uintmax_t max_files)
1052     {
1053         return file_collector_repository::get()->get_collector(target_dir, max_size, min_free_space, max_files);
1054     }
1055 
1056 } // namespace aux
1057 
1058 //! 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)1059 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
1060     unsigned char hour,
1061     unsigned char minute,
1062     unsigned char second
1063 ) :
1064     m_DayKind(not_specified),
1065     m_Day(0),
1066     m_Hour(hour),
1067     m_Minute(minute),
1068     m_Second(second),
1069     m_Previous(date_time::not_a_date_time)
1070 {
1071     check_time_point_validity(hour, minute, second);
1072 }
1073 
1074 //! 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)1075 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
1076     date_time::weekdays wday,
1077     unsigned char hour,
1078     unsigned char minute,
1079     unsigned char second
1080 ) :
1081     m_DayKind(weekday),
1082     m_Day(static_cast< unsigned char >(wday)),
1083     m_Hour(hour),
1084     m_Minute(minute),
1085     m_Second(second),
1086     m_Previous(date_time::not_a_date_time)
1087 {
1088     check_time_point_validity(hour, minute, second);
1089 }
1090 
1091 //! 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)1092 BOOST_LOG_API rotation_at_time_point::rotation_at_time_point(
1093     gregorian::greg_day mday,
1094     unsigned char hour,
1095     unsigned char minute,
1096     unsigned char second
1097 ) :
1098     m_DayKind(monthday),
1099     m_Day(static_cast< unsigned char >(mday.as_number())),
1100     m_Hour(hour),
1101     m_Minute(minute),
1102     m_Second(second),
1103     m_Previous(date_time::not_a_date_time)
1104 {
1105     check_time_point_validity(hour, minute, second);
1106 }
1107 
1108 //! Checks if it's time to rotate the file
operator ()() const1109 BOOST_LOG_API bool rotation_at_time_point::operator()() const
1110 {
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();
1117 
1118     if (m_Previous.is_special())
1119     {
1120         m_Previous = now;
1121         return false;
1122     }
1123 
1124     const bool time_of_day_passed = rotation_time.total_seconds() <= m_Previous.time_of_day().total_seconds();
1125     switch (m_DayKind)
1126     {
1127     case not_specified:
1128         {
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);
1135         }
1136         break;
1137 
1138     case weekday:
1139         {
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))
1145             {
1146                 next_date += gregorian::weeks(1);
1147             }
1148 
1149             posix_time::ptime next(next_date, rotation_time);
1150             result = (now >= next);
1151         }
1152         break;
1153 
1154     case monthday:
1155         {
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))
1162             {
1163                 next_date += gregorian::months(1);
1164             }
1165 
1166             posix_time::ptime next(next_date, rotation_time);
1167             result = (now >= next);
1168         }
1169         break;
1170 
1171     default:
1172         break;
1173     }
1174 
1175     if (result)
1176         m_Previous = now;
1177 
1178     return result;
1179 }
1180 
1181 //! Checks if it's time to rotate the file
operator ()() const1182 BOOST_LOG_API bool rotation_at_time_interval::operator()() const
1183 {
1184     bool result = false;
1185     posix_time::ptime now = posix_time::second_clock::universal_time();
1186     if (m_Previous.is_special())
1187     {
1188         m_Previous = now;
1189         return false;
1190     }
1191 
1192     result = (now - m_Previous) >= m_Interval;
1193 
1194     if (result)
1195         m_Previous = now;
1196 
1197     return result;
1198 }
1199 
1200 } // namespace file
1201 
1202 ////////////////////////////////////////////////////////////////////////////////
1203 //  File sink backend implementation
1204 ////////////////////////////////////////////////////////////////////////////////
1205 //! Sink implementation data
1206 struct text_file_backend::implementation
1207 {
1208     //! File open mode
1209     std::ios_base::openmode m_FileOpenMode;
1210 
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;
1217 
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;
1224 
1225     //! Stored files counter
1226     unsigned int m_FileCounter;
1227 
1228     //! Current file name
1229     filesystem::path m_FileName;
1230     //! File stream
1231     filesystem::ofstream m_File;
1232     //! Characters written
1233     uintmax_t m_CharactersWritten;
1234 
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;
1241 
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
1249     bool m_AutoFlush;
1250     //! The flag indicates whether the final rotation should be performed
1251     bool m_FinalRotationEnabled;
1252 
implementationboost::sinks::text_file_backend::implementation1253     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),
1255         m_FileCounter(0),
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)
1261     {
1262     }
1263 };
1264 
1265 //! Constructor. No streams attached to the constructed backend, auto flush feature disabled.
text_file_backend()1266 BOOST_LOG_API text_file_backend::text_file_backend()
1267 {
1268     construct(log::aux::empty_arg_list());
1269 }
1270 
1271 //! Destructor
~text_file_backend()1272 BOOST_LOG_API text_file_backend::~text_file_backend()
1273 {
1274     try
1275     {
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)
1278             rotate_file();
1279     }
1280     catch (...)
1281     {
1282     }
1283 
1284     delete m_pImpl;
1285 }
1286 
1287 //! Constructor implementation
construct(filesystem::path const & pattern,filesystem::path const & target_file_name,std::ios_base::openmode mode,uintmax_t rotation_size,time_based_rotation_predicate const & time_based_rotation,auto_newline_mode auto_newline,bool auto_flush,bool enable_final_rotation)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,
1295     bool auto_flush,
1296     bool enable_final_rotation)
1297 {
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);
1303 }
1304 
1305 //! The method sets maximum file size.
set_rotation_size(uintmax_t size)1306 BOOST_LOG_API void text_file_backend::set_rotation_size(uintmax_t size)
1307 {
1308     m_pImpl->m_FileRotationSize = size;
1309 }
1310 
1311 //! The method sets the maximum time interval between file rotations.
set_time_based_rotation(time_based_rotation_predicate const & predicate)1312 BOOST_LOG_API void text_file_backend::set_time_based_rotation(time_based_rotation_predicate const& predicate)
1313 {
1314     m_pImpl->m_TimeBasedRotation = predicate;
1315 }
1316 
1317 //! The method allows to enable or disable log file rotation on sink destruction.
enable_final_rotation(bool enable)1318 BOOST_LOG_API void text_file_backend::enable_final_rotation(bool enable)
1319 {
1320     m_pImpl->m_FinalRotationEnabled = enable;
1321 }
1322 
1323 //! Sets the flag to automatically flush write buffers of the file being written after each log record.
auto_flush(bool enable)1324 BOOST_LOG_API void text_file_backend::auto_flush(bool enable)
1325 {
1326     m_pImpl->m_AutoFlush = enable;
1327 }
1328 
1329 //! Selects whether a trailing newline should be automatically inserted after every log record.
set_auto_newline_mode(auto_newline_mode mode)1330 BOOST_LOG_API void text_file_backend::set_auto_newline_mode(auto_newline_mode mode)
1331 {
1332     m_pImpl->m_AutoNewlineMode = mode;
1333 }
1334 
1335 //! The method writes the message to the sink
consume(record_view const & rec,string_type const & formatted_message)1336 BOOST_LOG_API void text_file_backend::consume(record_view const& rec, string_type const& formatted_message)
1337 {
1338     typedef file_char_traits< string_type::value_type > traits_t;
1339 
1340     filesystem::path prev_file_name;
1341     bool use_prev_file_name = false;
1342     if (BOOST_UNLIKELY(!m_pImpl->m_File.good()))
1343     {
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;
1349         close_file();
1350 
1351         system::error_code ec;
1352         uintmax_t size = filesystem::file_size(prev_file_name, ec);
1353         if (!!ec || size == 0)
1354         {
1355             // To reuse the empty file avoid re-generating the new file name later
1356             use_prev_file_name = true;
1357         }
1358         else if (!!m_pImpl->m_pFileCollector)
1359         {
1360             // Complete file rotation
1361             m_pImpl->m_pFileCollector->store_file(prev_file_name);
1362         }
1363     }
1364     else if
1365     (
1366         m_pImpl->m_File.is_open() &&
1367         (
1368             m_pImpl->m_CharactersWritten + formatted_message.size() >= m_pImpl->m_FileRotationSize ||
1369             (!m_pImpl->m_TimeBasedRotation.empty() && m_pImpl->m_TimeBasedRotation())
1370         )
1371     )
1372     {
1373         rotate_file();
1374     }
1375 
1376     if (!m_pImpl->m_File.is_open())
1377     {
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++);
1381         else
1382             prev_file_name.swap(new_file_name);
1383 
1384         filesystem::create_directories(new_file_name.parent_path());
1385 
1386         m_pImpl->m_File.open(new_file_name, m_pImpl->m_FileOpenMode);
1387         if (BOOST_UNLIKELY(!m_pImpl->m_File.is_open()))
1388         {
1389             BOOST_THROW_EXCEPTION(filesystem_error(
1390                 "Failed to open file for writing",
1391                 new_file_name,
1392                 system::error_code(system::errc::io_error, system::generic_category())));
1393         }
1394         m_pImpl->m_FileName.swap(new_file_name);
1395 
1396         if (!m_pImpl->m_OpenHandler.empty())
1397             m_pImpl->m_OpenHandler(m_pImpl->m_File);
1398 
1399         m_pImpl->m_CharactersWritten = static_cast< std::streamoff >(m_pImpl->m_File.tellp());
1400     }
1401 
1402     m_pImpl->m_File.write(formatted_message.data(), static_cast< std::streamsize >(formatted_message.size()));
1403     m_pImpl->m_CharactersWritten += formatted_message.size();
1404 
1405     if (m_pImpl->m_AutoNewlineMode != disabled_auto_newline)
1406     {
1407         if (m_pImpl->m_AutoNewlineMode == always_insert || formatted_message.empty() || *formatted_message.rbegin() != traits_t::newline)
1408         {
1409             m_pImpl->m_File.put(traits_t::newline);
1410             ++m_pImpl->m_CharactersWritten;
1411         }
1412     }
1413 
1414     if (m_pImpl->m_AutoFlush)
1415         m_pImpl->m_File.flush();
1416 }
1417 
1418 //! The method flushes the currently open log file
flush()1419 BOOST_LOG_API void text_file_backend::flush()
1420 {
1421     if (m_pImpl->m_File.is_open())
1422         m_pImpl->m_File.flush();
1423 }
1424 
1425 //! The method sets file name pattern
set_file_name_pattern_internal(filesystem::path const & pattern)1426 BOOST_LOG_API void text_file_backend::set_file_name_pattern_internal(filesystem::path const& pattern)
1427 {
1428     typedef file_char_traits< path_char_type > traits_t;
1429 
1430     parse_file_name_pattern
1431     (
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
1436     );
1437 }
1438 
1439 //! The method sets target file name pattern
set_target_file_name_pattern_internal(filesystem::path const & pattern)1440 BOOST_LOG_API void text_file_backend::set_target_file_name_pattern_internal(filesystem::path const& pattern)
1441 {
1442     if (!pattern.empty())
1443     {
1444         parse_file_name_pattern(pattern, m_pImpl->m_TargetStorageDir, m_pImpl->m_TargetFileNamePattern, m_pImpl->m_TargetFileNameGenerator);
1445     }
1446     else
1447     {
1448         m_pImpl->m_TargetStorageDir.clear();
1449         m_pImpl->m_TargetFileNamePattern.clear();
1450         m_pImpl->m_TargetFileNameGenerator.clear();
1451     }
1452 }
1453 
1454 //! Closes the currently open file
close_file()1455 void text_file_backend::close_file()
1456 {
1457     if (m_pImpl->m_File.is_open())
1458     {
1459         if (!m_pImpl->m_CloseHandler.empty())
1460         {
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);
1469         }
1470 
1471         m_pImpl->m_File.close();
1472     }
1473 
1474     m_pImpl->m_File.clear();
1475     m_pImpl->m_CharactersWritten = 0;
1476     m_pImpl->m_FileName.clear();
1477 }
1478 
1479 //! The method rotates the file
rotate_file()1480 BOOST_LOG_API void text_file_backend::rotate_file()
1481 {
1482     filesystem::path prev_file_name = m_pImpl->m_FileName;
1483     close_file();
1484 
1485     if (!!m_pImpl->m_TargetFileNameGenerator)
1486     {
1487         filesystem::path new_file_name;
1488         new_file_name = m_pImpl->m_TargetStorageDir / m_pImpl->m_TargetFileNameGenerator(m_pImpl->m_FileCounter);
1489 
1490         if (new_file_name != prev_file_name)
1491         {
1492             filesystem::create_directories(new_file_name.parent_path());
1493             move_file(prev_file_name, new_file_name);
1494 
1495             prev_file_name.swap(new_file_name);
1496         }
1497     }
1498 
1499     if (!!m_pImpl->m_pFileCollector)
1500     {
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);
1506     }
1507 }
1508 
1509 //! The method sets the file open mode
set_open_mode(std::ios_base::openmode mode)1510 BOOST_LOG_API void text_file_backend::set_open_mode(std::ios_base::openmode mode)
1511 {
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;
1517 }
1518 
1519 //! The method sets file collector
set_file_collector(shared_ptr<file::collector> const & collector)1520 BOOST_LOG_API void text_file_backend::set_file_collector(shared_ptr< file::collector > const& collector)
1521 {
1522     m_pImpl->m_pFileCollector = collector;
1523 }
1524 
1525 //! The method sets file open handler
set_open_handler(open_handler_type const & handler)1526 BOOST_LOG_API void text_file_backend::set_open_handler(open_handler_type const& handler)
1527 {
1528     m_pImpl->m_OpenHandler = handler;
1529 }
1530 
1531 //! The method sets file close handler
set_close_handler(close_handler_type const & handler)1532 BOOST_LOG_API void text_file_backend::set_close_handler(close_handler_type const& handler)
1533 {
1534     m_pImpl->m_CloseHandler = handler;
1535 }
1536 
1537 //! The method returns name of the currently open log file. If no file is open, returns an empty path.
get_current_file_name() const1538 BOOST_LOG_API filesystem::path text_file_backend::get_current_file_name() const
1539 {
1540     return m_pImpl->m_FileName;
1541 }
1542 
1543 //! Performs scanning of the target directory for log files
scan_for_files(file::scan_method method,bool update_counter)1544 BOOST_LOG_API uintmax_t text_file_backend::scan_for_files(file::scan_method method, bool update_counter)
1545 {
1546     if (BOOST_LIKELY(!!m_pImpl->m_pFileCollector))
1547     {
1548         unsigned int* counter = update_counter ? &m_pImpl->m_FileCounter : static_cast< unsigned int* >(NULL);
1549         return m_pImpl->m_pFileCollector->scan_for_files
1550         (
1551             method,
1552             m_pImpl->m_TargetFileNamePattern.empty() ? m_pImpl->m_FileNamePattern : m_pImpl->m_TargetFileNamePattern,
1553             counter
1554         );
1555     }
1556     else
1557     {
1558         BOOST_LOG_THROW_DESCR(setup_error, "File collector is not set");
1559     }
1560 }
1561 
1562 } // namespace sinks
1563 
1564 BOOST_LOG_CLOSE_NAMESPACE // namespace log
1565 
1566 } // namespace boost
1567 
1568 #include <boost/log/detail/footer.hpp>
1569