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