diff --git a/src/log.cpp b/src/log.cpp index 5ec496e..c0e4387 100755 --- a/src/log.cpp +++ b/src/log.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace gz { namespace fs = std::filesystem; @@ -50,35 +51,65 @@ namespace gz { #ifdef LOG_MULTITHREAD +// Static member initialization std::mutex Log::mtx; #endif - Log::Log(std::string logfile, bool showLog, bool storeLog, std::string&& prefix_, Color prefixColor, bool showTime, Color timeColor, bool clearLogfileOnRestart, unsigned int writeAfterLines) - : iter(0), - writeToFileAfterLines(writeAfterLines), clearLogfileOnRestart(clearLogfileOnRestart), - logFile(logfile), storeLog(storeLog), - showLog(showLog), - prefixColor(prefixColor), prefix(prefix_ + ": "), - showTime(showTime), timeColor(timeColor) + + Log::Log() + : showLog(true), + prefixColor(Color::RESET), + prefix("") { +#ifdef LOG_SUBLOGS + resources = std::make_shared(); +#endif + iter() = 0; + writeToFileAfterLines() = 100; + clearLogfileOnRestart() = false; + logFile() = "default.log"; + storeLog() = false; + showTime() = false; + timeColor() = Color::RESET; + init(); } - Log::Log(LogCreateInfo&& ci) - : iter(0), - writeToFileAfterLines(ci.writeAfterLines), clearLogfileOnRestart(ci.clearLogfileOnRestart), - logFile(ci.logfile), storeLog(ci.storeLog), - showLog(ci.showLog), + : showLog(ci.showLog), prefixColor(ci.prefixColor), - prefix(ci.prefix + ": "), - showTime(ci.showTime), timeColor(ci.timeColor) + prefix(ci.prefix) { +#ifdef LOG_SUBLOGS + resources = std::make_shared(); +#endif + iter() = 0; + writeToFileAfterLines() = ci.writeAfterLines; + clearLogfileOnRestart() = ci.clearLogfileOnRestart; + logFile() = ci.logfile; + storeLog() = ci.storeLog; + showTime() = ci.showTime; + timeColor() = ci.timeColor; + init(); } + +#ifdef LOG_SUBLOGS + Log Log::createSublog(bool showLog, const std::string& prefix, Color prefixColor) { + return Log(std::shared_ptr(resources), showLog, prefix, prefixColor); + } + + Log::Log(std::shared_ptr&& resources_, bool showLog, const std::string& prefix, Color prefixColor) + : resources(std::move(resources_)), showLog(showLog), prefixColor(prefixColor), prefix(prefix) + { + + } +#endif + + void Log::init() { // get absolute path to the logfile - fs::path logpath(std::move(logFile)); + fs::path logpath(std::move(logFile())); if (!logpath.is_absolute()) { logpath = fs::current_path() / logpath; } @@ -86,30 +117,34 @@ namespace gz { if (!fs::is_directory(logpath.parent_path())) { fs::create_directory(logpath.parent_path()); } - logFile = logpath.string(); + logFile() = logpath.string(); // if clearLogfileOnRestart, open the file to clear it - if (clearLogfileOnRestart and fs::is_regular_file(logpath)) { - std::ofstream file(logFile, std::ofstream::trunc); + if (clearLogfileOnRestart() and fs::is_regular_file(logpath)) { + std::ofstream file(logFile(), std::ofstream::trunc); file.close(); } - if (writeToFileAfterLines == 0) { writeToFileAfterLines = 1; } - logLines.resize(writeToFileAfterLines); + if (writeToFileAfterLines() == 0) { writeToFileAfterLines() = 1; } + logLines().resize(writeToFileAfterLines()); // reserve memory for strings if (LOG_RESERVE_STRING_SIZE > 0) { - for (size_t i = 0; i < logLines.size(); i++) { - logLines[i].reserve(LOG_RESERVE_STRING_SIZE); + for (size_t i = 0; i < logLines().size(); i++) { + logLines()[i].reserve(LOG_RESERVE_STRING_SIZE); } } // reserve memory for argsBegin - argsBegin.reserve(ARG_COUNT_RESERVE_COUNT); + argsBegin().reserve(ARG_COUNT_RESERVE_COUNT); + + if (!prefix.empty()) { + prefix += ": "; + } } Log::~Log() { - if (storeLog) { writeLog(); } + if (storeLog()) { writeLog(); } } @@ -118,24 +153,24 @@ namespace gz { struct std::tm *tmp; tmp = std::localtime(&t); // stores the date and time in time: yyyy-mm-dd hh:mm:ss: - std::strftime(time, sizeof(time), "%F %T: ", tmp); + std::strftime(time(), LOG_TIMESTAMP_CHAR_COUNT, "%F %T: ", tmp); } void Log::writeLog() { - std::ofstream file(logFile, std::ios_base::app); + std::ofstream file(logFile(), std::ios_base::app); if (file.is_open()) { - for (std::string message : logLines) { + for (std::string message : logLines()) { file << message; } getTime(); - std::string message = time; - message += "Written log to file: " + logFile + "\n"; + std::string message = time(); + message += "Written log to file: " + logFile() + "\n"; /* file << message; */ if (showLog) { std::cout << message; } } else { - std::cout << COLORS[RED] << "LOG ERROR: " << COLORS[RESET] << "Could not open file '" << logFile << "'." << '\n'; + std::cout << COLORS[RED] << "LOG ERROR: " << COLORS[RESET] << "Could not open file '" << logFile() << "'." << '\n'; } file.close(); } diff --git a/src/log.hpp b/src/log.hpp index 438f3ff..ac45d8e 100755 --- a/src/log.hpp +++ b/src/log.hpp @@ -7,6 +7,14 @@ #include #include +#ifndef LOG_NO_SUBLOGS +#define LOG_SUBLOGS +#endif + +#ifdef LOG_SUBLOGS +#include +#endif + #ifdef LOG_MULTITHREAD #include #endif @@ -59,6 +67,7 @@ namespace gz { template concept Logable = ConvertibleToString; + class Log; /** * @brief Create info for a Log object */ @@ -81,8 +90,37 @@ namespace gz { bool clearLogfileOnRestart = true; /// @brief Actually write the log to the logfile after so many lines. Must be at least 1 unsigned int writeAfterLines = 100; + #ifdef LOG_SUBLOGS + std::shared_ptr parent; + #endif }; +#ifdef LOG_SUBLOGS + /** + * @brief Resources that are normally members of Log when not using `LOG_SUBLOGS` + */ + struct LogResources { + /// Where the lines are stored + std::vector logLines; + /// Used during log: string views into the single substrings in logLines[currentLine] + std::vector argsBegin; + /// The current position in logLines + size_t iter = 0; + + + unsigned int writeToFileAfterLines; + bool clearLogfileOnRestart; + /// Absolute path to the logfile + std::string logFile; + bool storeLog; + + bool showTime; + Color timeColor; + /// Stores the current time in yyyy-mm-dd hh:mm:ss format + char time[LOG_TIMESTAMP_CHAR_COUNT]; + }; // class LogResources +#endif + /** * @brief Manages printing messages to stdout and to logfiles. * @details @@ -95,6 +133,14 @@ namespace gz { * Log can use a static mutex for thread safety. To use this feature, you have to `#define LOG_MULTITHREAD` @b before including `log.hpp`. * Note that log uses the default std::cout buffer, so you should make sure it is not being used while logging something. * + * @subsection log_subs Sublogs + * If you want multiple log instances that share a logfile, you can @ref createSublog "create a sublog" from the parent. + * The sublog inherits all settings and resources from the parent, except for showLog and the prefix. + * This feature is not available by default, you need to `#define LOG_SUBLOGS`. + * + * Note that the sublog may outlive the parent, as they @ref LogResources "share their resources" with a shared_ptr. + * You can also create sublogs from sublogs. + * * @subsection log_logfile Logfile * The logs can be written to a logfile, which can be specified in the constructor. * The logs are not continuously written to the logfile, since file operations are expensive. @@ -124,23 +170,26 @@ namespace gz { class Log { public: /** - * @brief Creates a log object, which can print messages to stdout and/or write them to a log file - * @details - * By creating multiple instances with different parameters, logs can be easily turned on/off for different usages. - * - * The overload using LogCreateInfo might be more clear, so I recommend using that. - * @note Colors will only be shown when written to stdout, not in the logfile. - * @deprecated Use the overload using the LogCreateInfo struct + * @brief Create a log with default settings + * @details + * - `showLog = true` + * - `storeLog = false` + * - `prefix = ""` + * - `showTime = false` */ - Log(std::string logfile="log.log", bool showLog=true, bool storeLog=true, std::string&& prefix="", Color prefixColor=RESET, bool showTime=true, Color timeColor=RESET, bool clearLogfileOnRestart=true, unsigned int writeAfterLines=100); - + Log(); /** * @brief Creates a log object, which can print messages to stdout and/or write them to a log file * @details * By creating multiple instances with different parameters, logs can be easily turned on/off for different usages. */ Log(LogCreateInfo&& createInfo); - +#ifdef LOG_SUBLOGS + Log createSublog(bool showLog, const std::string& prefix, Color prefixColor); + private: + Log(std::shared_ptr&& resources, bool showLog, const std::string& prefix, Color prefixColor); + public: +#endif ~Log(); // @@ -275,40 +324,64 @@ class Log { private: void init(); + +#ifdef LOG_SUBLOGS + std::shared_ptr resources; + + std::vector& logLines() { return resources->logLines; }; + std::vector& argsBegin() { return resources->argsBegin; }; + size_t& iter() { return resources->iter; }; + + unsigned int& writeToFileAfterLines() { return resources->writeToFileAfterLines; }; + bool& clearLogfileOnRestart() { return resources->clearLogfileOnRestart; }; + std::string& logFile() { return resources->logFile; }; + bool& storeLog() { return resources->storeLog; }; + + bool& showTime() { return resources->showTime; }; + Color& timeColor() { return resources->timeColor; }; + char* time() { return resources->time; }; +#else /// Where the lines are stored - std::vector logLines; + std::vector logLines_; /// Used during log: string views into the single substrings in logLines[currentLine] - std::vector argsBegin; + std::vector argsBegin_; /// The current position in logLines - size_t iter = 0; - /** - * @name Writing to file - */ - /// @{ + size_t iter_ = 0; + /// When iter reaches writeToFileAfterLines, write log to file - unsigned int writeToFileAfterLines; - bool clearLogfileOnRestart; + unsigned int writeToFileAfterLines_; + bool clearLogfileOnRestart_; /// Absolute path to the logfile - std::string logFile; - bool storeLog; + std::string logFile_; + bool storeLog_; + + bool showTime_; + Color timeColor_; + /// Stores the current time in yyyy-mm-dd hh:mm:ss format + char[LOG_TIMESTAMP_CHAR_COUNT] time_; + + // getters + std::vector& logLines() { return logLines_; }; + std::vector& argsBegin() { return argsBegin_; }; + size_t& iter() { return iter_; }; + + unsigned int& writeToFileAfterLines() { return writeToFileAfterLines_; }; + bool& clearLogfileOnRestart() { return clearLogfileOnRestart_; }; + std::string& logFile() { return logFile_; }; + bool& storeLog() { return storeLog_; }; + + bool& showTime() { return showTime_; }; + Color& timeColor() { return timeColor_; }; + std::string& time() { return time_; }; +#endif void writeLog(); - /// @} bool showLog; Color prefixColor; std::string prefix; - /** - * @name Time - */ - /// @{ - bool showTime; - Color timeColor; - /// Stores the current time in yyyy-mm-dd hh:mm:ss format - char time[LOG_TIMESTAMP_CHAR_COUNT]; /// Store the current time in yyyy-mm-dd hh:mm:ss format in time member void getTime(); - /// @} #ifdef LOG_MULTITHREAD /// Lock for std::cout @@ -325,31 +398,31 @@ class Log { #ifdef LOG_MULTITHREAD mtx.lock(); #endif - argsBegin.clear(); - if (showTime) { + argsBegin().clear(); + if (showTime()) { getTime(); - logLines[iter] = time; + logLines()[iter()] = time(); } else { - logLines.clear(); + logLines().clear(); } - argsBegin.emplace_back(logLines[iter].size()); - logLines[iter] += prefix; + argsBegin().emplace_back(logLines()[iter()].size()); + logLines()[iter()] += prefix; vlog(" ", std::forward(args)...); - logLines[iter] += "\n"; - argsBegin.emplace_back(logLines[iter].size()); + logLines()[iter()] += "\n"; + argsBegin().emplace_back(logLines()[iter()].size()); if (showLog) { // time - std::cout << COLORS[timeColor] << std::string_view(logLines[iter].begin(), logLines[iter].begin() + argsBegin[0]) + std::cout << COLORS[timeColor()] << std::string_view(logLines()[iter()].begin(), logLines()[iter()].begin() + argsBegin()[0]) // prefix - << COLORS[prefixColor] << std::string_view(logLines[iter].begin() + argsBegin[0], logLines[iter].begin() + argsBegin[1]) << COLORS[RESET] + << COLORS[prefixColor] << std::string_view(logLines()[iter()].begin() + argsBegin()[0], logLines()[iter()].begin() + argsBegin()[1]) << COLORS[RESET] // message - << std::string_view(logLines[iter].begin() + argsBegin[1], logLines[iter].end()); + << std::string_view(logLines()[iter()].begin() + argsBegin()[1], logLines()[iter()].end()); } - if (++iter >= writeToFileAfterLines) { - iter = 0; + if (++iter() >= writeToFileAfterLines()) { + iter() = 0; writeLog(); } #ifdef LOG_MULTITHREAD @@ -363,36 +436,36 @@ class Log { #ifdef LOG_MULTITHREAD mtx.lock(); #endif - argsBegin.clear(); - if (showTime) { + argsBegin().clear(); + if (showTime()) { getTime(); - logLines[iter] = std::string(time); + logLines()[iter()] = std::string(time()); } else { - logLines.clear(); + logLines().clear(); } - argsBegin.emplace_back(logLines[iter].size()); - logLines[iter] += prefix; + argsBegin().emplace_back(logLines()[iter()].size()); + logLines()[iter()] += prefix; vlog(" ", std::forward(args)...); - logLines[iter] += "\n"; - argsBegin.emplace_back(logLines[iter].size()); + logLines()[iter()] += "\n"; + argsBegin().emplace_back(logLines()[iter()].size()); if (showLog) { // time - std::cout << COLORS[timeColor] << std::string_view(logLines[iter].begin(), logLines[iter].begin() + argsBegin[0]) + std::cout << COLORS[timeColor()] << std::string_view(logLines()[iter()].begin(), logLines()[iter()].begin() + argsBegin()[0]) // prefix - << COLORS[prefixColor] << std::string_view(logLines[iter].begin() + argsBegin[0], logLines[iter].begin() + argsBegin[1]) << COLORS[RESET]; + << COLORS[prefixColor] << std::string_view(logLines()[iter()].begin() + argsBegin()[0], logLines()[iter()].begin() + argsBegin()[1]) << COLORS[RESET]; // max index where i can be used for colors and i+2 can be used for currentViews - size_t maxI = std::min(colors.size(), argsBegin.size() - 2); + size_t maxI = std::min(colors.size(), argsBegin().size() - 2); for (size_t i = 0; i < maxI; i++) { - std::cout << COLORS[colors[i]] << std::string_view(logLines[iter].begin() + argsBegin[i+1], logLines[iter].begin() + argsBegin[i+2]); + std::cout << COLORS[colors[i]] << std::string_view(logLines()[iter()].begin() + argsBegin()[i+1], logLines()[iter()].begin() + argsBegin()[i+2]); } // log the rest, maxI is now <= argsBegin.size() - 2 - std::cout << std::string_view(logLines[iter].begin() + argsBegin[maxI+1], logLines[iter].end()) << COLORS[RESET]; + std::cout << std::string_view(logLines()[iter()].begin() + argsBegin()[maxI+1], logLines()[iter()].end()) << COLORS[RESET]; } - if (++iter >= writeToFileAfterLines) { - iter = 0; + if (++iter() >= writeToFileAfterLines()) { + iter() = 0; writeLog(); } #ifdef LOG_MULTITHREAD @@ -403,17 +476,17 @@ class Log { template void Log::vlog(const char* appendChars, T&& t, Args&&... args) { - argsBegin.emplace_back(logLines[iter].size()); - logLines[iter] += std::string(t); - logLines[iter] += appendChars; + argsBegin().emplace_back(logLines()[iter()].size()); + logLines()[iter()] += std::string(t); + logLines()[iter()] += appendChars; vlog(" ", std::forward(args)...); } /// Log anything where toString exists template void Log::vlog(const char* appendChars, T&& t, Args&&... args) requires (!util::Stringy) { - argsBegin.emplace_back(logLines[iter].size()); - logLines[iter] += toString(t); - logLines[iter] += appendChars; + argsBegin().emplace_back(logLines()[iter()].size()); + logLines()[iter()] += toString(t); + logLines()[iter()] += appendChars; vlog(" ", std::forward(args)...); }