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 | |
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(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 > ; |
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 > ; |
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 | |
1103 | namespace file { |
1104 | |
1105 | namespace 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 |
1120 | BOOST_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 |
1136 | BOOST_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 |
1153 | BOOST_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 |
1170 | BOOST_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 |
1243 | BOOST_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 |
1267 | struct 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. |
1334 | BOOST_LOG_API text_file_backend::text_file_backend() |
1335 | { |
1336 | construct(args: log::aux::empty_arg_list()); |
1337 | } |
1338 | |
1339 | //! Destructor |
1340 | BOOST_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 |
1356 | BOOST_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. |
1374 | BOOST_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. |
1380 | BOOST_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. |
1386 | BOOST_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. |
1392 | BOOST_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. |
1398 | BOOST_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 |
1404 | BOOST_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 |
1563 | BOOST_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 |
1570 | BOOST_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 |
1584 | BOOST_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 |
1599 | void 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 |
1624 | BOOST_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 |
1653 | BOOST_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 |
1665 | BOOST_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 |
1671 | BOOST_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 |
1677 | BOOST_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. |
1683 | BOOST_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 |
1689 | BOOST_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 | |
1716 | BOOST_LOG_CLOSE_NAMESPACE // namespace log |
1717 | |
1718 | } // namespace boost |
1719 | |
1720 | #include <boost/log/detail/footer.hpp> |
1721 | |