1 //
2 // Copyright(c) 2015 Gabi Melman.
3 // Distributed under the MIT License (http://opensource.org/licenses/MIT)
4 //
5 
6 /* -*- MODE: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
7 /*
8  *     Copyright 2017 Couchbase, Inc
9  *
10  *   Licensed under the Apache License, Version 2.0 (the "License");
11  *   you may not use this file except in compliance with the License.
12  *   You may obtain a copy of the License at
13  *
14  *       http://www.apache.org/licenses/LICENSE-2.0
15  *
16  *   Unless required by applicable law or agreed to in writing, software
17  *   distributed under the License is distributed on an "AS IS" BASIS,
18  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  *   See the License for the specific language governing permissions and
20  *   limitations under the License.
21  */
22 
23 #include "custom_rotating_file_sink.h"
24 
25 #include <platform/dirutils.h>
26 #include <platform/make_unique.h>
27 
find_first_logfile_id(const std::string& basename)28 static unsigned long find_first_logfile_id(const std::string& basename) {
29     unsigned long id = 0;
30 
31     auto files = cb::io::findFilesWithPrefix(basename);
32     for (auto& file : files) {
33         // the format of the name should be:
34         // fnm.number.txt
35         auto index = file.rfind(".txt");
36         if (index == std::string::npos) {
37             continue;
38         }
39 
40         file.resize(index);
41         index = file.rfind('.');
42         if (index != std::string::npos) {
43             try {
44                 unsigned long value = std::stoul(file.substr(index + 1));
45                 if (value > id) {
46                     id = value + 1;
47                 }
48             } catch (...) {
49                 // Ignore
50             }
51         }
52     }
53 
54     return id;
55 }
56 
57 template <class Mutex>
custom_rotating_file_sink( const spdlog::filename_t& base_filename, std::size_t max_size, const std::string& log_pattern)58 custom_rotating_file_sink<Mutex>::custom_rotating_file_sink(
59         const spdlog::filename_t& base_filename,
60         std::size_t max_size,
61         const std::string& log_pattern)
62     : _base_filename(base_filename),
63       _max_size(max_size),
64       _current_size(0),
65       _file_helper(std::make_unique<spdlog::details::file_helper>()),
66       _next_file_id(find_first_logfile_id(base_filename)) {
67     formatter = std::make_shared<spdlog::pattern_formatter>(
68             log_pattern, spdlog::pattern_time_type::local);
69     _file_helper->open(calc_filename());
70     _current_size = _file_helper->size(); // expensive. called only once
71     addHook(openingLogfile);
72 }
73 
74 /* In addition to the functionality of spdlog's rotating_file_sink,
75  * this class adds hooks marking the start and end of a logfile.
76  */
77 template <class Mutex>
_sink_it( const spdlog::details::log_msg& msg)78 void custom_rotating_file_sink<Mutex>::_sink_it(
79         const spdlog::details::log_msg& msg) {
80     _current_size += msg.formatted.size();
81     if (_current_size > _max_size) {
82         std::unique_ptr<spdlog::details::file_helper> next =
83                 std::make_unique<spdlog::details::file_helper>();
84         try {
85             next->open(calc_filename(), true);
86             addHook(closingLogfile);
87             std::swap(_file_helper, next);
88             _current_size = msg.formatted.size();
89             addHook(openingLogfile);
90         } catch (...) {
91             // Keep on logging to the this file, but try swap at the next
92             // insert of data (didn't use the next file we need to
93             // roll back the next_file_id to avoid getting a hole ;-)
94             _next_file_id--;
95         }
96     }
97     _file_helper->write(msg);
98 }
99 
100 template <class Mutex>
_flush()101 void custom_rotating_file_sink<Mutex>::_flush() {
102     _file_helper->flush();
103 }
104 
105 /* Takes a message, formats it and writes it to file */
106 template <class Mutex>
addHook(const std::string& hook)107 void custom_rotating_file_sink<Mutex>::addHook(const std::string& hook) {
108     spdlog::details::log_msg msg;
109     msg.time = spdlog::details::os::now();
110     msg.level = spdlog::level::info;
111     msg.raw << hook;
112 
113     if (hook == openingLogfile) {
114         msg.raw << fmt::StringRef(_file_helper->filename().data(),
115                                   _file_helper->filename().size());
116     }
117     formatter->format(msg);
118     _current_size += msg.formatted.size();
119 
120     _file_helper->write(msg);
121 }
122 
123 template <class Mutex>
calc_filename()124 spdlog::filename_t custom_rotating_file_sink<Mutex>::calc_filename() {
125     std::conditional<std::is_same<spdlog::filename_t::value_type, char>::value,
126                      fmt::MemoryWriter,
127                      fmt::WMemoryWriter>::type w;
128 
129     char fname[1024];
130     unsigned long try_id = _next_file_id;
131     do {
132         sprintf(fname, "%s.%06lu.txt", _base_filename.c_str(), try_id++);
133     } while (access(fname, F_OK) == 0);
134 
135     _next_file_id = try_id;
136 
137     w.write(SPDLOG_FILENAME_T("{}"), fname);
138     return w.str();
139 }
140 
141 template <class Mutex>
~custom_rotating_file_sink()142 custom_rotating_file_sink<Mutex>::~custom_rotating_file_sink() {
143     addHook(closingLogfile);
144 }
145 
146 template class custom_rotating_file_sink<std::mutex>;
147 template class custom_rotating_file_sink<spdlog::details::null_mutex>;
148