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 <spdlog/details/file_helper.h>
27 #include <spdlog/details/fmt_helper.h>
28 
29 #include <memory>
30 
find_first_logfile_id(const std::string& basename)31 static unsigned long find_first_logfile_id(const std::string& basename) {
32     unsigned long id = 0;
33 
34     auto files = cb::io::findFilesWithPrefix(basename);
35     for (auto& file : files) {
36         // the format of the name should be:
37         // fnm.number.txt
38         auto index = file.rfind(".txt");
39         if (index == std::string::npos) {
40             continue;
41         }
42 
43         file.resize(index);
44         index = file.rfind('.');
45         if (index != std::string::npos) {
46             try {
47                 unsigned long value = std::stoul(file.substr(index + 1));
48                 if (value > id) {
49                     id = value + 1;
50                 }
51             } catch (...) {
52                 // Ignore
53             }
54         }
55     }
56 
57     return id;
58 }
59 
60 template <class Mutex>
custom_rotating_file_sink( const spdlog::filename_t& base_filename, std::size_t max_size, const std::string& log_pattern)61 custom_rotating_file_sink<Mutex>::custom_rotating_file_sink(
62         const spdlog::filename_t& base_filename,
63         std::size_t max_size,
64         const std::string& log_pattern)
65     : _base_filename(base_filename),
66       _max_size(max_size),
67       _current_size(0),
68       _file_helper(std::make_unique<spdlog::details::file_helper>()),
69       _next_file_id(find_first_logfile_id(base_filename)) {
70     formatter = std::make_unique<spdlog::pattern_formatter>(
71             log_pattern, spdlog::pattern_time_type::local);
72     _file_helper->open(calc_filename());
73     _current_size = _file_helper->size(); // expensive. called only once
74     addHook(openingLogfile);
75 }
76 
77 /* In addition to the functionality of spdlog's rotating_file_sink,
78  * this class adds hooks marking the start and end of a logfile.
79  */
80 template <class Mutex>
sink_it_( const spdlog::details::log_msg& msg)81 void custom_rotating_file_sink<Mutex>::sink_it_(
82         const spdlog::details::log_msg& msg) {
83     _current_size += msg.raw.size();
84     if (_current_size > _max_size) {
85         std::unique_ptr<spdlog::details::file_helper> next =
86                 std::make_unique<spdlog::details::file_helper>();
87         try {
88             next->open(calc_filename(), true);
89             addHook(closingLogfile);
90             std::swap(_file_helper, next);
91             _current_size = msg.raw.size();
92             addHook(openingLogfile);
93         } catch (...) {
94             // Keep on logging to the this file, but try swap at the next
95             // insert of data (didn't use the next file we need to
96             // roll back the next_file_id to avoid getting a hole ;-)
97             _next_file_id--;
98         }
99     }
100     fmt::memory_buffer formatted;
101     formatter->format(msg, formatted);
102 
103     _file_helper->write(formatted);
104 }
105 
106 template <class Mutex>
flush_()107 void custom_rotating_file_sink<Mutex>::flush_() {
108     _file_helper->flush();
109 }
110 
111 /* Takes a message, formats it and writes it to file */
112 template <class Mutex>
addHook(const std::string& hook)113 void custom_rotating_file_sink<Mutex>::addHook(const std::string& hook) {
114     spdlog::details::log_msg msg;
115     msg.time = spdlog::details::os::now();
116     msg.level = spdlog::level::info;
117 
118     // Append the hook to the msg
119     spdlog::details::fmt_helper::append_str(hook, msg.raw);
120 
121     if (hook == openingLogfile) {
122         spdlog::details::fmt_helper::append_str(
123                 std::string(_file_helper->filename().data(),
124                             _file_helper->filename().size()),
125                 msg.raw);
126     }
127     fmt::memory_buffer formatted;
128     formatter->format(msg, formatted);
129     _current_size += formatted.size();
130 
131     _file_helper->write(formatted);
132 }
133 
134 /**
135  * Create a new filename. If this breaks when updating spdlog then compare
136  * functionality to the calc_filename() method in the spdlog rotating_file_sink
137  * @tparam Mutex
138  * @return An spdlog filename
139  */
140 template <class Mutex>
calc_filename()141 spdlog::filename_t custom_rotating_file_sink<Mutex>::calc_filename() {
142     char fname[1024];
143     unsigned long try_id = _next_file_id;
144     do {
145         sprintf(fname, "%s.%06lu.txt", _base_filename.c_str(), try_id++);
146     } while (cb::io::isFile(fname));
147 
148     _next_file_id = try_id;
149 #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
150     return fmt::to_wstring(fname);
151 #else
152     return fmt::to_string(fname);
153 #endif
154 }
155 
156 template <class Mutex>
~custom_rotating_file_sink()157 custom_rotating_file_sink<Mutex>::~custom_rotating_file_sink() {
158     addHook(closingLogfile);
159 }
160 
161 template class custom_rotating_file_sink<std::mutex>;
162 template class custom_rotating_file_sink<spdlog::details::null_mutex>;
163