diff --git a/src/log.cpp b/src/log.cpp index 96c6919..fe1459f 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -4,9 +4,10 @@ #include #include -#include - namespace gz { + + namespace fs = std::filesystem; + const char* COLORS[] = { "\033[0m", // RESET "\033[30m", // BLACK @@ -31,38 +32,65 @@ namespace gz { #ifdef LOG_MULTITHREAD std::mutex Log::mtx; #endif - Log::Log(std::string logfile, bool showLog, bool storeLog, std::string&& prefix_, Color prefixColor) - : showLog(showLog), storeLog(storeLog), prefixColor(prefixColor), prefix(prefix_ + ": "), prefixLength(prefix.size() + TIMESTAMP_CHAR_COUNT - 1) { - // store the absolute path to the logfile - logFile = std::string(std::filesystem::current_path().parent_path()) + "/logs/" + logfile; - log("Initialising log with settings: logFile: " + logFile + - ", showLog - " + boolToString(showLog) + ", storeLog - " + boolToString(storeLog)); + Log::Log(std::string logfile, bool showLog, bool storeLog, std::string&& prefix_, Color prefixColor, bool clearLogfileOnRestart, unsigned int writeAfterLines) + : showLog(showLog), storeLog(storeLog), prefixColor(prefixColor), prefix(prefix_ + ": "), prefixLength(prefix.size() + LOG_TIMESTAMP_CHAR_COUNT - 1), writeToFileAfterLines(writeAfterLines) { + // get absolute path to the logfile + fs::path logpath(logfile); + if (!logpath.is_absolute()) { + logpath = fs::current_path() / logpath; + } + // create directory of logfile + if (!fs::is_directory(logpath.parent_path())) { + fs::create_directory(logpath.parent_path()); + } + logFile = std::move(logpath.string()); + + // if clearLogfileOnRestart, open the file to clear it + if (clearLogfileOnRestart and fs::is_regular_file(logfile)) { + std::ofstream file(logFile, std::ofstream::trunc); + file.close(); + } + + if (writeToFileAfterLines == 0) { writeToFileAfterLines = 1; } + logLines.resize(writeAfterLines); + // 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); + } + } + + /* log("Initialising log with settings: logFile: " + logFile + */ + /* ", showLog - " + boolToString(showLog) + ", storeLog - " + boolToString(storeLog)); */ } + + Log::~Log() { if (storeLog) { writeLog(); } } - char* Log::getTime() { + + void Log::getTime() { std::time_t t = std::time(0); struct std::tm *tmp; tmp = std::localtime(&t); - //returs the date and time: yyyy-mm-dd hh:mm:ss: + // stores the date and time in time: yyyy-mm-dd hh:mm:ss: std::strftime(time, sizeof(time), "%F %T: ", tmp); - return time; } void Log::writeLog() { std::ofstream file(logFile); if (file.is_open()) { - for (std::string message : logArray) { + for (std::string message : logLines) { file << message; } - std::string message = getTime(); + getTime(); + std::string message = time; message += "Written log to file: " + logFile + "\n"; - file << message; - std::cout << message; + /* file << message; */ + if (showLog) { std::cout << message; } } else { std::cout << COLORS[RED] << "LOG ERROR: " << COLORS[RESET] << "Could not open file '" << logFile << "'." << '\n'; diff --git a/src/log.hpp b/src/log.hpp index 342ca19..0e5d276 100644 --- a/src/log.hpp +++ b/src/log.hpp @@ -7,40 +7,50 @@ #include #include #include +#include #ifdef LOG_MULTITHREAD #include #endif namespace gz { + + /// Reserve a string size for each string in logArray. Set to 0 if you do not want to reserve memory for strings. + constexpr unsigned int LOG_RESERVE_STRING_SIZE = 100; + + constexpr unsigned int LOG_TIMESTAMP_CHAR_COUNT = 22; + constexpr unsigned int LOG_POSTPREFIX_CHAR_COUNT = 2; + + inline const char* boolToString(bool b) { return b ? "true" : "false"; } - const int logLength = 100; - - constexpr unsigned int TIMESTAMP_CHAR_COUNT = 22; - constexpr unsigned int POSTPREFIX_CHAR_COUNT = 2; - // // CONCEPTS // - /// is std::string or convertible to std::string + /// is appendable to std::string template - concept Stringy = std::same_as || std::convertible_to; + /* concept Stringy = std::same_as || std::convertible_to; */ + concept Stringy = requires(T t, std::string s) { s += t; }; /// has .to_string() member template concept HasToString = !Stringy && requires(T t) { { t.to_string() }-> Stringy; }; + /// an overload of Stringy to_string(const T&) exists in global or gz namespace + template + concept ExistsToString = !Stringy && !HasToString && requires(const T& t) { { to_string(t) } -> Stringy; }; + /// works with std::to_string(), except bool template - concept WorksToString = !std::same_as && !Stringy && !HasToString && requires(T t) { { std::to_string(t) } -> Stringy; }; + concept WorksWithStdToString = !std::same_as && !Stringy && !HasToString && !ExistsToString && requires(T t) { { std::to_string(t) } -> Stringy; }; - /// string-like, has .to_string() member, works with std::to_string() or bool + /// string-like, has .to_string() member, to_string(const T&) exits, works with std::to_string() or bool template - concept PrintableNoPtr = Stringy || HasToString || WorksToString || std::same_as; + concept PrintableNoPtr = Stringy || HasToString || ExistsToString || WorksWithStdToString || std::same_as; + /// Everything from PrintableNoPtr but "behind" a pointer template concept Printable = PrintableNoPtr || requires(T t) { { *(t.get()) } -> PrintableNoPtr; }; @@ -88,14 +98,17 @@ namespace gz { * @brief Define types that can be logged with Log * @details * As of now you can log type T with instance t: - * -# Any string-like type - * -# Any type that works with std::to_string() - * -# Any type that has a to_string() member that returns a string - * -# Any type with t.x and t.y, provided t.x and t.y satisfy one of 1-3 - * -# Any type with t.first, t.second provided t.first satisfies one of 1-3 and t.second satisfies 1-4 - * -# Any type that has a forward_iterator which references any one of 1-5 + * -# Any @ref Stringy "string-like type": eg. std::string, std::string_view + * -# Any @ref WorksWithStdToString "type that works with std::to_string()" + * -# Any @ref ExistsToString "type for which an overload of" Stringy to_string(const T&) exists in global or gz namespace + * -# Any @ref HasToString "type that has a to_string() const member that returns a string" + * -# Any @ref Vector2Printable "type with t.x and t.y", provided t.x and t.y satisfy one of 1-3 + * -# Any @ref PairPrintable "type with t.first, t.second" provided t.first satisfies one of 1-3 and t.second satisfies 1-4 + * -# Any @ref ContainerPrintable "type that has a forward_iterator" which references any one of 1-5 * - * 1-6 include for example: + * The higher number takes precedence in overload resolution for the log function. + * + * 1-7 include for example: * - int, float, bool... * - std::vector, std::list * - std::map> if A.to_string() returns a string @@ -104,31 +117,51 @@ namespace gz { template concept Logable = LogableNotPointer || LogableSmartPointer; - template - class vec2; // defined in gz_math.hpp - /** * @brief Manages printing messages to stdout and to logfiles. * @details + * @subsection log_concepts Logable types + * Log uses concepts to determine if a type is logable and how it should be logged. See the documentation for concept Logable for more details. + * + * If you want your custom data type to be logable, it easiest to provide a member function with this signature: + * @code + * public: + * std::string to_string() const; + * @endcode + * Alternatively, or if the type is not a class overload std::string to_string(const T& t) in global or gz namespace. + * * @subsection log_threads Thread safety * Log can use a static mutex for thread safety. To use this feature, you have to #define LOG_MULTITHREAD at the top of 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_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. + * Instead, the log is stored in memory and written to the file if a certain number of lines is reached, which you can specify in the constructor. + * If you want the the log to be continuously written to the file, set writeAfterLines=1. + * + * @todo Exception policies + * @todo Remove vec2 or add vec3, vec4 + * */ 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. + * If not existent, the parent directory of the logfile and the file itself will be created when initializing a log. * - * @param logfile: Name of the file in the logs folder + * @param logfile: Absolute or relative path to the logfile * @param showLog: Wether to print the messages to stdout * @param storeLog: Wether to save the messages to the logfile * @param prefix: A prefix that comes between the timestamp and the message. ": " is automatically appended to the prefix * @param prefixColor: The color of the prefix + * @param clearLogfileOnRestart: If true, clear the logfile when initializing the log. That means only the log of most recent run is stored + * @param writeAfterLines: Actually write the log to the logfile after so many lines. Must be at least 1 * * @note Colors will only be shown when written to stdout, not in the logfile. */ - Log(std::string logfile="log.log", bool showLog=true, bool storeLog=true, std::string&& prefix="", Color prefixColor=RESET); + Log(std::string logfile="log.log", bool showLog=true, bool storeLog=true, std::string&& prefix="", Color prefixColor=RESET, bool clearLogfileOnRestart=true, unsigned int writeAfterLines=100); ~Log(); @@ -140,22 +173,21 @@ class Log { *