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