#pragma once #include "../string/StringBuilder.hpp" #include "../string/String.hpp" #include "../time/time.hpp" #include <functional> #include <memory> namespace sprawl { namespace threading { class ThreadManager; } namespace filesystem { class File; } namespace logging { class Category; struct Options; struct Message; enum class Color { LightGrey, DarkGrey, Red, Green, Yellow, Blue, Magenta, Cyan, White, Black, DarkRed, DarkGreen, DarkYellow, DarkBlue, DarkMagenta, DarkCyan, Default = Color::LightGrey, }; #ifndef SPRAWL_LOG_ENFORCE_CATEGORIES /** * @brief Log a message with no category * * @param level Log level (to be compared against runtime maximum) * @param levelStr String representation of log level (to be printed) * @param message Message that was logged * @param file File containing the log statement * @param function Function containing the log statement * @param line Line on which the log statement appears */ void Log(int level, String const& levelStr, String const& message, String const& file, String const& function, int line); #endif /** * @brief Log a message with a filterable category * * @param level Log level (to be compared against runtime maximum) * @param levelStr String representation of log level (to be printed) * @param category Category of the log message (to be compared against category filters and possibly printed) * @param message Message that was logged * @param file File containing the log statement * @param function Function containing the log statement * @param line Line on which the log statement appears */ void Log(int level, String const& levelStr, Category const& category, sprawl::String const& message, String const& file, String const& function, int line); /** * @brief Set the global options that will apply to any log level without its own options set * * @param options The options to apply */ void SetDefaultOptions(Options const& options); /** * @brief Set the options for the given log level * * @param level The level to set options on * @param options The options to be set */ void SetLevelOptions(int level, Options const& options); template<typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> void SetLevelOptions(T level, Options const& options) { SetLevelOptions(int(level), options); } /** * @brief Sets the runtime minimum log level. Any log message with a level below 'level' will not be printed after this function has been called. * * @param level The minimum level to allow logging for. */ void SetRuntimeMinimumLevel(int level); template<typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> void SetRuntimeMinimumLevel(T level) { SetRuntimeMinimumLevel(int(level)); } /** * @brief Disable the given category. Any categories that contain this category as a prefix will not be printed after this call. * * @param category The category to disable */ void DisableCategory(Category const& category); void DisableCategory(sprawl::String const& categoryStr); /** * @brief Re-enables the given category and its children. No effect if the category was not previously disabled by DisableCategory. * * @param category The category to enable */ void EnableCategory(Category const& category); void EnableCategory(sprawl::String const& categoryStr); /** * @brief Set the format string to use for printing log messages. * * @details The format string accepts the following macro parameters: * {timestamp} - A timestamp whose format is specified by SetTimeFormat * {level} - A string representation of the level of this log message * {category} - The category, if any, of this message. If logged without a category, this will be an empty string. * {message} - The message that was printed * {file} - The file in which the message appeared * {function} - The function in which the message appeared * {line} - The line on which the message appeared * * @param format The format string to use */ void SetFormat(String const& format); /** * @brief Set the format to be used to print timestamps * * @param strftimeFormat The format of the seconds portion of the timestamp in stftime syntax * @param maxResolution The resolution below seconds to append to the timestamp; i.e., if this is sprawl::time::Resolution::Milliseconds, * then a dot and three digits representing the millisecond portion of the timestamp will be appended to the strftime * string. If this is set to sprawl::time::Resolution::Seconds or higher, nothing will be printed. */ void SetTimeFormat(String const& strftimeFormat, sprawl::time::Resolution maxResolution); typedef std::function<void(std::shared_ptr<Message> const&)> Handler; enum class CategoryCombinationType { Exclusive, Combined, }; /** * @brief Set the handler for the categories that have not been explicitly bound to another handler. * By default, if this is not set, logs will print to stdout. * * @param handler The Handler to use to default-handle messages */ void SetDefaultHandler(Handler const& handler); /** * @brief Add a handler for the given category. * * @details If 'type' is CategoryCombinationType::Combined (the default), then if this message matches any other criteria, * it will be handled by both this handler and the other one. For example, if a handler is set for category * 'Sprawl' and another is added for category 'Sprawl::Logging', then a message of category 'Sprawl::Logging' will be handled * by both the 'Sprawl::Logging' handler AND the 'Sprawl' handler. If the type is CategoryCombinationType::Exclusive, * then it will only be handled by the 'Sprawl::Logging' handler. * * Node that for performance reasons, the 'Sprawl' handler in this example must be registered before the 'Sprawl::Logging' handler, * otherwise only the 'Sprawl::Logging' handler will handle those messages. * * @param category The category to handle * @param handler The handler to handle it with * @param type The combination type */ void AddCategoryHandler(Category const& category, Handler const& handler, CategoryCombinationType type = CategoryCombinationType::Combined); /** * @brief Print a message to the passed-in file. This is an immediate print operation. * This function is exposed to enable easier creation of new Handlers * @param message The message to print * @param file The file to print to * @param filename The base name of the file (without timestamps and/or .1/.2/etc) - this is used for file rotation to create a new file if needed. */ void PrintMessageToFile(std::shared_ptr<Message> const& message, filesystem::File& file, sprawl::String const& filename); /** * @brief Prints a message directly to a stream (i.e., stdout/stderr). * If the stream is a TTY, then the message will be printed in color if the options indicate it should. * * @param message The message to print * @param stream The stream to print to */ void PrintMessageToStream(const std::shared_ptr<Message>& message, FILE* stream); /** * @brief Returns a handler to print to a file of the given name (modified according to file naming rules) * * @param filename The base filename without any naming rules modifications * @return A valid sprawl::logging::Handler instance */ Handler PrintToFile(sprawl::String const& filename); /** * @brief Returns a handler to print to a file of the given name (modified according to file naming rules). * In contrast with PrintToFile, PrintToFile_Threaded launches a dedicated thread and passes messages to it for printing. * * @param filename The base filename without any naming rules modifications * @return A valid sprawl::logging::Handler instance */ Handler PrintToFile_Threaded(sprawl::String const& filename); /** * @brief Returns a handler to print to a file of the given name (modified according to file naming rules). * In contrast with PrintToFile_Threaded, PrintToFile_ThreadManager integrates with an existing sprawl::threading::ThreadManager instance * and pushes log operations to threads with the given flag. Note that if your manager is threaded, tasks will be added for the current stage. * If the stage advances after this point, the log printing may be delayed until the next time that stage is run. * * @param filename The base filename without any naming rules modifications * @param manager The ThreadManager instance to use. This instance must not go out of scope or be destroyed before sprawl::logging::ShutDown() is called. * @param threadFlag The flags to be passed into ThreadManager::AddTask() * @return A valid sprawl::logging::Handler instance */ Handler PrintToFile_ThreadManager(sprawl::String const& filename, threading::ThreadManager& manager, int64_t threadFlag); /** * @brief Returns a simple handler that prints all messages directly to stdout * * @return A valid sprawl::logging::Handler instance */ Handler PrintToStdout(); /** * @brief Returns a simple handler that prints all messages directly to stderr * * @return A valid sprawl::logging::Handler instance */ Handler PrintToStderr(); enum class RenameMethod { Timestamp, Counter, }; /** * @brief Sets the method of renaming files for file rotation. * * @details The 'arg' parameter is context-sensitive. * If method is RenameMethod::Timestamp, 'arg' should be a sprawl::time::Resolution value (cast to int); any other value is undefined behavior. * If method is RenameMethod::Counter, 'arg' specifies the maximum number of files to keep around before deleting old ones. * i.e., if arg is 5, it will rename files to .log.1, .log.2, .log.3, .log.4, and .log.5, and delete any existing .log.5 files instead of renaming to .log.6. * * @param method Method by which to cycle files * @param arg Context-sensitive arg (see details) */ void SetRenameMethod(RenameMethod method, int arg); /** * @brief Set the maximum allowable filesize before closing the file and opening a new one. * * @param maxSizeBytes Max size in bytes */ void SetMaxFilesize(size_t maxSizeBytes); /** * @brief Initialize the logging system. * Currently only needed if using a PrintToFile_Threaded handler, but may be necessary for other reasons in the future; * therefore future-proof code should call this regardless of handlers used. */ void Init(); /** * @brief Ensure all log messages are flushed to the OS buffers. Only useful for PrintToFile_Threaded handlers; * with PrintToFile handlers, output is flushed immediately, and with PrintToFile_ThreadManager handlers, * the logging system does not have sufficient control of the thread manager to ensure a proper flush. */ void Flush(); /** * @brief Stop the logging system. For PrintToFile_Threaded handlers, no more logs will be written, but they will be queued. * Init() can be called again to resume logging. * For other handler types, this has no effect. */ void Stop(); /** * @brief Terminate the logging system entirely. This resets all parameters to their defaults and joins any internal threads that have been started. */ void ShutDown(); inline void Nop() {} } } class sprawl::logging::Category { public: template<typename... Params> Category(Params... params) : m_str() { init_(params...); } Category() : m_str() { // } String const& Str() const { return m_str; } private: void init_(char const* const param) { m_str = param; } void init_(StringBuilder& builder, char const* const param) { builder << param; m_str = builder.Str(); } template<typename... Params> void init_(StringBuilder& builder, char const* const param1, Params... params) { builder << param1 << "::"; init_(builder, params...); } template<typename... Params> void init_(char const* const param1, Params... params) { StringBuilder builder; builder << param1 << "::"; init_(builder, params...); } String m_str; }; struct sprawl::logging::Options { Options() : color(Color::Default) , logToStdout(false) , logToStderr(false) , includeBacktrace(false) , nameOverride(nullptr) { // } Color color; bool logToStdout; bool logToStderr; bool includeBacktrace; char const* nameOverride; }; struct sprawl::logging::Message { Message(int64_t timestamp_, String const& level_, String const& category_, String const& message_, String const& file_, String const& function_, int line_, Options* options_) : timestamp(timestamp_) , level(level_) , category(category_) , message(message_) , file(file_) , function(function_) , line(line_) , messageOptions(options_) { } int64_t timestamp; String level; String category; String message; String file; String function; int line; Options* messageOptions; String ToString(); }; #ifndef SPRAWL_LOG_LEVEL_TYPE #define SPRAWL_LOG_LEVEL_TYPE int #define SPRAWL_LOG_LEVEL_PREFIX #else #define SPRAWL_LOG_LEVEL_PREFIX SPRAWL_LOG_LEVEL_TYPE :: #endif #ifndef SPRAWL_MINIMUM_LOG_LEVEL #define SPRAWL_MINIMUM_LOG_LEVEL 0 #endif #define LOG(LEVEL, ...) SPRAWL_LOG_LEVEL_TYPE( SPRAWL_LOG_LEVEL_PREFIX SPRAWL_MINIMUM_LOG_LEVEL ) <= SPRAWL_LOG_LEVEL_PREFIX LEVEL ? ::sprawl::logging::Log(int(SPRAWL_LOG_LEVEL_PREFIX LEVEL), #LEVEL, __VA_ARGS__, __FILE__, __FUNCTION__, __LINE__) : ::sprawl::logging::Nop() ///@TODO: Backtraces
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#8 | 16246 | ShadauxCat |
- Fixed size and type computation for OpaqueTypeList, which was incorrect because it was building the struct in reverse order - and windows x86 also seems to have strange behavior with inheritance or including structs as class members when it comes to size calculation. So I'm now calculating the size manually. (Yay recursive compile-time math...) - Added function in logging to get the file object corresponding with a PrintToFile handler. #review-16247 |
||
#7 | 16171 | ShadauxCat |
- Created type traits header including macros to make checks for types having any operator or member function, and seeded it with every operator operating on the same type. For function and/or type combinations not already handled, creating a new type traits struct is just a single line of code. - Updated coroutine to use the new type traits header and removed the explicit operator() check - Added alternative to SPRAWL_THROW_EXCEPTION - SPRAWL_THROW_EXCEPTION_OR_ABORT, which as it sounds, will either throw the exception if exceptions are enabled, otherwise will print the message and abort. This is for cases where returning a value reliably isn't possible, such as in constructors or code that returns a template type that may not be default constructible. - Added a layer of safety around coroutines; trying to call the various functions that pause the current coroutine will now throw an exception (if enabled) or abort (if not) if you try to call the wrong pause function (i.e., calling an empty yield() on a coroutine that's supposed to return a value) - Added ability to determine what type a CoroutineBase object actually is underneath - Added function in logging to flush one specific category instead of flushing everything #review-16172 |
||
#6 | 16153 | ShadauxCat |
- Changed compile-time-bound strings to be sprawl::StringLiteral instead of sprawl::String. (Was going to do raw char*, but StringLiteral allows the length of the string to be baked in at compile time and thus avoids costly strlen() operations.) - Split Event::WaitMultiple() into Event::WaitAny() and Event::WaitAll() - Added Event::NotifyAll() - Added variadic template functions to make WaitAny(), WaitAll(), and NotifyAll() easier to work with when the list of events is known at compile time (i.e., Event::WaitAny(event1, event2, event3); to wait for all thre events instead of having to construct the EventGroup manually - also ensures EventGroup construction eficiency by constructing it with the proper capacity for the number of events being waited on) #review-16154 |
||
#5 | 16135 | ShadauxCat |
- Changed the way category handlers are registered a bit so that a category can't get the same handler added to it twice (i.e., from having both a parent and a grandparent with the same handler, which would happen if, for example, "Test" were registered with a handler, then "Test::Test2" were set to inherite handlers from "Test", and then "Test::Test2::Test3" were set to inherit from "Test::Test2") - Changed levelStr parameter to log function to be a char* so there's one less string to construct - Moved runtime level checking up higher so that arguments wouldn't be called/constructed if the log level is runtime-disabled - Added unit tests to verify the above #review-16136 |
||
#4 | 16131 | ShadauxCat |
- Exposed FILE* object in sprawl::filesystem::File - Added ability to specify flush behavior for custom handlers via std::function (interesting note - apparently with optimization enabled, calls to std::function can execute faster than virtual function calls) - Threads that destruct with no Join() after exiting with an uncaught exception will terminate with an error message rather than swallowing the exception and letting it disappear #review-16132 |
||
#3 | 16054 | ShadauxCat |
Fixed a compile error due to renaming a file at the last minute... #review-16055 |
||
#2 | 16052 | ShadauxCat |
- Changed default block size for concurrent queue to a more reasonable value - Changed some memory orders to memory_order_seq_cst when they don't actually need to be that to get around a bug in visual studio 2013 - debug builds assert when memory_order_acq_rel is used for a compare_exchange_strong (this is a standard library bug and is fixed in VS2015) - Added Event API - events are an alternative to condition variables that do not require a mutex and are guaranteed not to miss any signals, even if the signal comes while the thread is not listening for it. Unlike condition variables, however, they do not support broadcasting (and in fact, in general, are not safe to use with multiple threads listening for the same event simultaneously - though notifying on the same event is fine) - Rewrote ThreadManager around ConcurrentQueue and Event API so it is now lock-free. Also improved some behaviors of the staged thread manager operation so it now supports tasks that can be run on multiple stages via a bitmask. - Fixed an issue where the Coroutine copy constructor was calling the std::function constructor instead and another where initializing with a stack might try to call the wrong constructor and vice-versa - Fixed Coroutine never calling munmap() on its stack in linux and causing a memory leak - Added default arguments to time functions - Attempted to fix some issues with BinaryTree. Fixed some but not all. It's currently not suitable for use, sadly. - Logging Improvements: - - Added thread ID to logging - - Fixed some issues with category handlers - - Added backtraces - - Added the following additional log macros: - - - LOG_IF - - - LOG_EVERY_N - - - LOG_FIRST_N - - - LOG_IF_EVERY_N - - - LOG_IF_FIRST_N - - - LOG_ASSERT - - Added the ability to set extra info callbacks to get data such as script backtraces - - Removed the thread-related handlers and replaced them with RunHandler_Threaded and RunHandler_ThreadManager, which will enable any passed-in handler to be run in a threaded fashion - Removed StaticPoolAllocator and renamed DynamicPoolAllocator to PoolAllocator; adjusted unit tests accordingly - PoolAllocator now allocates its pool with mmap and VirtualAlloc, rather than with malloc - Fixed a bug with Vector copy assignment operator - Improved performance of StringBuilder considerably for cases where there are no modifier strings - Removed Copy-On-Write behavior of JSONToken as it was broken; copies are now performed with explicit DeepCopy() and ShallowCopy() functions - Fixed some parser bugs with JSONToken - Added iteration to JSONToken to iterate its children - Fixed crash when reading a negative number of bytes from a file - Changed StringBuilder to favor speed instead of memory by default - Added some performance unit tests for JSON token #review-16053 |
||
#1 | 14833 | ShadauxCat |
First checkin of logging module. Also fixes the following issues: -Added UpperBound() and LowerBound() to BinaryTree and created appropriate unit tests -Added Sync() to ThreadManager to force it to run all tasks to completion and not return until it has no tasks left -Fixed a bug in String::format() where a non-numeric value inside {} would be treated as an empty {}; it now simply prints whatever the value was. (i.e., "{blah}".format(foo) simply returns "{blah}") -Added Reset() to sprawl::StringBuilder -Disabled the switch-enum warning flag in gcc because it's stupid and ridiculous that a default case doesn't shut it up -Made sprawl::Mutex movable. This may turn out to be a bad idea but it enabled keeping them in a map. -Fixed a name collission between HashMap and BinaryTree; both defined sprawl::collections::detail::UnderlyingType and ::MethodType. Prefixed the ones in BinaryTree with "Tree". This isn't the best solution, but it works for now. #review-14834 |