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