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