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