#include "Logging.hpp" #include <stdio.h> #include "../threading/threadmanager.hpp" #include "../collections/HashMap.hpp" #include <map> #include <set> #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] ); } #define snprintf _snprintf #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; struct FileData { FileData(sprawl::filesystem::File const& file_, sprawl::String const& filename_) : file(file_) , size(0) , filename(filename_) , mutex() { } FileData(FileData&& other) : file(std::move(other.file)) , size(other.size) , filename(std::move(other.filename)) , mutex(std::move(other.mutex)) { other.size = 0; } sprawl::filesystem::File file; size_t size; sprawl::String filename; sprawl::threading::Mutex mutex; }; static sprawl::collections::BasicHashMap<sprawl::String, FileData> fileMap_; static sprawl::logging::RenameMethod renameMethod_ = sprawl::logging::RenameMethod::Timestamp; static int renameArg_ = int(sprawl::time::Resolution::Milliseconds); static size_t maxFileSize_ = 500 * 1024 * 1024; static sprawl::String format_ = sprawl::String("{} {} [{}] {} [{}] ({}:{}:{})") + sprawl::filesystem::LineSeparator(); static void addToThreadManager_(sprawl::logging::Handler const& handler, std::shared_ptr<sprawl::logging::Message> const& message, sprawl::threading::ThreadManager& manager, int64_t threadFlag) { manager.AddTask(std::bind(handler.Log, message), threadFlag); } static void flushThreadManager_(sprawl::logging::Handler const& handler, sprawl::threading::ThreadManager& manager, int64_t threadFlag) { manager.AddTask(handler.Flush, threadFlag); manager.Sync(); } 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 int resolutionSize_ = 6; static sprawl::collections::BasicHashMap<FILE*, sprawl::threading::Mutex> streamMutexes_; static sprawl::logging::Handler defaultHandler_ = sprawl::logging::PrintToStdout(); static std::map<sprawl::String, sprawl::collections::BasicHashMap<void*, sprawl::logging::Handler>> handlers_; static sprawl::logging::Options defaultOptions_; static sprawl::collections::BasicHashMap<int, sprawl::logging::Options> options_; static std::set<sprawl::String> disabledCategories_; static int minLevel_ = 0; template<typename Key, typename Value> static typename std::map<Key, Value>::const_iterator getClosestParentCategory_(std::map<Key, Value>& map, sprawl::String const& categoryStr) { if(map.empty()) { return map.end(); } auto it = map.find(categoryStr); if(it == map.end()) { it = map.upper_bound(categoryStr); if(it == map.begin()) { return map.end(); } else if(it == map.end()) { it = (++map.rbegin()).base(); } else { --it; } } sprawl::String checkStr = it->first; while(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { if(categoryStr == checkStr) { return it; } else if(categoryStr.length() > checkStr.length()) { if(categoryStr[checkStr.length()] == ':') { if(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { return it; } } } if(it == map.begin()) { return map.end(); } --it; checkStr = it->first; } return map.end(); } template<typename T> static typename std::set<T>::const_iterator getClosestParentCategory_(std::set<T>& map, sprawl::String const& categoryStr) { if(map.empty()) { return map.end(); } auto it = map.find(categoryStr); if(it == map.end()) { it = map.upper_bound(categoryStr); if(it == map.begin()) { return map.end(); } else if(it == map.end()) { it = (++map.rbegin()).base(); } else { --it; } } sprawl::String checkStr = *it; while(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { if(categoryStr == checkStr) { return it; } else if(categoryStr.length() > checkStr.length()) { if(categoryStr[checkStr.length()] == ':') { if(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { return it; } } } if(it == map.begin()) { return map.end(); } --it; checkStr = *it; } return map.end(); } struct ExtraInfoCallbackPair { std::function<void*()> Get; std::function<void(void*, sprawl::StringBuilder&)> Print; }; static sprawl::collections::BasicHashMap<int, sprawl::collections::Vector<ExtraInfoCallbackPair>> extraInfoCallbacks_; static void printMessageToFile_(std::shared_ptr<sprawl::logging::Message> const& message, LoggingStatic::FileData& fileData) { sprawl::String strMessage = message->ToString(); sprawl::String extraData = message->CollectExtraData(); { sprawl::threading::ScopedLock(fileData.mutex); if(fileData.size + strMessage.length() + extraData.length() > LoggingStatic::maxFileSize_) { fileData.file.Close(); fileData.file = sprawl::filesystem::Open(LoggingStatic::modifyFilename_(fileData.filename), "w"); } fileData.file.Write(strMessage); if(!extraData.empty()) { fileData.file.Write(extraData); } fileData.size += strMessage.length() + extraData.length(); } if(message->messageOptions->logToStdout) { LoggingStatic::setColor_(message->messageOptions->color, stdout); fwrite(strMessage.c_str(), 1, strMessage.length(), stdout); LoggingStatic::resetColor_(stdout); } if(message->messageOptions->logToStderr) { LoggingStatic::setColor_(message->messageOptions->color, stderr); fwrite(strMessage.c_str(), 1, strMessage.length(), stderr); LoggingStatic::resetColor_(stderr); } } static void printMessageToStream_(std::shared_ptr<sprawl::logging::Message> const& message, FILE* stream) { sprawl::String strMessage = message->ToString(); sprawl::String extraData = message->CollectExtraData(); { sprawl::threading::ScopedLock(LoggingStatic::streamMutexes_.GetOrInsert(stream)); LoggingStatic::setColor_(message->messageOptions->color, stream); fwrite(strMessage.c_str(), 1, strMessage.length(), stream); LoggingStatic::resetColor_(stream); if(!extraData.empty()) { fwrite(extraData.c_str(), 1, extraData.length(), stream); } } } } void sprawl::logging::Log(int level, StringLiteral const& levelStr, sprawl::String const& message, StringLiteral const& file, StringLiteral const& function, StringLiteral const& line) { Options* options; auto it = LoggingStatic::options_.find(level); if(it.Valid()) { options = &it.Value(); } else { options = &LoggingStatic::defaultOptions_; } std::shared_ptr<Message> messagePtr = std::make_shared<Message>(sprawl::time::Now(sprawl::time::Resolution::Nanoseconds), sprawl::this_thread::GetHandle().GetUniqueId(), level, levelStr, "", message, file, function, line, &LoggingStatic::defaultOptions_); if(options->includeBacktrace) { messagePtr->backtrace = Backtrace::Get(); } auto it2 = LoggingStatic::extraInfoCallbacks_.find(level); if(it2.Valid()) { auto& callbacks = it2.Value(); for(auto& callbackPair : callbacks) { messagePtr->extraInfo.PushBack(callbackPair.Get()); } } LoggingStatic::defaultHandler_.Log(messagePtr); } void sprawl::logging::Log(int level, StringLiteral const& levelStr, sprawl::logging::Category const& category, String const& message, StringLiteral const& file, StringLiteral const& function, StringLiteral const& line) { Options* options; auto it = LoggingStatic::options_.find(level); if(it.Valid()) { options = &it.Value(); } else { options = &LoggingStatic::defaultOptions_; } sprawl::String categoryStr = category.Str(); auto disableIt = LoggingStatic::getClosestParentCategory_(LoggingStatic::disabledCategories_, categoryStr); if(disableIt != LoggingStatic::disabledCategories_.end()) { return; } std::shared_ptr<Message> messagePtr = std::make_shared<Message>(sprawl::time::Now(sprawl::time::Resolution::Nanoseconds), sprawl::this_thread::GetHandle().GetUniqueId(), level, levelStr, categoryStr, message, file, function, line, options); if(options->includeBacktrace) { messagePtr->backtrace = Backtrace::Get(); } auto it2 = LoggingStatic::extraInfoCallbacks_.find(level); if(it2.Valid()) { auto& callbacks = it2.Value(); for(auto& callbackPair : callbacks) { messagePtr->extraInfo.PushBack(callbackPair.Get()); } } auto it3 = LoggingStatic::getClosestParentCategory_(LoggingStatic::handlers_, categoryStr); if(it3 != LoggingStatic::handlers_.end()) { for(auto& handler : it3->second) { handler.Value().Log(messagePtr); } } else { LoggingStatic::defaultHandler_.Log(messagePtr); } } 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; } int sprawl::logging::GetRuntimeMinimumLevel() { return LoggingStatic::minLevel_; } 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("threadid", i++); LoggingStatic::idx_.Insert("level", i++); LoggingStatic::idx_.Insert("message", i++); LoggingStatic::idx_.Insert("category", 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::BasicHashMap<void*, Handler>& handlers = LoggingStatic::handlers_[categoryStr]; handlers.Insert(handler.uniqueId, handler); if(type == CategoryCombinationType::Combined && !LoggingStatic::handlers_.empty()) { auto it = LoggingStatic::handlers_.upper_bound(categoryStr); if(it == LoggingStatic::handlers_.begin()) { it = LoggingStatic::handlers_.end(); } else if(it == LoggingStatic::handlers_.end()) { it = (++LoggingStatic::handlers_.rbegin()).base(); } else { --it; } if(it != LoggingStatic::handlers_.end()) { sprawl::String checkStr = it->first; while(sprawl::String(sprawl::StringRef(categoryStr.c_str(), checkStr.length())) == checkStr) { if(categoryStr == checkStr) { for(auto& absorbedHandler : it->second) { handlers.Insert(absorbedHandler.Key(), absorbedHandler.Value()); } } 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->second) { handlers.Insert(absorbedHandler.Key(), absorbedHandler.Value()); } } } } if(it == LoggingStatic::handlers_.begin()) { break; } --it; checkStr = it->first; } } } } 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, LoggingStatic::FileData(file, filename)); } LoggingStatic::FileData& data = LoggingStatic::fileMap_.Get(filename); return{ std::bind(LoggingStatic::printMessageToFile_, std::placeholders::_1, std::ref(data)), [=]() { LoggingStatic::fileMap_.Get(filename).file.Flush(); }, &data }; } sprawl::logging::Handler sprawl::logging::RunHandler_Threaded(Handler const& handler) { static bool managerInitialized_ = false; if(!managerInitialized_) { LoggingStatic::logManager_.AddThread(LoggingStatic::LOG_THREAD); managerInitialized_ = true; } LoggingStatic::useManager_ = true; return RunHandler_ThreadManager(handler, LoggingStatic::logManager_, LoggingStatic::LOG_THREAD); } sprawl::logging::Handler sprawl::logging::RunHandler_ThreadManager(Handler const& handler, sprawl::threading::ThreadManager& manager, int64_t threadFlag) { return { std::bind(LoggingStatic::addToThreadManager_, handler, std::placeholders::_1, std::ref(manager), threadFlag), std::bind(LoggingStatic::flushThreadManager_, handler, std::ref(manager), threadFlag), &manager }; } sprawl::logging::Handler sprawl::logging::PrintToStdout() { return{ std::bind(LoggingStatic::printMessageToStream_, std::placeholders::_1, stdout), []() { fflush(stdout); }, stdout }; } sprawl::logging::Handler sprawl::logging::PrintToStderr() { return{ std::bind(LoggingStatic::printMessageToStream_, std::placeholders::_1, stderr), [](){fflush(stderr);}, stderr }; } sprawl::filesystem::File sprawl::logging::GetHandleForFile(sprawl::String const& filename) { return LoggingStatic::fileMap_.Get(filename).file; } 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() { Backtrace::Init(); if(LoggingStatic::useManager_) { LoggingStatic::logManager_.Start(0); } } void sprawl::logging::Flush() { sprawl::collections::HashSet<void*> alreadyFlushed; for(auto& kvp : LoggingStatic::handlers_) { for(auto& handler : kvp.second) { if(!alreadyFlushed.Has(handler.Key())) { handler.Value().Flush(); alreadyFlushed.Insert(handler.Key()); } } } if(!alreadyFlushed.Has(LoggingStatic::defaultHandler_.uniqueId)) { LoggingStatic::defaultHandler_.Flush(); alreadyFlushed.Insert(LoggingStatic::defaultHandler_.uniqueId); } } void sprawl::logging::Flush(sprawl::logging::Category const& category) { sprawl::String categoryStr = category.Str(); auto it = LoggingStatic::getClosestParentCategory_(LoggingStatic::handlers_, categoryStr); if(it != LoggingStatic::handlers_.end()) { for(auto& handler : it->second) { handler.Value().Flush(); } } else { LoggingStatic::defaultHandler_.Flush(); } } 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::renameMethod_ = sprawl::logging::RenameMethod::Timestamp; LoggingStatic::renameArg_ = int(sprawl::time::Resolution::Milliseconds); LoggingStatic::maxFileSize_ = 500 * 1024 * 1024; LoggingStatic::format_ = sprawl::String("{} {} [{}] {} [{}] ({}:{}:{})") + sprawl::filesystem::LineSeparator(); LoggingStatic::fileMap_.Clear(); LoggingStatic::useManager_ = false; LoggingStatic::disabledCategories_.clear(); LoggingStatic::extraInfoCallbacks_.Clear(); Backtrace::ShutDown(); } sprawl::String sprawl::logging::Message::ToString() { sprawl::String timestampStr; if (!LoggingStatic::strftimeFormat_.empty()) { int64_t seconds = sprawl::time::Convert(timestamp, sprawl::time::Resolution::Nanoseconds, sprawl::time::Resolution::Seconds); char buffer[128]; struct tm tmInfo; time_t asTimeT = time_t(seconds); #ifdef _WIN32 localtime_s(&tmInfo, &asTimeT); #else localtime_r(&asTimeT, &tmInfo); #endif 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_); char buf2[128]; snprintf(buf2, 128, "%s.%0*" SPRAWL_I64FMT "d", buffer, LoggingStatic::resolutionSize_, nanosecsAsMaxRes - secsAsMaxRes); timestampStr = buf2; } else { timestampStr = buffer; } } return LoggingStatic::format_.format(timestampStr, threadid, messageOptions->nameOverride != nullptr ? messageOptions->nameOverride : level.GetPtr(), message, category, sprawl::path::Basename(file), function, line); } void sprawl::logging::BacktraceToString(Backtrace const& backtrace, sprawl::StringBuilder& builder) { for(size_t i = 0; i < backtrace.Size(); ++i) { FormatStackFrame(backtrace.GetFrame(i), true, builder); } } void sprawl::logging::FormatStackFrame(Backtrace::Frame const& frame, bool printMemoryData, sprawl::StringBuilder& builder) { if(printMemoryData) { builder << " [0x"; builder.AppendElementToBuffer(frame.address, "x"); builder << "] " << frame.func << " (+0x"; builder.AppendElementToBuffer(frame.offset, "x"); builder << ")"; } else { builder << " " << frame.func; } builder << sprawl::filesystem::LineSeparator(); builder << " Line: " << frame.line << " (+/-1), File: " << frame.baseFile << ", in module: " << frame.module << sprawl::filesystem::LineSeparator(); if(frame.text[0] != '\0') { builder << " " << frame.text; } else if(sprawl::path::Exists(frame.file)) { sprawl::filesystem::File f = sprawl::filesystem::Open(frame.file, "r"); if (f.Valid()) { for (int i = 1; i <= frame.line + 1; i++) { sprawl::String str = f.ReadLine(); if (i >= frame.line - 1) { builder << " <" << i << ">" << str; } } } else { builder << " Code unavailable." << sprawl::filesystem::LineSeparator(); } } else { builder << " Code unavailable." << sprawl::filesystem::LineSeparator(); } builder << " " << sprawl::filesystem::LineSeparator(); } void sprawl::logging::AddExtraInfoCallback(int logLevel, std::function<void*()> collect, std::function<void(void* data, sprawl::StringBuilder& builder)> print) { LoggingStatic::extraInfoCallbacks_[logLevel].PushBack({collect, print}); } sprawl::String sprawl::logging::Message::CollectExtraData() { sprawl::StringBuilder builder; if(messageOptions->includeBacktrace) { BacktraceToString(backtrace, builder); } auto it = LoggingStatic::extraInfoCallbacks_.find(levelInt); if(it.Valid()) { auto& callbacks = it.Value(); for(size_t i = 0, size = extraInfo.Size(); i < size; ++i) { callbacks[i].Print(extraInfo[i], builder); builder << sprawl::filesystem::LineSeparator(); } } return builder.Str(); }
# | 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 |