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