source: launchers/macosx/LoggerWorker.cpp @ 5a0017a

Last change on this file since 5a0017a was 5a0017a, checked in by meeh <meeh@…>, 20 months ago

Mac OSX Launcher: Adding a logger library I wrote a good while ago, refactored it to work with the launcher.

  • Property mode set to 100644
File size: 11.9 KB
Line 
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
22using namespace MeehLog;
23using namespace MeehLog::internal;
24
25Active::Active(): mDone(false){}
26
27Active::~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
34void 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
42void 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
53std::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
59namespace {
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 */
159struct 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 
174private:
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
182SharedLogWorkerImpl::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
207SharedLogWorkerImpl::~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
215void 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
227void 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
240std::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
267std::string  SharedLogWorkerImpl::backgroundFileName() {
268  return log_file_with_path_;
269}
270
271
272// Public API implementation
273//
274SharedLogWorker::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
280SharedLogWorker::~SharedLogWorker() {
281  pimpl.reset();
282  MeehLog::shutDownLoggingForActiveOnly(this);
283  std::cerr << "\nExiting, log location: " << logFileWithPath << std::endl << std::flush;
284}
285
286void SharedLogWorker::save(const MeehLog::internal::LogEntry& msg) {
287  pimpl->mBg->send(std::bind(&SharedLogWorkerImpl::backgroundFileWrite, pimpl.get(), msg));
288}
289
290void 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
298std::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
306std::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
312std::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
Note: See TracBrowser for help on using the repository browser.