1 | // |
---|
2 | // LoggerWorker.cpp |
---|
3 | // I2PLauncher |
---|
4 | // |
---|
5 | // Created by Mikal Villa on 27/09/2018. |
---|
6 | // Copyright © 2018 The I2P Project. All rights reserved. |
---|
7 | // Imported/Refactored from earlier C++ project of Meeh |
---|
8 | // |
---|
9 | |
---|
10 | #include "LoggerWorker.hpp" |
---|
11 | #include "Logger.h" |
---|
12 | |
---|
13 | #include <fstream> |
---|
14 | #include <sstream> |
---|
15 | #include <cassert> |
---|
16 | #include <algorithm> |
---|
17 | #include <string> |
---|
18 | #include <chrono> |
---|
19 | #include <functional> |
---|
20 | #include <future> |
---|
21 | |
---|
22 | using namespace MeehLog; |
---|
23 | using namespace MeehLog::internal; |
---|
24 | |
---|
25 | Active::Active(): mDone(false){} |
---|
26 | |
---|
27 | Active::~Active() { |
---|
28 | Callback quit_token = std::bind(&Active::doDone, this); |
---|
29 | send(quit_token); // tell thread to exit |
---|
30 | mThd.join(); |
---|
31 | } |
---|
32 | |
---|
33 | // Add asynchronously a work-message to queue |
---|
34 | void Active::send(Callback msg_){ |
---|
35 | mMq.push(msg_); |
---|
36 | } |
---|
37 | |
---|
38 | |
---|
39 | // Will wait for msgs if queue is empty |
---|
40 | // A great explanation of how this is done (using Qt's library): |
---|
41 | // http://doc.qt.nokia.com/stable/qwaitcondition.html |
---|
42 | void Active::run() { |
---|
43 | while (!mDone) { |
---|
44 | // wait till job is available, then retrieve it and |
---|
45 | // executes the retrieved job in this thread (background) |
---|
46 | Callback func; |
---|
47 | mMq.wait_and_pop(func); |
---|
48 | func(); |
---|
49 | } |
---|
50 | } |
---|
51 | |
---|
52 | // Factory: safe construction of object before thread start |
---|
53 | std::unique_ptr<Active> Active::createActive(){ |
---|
54 | std::unique_ptr<Active> aPtr(new Active()); |
---|
55 | aPtr->mThd = std::thread(&Active::run, aPtr.get()); |
---|
56 | return aPtr; |
---|
57 | } |
---|
58 | |
---|
59 | namespace { |
---|
60 | static const std::string date_formatted = "%Y/%m/%d"; |
---|
61 | static const std::string time_formatted = "%H:%M:%S"; |
---|
62 | static const std::string file_name_time_formatted = "%Y%m%d-%H%M%S"; |
---|
63 | |
---|
64 | // check for filename validity - filename should not be part of PATH |
---|
65 | bool isValidFilename(const std::string prefix_filename) { |
---|
66 | |
---|
67 | std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* "); |
---|
68 | size_t pos = prefix_filename.find_first_of(illegal_characters, 0); |
---|
69 | if (pos != std::string::npos) { |
---|
70 | std::cerr << "Illegal character [" << prefix_filename.at(pos) << "] in logname prefix: " << "[" << prefix_filename << "]" << std::endl; |
---|
71 | return false; |
---|
72 | } else if (prefix_filename.empty()) { |
---|
73 | std::cerr << "Empty filename prefix is not allowed" << std::endl; |
---|
74 | return false; |
---|
75 | } |
---|
76 | |
---|
77 | return true; |
---|
78 | } |
---|
79 | |
---|
80 | // Clean up the path if put in by mistake in the prefix |
---|
81 | std::string prefixSanityFix(std::string prefix) { |
---|
82 | prefix.erase(std::remove_if(prefix.begin(), prefix.end(), ::isspace), prefix.end()); |
---|
83 | prefix.erase(std::remove( prefix.begin(), prefix.end(), '\\'), prefix.end()); // '\\' |
---|
84 | prefix.erase(std::remove( prefix.begin(), prefix.end(), '.'), prefix.end()); // '.' |
---|
85 | prefix.erase(std::remove(prefix.begin(), prefix.end(), ':'), prefix.end()); // ':' |
---|
86 | |
---|
87 | if (!isValidFilename(prefix)) { |
---|
88 | return ""; |
---|
89 | } |
---|
90 | return prefix; |
---|
91 | } |
---|
92 | std::string pathSanityFix(std::string path, std::string file_name) { |
---|
93 | // Unify the delimeters,. maybe sketchy solution but it seems to work |
---|
94 | // on at least win7 + ubuntu. All bets are off for older windows |
---|
95 | std::replace(path.begin(), path.end(), '\\', '/'); |
---|
96 | |
---|
97 | // clean up in case of multiples |
---|
98 | auto contains_end = [&](std::string & in) -> bool { |
---|
99 | size_t size = in.size(); |
---|
100 | if (!size) return false; |
---|
101 | char end = in[size - 1]; |
---|
102 | return (end == '/' || end == ' '); |
---|
103 | }; |
---|
104 | |
---|
105 | while (contains_end(path)) { path.erase(path.size() - 1); } |
---|
106 | if (!path.empty()) { |
---|
107 | path.insert(path.end(), '/'); |
---|
108 | } |
---|
109 | path.insert(path.size(), file_name); |
---|
110 | return path; |
---|
111 | } |
---|
112 | |
---|
113 | |
---|
114 | std::string createLogFileName(const std::string& verified_prefix) { |
---|
115 | std::stringstream ossName; |
---|
116 | ossName.fill('0'); |
---|
117 | ossName << verified_prefix << MeehLog::localtime_formatted(systemtime_now(), file_name_time_formatted); |
---|
118 | ossName << ".log"; |
---|
119 | return ossName.str(); |
---|
120 | } |
---|
121 | |
---|
122 | |
---|
123 | bool openLogFile(const std::string& complete_file_with_path, std::ofstream& outstream) { |
---|
124 | std::ios_base::openmode mode = std::ios_base::out; // for clarity: it's really overkill since it's an ofstream |
---|
125 | mode |= std::ios_base::trunc; |
---|
126 | outstream.open(complete_file_with_path, mode); |
---|
127 | if (!outstream.is_open()) { |
---|
128 | std::ostringstream ss_error; |
---|
129 | ss_error << "FILE ERROR: could not open log file:[" << complete_file_with_path << "]"; |
---|
130 | ss_error << "\nstd::ios_base state = " << outstream.rdstate(); |
---|
131 | std::cerr << ss_error.str().c_str() << std::endl << std::flush; |
---|
132 | outstream.close(); |
---|
133 | return false; |
---|
134 | } |
---|
135 | std::ostringstream ss_entry; |
---|
136 | // Day Month Date Time Year: is written as "%a %b %d %H:%M:%S %Y" and formatted output as : Wed Aug 10 08:19:45 2014 |
---|
137 | ss_entry << "MeehLog created log file at: " << MeehLog::localtime_formatted(systemtime_now(), "%a %b %d %H:%M:%S %Y") << "\n"; |
---|
138 | ss_entry << "LOG format: [YYYY/MM/DD hh:mm:ss uuu* LEVEL FILE:LINE] message (uuu*: microsecond counter since initialization of log worker)\n\n"; |
---|
139 | outstream << ss_entry.str() << std::flush; |
---|
140 | outstream.fill('0'); |
---|
141 | return true; |
---|
142 | } |
---|
143 | |
---|
144 | |
---|
145 | std::unique_ptr<std::ofstream> createLogFile(const std::string& file_with_full_path) { |
---|
146 | std::unique_ptr<std::ofstream> out(new std::ofstream); |
---|
147 | std::ofstream& stream(*(out.get())); |
---|
148 | bool success_with_open_file = openLogFile(file_with_full_path, stream); |
---|
149 | if (false == success_with_open_file) { |
---|
150 | out.release(); // nullptr contained ptr<file> signals error in creating the log file |
---|
151 | } |
---|
152 | return out; |
---|
153 | } |
---|
154 | } // end anonymous namespace |
---|
155 | |
---|
156 | /** The Real McCoy Background worker, while g2LogWorker gives the |
---|
157 | * asynchronous API to put job in the background the g2LogWorkerImpl |
---|
158 | * does the actual background thread work */ |
---|
159 | struct SharedLogWorkerImpl { |
---|
160 | SharedLogWorkerImpl(const std::string& log_prefix, const std::string& log_directory); |
---|
161 | ~SharedLogWorkerImpl(); |
---|
162 | |
---|
163 | void backgroundFileWrite(MeehLog::internal::LogEntry message); |
---|
164 | void backgroundExitFatal(MeehLog::internal::FatalMessage fatal_message); |
---|
165 | std::string backgroundChangeLogFile(const std::string& directory); |
---|
166 | std::string backgroundFileName(); |
---|
167 | |
---|
168 | std::string log_file_with_path_; |
---|
169 | std::string log_prefix_backup_; // needed in case of future log file changes of directory |
---|
170 | std::unique_ptr<MeehLog::Active> mBg; |
---|
171 | std::unique_ptr<std::ofstream> mOutptr; |
---|
172 | steady_time_point steady_start_time_; |
---|
173 | |
---|
174 | private: |
---|
175 | SharedLogWorkerImpl& operator=(const SharedLogWorkerImpl&); |
---|
176 | SharedLogWorkerImpl(const SharedLogWorkerImpl& other); |
---|
177 | std::ofstream& filestream() {return *(mOutptr.get());} |
---|
178 | }; |
---|
179 | |
---|
180 | // |
---|
181 | // Private API implementation : SharedLogWorkerImpl |
---|
182 | SharedLogWorkerImpl::SharedLogWorkerImpl(const std::string& log_prefix, const std::string& log_directory) |
---|
183 | : log_file_with_path_(log_directory) |
---|
184 | , log_prefix_backup_(log_prefix) |
---|
185 | , mBg(MeehLog::Active::createActive()) |
---|
186 | , mOutptr(new std::ofstream) |
---|
187 | , steady_start_time_(std::chrono::steady_clock::now()) { // TODO: ha en timer function steadyTimer som har koll på start |
---|
188 | log_prefix_backup_ = prefixSanityFix(log_prefix); |
---|
189 | if (!isValidFilename(log_prefix_backup_)) { |
---|
190 | // illegal prefix, refuse to start |
---|
191 | std::cerr << "MeehLog: forced abort due to illegal log prefix [" << log_prefix << "]" << std::endl << std::flush; |
---|
192 | abort(); |
---|
193 | } |
---|
194 | |
---|
195 | std::string file_name = createLogFileName(log_prefix_backup_); |
---|
196 | log_file_with_path_ = pathSanityFix(log_file_with_path_, file_name); |
---|
197 | mOutptr = createLogFile(log_file_with_path_); |
---|
198 | if (!mOutptr) { |
---|
199 | std::cerr << "Cannot write logfile to location, attempting current directory" << std::endl; |
---|
200 | log_file_with_path_ = "./" + file_name; |
---|
201 | mOutptr = createLogFile(log_file_with_path_); |
---|
202 | } |
---|
203 | assert((mOutptr) && "cannot open log file at startup"); |
---|
204 | } |
---|
205 | |
---|
206 | |
---|
207 | SharedLogWorkerImpl::~SharedLogWorkerImpl() { |
---|
208 | std::ostringstream ss_exit; |
---|
209 | mBg.reset(); // flush the log queue |
---|
210 | ss_exit << "\nMeehLog file shutdown at: " << MeehLog::localtime_formatted(systemtime_now(), time_formatted); |
---|
211 | filestream() << ss_exit.str() << std::flush; |
---|
212 | } |
---|
213 | |
---|
214 | |
---|
215 | void SharedLogWorkerImpl::backgroundFileWrite(LogEntry message) { |
---|
216 | using namespace std; |
---|
217 | std::ofstream& out(filestream()); |
---|
218 | auto log_time = message.mTimestamp; |
---|
219 | auto steady_time = std::chrono::steady_clock::now(); |
---|
220 | out << "\n" << MeehLog::localtime_formatted(log_time, date_formatted); |
---|
221 | out << " " << MeehLog::localtime_formatted(log_time, time_formatted); |
---|
222 | out << " " << chrono::duration_cast<std::chrono::microseconds>(steady_time - steady_start_time_).count(); |
---|
223 | out << "\t" << message.mMsg << std::flush; |
---|
224 | } |
---|
225 | |
---|
226 | |
---|
227 | void SharedLogWorkerImpl::backgroundExitFatal(FatalMessage fatal_message) { |
---|
228 | backgroundFileWrite(fatal_message.mMessage); |
---|
229 | LogEntry flushEntry{"Log flushed successfully to disk \nExiting", MeehLog::internal::systemtime_now()}; |
---|
230 | backgroundFileWrite(flushEntry); |
---|
231 | std::cerr << "MeehLog exiting after receiving fatal event" << std::endl; |
---|
232 | std::cerr << "Log file at: [" << log_file_with_path_ << "]\n" << std::endl << std::flush; |
---|
233 | filestream().close(); |
---|
234 | MeehLog::shutDownLogging(); // only an initialized logger can recieve a fatal message. So shutting down logging now is fine. |
---|
235 | //exitWithDefaultSignalHandler(fatal_message.mSignalId); |
---|
236 | perror("MeehLog exited after receiving FATAL trigger. Flush message status: "); // should never reach this point |
---|
237 | } |
---|
238 | |
---|
239 | |
---|
240 | std::string SharedLogWorkerImpl::backgroundChangeLogFile(const std::string& directory) { |
---|
241 | std::string file_name = createLogFileName(log_prefix_backup_); |
---|
242 | std::string prospect_log = directory + file_name; |
---|
243 | std::unique_ptr<std::ofstream> log_stream = createLogFile(prospect_log); |
---|
244 | if (nullptr == log_stream) { |
---|
245 | LogEntry error("Unable to change log file. Illegal filename or busy? Unsuccessful log name was:" + prospect_log, MeehLog::internal::systemtime_now()); |
---|
246 | backgroundFileWrite(error); |
---|
247 | |
---|
248 | return ""; // no success |
---|
249 | } |
---|
250 | |
---|
251 | std::ostringstream ss_change; |
---|
252 | ss_change << "\nChanging log file from : " << log_file_with_path_; |
---|
253 | ss_change << "\nto new location: " << prospect_log << "\n"; |
---|
254 | backgroundFileWrite({ss_change.str().c_str(), MeehLog::internal::systemtime_now()}); |
---|
255 | ss_change.str(""); |
---|
256 | |
---|
257 | // setting the new log as active |
---|
258 | std::string old_log = log_file_with_path_; |
---|
259 | log_file_with_path_ = prospect_log; |
---|
260 | mOutptr = std::move(log_stream); |
---|
261 | ss_change << "\nNew log file. The previous log file was at: "; |
---|
262 | ss_change << old_log; |
---|
263 | backgroundFileWrite({ss_change.str(), MeehLog::internal::systemtime_now()}); |
---|
264 | return log_file_with_path_; |
---|
265 | } |
---|
266 | |
---|
267 | std::string SharedLogWorkerImpl::backgroundFileName() { |
---|
268 | return log_file_with_path_; |
---|
269 | } |
---|
270 | |
---|
271 | |
---|
272 | // Public API implementation |
---|
273 | // |
---|
274 | SharedLogWorker::SharedLogWorker(const std::string& log_prefix, const std::string& log_directory) |
---|
275 | : pimpl(new SharedLogWorkerImpl(log_prefix, log_directory)) |
---|
276 | , logFileWithPath(pimpl->log_file_with_path_) { |
---|
277 | assert((pimpl != nullptr) && "shouild never happen"); |
---|
278 | } |
---|
279 | |
---|
280 | SharedLogWorker::~SharedLogWorker() { |
---|
281 | pimpl.reset(); |
---|
282 | MeehLog::shutDownLoggingForActiveOnly(this); |
---|
283 | std::cerr << "\nExiting, log location: " << logFileWithPath << std::endl << std::flush; |
---|
284 | } |
---|
285 | |
---|
286 | void SharedLogWorker::save(const MeehLog::internal::LogEntry& msg) { |
---|
287 | pimpl->mBg->send(std::bind(&SharedLogWorkerImpl::backgroundFileWrite, pimpl.get(), msg)); |
---|
288 | } |
---|
289 | |
---|
290 | void SharedLogWorker::fatal(MeehLog::internal::FatalMessage fatal_message) { |
---|
291 | pimpl->mBg->send(std::bind(&SharedLogWorkerImpl::backgroundExitFatal, pimpl.get(), fatal_message)); |
---|
292 | } |
---|
293 | |
---|
294 | |
---|
295 | #pragma GCC diagnostic push |
---|
296 | #pragma GCC diagnostic ignored "-Wpessimizing-move" |
---|
297 | |
---|
298 | std::future<std::string> SharedLogWorker::changeLogFile(const std::string& log_directory) { |
---|
299 | MeehLog::Active* bgWorker = pimpl->mBg.get(); |
---|
300 | auto mBgcall = [this, log_directory]() {return pimpl->backgroundChangeLogFile(log_directory);}; |
---|
301 | auto future_result = MeehLog::spawn_task(mBgcall, bgWorker); |
---|
302 | return std::move(future_result); |
---|
303 | } |
---|
304 | |
---|
305 | |
---|
306 | std::future<void> SharedLogWorker::genericAsyncCall(std::function<void()> f) { |
---|
307 | auto bgWorker = pimpl->mBg.get(); |
---|
308 | auto future_result = MeehLog::spawn_task(f, bgWorker); |
---|
309 | return std::move(future_result); |
---|
310 | } |
---|
311 | |
---|
312 | std::future<std::string> SharedLogWorker::logFileName() { |
---|
313 | MeehLog::Active* bgWorker = pimpl->mBg.get(); |
---|
314 | auto mBgcall = [&]() {return pimpl->backgroundFileName();}; |
---|
315 | auto future_result = MeehLog::spawn_task(mBgcall , bgWorker); |
---|
316 | return std::move(future_result); |
---|
317 | } |
---|
318 | |
---|
319 | #pragma GCC diagnostic pop |
---|
320 | |
---|