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

source code of boost/libs/log/src/text_file_backend.cpp