#include "Logging.hpp" #include <stdio.h> #include "../threading/threadmanager.hpp" #include "../collections/HashMap.hpp" #include "../collections/BinaryTree.hpp" #include "../filesystem/filesystem.hpp" #include "../filesystem/path.hpp" #include <time.h> namespace LoggingStatic { #ifdef _WIN32 #include <io.h> static int colorArray[int(sprawl::logging::Color::DarkCyan) + 1] = { FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, FOREGROUND_INTENSITY, FOREGROUND_RED | FOREGROUND_INTENSITY, FOREGROUND_GREEN | FOREGROUND_INTENSITY, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY, FOREGROUND_BLUE | FOREGROUND_INTENSITY, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY, 0, FOREGROUND_RED, FOREGROUND_GREEN, FOREGROUND_GREEN | FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_BLUE | FOREGROUND_RED, FOREGROUND_BLUE | FOREGROUND_GREEN }; static void setColor_(sprawl::logging::Color color, FILE* stream) { if (!_isatty(_fileno(stream))) { return; } HANDLE handle = HANDLE(_get_osfhandle(_fileno(stream))); SetConsoleTextAttribute( handle, colorArray[int(color)] ); } static void resetColor_(FILE* stream) { if (!_isatty(_fileno(stream))) { return; } HANDLE handle = HANDLE(_get_osfhandle(_fileno(stream))); SetConsoleTextAttribute( handle, colorArray[0] ); } #else #include <unistd.h> static char const* const colorArray[int(sprawl::logging::Color::DarkCyan) + 1] = { "\033[22;37m", "\033[1;30m", "\033[1;31m", "\033[1;32m", "\033[1;33m", "\033[1;34m", "\033[1;35m", "\033[1;36m", "\033[1;37m", "\033[22;30m", "\033[22;31m", "\033[22;32m", "\033[22;33m", "\033[22;34m", "\033[22;35m", "\033[22;36m" }; static void setColor_(sprawl::logging::Color color, FILE* stream) { if(!isatty(fileno(stream))) { return; } fputs(colorArray[int(color)], stream); } static void resetColor_(FILE* stream) { if(!isatty(fileno(stream))) { return; } fputs("\033[0m", stream); } #endif enum { NONE = 0, LOG_THREAD = 1 }; static sprawl::threading::ThreadManager logManager_; static bool useManager_ = false; static sprawl::collections::BasicHashMap<sprawl::String, sprawl::filesystem::File> fileMap_; static sprawl::logging::RenameMethod renameMethod_ = sprawl::logging::RenameMethod::Timestamp; static int renameArg_ = int(sprawl::time::Resolution::Milliseconds); static size_t maxFileSize_ = 500000; static sprawl::String format_ = sprawl::String("{} [{}] ({}) {} ({}:{}:{})") + sprawl::filesystem::LineSeparator(); static void addToThreadManager_(std::shared_ptr<sprawl::logging::Message> const& message, sprawl::filesystem::File const& file, sprawl::threading::ThreadManager& manager, int64_t threadFlag, sprawl::String const& filename) { manager.AddTask(std::bind(sprawl::logging::PrintMessageToFile, message, file, filename), threadFlag); } static sprawl::String modifyFilename_(sprawl::String const& filename) { if(renameMethod_ == sprawl::logging::RenameMethod::Timestamp) { sprawl::path::ExtResult result = sprawl::path::SplitExt(filename); return sprawl::Format("{}_{}.{}", result.path, sprawl::time::Now(sprawl::time::Resolution(renameArg_)), result.extension); } if(sprawl::path::Exists(filename)) { int max = renameArg_; sprawl::String maxPath = sprawl::Format("{}.{}", filename, max); if(sprawl::path::Exists(maxPath)) { sprawl::filesystem::Remove(maxPath); } for(int i = max - 1; i > 0; --i) { sprawl::String path = sprawl::Format("{}.{}", filename, i); if(sprawl::path::Exists(path)) { sprawl::filesystem::Rename(path, Format("{}.{}", filename, i + 1)); } } sprawl::filesystem::Rename(filename, Format("{}.1", filename)); } return filename; } static sprawl::collections::BasicHashMap<sprawl::String, int> idx_; static sprawl::String strftimeFormat_ = "%Y-%m-%dT%H:%M:%S"; static sprawl::time::Resolution maxResolution_ = sprawl::time::Resolution::Microseconds; static sprawl::collections::BasicHashMap<sprawl::String, sprawl::threading::Mutex> fileMutexes_; static int resolutionSize_ = 6; static sprawl::collections::BasicHashMap<FILE*, sprawl::threading::Mutex> streamMutexes_; static sprawl::logging::Handler defaultHandler_ = sprawl::logging::PrintToStdout(); static sprawl::collections::BasicBinaryTree<sprawl::String, sprawl::collections::Vector<sprawl::logging::Handler>> handlers_; static sprawl::logging::Options defaultOptions_; static sprawl::collections::BasicHashMap<int, sprawl::logging::Options> options_; static sprawl::collections::BinarySet<sprawl::String> disabledCategories_; static int minLevel_ = 0; template<typename T> static typename T::template const_iterator<0> getClosestParentCategory_(T& map, sprawl::String const& categoryStr) { auto it = map.find(categoryStr); if(!it.Valid()) { it = map.LowerBound(categoryStr); } if(it.Valid()) { sprawl::String checkStr = it.Key(); while(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { if(categoryStr == checkStr) { return it; break; } else if(categoryStr.length() > checkStr.length()) { if(categoryStr[checkStr.length()] == ':') { if(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { return it; break; } } } --it; if(!it.Valid()) { break; } checkStr = it.Key(); } } return map.end(); } } void sprawl::logging::Log(int level, sprawl::String const& levelStr, sprawl::String const& message, sprawl::String const& file, sprawl::String const& function, int line) { if(level < LoggingStatic::minLevel_) { return; } Options* options; if(LoggingStatic::options_.Has(level)) { options = &LoggingStatic::options_.Get(level); } else { options = &LoggingStatic::defaultOptions_; } LoggingStatic::defaultHandler_(std::make_shared<Message>(sprawl::time::Now(sprawl::time::Resolution::Nanoseconds), levelStr, "", message, file, function, line, options)); } void sprawl::logging::Log(int level, sprawl::String const& levelStr, sprawl::logging::Category const& category, sprawl::String const& message, sprawl::String const& file, sprawl::String const& function, int line) { if(level < LoggingStatic::minLevel_) { return; } Options* options; if(LoggingStatic::options_.Has(level)) { options = &LoggingStatic::options_.Get(level); } else { options = &LoggingStatic::defaultOptions_; } sprawl::String categoryStr = category.Str(); auto disableIt = LoggingStatic::getClosestParentCategory_(LoggingStatic::disabledCategories_, categoryStr); if(disableIt.Valid()) { return; } auto it = LoggingStatic::getClosestParentCategory_(LoggingStatic::handlers_, categoryStr); if(it.Valid()) { for(auto& handler : it.Value()) { handler(std::make_shared<Message>(sprawl::time::Now(sprawl::time::Resolution::Nanoseconds), levelStr, categoryStr, message, file, function, line, options)); } } else { LoggingStatic::defaultHandler_(std::make_shared<Message>(sprawl::time::Now(sprawl::time::Resolution::Nanoseconds), levelStr, categoryStr, message, file, function, line, options)); } } void sprawl::logging::SetDefaultOptions(Options const& options) { LoggingStatic::defaultOptions_ = options; } void sprawl::logging::SetLevelOptions(int level, Options const& options) { LoggingStatic::options_[level] = options; } void sprawl::logging::SetRuntimeMinimumLevel(int level) { LoggingStatic::minLevel_ = level; } void sprawl::logging::DisableCategory(Category const& category) { DisableCategory(category.Str()); } void sprawl::logging::DisableCategory(sprawl::String const& categoryStr) { LoggingStatic::disabledCategories_.Insert(categoryStr); } void sprawl::logging::EnableCategory(Category const& category) { EnableCategory(category.Str()); } void sprawl::logging::EnableCategory(sprawl::String const& categoryStr) { LoggingStatic::disabledCategories_.Erase(categoryStr); } void sprawl::logging::SetFormat(sprawl::String const& format) { static bool idxPopulated = false; if(!idxPopulated) { int i = 0; LoggingStatic::idx_.Insert("timestamp", i++); LoggingStatic::idx_.Insert("level", i++); LoggingStatic::idx_.Insert("category", i++); LoggingStatic::idx_.Insert("message", i++); LoggingStatic::idx_.Insert("file", i++); LoggingStatic::idx_.Insert("function", i++); LoggingStatic::idx_.Insert("line", i++); } sprawl::StringBuilder builder; sprawl::StringBuilder name; size_t const formatLength = format.length(); char const* const data = format.c_str(); bool inBracket = false; for(size_t i = 0; i < formatLength; ++i) { const char c = data[i]; if(c == '{') { if(inBracket) { builder << '{' << '{'; } inBracket = !inBracket; continue; } if(inBracket) { if(c == '}') { sprawl::String str = name.TempStr(); if(LoggingStatic::idx_.Has(str)) { builder << '{' << LoggingStatic::idx_.Get(str) << '}'; } else { builder << '{' << str << '}'; } name.Reset(); inBracket = false; } else { name << c; } continue; } builder << c; } builder << sprawl::filesystem::LineSeparator(); LoggingStatic::format_ = builder.Str(); } void sprawl::logging::SetTimeFormat(const sprawl::String& strftimeFormat, sprawl::time::Resolution maxResolution) { LoggingStatic::strftimeFormat_ = strftimeFormat.GetOwned(); LoggingStatic::maxResolution_ = maxResolution; switch(maxResolution) { case sprawl::time::Resolution::Nanoseconds: LoggingStatic::resolutionSize_ = 9; return; case sprawl::time::Resolution::Microseconds: LoggingStatic::resolutionSize_ = 6; return; case sprawl::time::Resolution::Milliseconds: LoggingStatic::resolutionSize_ = 3; return; default: LoggingStatic::resolutionSize_ = 0; return; } } void sprawl::logging::SetDefaultHandler(Handler const& handler) { LoggingStatic::defaultHandler_ = handler; } void sprawl::logging::AddCategoryHandler(Category const& category, Handler const& handler, CategoryCombinationType type) { sprawl::String categoryStr = category.Str(); sprawl::collections::Vector<Handler> handlers; handlers.PushBack(handler); if(type == CategoryCombinationType::Combined) { auto it = LoggingStatic::handlers_.LowerBound(categoryStr); if(it.Valid()) { sprawl::String checkStr = it.Key(); while(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { if(categoryStr == checkStr) { for(auto& absorbedHandler : it.Value()) { handlers.PushBack(absorbedHandler); } } else if(categoryStr.length() > checkStr.length()) { if(categoryStr[checkStr.length()] == ':') { if(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { for(auto& absorbedHandler : it.Value()) { handlers.PushBack(absorbedHandler); } } } } --it; if(!it.Valid()) { break; } checkStr = it.Key(); } } } LoggingStatic::handlers_.Insert(categoryStr, handlers); } void sprawl::logging::PrintMessageToFile(std::shared_ptr<sprawl::logging::Message> const& message, sprawl::filesystem::File& file, sprawl::String const& filename) { sprawl::String strMessage = message->ToString(); { sprawl::threading::ScopedLock(LoggingStatic::fileMutexes_.GetOrInsert(filename)); if(file.FileSize() + strMessage.length() > LoggingStatic::maxFileSize_) { file.Close(); file = sprawl::filesystem::Open(LoggingStatic::modifyFilename_(filename), "w"); } file.Write(strMessage); } file.Flush(); if(message->messageOptions->logToStdout) { LoggingStatic::setColor_(message->messageOptions->color, stdout); fwrite(strMessage.c_str(), 1, strMessage.length(), stdout); LoggingStatic::resetColor_(stdout); fflush(stdout); } if(message->messageOptions->logToStderr) { LoggingStatic::setColor_(message->messageOptions->color, stderr); fwrite(strMessage.c_str(), 1, strMessage.length(), stderr); LoggingStatic::resetColor_(stderr); fflush(stderr); } } void sprawl::logging::PrintMessageToStream(std::shared_ptr<sprawl::logging::Message> const& message, FILE* stream) { sprawl::String strMessage = message->ToString(); { sprawl::threading::ScopedLock(LoggingStatic::streamMutexes_.GetOrInsert(stream)); LoggingStatic::setColor_(message->messageOptions->color, stream); fwrite(strMessage.c_str(), 1, strMessage.length(), stream); LoggingStatic::resetColor_(stream); fflush(stream); } } sprawl::logging::Handler sprawl::logging::PrintToFile(sprawl::String const& filename) { sprawl::filesystem::File file; if(!LoggingStatic::fileMap_.Has(filename)) { sprawl::String modifiedFilename = LoggingStatic::modifyFilename_(filename); file = sprawl::filesystem::Open(modifiedFilename, "w"); LoggingStatic::fileMap_.Insert(filename, file); } return std::bind(PrintMessageToFile, std::placeholders::_1, std::ref(LoggingStatic::fileMap_.Get(filename)), filename); } sprawl::logging::Handler sprawl::logging::PrintToFile_Threaded(sprawl::String const& filename) { static bool managerInitialized_ = false; if(!managerInitialized_) { LoggingStatic::logManager_.AddThread(LoggingStatic::LOG_THREAD); managerInitialized_ = true; } LoggingStatic::useManager_ = true; return PrintToFile_ThreadManager(filename, LoggingStatic::logManager_, LoggingStatic::LOG_THREAD); } sprawl::logging::Handler sprawl::logging::PrintToFile_ThreadManager(sprawl::String const& filename, sprawl::threading::ThreadManager& manager, int64_t threadFlag) { sprawl::filesystem::File file; if(!LoggingStatic::fileMap_.Has(filename)) { sprawl::String modifiedFilename = LoggingStatic::modifyFilename_(filename); file = sprawl::filesystem::Open(modifiedFilename, "w"); LoggingStatic::fileMap_.Insert(filename, file); } return std::bind(LoggingStatic::addToThreadManager_, std::placeholders::_1, std::ref(LoggingStatic::fileMap_.Get(filename)), std::ref(manager), threadFlag, filename); } sprawl::logging::Handler sprawl::logging::PrintToStdout() { return std::bind(PrintMessageToStream, std::placeholders::_1, stdout); } sprawl::logging::Handler sprawl::logging::PrintToStderr() { return std::bind(PrintMessageToStream, std::placeholders::_1, stderr); } void sprawl::logging::SetRenameMethod(RenameMethod method, int arg) { LoggingStatic::renameMethod_ = method; LoggingStatic::renameArg_ = arg; } void sprawl::logging::SetMaxFilesize(size_t maxSizeBytes) { LoggingStatic::maxFileSize_ = maxSizeBytes; } void sprawl::logging::Init() { if(LoggingStatic::useManager_) { LoggingStatic::logManager_.Start(0); } } void sprawl::logging::Flush() { if(LoggingStatic::useManager_) { LoggingStatic::logManager_.Sync(); } } void sprawl::logging::Stop() { if(LoggingStatic::useManager_) { LoggingStatic::logManager_.Stop(); } } void sprawl::logging::ShutDown() { Flush(); if(LoggingStatic::useManager_) { LoggingStatic::logManager_.ShutDown(); } LoggingStatic::options_.Clear(); LoggingStatic::handlers_.Clear(); LoggingStatic::streamMutexes_.Clear(); LoggingStatic::fileMutexes_.Clear(); LoggingStatic::renameMethod_ = sprawl::logging::RenameMethod::Timestamp; LoggingStatic::renameArg_ = int(sprawl::time::Resolution::Milliseconds); LoggingStatic::maxFileSize_ = 500000; LoggingStatic::format_ = sprawl::String("{} [{}] ({}) {} ({}:{}:{})") + sprawl::filesystem::LineSeparator(); LoggingStatic::fileMap_.Clear(); LoggingStatic::useManager_ = false; } sprawl::String sprawl::logging::Message::ToString() { int64_t seconds = sprawl::time::Convert(timestamp, sprawl::time::Resolution::Nanoseconds, sprawl::time::Resolution::Seconds); sprawl::String timestampStr; if (!LoggingStatic::strftimeFormat_.empty()) { char buffer[128]; struct tm* tmInfo; time_t asTimeT = time_t(seconds); tmInfo = localtime(&asTimeT); strftime(buffer, sizeof(buffer), LoggingStatic::strftimeFormat_.c_str(), tmInfo); if(LoggingStatic::maxResolution_ < sprawl::time::Resolution::Seconds) { int64_t secsAsMaxRes = sprawl::time::Convert(seconds, sprawl::time::Resolution::Seconds, LoggingStatic::maxResolution_); int64_t nanosecsAsMaxRes = sprawl::time::Convert(timestamp, sprawl::time::Resolution::Nanoseconds, LoggingStatic::maxResolution_); sprawl::String formatStr = sprawl::Format("{{}.{{:0{}}", LoggingStatic::resolutionSize_); timestampStr = formatStr.format(buffer, nanosecsAsMaxRes - secsAsMaxRes); } else { timestampStr = buffer; } } return LoggingStatic::format_.format(timestampStr, messageOptions->nameOverride != nullptr ? messageOptions->nameOverride : level, category, message, sprawl::path::Basename(file), function, line); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#7 | 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 |
||
#6 | 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 |
||
#5 | 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 |
||
#4 | 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 |
||
#3 | 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 |
||
#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 |