#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 | |
---|---|---|---|---|---|
#2 | 18645 | brandon_m_bare | Integrated latest version of libsprawl. | ||
#1 | 15089 | brandon_m_bare | First integration of sprawl. | ||
//guest/ShadauxCat/Sprawl/Mainline/logging/Logging.cpp | |||||
#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 |