InstallLog.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. * Copyright (C) 2022 The V-Gears Team
  3. *
  4. * This file is part of V-Gears
  5. *
  6. * V-Gears is free software: you can redistribute it and/or modify it under
  7. * terms of the GNU General Public License as published by the Free Software
  8. * Foundation, version 3.0 (GPLv3) of the License.
  9. *
  10. * V-Gears is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. */
  15. #include "InstallLog.h"
  16. /**
  17. * Number of messages of each severity that are kept for the summary.
  18. */
  19. static const size_t kKeptMessages = 200;
  20. /**
  21. * Stream buffer that passes everything through to the original buffer and sends each complete line to the log.
  22. */
  23. class InstallLog::Tee : public std::streambuf{
  24. public:
  25. Tee(std::ostream& stream, InstallLog& log): stream_(stream), log_(log), original_(stream.rdbuf(this)){}
  26. ~Tee(){stream_.rdbuf(original_);}
  27. /**
  28. * Writes directly to the original stream, without capturing it.
  29. */
  30. void WriteOriginal(const std::string& text){original_->sputn(text.data(), text.size());}
  31. protected:
  32. int_type overflow(int_type c) override{
  33. if (c == traits_type::eof()) return traits_type::not_eof(c);
  34. original_->sputc(static_cast<char>(c));
  35. if (c == '\n'){
  36. std::string line;
  37. line.swap(line_);
  38. if (!line.empty()) log_.AddCaptured(line);
  39. }
  40. else line_ += static_cast<char>(c);
  41. return c;
  42. }
  43. int sync() override{return original_->pubsync();}
  44. private:
  45. std::ostream& stream_;
  46. InstallLog& log_;
  47. std::streambuf* original_;
  48. std::string line_;
  49. };
  50. InstallLog::InstallLog(): counts_{0, 0, 0}{}
  51. InstallLog& InstallLog::Get(){
  52. static InstallLog log;
  53. return log;
  54. }
  55. void InstallLog::Open(const std::string& path, bool append){
  56. Close();
  57. std::lock_guard<std::mutex> lock(mutex_);
  58. path_ = path;
  59. file_.open(path, std::ios::out | (append ? std::ios::app : std::ios::trunc));
  60. for (int i = 0; i < 3; ++ i){
  61. counts_[i] = 0;
  62. first_[i].clear();
  63. }
  64. last_message_.clear();
  65. in_step_ = false;
  66. tee_out_ = std::make_unique<Tee>(std::cout, *this);
  67. tee_err_ = std::make_unique<Tee>(std::cerr, *this);
  68. }
  69. void InstallLog::Close(){
  70. // The tees restore the streams on destruction. Destroy them without holding the lock, since they
  71. // don't use it.
  72. tee_out_.reset();
  73. tee_err_.reset();
  74. std::lock_guard<std::mutex> lock(mutex_);
  75. if (file_.is_open()) file_.close();
  76. }
  77. void InstallLog::SetSink(Sink sink){
  78. std::lock_guard<std::mutex> lock(mutex_);
  79. sink_ = std::move(sink);
  80. }
  81. InstallLog::Level InstallLog::Classify(const std::string& message){
  82. if (message.compare(0, 7, "[ERROR]") == 0 || message.compare(0, 9, "Exception") == 0) return ERROR_LEVEL;
  83. if (message.compare(0, 9, "[WARNING]") == 0) return WARNING;
  84. return INFO;
  85. }
  86. void InstallLog::Add(const std::string& message, bool show){
  87. const Level level = Classify(message);
  88. Sink sink;
  89. {
  90. std::lock_guard<std::mutex> lock(mutex_);
  91. // Some messages are written twice in a row by the code that produces them.
  92. if (level != INFO && message == last_message_) return;
  93. last_message_ = message;
  94. ++ counts_[level];
  95. if (first_[level].size() < kKeptMessages) first_[level].push_back(message);
  96. if (file_.is_open()) file_ << message << '\n' << std::flush;
  97. if (show) sink = sink_;
  98. }
  99. // Echo to the terminal without capturing the message again.
  100. if (tee_out_) tee_out_->WriteOriginal(message + "\n");
  101. if (sink) sink(level, message);
  102. }
  103. void InstallLog::AddCaptured(const std::string& line){
  104. const Level level = Classify(line);
  105. Sink sink;
  106. {
  107. std::lock_guard<std::mutex> lock(mutex_);
  108. if (level != INFO && line == last_message_) return;
  109. if (level != INFO) last_message_ = line;
  110. ++ counts_[level];
  111. if (level != INFO && first_[level].size() < kKeptMessages) first_[level].push_back(line);
  112. if (file_.is_open()) file_ << line << '\n';
  113. // Only problems are shown in the window; the rest of the captured output is too verbose.
  114. if (level != INFO) sink = sink_;
  115. }
  116. if (sink) sink(level, line);
  117. }
  118. void InstallLog::BeginStep(const std::string& name){
  119. EndStep();
  120. std::lock_guard<std::mutex> lock(mutex_);
  121. step_name_ = name;
  122. for (int i = 0; i < 3; ++ i) step_start_[i] = counts_[i];
  123. in_step_ = true;
  124. }
  125. void InstallLog::EndStep(){
  126. std::string summary;
  127. {
  128. std::lock_guard<std::mutex> lock(mutex_);
  129. if (!in_step_) return;
  130. in_step_ = false;
  131. const size_t errors = counts_[ERROR_LEVEL] - step_start_[ERROR_LEVEL];
  132. const size_t warnings = counts_[WARNING] - step_start_[WARNING];
  133. if (errors == 0 && warnings == 0) return;
  134. summary = "Step \"" + step_name_ + "\" finished with " + std::to_string(errors) + " error(s) and "
  135. + std::to_string(warnings) + " warning(s).";
  136. }
  137. // Not prefixed, so it is information and doesn't count as a problem itself.
  138. Add(summary);
  139. }
  140. size_t InstallLog::Count(Level level) const{
  141. std::lock_guard<std::mutex> lock(mutex_);
  142. return counts_[level];
  143. }
  144. std::vector<std::string> InstallLog::First(Level level, size_t max) const{
  145. std::lock_guard<std::mutex> lock(mutex_);
  146. std::vector<std::string> messages = first_[level];
  147. if (messages.size() > max) messages.resize(max);
  148. return messages;
  149. }
  150. std::string InstallLog::Path() const{
  151. std::lock_guard<std::mutex> lock(mutex_);
  152. return path_;
  153. }