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.hpp
9  * \author Andrey Semashev
10  * \date   09.06.2009
11  *
12  * The header contains implementation of a text file sink backend.
13  */
14 
15 #ifndef BOOST_LOG_SINKS_TEXT_FILE_BACKEND_HPP_INCLUDED_
16 #define BOOST_LOG_SINKS_TEXT_FILE_BACKEND_HPP_INCLUDED_
17 
18 #include <ios>
19 #include <string>
20 #include <ostream>
21 #include <boost/limits.hpp>
22 #include <boost/cstdint.hpp>
23 #include <boost/smart_ptr/shared_ptr.hpp>
24 #include <boost/date_time/date_defs.hpp>
25 #include <boost/date_time/special_defs.hpp>
26 #include <boost/date_time/gregorian/greg_day.hpp>
27 #include <boost/date_time/posix_time/posix_time_types.hpp>
28 #include <boost/filesystem/path.hpp>
29 #include <boost/log/keywords/max_size.hpp>
30 #include <boost/log/keywords/min_free_space.hpp>
31 #include <boost/log/keywords/target.hpp>
32 #include <boost/log/keywords/file_name.hpp>
33 #include <boost/log/keywords/open_mode.hpp>
34 #include <boost/log/keywords/auto_flush.hpp>
35 #include <boost/log/keywords/rotation_size.hpp>
36 #include <boost/log/keywords/time_based_rotation.hpp>
37 #include <boost/log/detail/config.hpp>
38 #include <boost/log/detail/light_function.hpp>
39 #include <boost/log/detail/parameter_tools.hpp>
40 #include <boost/log/sinks/basic_sink_backend.hpp>
41 #include <boost/log/sinks/frontend_requirements.hpp>
42 #include <boost/log/detail/header.hpp>
43 
44 #ifdef BOOST_HAS_PRAGMA_ONCE
45 #pragma once
46 #endif
47 
48 namespace boost {
49 
50 BOOST_LOG_OPEN_NAMESPACE
51 
52 namespace sinks {
53 
54 namespace file {
55 
56 //! The enumeration of the stored files scan methods
57 enum scan_method
58 {
59     no_scan,        //!< Don't scan for stored files
60     scan_matching,  //!< Scan for files with names matching the specified mask
61     scan_all        //!< Scan for all files in the directory
62 };
63 
64 /*!
65  * \brief Base class for file collectors
66  *
67  * All file collectors, supported by file sink backends, should inherit this class.
68  */
69 struct BOOST_LOG_NO_VTABLE collector
70 {
71     /*!
72      * Default constructor
73      */
collectorboost::sinks::file::collector74     BOOST_DEFAULTED_FUNCTION(collector(), {})
75 
76     /*!
77      * Virtual destructor
78      */
79     virtual ~collector() {}
80 
81     /*!
82      * The function stores the specified file in the storage. May lead to an older file
83      * deletion and a long file moving.
84      *
85      * \param src_path The name of the file to be stored
86      */
87     virtual void store_file(filesystem::path const& src_path) = 0;
88 
89     /*!
90      * Scans the target directory for the files that have already been stored. The found
91      * files are added to the collector in order to be tracked and erased, if needed.
92      *
93      * The function may scan the directory in two ways: it will either consider every
94      * file in the directory a log file, or will only consider files with names that
95      * match the specified pattern. The pattern may contain the following placeholders:
96      *
97      * \li %y, %Y, %m, %d - date components, in Boost.DateTime meaning.
98      * \li %H, %M, %S, %f - time components, in Boost.DateTime meaning.
99      * \li %N - numeric file counter. May also contain width specification
100      *     in printf-compatible form (e.g. %5N). The resulting number will always be zero-filled.
101      * \li %% - a percent sign
102      *
103      * All other placeholders are not supported.
104      *
105      * \param method The method of scanning. If \c no_scan is specified, the call has no effect.
106      * \param pattern The file name pattern if \a method is \c scan_matching. Otherwise the parameter
107      *                is not used.
108      * \param counter If not \c NULL and \a method is \c scan_matching, the method suggests initial value
109      *                of a file counter that may be used in the file name pattern. The parameter
110      *                is not used otherwise.
111      * \return The number of found files.
112      *
113      * \note In case if \a method is \c scan_matching the effect of this function is highly dependent
114      *       on the \a pattern definition. It is recommended to choose patterns with easily
115      *       distinguished placeholders (i.e. having delimiters between them). Otherwise
116      *       either some files can be mistakenly found or not found, which in turn may lead
117      *       to an incorrect file deletion.
118      */
119     virtual uintmax_t scan_for_files(
120         scan_method method, filesystem::path const& pattern = filesystem::path(), unsigned int* counter = 0) = 0;
121 
122     BOOST_DELETED_FUNCTION(collector(collector const&))
123     BOOST_DELETED_FUNCTION(collector& operator= (collector const&))
124 };
125 
126 namespace aux {
127 
128     //! Creates and returns a file collector with the specified parameters
129     BOOST_LOG_API shared_ptr< collector > make_collector(
130         filesystem::path const& target_dir,
131         uintmax_t max_size,
132         uintmax_t min_free_space
133     );
134     template< typename ArgsT >
make_collector(ArgsT const & args)135     inline shared_ptr< collector > make_collector(ArgsT const& args)
136     {
137         return aux::make_collector(
138             filesystem::path(args[keywords::target]),
139             args[keywords::max_size | (std::numeric_limits< uintmax_t >::max)()],
140             args[keywords::min_free_space | static_cast< uintmax_t >(0)]);
141     }
142 
143 } // namespace aux
144 
145 #ifndef BOOST_LOG_DOXYGEN_PASS
146 
147 template< typename T1 >
make_collector(T1 const & a1)148 inline shared_ptr< collector > make_collector(T1 const& a1)
149 {
150     return aux::make_collector(a1);
151 }
152 template< typename T1, typename T2 >
make_collector(T1 const & a1,T2 const & a2)153 inline shared_ptr< collector > make_collector(T1 const& a1, T2 const& a2)
154 {
155     return aux::make_collector((a1, a2));
156 }
157 template< typename T1, typename T2, typename T3 >
make_collector(T1 const & a1,T2 const & a2,T3 const & a3)158 inline shared_ptr< collector > make_collector(T1 const& a1, T2 const& a2, T3 const& a3)
159 {
160     return aux::make_collector((a1, a2, a3));
161 }
162 
163 #else
164 
165 /*!
166  * The function creates a file collector for the specified target directory.
167  * Each target directory is managed by a single file collector, so if
168  * this function is called several times for the same directory,
169  * it will return a reference to the same file collector. It is safe
170  * to use the same collector in different sinks, even in a multithreaded
171  * application.
172  *
173  * One can specify certain restrictions for the stored files, such as
174  * maximum total size or minimum free space left in the target directory.
175  * If any of the specified restrictions is not met, the oldest stored file
176  * is deleted. If the same collector is requested more than once with
177  * different restrictions, the collector will act according to the most strict
178  * combination of all specified restrictions.
179  *
180  * The following named parameters are supported:
181  *
182  * \li \c target - Specifies the target directory for the files being stored in. This parameter
183  *                 is mandatory.
184  * \li \c max_size - Specifies the maximum total size, in bytes, of stored files that the collector
185  *                   will try not to exceed. If the size exceeds this threshold the oldest file(s) is
186  *                   deleted to free space. Note that the threshold may be exceeded if the size of
187  *                   individual files exceed the \c max_size value. The threshold is not maintained,
188  *                   if not specified.
189  * \li \c min_free_space - Specifies the minimum free space, in bytes, in the target directory that
190  *                         the collector tries to maintain. If the threshold is exceeded, the oldest
191  *                         file(s) is deleted to free space. The threshold is not maintained, if not
192  *                         specified.
193  *
194  * \return The file collector.
195  */
196 template< typename... ArgsT >
197 shared_ptr< collector > make_collector(ArgsT... const& args);
198 
199 #endif // BOOST_LOG_DOXYGEN_PASS
200 
201 /*!
202  * The class represents the time point of log file rotation. One can specify one of three
203  * types of time point based rotation:
204  *
205  * \li rotation takes place every day, at the specified time
206  * \li rotation takes place on the specified day of every week, at the specified time
207  * \li rotation takes place on the specified day of every month, at the specified time
208  *
209  * The time points are considered to be local time.
210  */
211 class rotation_at_time_point
212 {
213 public:
214     typedef bool result_type;
215 
216 private:
217     enum day_kind
218     {
219         not_specified,
220         weekday,
221         monthday
222     };
223 
224     day_kind m_DayKind : 2;
225     unsigned char m_Day : 6;
226     unsigned char m_Hour, m_Minute, m_Second;
227 
228     mutable posix_time::ptime m_Previous;
229 
230 public:
231     /*!
232      * Creates a rotation time point of every day at the specified time
233      *
234      * \param hour The rotation hour, should be within 0 and 23
235      * \param minute The rotation minute, should be within 0 and 59
236      * \param second The rotation second, should be within 0 and 59
237      */
238     BOOST_LOG_API explicit rotation_at_time_point(unsigned char hour, unsigned char minute, unsigned char second);
239 
240     /*!
241      * Creates a rotation time point of each specified weekday at the specified time
242      *
243      * \param wday The weekday of the rotation
244      * \param hour The rotation hour, should be within 0 and 23
245      * \param minute The rotation minute, should be within 0 and 59
246      * \param second The rotation second, should be within 0 and 59
247      */
248     BOOST_LOG_API explicit rotation_at_time_point(
249         date_time::weekdays wday,
250         unsigned char hour = 0,
251         unsigned char minute = 0,
252         unsigned char second = 0);
253 
254     /*!
255      * Creates a rotation time point of each specified day of month at the specified time
256      *
257      * \param mday The monthday of the rotation, should be within 1 and 31
258      * \param hour The rotation hour, should be within 0 and 23
259      * \param minute The rotation minute, should be within 0 and 59
260      * \param second The rotation second, should be within 0 and 59
261      */
262     BOOST_LOG_API explicit rotation_at_time_point(
263         gregorian::greg_day mday,
264         unsigned char hour = 0,
265         unsigned char minute = 0,
266         unsigned char second = 0);
267 
268     /*!
269      * Checks if it's time to rotate the file
270      */
271     BOOST_LOG_API bool operator() () const;
272 };
273 
274 /*!
275  * The class represents the time interval of log file rotation. The log file will be rotated
276  * after the specified time interval has passed.
277  */
278 class rotation_at_time_interval
279 {
280 public:
281     typedef bool result_type;
282 
283 private:
284     posix_time::time_duration m_Interval;
285     mutable posix_time::ptime m_Previous;
286 
287 public:
288     /*!
289      * Creates a rotation time interval of the specified duration
290      *
291      * \param interval The interval of the rotation, should be no less than 1 second
292      */
rotation_at_time_interval(posix_time::time_duration const & interval)293     explicit rotation_at_time_interval(posix_time::time_duration const& interval) :
294         m_Interval(interval)
295     {
296         BOOST_ASSERT(!interval.is_special());
297         BOOST_ASSERT(interval.total_seconds() > 0);
298     }
299 
300     /*!
301      * Checks if it's time to rotate the file
302      */
303     BOOST_LOG_API bool operator() () const;
304 };
305 
306 } // namespace file
307 
308 
309 /*!
310  * \brief An implementation of a text file logging sink backend
311  *
312  * The sink backend puts formatted log records to a text file.
313  * The sink supports file rotation and advanced file control, such as
314  * size and file count restriction.
315  */
316 class text_file_backend :
317     public basic_formatted_sink_backend<
318         char,
319         combine_requirements< synchronized_feeding, flushing >::type
320     >
321 {
322     //! Base type
323     typedef basic_formatted_sink_backend<
324         char,
325         combine_requirements< synchronized_feeding, flushing >::type
326     > base_type;
327 
328 public:
329     //! Character type
330     typedef base_type::char_type char_type;
331     //! String type to be used as a message text holder
332     typedef base_type::string_type string_type;
333     //! Stream type
334     typedef std::basic_ostream< char_type > stream_type;
335 
336     //! File open handler
337     typedef boost::log::aux::light_function< void (stream_type&) > open_handler_type;
338     //! File close handler
339     typedef boost::log::aux::light_function< void (stream_type&) > close_handler_type;
340 
341     //! Predicate that defines the time-based condition for file rotation
342     typedef boost::log::aux::light_function< bool () > time_based_rotation_predicate;
343 
344 private:
345     //! \cond
346 
347     struct implementation;
348     implementation* m_pImpl;
349 
350     //! \endcond
351 
352 public:
353     /*!
354      * Default constructor. The constructed sink backend uses default values of all the parameters.
355      */
356     BOOST_LOG_API text_file_backend();
357 
358     /*!
359      * Constructor. Creates a sink backend with the specified named parameters.
360      * The following named parameters are supported:
361      *
362      * \li \c file_name - Specifies the file name pattern where logs are actually written to. The pattern may
363      *                    contain directory and file name portions, but only the file name may contain
364      *                    placeholders. The backend supports Boost.DateTime placeholders for injecting
365      *                    current time and date into the file name. Also, an additional %N placeholder is
366      *                    supported, it will be replaced with an integral increasing file counter. The placeholder
367      *                    may also contain width specification in the printf-compatible form (e.g. %5N). The
368      *                    printed file counter will always be zero-filled. If \c file_name is not specified,
369      *                    pattern "%5N.log" will be used.
370      * \li \c open_mode - File open mode. The mode should be presented in form of mask compatible to
371      *                    <tt>std::ios_base::openmode</tt>. If not specified, <tt>trunc | out</tt> will be used.
372      * \li \c rotation_size - Specifies the approximate size, in characters written, of the temporary file
373      *                        upon which the file is passed to the file collector. Note the size does
374      *                        not count any possible character conversions that may take place during
375      *                        writing to the file. If not specified, the file won't be rotated upon reaching
376      *                        any size.
377      * \li \c time_based_rotation - Specifies the predicate for time-based file rotation.
378      *                              No time-based file rotations will be performed, if not specified.
379      * \li \c auto_flush - Specifies a flag, whether or not to automatically flush the file after each
380      *                     written log record. By default, is \c false.
381      *
382      * \note Read the caution note regarding file name pattern in the <tt>sinks::file::collector::scan_for_files</tt>
383      *       documentation.
384      */
385 #ifndef BOOST_LOG_DOXYGEN_PASS
386     BOOST_LOG_PARAMETRIZED_CONSTRUCTORS_CALL(text_file_backend, construct)
387 #else
388     template< typename... ArgsT >
389     explicit text_file_backend(ArgsT... const& args);
390 #endif
391 
392     /*!
393      * Destructor
394      */
395     BOOST_LOG_API ~text_file_backend();
396 
397     /*!
398      * The method sets file name wildcard for the files being written. The wildcard supports
399      * date and time injection into the file name.
400      *
401      * \param pattern The name pattern for the file being written.
402      */
403     template< typename PathT >
set_file_name_pattern(PathT const & pattern)404     void set_file_name_pattern(PathT const& pattern)
405     {
406         set_file_name_pattern_internal(filesystem::path(pattern));
407     }
408 
409     /*!
410      * The method sets the file open mode
411      *
412      * \param mode File open mode
413      */
414     BOOST_LOG_API void set_open_mode(std::ios_base::openmode mode);
415 
416     /*!
417      * The method sets the log file collector function. The function is called
418      * on file rotation and is being passed the written file name.
419      *
420      * \param collector The file collector function object
421      */
422     BOOST_LOG_API void set_file_collector(shared_ptr< file::collector > const& collector);
423 
424     /*!
425      * The method sets file opening handler. The handler will be called every time
426      * the backend opens a new temporary file. The handler may write a header to the
427      * opened file in order to maintain file validity.
428      *
429      * \param handler The file open handler function object
430      */
431     BOOST_LOG_API void set_open_handler(open_handler_type const& handler);
432 
433     /*!
434      * The method sets file closing handler. The handler will be called every time
435      * the backend closes a temporary file. The handler may write a footer to the
436      * opened file in order to maintain file validity.
437      *
438      * \param handler The file close handler function object
439      */
440     BOOST_LOG_API void set_close_handler(close_handler_type const& handler);
441 
442     /*!
443      * The method sets maximum file size. When the size is reached, file rotation is performed.
444      *
445      * \note The size does not count any possible character translations that may happen in
446      *       the underlying API. This may result in greater actual sizes of the written files.
447      *
448      * \param size The maximum file size, in characters.
449      */
450     BOOST_LOG_API void set_rotation_size(uintmax_t size);
451 
452     /*!
453      * The method sets the predicate that defines the time-based condition for file rotation.
454      *
455      * \note The rotation always occurs on writing a log record, so the rotation is
456      *       not strictly bound to the specified condition.
457      *
458      * \param predicate The predicate that defines the time-based condition for file rotation.
459      *                  If empty, no time-based rotation will take place.
460      */
461     BOOST_LOG_API void set_time_based_rotation(time_based_rotation_predicate const& predicate);
462 
463     /*!
464      * Sets the flag to automatically flush buffers of all attached streams after each log record
465      */
466     BOOST_LOG_API void auto_flush(bool f = true);
467 
468     /*!
469      * Performs scanning of the target directory for log files that may have been left from
470      * previous runs of the application. The found files are considered by the file collector
471      * as if they were rotated.
472      *
473      * The file scan can be performed in two ways: either all files in the target directory will
474      * be considered as log files, or only those files that satisfy the file name pattern.
475      * See documentation on <tt>sinks::file::collector::scan_for_files</tt> for more information.
476      *
477      * \pre File collector and the proper file name pattern have already been set.
478      *
479      * \param method File scanning method
480      * \param update_counter If \c true and \a method is \c scan_matching, the method attempts
481      *        to update the internal file counter according to the found files. The counter
482      *        is unaffected otherwise.
483      * \return The number of files found.
484      *
485      * \note The method essentially delegates to the same-named function of the file collector.
486      */
487     BOOST_LOG_API uintmax_t scan_for_files(
488         file::scan_method method = file::scan_matching, bool update_counter = true);
489 
490     /*!
491      * The method writes the message to the sink
492      */
493     BOOST_LOG_API void consume(record_view const& rec, string_type const& formatted_message);
494 
495     /*!
496      * The method flushes the currently open log file
497      */
498     BOOST_LOG_API void flush();
499 
500     /*!
501      * The method rotates the file
502      */
503     BOOST_LOG_API void rotate_file();
504 
505 private:
506 #ifndef BOOST_LOG_DOXYGEN_PASS
507     //! Constructor implementation
508     template< typename ArgsT >
construct(ArgsT const & args)509     void construct(ArgsT const& args)
510     {
511         construct(
512             filesystem::path(args[keywords::file_name | filesystem::path()]),
513             args[keywords::open_mode | (std::ios_base::trunc | std::ios_base::out)],
514             args[keywords::rotation_size | (std::numeric_limits< uintmax_t >::max)()],
515             args[keywords::time_based_rotation | time_based_rotation_predicate()],
516             args[keywords::auto_flush | false]);
517     }
518     //! Constructor implementation
519     BOOST_LOG_API void construct(
520         filesystem::path const& pattern,
521         std::ios_base::openmode mode,
522         uintmax_t rotation_size,
523         time_based_rotation_predicate const& time_based_rotation,
524         bool auto_flush);
525 
526     //! The method sets file name mask
527     BOOST_LOG_API void set_file_name_pattern_internal(filesystem::path const& pattern);
528 
529     //! Closes the currently open file
530     void close_file();
531 #endif // BOOST_LOG_DOXYGEN_PASS
532 };
533 
534 } // namespace sinks
535 
536 BOOST_LOG_CLOSE_NAMESPACE // namespace log
537 
538 } // namespace boost
539 
540 #include <boost/log/detail/footer.hpp>
541 
542 #endif // BOOST_LOG_SINKS_TEXT_FILE_BACKEND_HPP_INCLUDED_
543