diff options
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/linden_common.h | 1 | ||||
-rw-r--r-- | indra/llcommon/llapp.cpp | 8 | ||||
-rw-r--r-- | indra/llcommon/llerror.cpp | 1031 | ||||
-rw-r--r-- | indra/llcommon/llerror.h | 404 | ||||
-rw-r--r-- | indra/llcommon/llerrorcontrol.h | 123 | ||||
-rw-r--r-- | indra/llcommon/llerrorlegacy.h | 98 | ||||
-rw-r--r-- | indra/llcommon/llfile.cpp | 7 | ||||
-rw-r--r-- | indra/llcommon/llfile.h | 6 | ||||
-rw-r--r-- | indra/llcommon/llformat.cpp | 28 | ||||
-rw-r--r-- | indra/llcommon/llformat.h | 23 | ||||
-rw-r--r-- | indra/llcommon/lllivefile.cpp | 88 | ||||
-rw-r--r-- | indra/llcommon/lllivefile.h | 21 | ||||
-rw-r--r-- | indra/llcommon/llsd.cpp | 1 | ||||
-rw-r--r-- | indra/llcommon/llstring.h | 1 | ||||
-rw-r--r-- | indra/llcommon/llstringtable.h | 2 |
15 files changed, 1599 insertions, 243 deletions
diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 3e9ec14eb3..2ecda65d6f 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -30,6 +30,7 @@ #include "stdtypes.h" #include "lldefs.h" #include "llerror.h" +#include "llformat.h" #include "llstring.h" #include "lltimer.h" #include "llfasttimer.h" diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 87e7016f39..fe5587365c 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -11,9 +11,11 @@ #include "llcommon.h" #include "llapr.h" +#include "llerrorcontrol.h" #include "llerrorthread.h" #include "llframetimer.h" #include "llmemory.h" +#include "lltimer.h" // // Signal handling @@ -174,10 +176,8 @@ LLSD LLApp::getOptionData(OptionPriority level) void LLApp::stepFrame() { - // Update the static frame timer. LLFrameTimer::updateFrameTime(); - - // Run ready runnables + LLEventTimer::updateClass(); mRunner.run(); } @@ -544,7 +544,7 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) else { // Don't log anything, even errors - this is because this signal could happen anywhere. - gErrorStream.setLevel(LLErrorStream::NONE); + LLError::setDefaultLevel(LLError::LEVEL_NONE); } // Change the signal that we reraise to SIGABRT, so we generate a core dump. diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 9f643fd9eb..52b37bb05c 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1,40 +1,1041 @@ /** * @file llerror.cpp - * @brief Function to crash. + * @date December 2006 + * @brief error message system * - * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. * $License$ */ + #include "linden_common.h" #include "llerror.h" +#include "llerrorcontrol.h" + +#include "llapr.h" +extern apr_thread_mutex_t *gLogMutexp; +#include "llfile.h" +#include "llfixedbuffer.h" +#include "lllivefile.h" +#include "llsd.h" +#include "llsdserialize.h" + +#include <algorithm> +#include <cctype> +#include <map> +#include <sstream> +#if !LL_WINDOWS +#include <stdio.h> +#include <syslog.h> +#endif +#include <time.h> +#if LL_WINDOWS +#include <windows.h> +#endif +#include <vector> + + +#ifdef __GNUC__ +#include <cxxabi.h> +#endif + +namespace { +#if !LL_WINDOWS + class RecordToSyslog : public LLError::Recorder + { + public: + RecordToSyslog(const std::string& identity) + : mIdentity(identity) + { + openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); + // we need to set the string from a local copy of the string + // since apparanetly openlog expects the const char* to remain + // valid even after it returns (presumably until closelog) + } + + ~RecordToSyslog() + { + closelog(); + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + int syslogPriority = LOG_CRIT; + switch (level) { + case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; + case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; + case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; + case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; + default: syslogPriority = LOG_CRIT; + } + + syslog(syslogPriority, "%s", message.c_str()); + } + private: + std::string mIdentity; + }; +#endif + + class RecordToFile : public LLError::Recorder + { + public: + RecordToFile(const std::string& filename) + { + mFile.open(filename.c_str(), llofstream::out | llofstream::app); + if (!mFile) + { + llinfos << "Error setting log file to " << filename << llendl; + } + } + + ~RecordToFile() + { + mFile.close(); + } + + bool okay() { return mFile; } + + virtual bool wantsTime() { return true; } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + mFile << message << std::endl; + // mFile.flush(); + // *FIX: should we do this? + } + + private: + llofstream mFile; + }; + + + class RecordToStderr : public LLError::Recorder + { + public: + RecordToStderr(bool timestamp) : mTimestamp(timestamp) { } + + virtual bool wantsTime() { return mTimestamp; } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + fprintf(stderr, "%s\n", message.c_str()); + } + + private: + bool mTimestamp; + }; -LLErrorBuffer gErrorBuffer; -LLErrorStream gErrorStream(&gErrorBuffer); + class RecordToFixedBuffer : public LLError::Recorder + { + public: + RecordToFixedBuffer(LLFixedBuffer& buffer) : mBuffer(buffer) { } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + mBuffer.addLine(message.c_str()); + } + + private: + LLFixedBuffer& mBuffer; + }; + +#if LL_WINDOWS + class RecordToWinDebug: public LLError::Recorder + { + public: + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + llutf16string utf16str = + wstring_to_utf16str(utf8str_to_wstring(message)); + utf16str += '\n'; + OutputDebugString(utf16str.c_str()); + } + }; +#endif +} -void _llcrash_and_loop() +namespace { - // Now, we go kaboom! - U32* crash = NULL; + std::string className(const std::type_info& type) + { +#ifdef __GNUC__ + // GCC: type_info::name() returns a mangled class name, must demangle + + static size_t abi_name_len = 100; + static char* abi_name_buf = (char*)malloc(abi_name_len); + // warning: above is voodoo inferred from the GCC manual, + // do NOT change + + int status; + // We don't use status, and shouldn't have to pass apointer to it + // but gcc 3.3 libstc++'s implementation of demangling is broken + // and fails without. + + char* name = abi::__cxa_demangle(type.name(), + abi_name_buf, &abi_name_len, &status); + // this call can realloc the abi_name_buf pointer (!) + + return name ? name : type.name(); + +#elif LL_WINDOWS + // DevStudio: type_info::name() includes the text "class " at the start + + static const std::string class_prefix = "class "; + + std::string name = type.name(); + std::string::size_type p = name.find(class_prefix); + if (p == std::string::npos) + { + return name; + } + + return name.substr(p + class_prefix.size()); + +#else + return type.name(); +#endif + } + + std::string functionName(const std::string& preprocessor_name) + { +#if LL_WINDOWS + // DevStudio: the __FUNCTION__ macro string includes + // the type and/or namespace prefixes + + std::string::size_type p = preprocessor_name.rfind(':'); + if (p == std::string::npos) + { + return preprocessor_name; + } + return preprocessor_name.substr(p + 1); + +#else + return preprocessor_name; +#endif + } + + + class LogControlFile : public LLLiveFile + { + LOG_CLASS(LogControlFile); + + public: + static LogControlFile& fromDirectory(const std::string& dir); + + virtual void loadFile(); + + private: + LogControlFile(const std::string &filename) + : LLLiveFile(filename) + { } + }; + + LogControlFile& LogControlFile::fromDirectory(const std::string& dir) + { + std::string dirBase = dir + "/"; + // NB: We have no abstraction in llcommon for the "proper" + // delimiter but it turns out that "/" works on all three platforms + + std::string file = dirBase + "logcontrol-dev.xml"; + + llstat stat_info; + if (LLFile::stat(file.c_str(), &stat_info)) { + // NB: stat returns non-zero if it can't read the file, for example + // if it doesn't exist. LLFile has no better abstraction for + // testing for file existence. + + file = dirBase + "logcontrol.xml"; + } + return * new LogControlFile(file); + // NB: This instance is never freed + } + + void LogControlFile::loadFile() + { + LLSD configuration; + + { + llifstream file(filename().c_str()); + if (file.is_open()) + { + LLSDSerialize::fromXML(configuration, file); + } + + if (configuration.isUndefined()) + { + llwarns << filename() << " missing, ill-formed," + " or simply undefined; not changing configuration" + << llendl; + return; + } + } + + LLError::configure(configuration); + llwarns << "error logging reconfigured from " << filename() << llendl; + } + + + typedef std::map<std::string, LLError::ELevel> LevelMap; + typedef std::vector<LLError::Recorder*> Recorders; + typedef std::vector<LLError::CallSite*> CallSiteVector; + + class Globals + { + public: + std::ostringstream messageStream; + bool messageStreamInUse; + + void addCallSite(LLError::CallSite&); + void invalidateCallSites(); + + static Globals& get(); + // return the one instance of the globals + + private: + CallSiteVector callSites; + + Globals() + : messageStreamInUse(false) + { } + + }; + + void Globals::addCallSite(LLError::CallSite& site) + { + callSites.push_back(&site); + } + + void Globals::invalidateCallSites() + { + for (CallSiteVector::const_iterator i = callSites.begin(); + i != callSites.end(); + ++i) + { + (*i)->invalidate(); + } + + callSites.clear(); + } - *crash = 0; + Globals& Globals::get() + { + /* This pattern, of returning a reference to a static function + variable, is to ensure that this global is constructed before + it is used, no matter what the global initializeation sequence + is. + See C++ FAQ Lite, sections 10.12 through 10.14 + */ + static Globals* globals = new Globals; + return *globals; + } +} + +namespace LLError +{ + class Settings + { + public: + bool printLocation; - while(TRUE) + LLError::ELevel defaultLevel; + + LevelMap functionLevelMap; + LevelMap classLevelMap; + LevelMap fileLevelMap; + + LLError::FatalFunction crashFunction; + LLError::TimeFunction timeFunction; + + Recorders recorders; + Recorder* fileRecorder; + Recorder* fixedBufferRecorder; + std::string fileRecorderFileName; + + int shouldLogCallCounter; + + static Settings& get(); + + static void reset(); + static Settings* saveAndReset(); + static void restore(Settings*); + + private: + Settings() + : printLocation(false), + defaultLevel(LLError::LEVEL_DEBUG), + crashFunction(NULL), + timeFunction(NULL), + fileRecorder(NULL), + fixedBufferRecorder(NULL), + shouldLogCallCounter(0) + { } + + static Settings*& getPtr(); + }; + + Settings& Settings::get() + { + Settings* p = getPtr(); + if (!p) + { + reset(); + p = getPtr(); + } + return *p; + } + + void Settings::reset() + { + Globals::get().invalidateCallSites(); + + Settings*& p = getPtr(); + delete p; + p = new Settings(); + } + + Settings* Settings::saveAndReset() + { + Globals::get().invalidateCallSites(); + + Settings*& p = getPtr(); + Settings* originalSettings = p; + p = new Settings(); + return originalSettings; + } + + void Settings::restore(Settings* originalSettings) { + Globals::get().invalidateCallSites(); + + Settings*& p = getPtr(); + delete p; + p = originalSettings; + } - // Loop forever, in case the crash didn't work? + Settings*& Settings::getPtr() + { + static Settings* currentSettings = NULL; + return currentSettings; } } -LLScopedErrorLevel::LLScopedErrorLevel(LLErrorBuffer::ELevel error_level) +namespace LLError { - mOrigErrorLevel = gErrorStream.getErrorLevel(); - gErrorStream.setErrorLevel(error_level); + CallSite::CallSite(ELevel level, + const char* file, int line, + const std::type_info& class_info, const char* function) + : mLevel(level), mFile(file), mLine(line), + mClassInfo(class_info), mFunction(function), + mCached(false), mShouldLog(false) + { } + + + void CallSite::invalidate() + { mCached = false; } } +namespace +{ + bool shouldLogToStderr() + { +#if LL_DARWIN + // On Mac OS X, stderr from apps launched from the Finder goes to the + // console log. It's generally considered bad form to spam too much + // there. + + // If stdin is a tty, assume the user launched from the command line and + // therefore wants to see stderr. Otherwise, assume we've been launched + // from the finder and shouldn't spam stderr. + return isatty(0); +#else + return true; +#endif + } + + bool stderrLogWantsTime() + { +#if LL_WINDOWS + return false; +#else + return true; +#endif + } + + + void commonInit(const std::string& dir) + { + LLError::Settings::reset(); + + LLError::setDefaultLevel(LLError::LEVEL_INFO); + LLError::setFatalFunction(LLError::crashAndLoop); + LLError::setTimeFunction(LLError::utcTime); + + if (shouldLogToStderr()) + { + LLError::addRecorder(new RecordToStderr(stderrLogWantsTime())); + } + +#if LL_WINDOWS + LLError::addRecorder(new RecordToWinDebug); +#endif + + LogControlFile& e = LogControlFile::fromDirectory(dir); + e.addToEventTimer(); + } +} -LLScopedErrorLevel::~LLScopedErrorLevel() +namespace LLError { - gErrorStream.setErrorLevel(mOrigErrorLevel); + void initForServer(const std::string& identity) + { + std::string dir = LLApp::instance()->getOption("configdir"); + commonInit(dir); +#if !LL_WINDOWS + addRecorder(new RecordToSyslog(identity)); +#endif + } + + void initForApplication(const std::string& dir) + { + commonInit(dir); + } + + void setPrintLocation(bool print) + { + Settings& s = Settings::get(); + s.printLocation = print; + } + + void setFatalFunction(FatalFunction f) + { + Settings& s = Settings::get(); + s.crashFunction = f; + } + + void setTimeFunction(TimeFunction f) + { + Settings& s = Settings::get(); + s.timeFunction = f; + } + + void setDefaultLevel(ELevel level) + { + Globals& g = Globals::get(); + Settings& s = Settings::get(); + g.invalidateCallSites(); + s.defaultLevel = level; + } + + void setFunctionLevel(const std::string& function_name, ELevel level) + { + Globals& g = Globals::get(); + Settings& s = Settings::get(); + g.invalidateCallSites(); + s.functionLevelMap[function_name] = level; + } + + void setClassLevel(const std::string& class_name, ELevel level) + { + Globals& g = Globals::get(); + Settings& s = Settings::get(); + g.invalidateCallSites(); + s.classLevelMap[class_name] = level; + } + + void setFileLevel(const std::string& file_name, ELevel level) + { + Globals& g = Globals::get(); + Settings& s = Settings::get(); + g.invalidateCallSites(); + s.fileLevelMap[file_name] = level; + } } + +namespace { + LLError::ELevel decodeLevel(std::string name) + { + static LevelMap level_names; + if (level_names.empty()) + { + level_names["ALL"] = LLError::LEVEL_ALL; + level_names["DEBUG"] = LLError::LEVEL_DEBUG; + level_names["INFO"] = LLError::LEVEL_INFO; + level_names["WARN"] = LLError::LEVEL_WARN; + level_names["ERROR"] = LLError::LEVEL_ERROR; + level_names["NONE"] = LLError::LEVEL_NONE; + } + + std::transform(name.begin(), name.end(), name.begin(), toupper); + + LevelMap::const_iterator i = level_names.find(name); + if (i == level_names.end()) + { + llwarns << "unrecognized logging level: '" << name << "'" << llendl; + return LLError::LEVEL_INFO; + } + + return i->second; + } + + void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) + { + LLSD::array_const_iterator i, end; + for (i = list.beginArray(), end = list.endArray(); i != end; ++i) + { + map[*i] = level; + } + } +} + +namespace LLError +{ + void configure(const LLSD& config) + { + Globals& g = Globals::get(); + Settings& s = Settings::get(); + + g.invalidateCallSites(); + s.functionLevelMap.clear(); + s.classLevelMap.clear(); + s.fileLevelMap.clear(); + + setPrintLocation(config["print-location"]); + setDefaultLevel(decodeLevel(config["default-level"])); + + LLSD sets = config["settings"]; + LLSD::array_const_iterator a, end; + for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) + { + const LLSD& entry = *a; + + ELevel level = decodeLevel(entry["level"]); + + setLevels(s.functionLevelMap, entry["functions"], level); + setLevels(s.classLevelMap, entry["classes"], level); + setLevels(s.fileLevelMap, entry["files"], level); + } + } +} + + +namespace LLError +{ + Recorder::~Recorder() + { } + + // virtual + bool Recorder::wantsTime() + { return false; } + + + + void addRecorder(Recorder* recorder) + { + if (recorder == NULL) + { + return; + } + Settings& s = Settings::get(); + s.recorders.push_back(recorder); + } + + void removeRecorder(Recorder* recorder) + { + if (recorder == NULL) + { + return; + } + Settings& s = Settings::get(); + s.recorders.erase( + std::remove(s.recorders.begin(), s.recorders.end(), recorder), + s.recorders.end()); + } +} + +namespace LLError +{ + void logToFile(const std::string& file_name) + { + LLError::Settings& s = LLError::Settings::get(); + + removeRecorder(s.fileRecorder); + delete s.fileRecorder; + s.fileRecorder = NULL; + s.fileRecorderFileName.clear(); + + if (file_name.empty()) + { + return; + } + + RecordToFile* f = new RecordToFile(file_name); + if (!f->okay()) + { + delete f; + return; + } + + s.fileRecorderFileName = file_name; + s.fileRecorder = f; + addRecorder(f); + } + + void logToFixedBuffer(LLFixedBuffer* fixedBuffer) + { + LLError::Settings& s = LLError::Settings::get(); + + removeRecorder(s.fixedBufferRecorder); + delete s.fixedBufferRecorder; + s.fixedBufferRecorder = NULL; + + if (!fixedBuffer) + { + return; + } + + s.fixedBufferRecorder = new RecordToFixedBuffer(*fixedBuffer); + addRecorder(s.fixedBufferRecorder); + } + + std::string logFileName() + { + LLError::Settings& s = LLError::Settings::get(); + return s.fileRecorderFileName; + } +} + +namespace +{ + void writeToRecorders(LLError::ELevel level, const std::string& message) + { + LLError::Settings& s = LLError::Settings::get(); + + std::string messageWithTime; + + for (Recorders::const_iterator i = s.recorders.begin(); + i != s.recorders.end(); + ++i) + { + LLError::Recorder* r = *i; + + if (r->wantsTime() && s.timeFunction != NULL) + { + if (messageWithTime.empty()) + { + messageWithTime = s.timeFunction() + " " + message; + } + + r->recordMessage(level, messageWithTime); + } + else + { + r->recordMessage(level, message); + } + } + } +} + + +/* +Recorder formats: + +$type = "ERROR" | "WARNING" | "ALERT" | "INFO" | "DEBUG" +$loc = "$file($line)" +$msg = "$loc : " if FATAL or printing loc + "" otherwise +$msg += "$type: " +$msg += contents of stringstream + +$time = "%Y-%m-%dT%H:%M:%SZ" if UTC + or "%Y-%m-%dT%H:%M:%S %Z" if local + +syslog: "$msg" +file: "$time $msg\n" +stderr: "$time $msg\n" except on windows, "$msg\n" +fixedbuf: "$msg" +winddebug: "$msg\n" + +Note: if FATAL, an additional line gets logged first, with $msg set to + "$loc : error" + +You get: + llfoo.cpp(42) : error + llfoo.cpp(42) : ERROR: something + +*/ + +namespace { + bool checkLevelMap(const LevelMap& map, const std::string& key, + LLError::ELevel& level) + { + LevelMap::const_iterator i = map.find(key); + if (i == map.end()) + { + return false; + } + + level = i->second; + return true; + } + + class LogLock + { + public: + LogLock(); + ~LogLock(); + bool ok() const { return mOK; } + private: + bool mLocked; + bool mOK; + }; + + LogLock::LogLock() + : mLocked(false), mOK(false) + { + if (!gLogMutexp) + { + mOK = true; + return; + } + + const int MAX_RETRIES = 5; + for (int attempts = 0; attempts < MAX_RETRIES; ++attempts) + { + apr_status_t s = apr_thread_mutex_trylock(gLogMutexp); + if (!APR_STATUS_IS_EBUSY(s)) + { + mLocked = true; + mOK = true; + return; + } + + ms_sleep(1); + //apr_thread_yield(); + // Just yielding won't necessarily work, I had problems with + // this on Linux - doug 12/02/04 + } + + // We're hosed, we can't get the mutex. Blah. + std::cerr << "LogLock::LogLock: failed to get mutex for log" + << std::endl; + } + + LogLock::~LogLock() + { + if (mLocked) + { + apr_thread_mutex_unlock(gLogMutexp); + } + } +} + +namespace LLError +{ + bool Log::shouldLog(CallSite& site) + { + LogLock lock; + if (!lock.ok()) + { + return false; + } + + Globals& g = Globals::get(); + Settings& s = Settings::get(); + + s.shouldLogCallCounter += 1; + + std::string class_name = className(site.mClassInfo); + std::string function_name = functionName(site.mFunction); + if (site.mClassInfo != typeid(NoClassInfo)) + { + function_name = class_name + "::" + function_name; + } + + ELevel compareLevel = s.defaultLevel; + + checkLevelMap(s.functionLevelMap, function_name, compareLevel) + || checkLevelMap(s.classLevelMap, class_name, compareLevel) + || checkLevelMap(s.fileLevelMap, abbreviateFile(site.mFile), compareLevel); + + site.mCached = true; + g.addCallSite(site); + return site.mShouldLog = site.mLevel >= compareLevel; + } + + + std::ostringstream* Log::out() + { + LogLock lock; + if (lock.ok()) + { + Globals& g = Globals::get(); + + if (!g.messageStreamInUse) + { + g.messageStreamInUse = true; + return &g.messageStream; + } + } + + return new std::ostringstream; + } + + void Log::flush(std::ostringstream* out, const CallSite& site) + { + LogLock lock; + if (!lock.ok()) + { + return; + } + + Globals& g = Globals::get(); + Settings& s = Settings::get(); + + std::string message = out->str(); + if (out == &g.messageStream) + { + g.messageStream.clear(); + g.messageStream.str(""); + g.messageStreamInUse = false; + } + else + { + delete out; + } + + if (site.mLevel == LEVEL_ERROR) + { + std::ostringstream fatalMessage; + fatalMessage << abbreviateFile(site.mFile) + << "(" << site.mLine << ") : error"; + + writeToRecorders(site.mLevel, fatalMessage.str()); + } + + + std::ostringstream prefix; + + switch (site.mLevel) + { + case LEVEL_DEBUG: prefix << "DEBUG: "; break; + case LEVEL_INFO: prefix << "INFO: "; break; + case LEVEL_WARN: prefix << "WARNING: "; break; + case LEVEL_ERROR: prefix << "ERROR: "; break; + default: prefix << "XXX: "; break; + }; + + if (s.printLocation) + { + prefix << abbreviateFile(site.mFile) + << "(" << site.mLine << ") : "; + } + + if (message.find(functionName(site.mFunction)) == std::string::npos) + { + #if LL_WINDOWS + // DevStudio: __FUNCTION__ already includes the full class name + #else + if (site.mClassInfo != typeid(NoClassInfo)) + { + prefix << className(site.mClassInfo) << "::"; + } + #endif + prefix << site.mFunction << ": "; + } + + prefix << message; + message = prefix.str(); + + writeToRecorders(site.mLevel, message); + + if (site.mLevel == LEVEL_ERROR && s.crashFunction) + { + s.crashFunction(message); + } + } +} + + + + +namespace LLError +{ + Settings* saveAndResetSettings() + { + return Settings::saveAndReset(); + } + + void restoreSettings(Settings* s) + { + return Settings::restore(s); + } + + std::string removePrefix(std::string& s, const std::string& p) + { + std::string::size_type where = s.find(p); + if (where == std::string::npos) + { + return s; + } + + return std::string(s, where + p.size()); + } + + std::string abbreviateFile(const std::string& filePath) + { + std::string f = filePath; + +#if LL_WINDOWS + static std::string indra_prefix = "indra\\"; +#else + static std::string indra_prefix = "indra/"; +#endif + f = removePrefix(f, indra_prefix); + +#if LL_DARWIN + static std::string newview_prefix = "newview/../"; + f = removePrefix(f, newview_prefix); +#endif + + return f; + } + + int shouldLogCallCount() + { + Settings& s = Settings::get(); + return s.shouldLogCallCounter; + } + + void crashAndLoop(const std::string& message) + { + // Now, we go kaboom! + int* crash = NULL; + + *crash = 0; + + while(true) + { + // Loop forever, in case the crash didn't work? + } + } + + std::string utcTime() + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + int chars = strftime(time_str, BUF_SIZE, + "%Y-%m-%dT%H:%M:%SZ", + gmtime(&now)); + + return chars ? time_str : "time error"; + } +} + diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index d3d680ed33..84ac0fa7f0 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -1,8 +1,9 @@ /** * @file llerror.h - * @brief Constants, functions, and macros for logging and runtime errors. + * @date December 2006 + * @brief error message system * - * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. * $License$ */ @@ -10,209 +11,208 @@ #define LL_LLERROR_H #include <sstream> -#include <stdio.h> -#include <stdarg.h> - -#include "llerrorstream.h" -#include "llerrorbuffer.h" - -// Specific error codes -const S32 LL_ERR_NOERR = 0; -const S32 LL_ERR_ASSET_REQUEST_FAILED = -1; -//const S32 LL_ERR_ASSET_REQUEST_INVALID = -2; -const S32 LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3; -const S32 LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; -const S32 LL_ERR_INSUFFICIENT_PERMISSIONS = -5; -const S32 LL_ERR_EOF = -39; -const S32 LL_ERR_CANNOT_OPEN_FILE = -42; -const S32 LL_ERR_FILE_NOT_FOUND = -43; -const S32 LL_ERR_FILE_EMPTY = -44; -const S32 LL_ERR_TCP_TIMEOUT = -23016; -const S32 LL_ERR_CIRCUIT_GONE = -23017; - -// Error types - -#define LLERR_IMAGE (1 << 1) // Image requests -#define LLERR_MESSAGE (1 << 2) // Messaging -#define LLERR_PERF (1 << 3) // Performance -#define LLERR_SQL (1 << 4) // SQL statements -#define LLERR_DOUG (1 << 5) // Doug's debugging -#define LLERR_USER_INPUT (1 << 6) // Keyboard and mouse -#define LLERR_TIMING (1 << 7) // Verbose time info -#define LLERR_TASK (1 << 8) // Tracking tasks -#define LLERR_MSG_HANDLER (1 << 9) // -#define LLERR_CIRCUIT_INFO (1 << 10) // Message system circuit info -#define LLERR_PHYSICS (1 << 11) // physics -#define LLERR_VFS (1 << 12) // VFS -const U32 LLERR_ALL = 0xffff; -const U32 LLERR_NONE = 0x0; - -// Define one of these for different error levels in release... -// #define RELEASE_SHOW_DEBUG // Define this if you want your release builds to show lldebug output. -#define RELEASE_SHOW_INFO // Define this if you want your release builds to show llinfo output -#define RELEASE_SHOW_WARN // Define this if you want your release builds to show llwarn output. - - -////////////////////////////////////////// -// -// Implementation - ignore -// -// -#ifdef _DEBUG -#define SHOW_DEBUG -#define SHOW_WARN -#define SHOW_INFO -#define SHOW_ASSERT -#else // _DEBUG - -#ifdef RELEASE_SHOW_DEBUG -#define SHOW_DEBUG -#endif - -#ifdef RELEASE_SHOW_WARN -#define SHOW_WARN -#endif - -#ifdef RELEASE_SHOW_INFO -#define SHOW_INFO -#endif - -#ifdef RELEASE_SHOW_ASSERT -#define SHOW_ASSERT -#endif - -#endif // _DEBUG - - -extern LLErrorStream gErrorStream; - - -// LL Error macros -// -// Usage: -// -// llerrs << "An error, oh my!" << variable << endl; -// llwarns << "Another error, fuck me!" << variable << endl; -// llwarnst(LLERR_IMAGE) << "Debug, mother fucker" << endl; -// -// NOTE: The output format of filename(lineno): is so that MS DevStudio -// can parse the output and automatically jump to that location - -inline std::string llerrno_string(int errnum) +#include <typeinfo> + +#include "llerrorlegacy.h" + + +/* Error Logging Facility + + Information for most users: + + Code can log messages with constuctions like this: + + llinfos << "request to fizzbip agent " << agent_id + << " denied due to timeout" << llendl; + + Messages can be logged to one of four increasing levels of concern, + using one of four "streams": + + lldebugs - debug messages that are normally supressed + llinfos - informational messages that are normall shown + llwarns - warning messages that singal a problem + llerrs - error messages that are major, unrecoverable failures + + The later (llerrs) automatically crashes the process after the message + is logged. + + Note that these "streams" are actually #define magic. Rules for use: + * they cannot be used as normal streams, only to start a message + * messages written to them MUST be terminated with llendl + * between the opening and closing, the << operator is indeed + writing onto a std::ostream, so all conversions and stream + formating are available + + These messages are automatically logged with function name, and (if enabled) + file and line of the message. (Note: Existing messages that already include + the function name don't get name printed twice.) + + If you have a class, adding LOG_CLASS line to the declaration will cause + all messages emitted from member functions (normal and static) to be tagged + with the proper class name as well as the function name: + + class LLFoo + { + LOG_CLASS(LLFoo); + public: + ... + }; + + void LLFoo::doSomething(int i) + { + if (i > 100) + { + llwanrs << "called with a big value for i: " << i << llendl; + } + ... + } + + will result in messages like: + + WARN: LLFoo::doSomething: called with a big value for i: 283 + + Which messages are logged and which are supressed can be controled at run + time from the live file logcontrol.xml based on function, class and/or + source file. See etc/logcontrol-dev.xml for details. + + Lastly, logging is now very efficient in both compiled code and execution + when skipped. There is no need to wrap messages, even debugging ones, in + #ifdef _DEBUG constructs. lldebugs messages are compiled into all builds, + even release. Which means you can use them to help debug even when deployed + to a real grid. +*/ + +namespace LLError { - std::stringstream res; - res << "error(" << errnum << "):" << strerror(errnum) << " "; - return res.str(); + enum ELevel + { + LEVEL_ALL = 0, + // used to indicate that all messagess should be logged + + LEVEL_DEBUG = 0, + LEVEL_INFO = 1, + LEVEL_WARN = 2, + LEVEL_ERROR = 3, // used to be called FATAL + + LEVEL_NONE = 4 + // not really a level + // used to indicate that no messages should be logged + }; + + /* Macro support + The classes CallSite and Log are used by the logging macros below. + They are not intended for general use. + */ + + class CallSite; + + class Log + { + public: + static bool shouldLog(CallSite&); + static std::ostringstream* out(); + static void flush(std::ostringstream*, const CallSite&); + }; + + class CallSite + { + // Represents a specific place in the code where a message is logged + // This is public because it is used by the macros below. It is not + // intended for public use. + public: + CallSite(ELevel, const char* file, int line, + const std::type_info& class_info, const char* function); + + bool shouldLog() + { return mCached ? mShouldLog : Log::shouldLog(*this); } + // this member function needs to be in-line for efficiency + + void invalidate(); + + private: + // these describe the call site and never change + const ELevel mLevel; + const char* const mFile; + const int mLine; + const std::type_info& mClassInfo; + const char* const mFunction; + + // these implement a cache of the call to shouldLog() + bool mCached; + bool mShouldLog; + + friend class Log; + }; + + + class End { }; + inline std::ostream& operator<<(std::ostream& s, const End&) + { return s; } + // used to indicate the end of a message + + class NoClassInfo { }; + // used to indicate no class info known for logging } -inline std::string llerror_file_line(const char* file, S32 line) -{ - std::stringstream res; - res << file << "(" <<line << ")"; - return res.str(); -} -// Used to throw an error which is always causes a system halt. -#define llerrs if (gErrorStream.isEnabledFor(LLErrorBuffer::FATAL)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::FATAL; \ - llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : error\n"; \ - llerror_oss << "ERROR: " << llerror_file_line(__FILE__, __LINE__) << " " - -// Used to show warnings -#define llwarns if (gErrorStream.isEnabledFor(LLErrorBuffer::WARN)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::WARN; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : WARNING: "; \ - else llerror_oss << "WARNING: "; \ - llerror_oss - -// Alerts are for serious non-fatal situations that are not supposed to happen and need to alert someone -#define llalerts if (gErrorStream.isEnabledFor(LLErrorBuffer::WARN)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::WARN; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : ALERT: "; \ - else llerror_oss << "ALERT: "; \ - llerror_oss - -// Used to show informational messages that don't get disabled -#define llinfos if (gErrorStream.isEnabledFor(LLErrorBuffer::INFO)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::INFO; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : INFO: "; \ - else llerror_oss << "INFO: "; \ - llerror_oss - -#define llinfost(type) if (gErrorStream.isEnabledFor(LLErrorBuffer::INFO, type)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::INFO; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : INFO: "; \ - else llerror_oss << "INFO: [" << #type << "] "; \ - llerror_oss - -// Used for general debugging output -#define lldebugs if (gErrorStream.isEnabledFor(LLErrorBuffer::DEBUG)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::DEBUG; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : DEBUG: "; \ - else llerror_oss << "DEBUG: "; \ - llerror_oss - -#define lldebugst(type) if (gErrorStream.isEnabledFor(LLErrorBuffer::DEBUG, type)) \ - { std::ostringstream llerror_oss; LLErrorBuffer::ELevel llerror_level = LLErrorBuffer::DEBUG; \ - if (gErrorStream.getPrintLocation()) llerror_oss << llerror_file_line(__FILE__, __LINE__) << " : DEBUG: "; \ - else llerror_oss << "DEBUG: [" << #type << "] "; \ - llerror_oss - -#define llendl std::endl; gErrorStream.crashOnError(llerror_oss, llerror_level); } -#define llendflush std::endl << std::flush; gErrorStream.crashOnError(llerror_oss, llerror_level); } -#define llcont llerror_oss - -#define llerror(msg, num) llerrs << "Error # " << num << ": " << msg << llendl; - -#define llwarning(msg, num) llwarns << "Warning # " << num << ": " << msg << llendl; - -#ifdef SHOW_ASSERT -#define llassert(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; -#else -#define llassert(func) -#endif -#define llassert_always(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; - -#ifdef SHOW_ASSERT -#define llverify(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; -#else -#define llverify(func) (func); // get rid of warning C4189 -#endif - -// handy compile-time assert - enforce those template parameters! -#define cassert(expn) typedef char __C_ASSERT__[(expn)?1:-1] /* Flawfinder: ignore */ - -// Makes the app go down in flames, but on purpose! -void _llcrash_and_loop(); - -// Use as follows: -// llinfos << llformat("Test:%d (%.2f %.2f)", idx, x, y) << llendl; -// -// *NOTE: buffer limited to 1024, (but vsnprintf prevents overrun) -// should perhaps be replaced with boost::format. -inline std::string llformat(const char *fmt, ...) -{ - char tstr[1024]; /* Flawfinder: ignore */ - va_list va; - va_start(va, fmt); -#if LL_WINDOWS - _vsnprintf(tstr, 1024, fmt, va); -#else - vsnprintf(tstr, 1024, fmt, va); /* Flawfinder: ignore */ -#endif - va_end(va); - return std::string(tstr); -} -// Helper class to temporarily change error level for the current scope. -class LLScopedErrorLevel -{ -public: - LLScopedErrorLevel(LLErrorBuffer::ELevel error_level); - ~LLScopedErrorLevel(); - -private: - LLErrorBuffer::ELevel mOrigErrorLevel; -}; - +/* + Class type information for logging + */ + +#define LOG_CLASS(s) typedef s _LL_CLASS_TO_LOG + // Declares class to tag logged messages with. + // See top of file for example of how to use this + +typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; + // Outside a class declartion, or in class without LOG_CLASS(), this + // typedef causes the messages to not be associated with any class. + + + + + +/* + Error Logging Macros + See top of file for common usage. +*/ + +#define lllog(level) \ + { \ + static LLError::CallSite _site( \ + level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), __FUNCTION__);\ + if (_site.shouldLog()) \ + { \ + std::ostringstream* _out = LLError::Log::out(); \ + (*_out) + +#define llendl \ + LLError::End(); \ + LLError::Log::flush(_out, _site); \ + } \ + } + +#define llinfos lllog(LLError::LEVEL_INFO) +#define lldebugs lllog(LLError::LEVEL_DEBUG) +#define llwarns lllog(LLError::LEVEL_WARN) +#define llerrs lllog(LLError::LEVEL_ERROR) + +#define llcont (*_out) + /* + Use this construct if you need to do computation in the middle of a + message: + + llinfos << "the agent " << agend_id; + switch (f) + { + case FOP_SHRUGS: llcont << "shrugs"; break; + case FOP_TAPS: llcont << "points at " << who; break; + case FOP_SAYS: llcont << "says " << message; break; + } + llcont << " for " << t << " seconds" << llendl; + + Such computation is done iff the message will be logged. + */ + + #endif // LL_LLERROR_H diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h new file mode 100644 index 0000000000..b1950eebf0 --- /dev/null +++ b/indra/llcommon/llerrorcontrol.h @@ -0,0 +1,123 @@ +/** + * @file llerrorcontrol.h + * @date December 2006 + * @brief error message system control + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLERRORCONTROL_H +#define LL_LLERRORCONTROL_H + +#include "llerror.h" + +#include <string> + +class LLFixedBuffer; +class LLSD; + +/* + This is the part of the LLError namespace that manages the messages + produced by the logging. The logging support is defined in llerror.h. + Most files do not need to include this. + + These implementations are in llerror.cpp. +*/ + + +namespace LLError +{ + void initForServer(const std::string& identity); + // resets all logging settings to defaults needed by server processes + // logs to stderr, syslog, and windows debug log + // the identity string is used for in the syslog + + void initForApplication(const std::string& dir); + // resets all logging settings to defaults needed by applicaitons + // logs to stderr and windows debug log + // sets up log configuration from the file logcontrol.xml in dir + + + /* + Settings that control what is logged. + Setting a level means log messages at that level or above. + */ + + void setPrintLocation(bool); + void setDefaultLevel(LLError::ELevel); + void setFunctionLevel(const std::string& function_name, LLError::ELevel); + void setClassLevel(const std::string& class_name, LLError::ELevel); + void setFileLevel(const std::string& file_name, LLError::ELevel); + + void configure(const LLSD&); + // the LLSD can configure all of the settings + // usually read automatically from the live errorlog.xml file + + + /* + Control functions. + */ + + typedef void (*FatalFunction)(const std::string& message); + void crashAndLoop(const std::string& message); + // Default fatal funtion: divides by zero and loops forever + + void setFatalFunction(FatalFunction); + // The fatal function will be called when an message of LEVEL_ERROR + // is logged. Note: supressing a LEVEL_ERROR message from being logged + // (by, for example, setting a class level to LEVEL_NONE), will keep + // the that message from causing the fatal funciton to be invoked. + + typedef std::string (*TimeFunction)(); + std::string utcTime(); + + void setTimeFunction(TimeFunction); + // The function is use to return the current time, formatted for + // display by those error recorders that want the time included. + + + + class Recorder + { + // An object that handles the actual output or error messages. + public: + virtual ~Recorder(); + + virtual void recordMessage(LLError::ELevel, const std::string& message) = 0; + // use the level for better display, not for filtering + + virtual bool wantsTime(); // default returns false + // override and return true if the recorder wants the time string + // included in the text of the message + }; + + void addRecorder(Recorder*); + void removeRecorder(Recorder*); + // each error message is passed to each recorder via recordMessage() + + void logToFile(const std::string& filename); + void logToFixedBuffer(LLFixedBuffer*); + // Utilities to add recorders for logging to a file or a fixed buffer + // A second call to the same function will remove the logger added + // with the first. + // Passing the empty string or NULL to just removes any prior. + std::string logFileName(); + // returns name of current logging file, empty string if none + + + /* + Utilities for use by the unit tests of LLError itself. + */ + + class Settings; + Settings* saveAndResetSettings(); + void restoreSettings(Settings *); + + std::string abbreviateFile(const std::string& filePath); + int shouldLogCallCount(); + +}; + +#endif // LL_LLERRORCONTROL_H + diff --git a/indra/llcommon/llerrorlegacy.h b/indra/llcommon/llerrorlegacy.h new file mode 100644 index 0000000000..19523512a6 --- /dev/null +++ b/indra/llcommon/llerrorlegacy.h @@ -0,0 +1,98 @@ +/** + * @file llerrorlegacy.h + * @date January 2007 + * @brief old things from the older error system + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLERRORLEGACY_H +#define LL_LLERRORLEGACY_H + + + +/* + LEGACY -- DO NOT USE THIS STUFF ANYMORE +*/ + +// Specific error codes +const int LL_ERR_NOERR = 0; +const int LL_ERR_ASSET_REQUEST_FAILED = -1; +//const int LL_ERR_ASSET_REQUEST_INVALID = -2; +const int LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3; +const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; +const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5; +const int LL_ERR_EOF = -39; +const int LL_ERR_CANNOT_OPEN_FILE = -42; +const int LL_ERR_FILE_NOT_FOUND = -43; +const int LL_ERR_FILE_EMPTY = -44; +const int LL_ERR_TCP_TIMEOUT = -23016; +const int LL_ERR_CIRCUIT_GONE = -23017; + + + +// Define one of these for different error levels in release... +// #define RELEASE_SHOW_DEBUG // Define this if you want your release builds to show lldebug output. +#define RELEASE_SHOW_INFO // Define this if you want your release builds to show llinfo output +#define RELEASE_SHOW_WARN // Define this if you want your release builds to show llwarn output. + + +////////////////////////////////////////// +// +// Implementation - ignore +// +// +#ifdef _DEBUG +#define SHOW_DEBUG +#define SHOW_WARN +#define SHOW_INFO +#define SHOW_ASSERT +#else // _DEBUG + +#ifdef RELEASE_SHOW_DEBUG +#define SHOW_DEBUG +#endif + +#ifdef RELEASE_SHOW_WARN +#define SHOW_WARN +#endif + +#ifdef RELEASE_SHOW_INFO +#define SHOW_INFO +#endif + +#ifdef RELEASE_SHOW_ASSERT +#define SHOW_ASSERT +#endif + +#endif // _DEBUG + + + +#define lldebugst(type) lldebugs +#define llendflush llendl + + +#define llerror(msg, num) llerrs << "Error # " << num << ": " << msg << llendl; + +#define llwarning(msg, num) llwarns << "Warning # " << num << ": " << msg << llendl; + +#ifdef SHOW_ASSERT +#define llassert(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; +#else +#define llassert(func) +#endif +#define llassert_always(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; + +#ifdef SHOW_ASSERT +#define llverify(func) if (!(func)) llerrs << "ASSERT (" << #func << ")" << llendl; +#else +#define llverify(func) (func); // get rid of warning C4189 +#endif + +// handy compile-time assert - enforce those template parameters! +#define cassert(expn) typedef char __C_ASSERT__[(expn)?1:-1] /* Flawfinder: ignore */ + //XXX: used in two places in llcommon/llskipmap.h + +#endif // LL_LLERRORLEGACY_H diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 16e2f5c5a1..4acd94f943 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -178,6 +178,7 @@ void llifstream::open(const char* _Filename, /* Flawfinder: ignore */ } llassert(_Filebuffer == NULL); _Filebuffer = new _Myfb(filep); + _ShouldClose = true; _Myios::init(_Filebuffer); } @@ -189,13 +190,17 @@ bool llifstream::is_open() const } llifstream::~llifstream() { + if (_ShouldClose) + { + close(); + } delete _Filebuffer; } llifstream::llifstream(const char *_Filename, ios_base::openmode _Mode, int _Prot) - : std::basic_istream< char , std::char_traits< char > >(NULL,true),_Filebuffer(NULL) + : std::basic_istream< char , std::char_traits< char > >(NULL,true),_Filebuffer(NULL),_ShouldClose(false) { // construct with named file and specified mode open(_Filename, _Mode | ios_base::in, _Prot); /* Flawfinder: ignore */ diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 67de0f43fc..2899f51a60 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -68,7 +68,7 @@ public: typedef std::basic_ios<char,std::char_traits< char > > _Myios; llifstream() - : std::basic_istream<char,std::char_traits< char > >(NULL,true),_Filebuffer(NULL) + : std::basic_istream<char,std::char_traits< char > >(NULL,true),_Filebuffer(NULL),_ShouldClose(false) { // construct unopened } @@ -78,7 +78,8 @@ public: explicit llifstream(_Filet *_File) : std::basic_istream<char,std::char_traits< char > >(NULL,true), - _Filebuffer(new _Myfb(_File)) + _Filebuffer(new _Myfb(_File)), + _ShouldClose(false) { // construct with specified C stream } virtual ~llifstream(); @@ -95,6 +96,7 @@ public: private: _Myfb* _Filebuffer; // the file buffer + bool _ShouldClose; }; diff --git a/indra/llcommon/llformat.cpp b/indra/llcommon/llformat.cpp new file mode 100644 index 0000000000..0c2a6d3b1e --- /dev/null +++ b/indra/llcommon/llformat.cpp @@ -0,0 +1,28 @@ +/** + * @file llformat.cpp + * @date January 2007 + * @brief string formatting utility + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llformat.h" + +#include <stdarg.h> + +std::string llformat(const char *fmt, ...) +{ + char tstr[1024]; /* Flawfinder: ignore */ + va_list va; + va_start(va, fmt); +#if LL_WINDOWS + _vsnprintf(tstr, 1024, fmt, va); +#else + vsnprintf(tstr, 1024, fmt, va); /* Flawfinder: ignore */ +#endif + va_end(va); + return std::string(tstr); +} diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h new file mode 100644 index 0000000000..7079656b72 --- /dev/null +++ b/indra/llcommon/llformat.h @@ -0,0 +1,23 @@ +/** + * @file llformat.h + * @date January 2007 + * @brief string formatting utility + * + * Copyright (c) 2007-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFORMAT_H +#define LL_LLFORMAT_H + +#include <string> + +// Use as follows: +// llinfos << llformat("Test:%d (%.2f %.2f)", idx, x, y) << llendl; +// +// *NOTE: buffer limited to 1024, (but vsnprintf prevents overrun) +// should perhaps be replaced with boost::format. + +std::string llformat(const char *fmt, ...); + +#endif // LL_LLFORMAT_H diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index 7dad6f82d8..df2e940352 100644 --- a/indra/llcommon/lllivefile.cpp +++ b/indra/llcommon/lllivefile.cpp @@ -8,24 +8,56 @@ #include "linden_common.h" #include "lllivefile.h" +#include "llframetimer.h" +#include "lltimer.h" +class LLLiveFile::Impl +{ +public: + Impl(const std::string &filename, const F32 refresh_period); + ~Impl(); + + bool check(); + + + bool mForceCheck; + F32 mRefreshPeriod; + LLFrameTimer mRefreshTimer; -LLLiveFile::LLLiveFile(const std::string &filename, const F32 refresh_period) : -mForceCheck(true), -mRefreshPeriod(refresh_period), -mFilename(filename), -mLastModTime(0), -mLastExists(false) + std::string mFilename; + time_t mLastModTime; + bool mLastExists; + + LLEventTimer* mEventTimer; +}; + +LLLiveFile::Impl::Impl(const std::string &filename, const F32 refresh_period) + : mForceCheck(true), + mRefreshPeriod(refresh_period), + mFilename(filename), + mLastModTime(0), + mLastExists(false), + mEventTimer(NULL) { } +LLLiveFile::Impl::~Impl() +{ + delete mEventTimer; +} + +LLLiveFile::LLLiveFile(const std::string &filename, const F32 refresh_period) + : impl(* new Impl(filename, refresh_period)) +{ +} LLLiveFile::~LLLiveFile() { + delete &impl; } -bool LLLiveFile::checkAndReload() +bool LLLiveFile::Impl::check() { if (!mForceCheck && mRefreshTimer.getElapsedTimeF32() < mRefreshPeriod) { @@ -46,9 +78,8 @@ bool LLLiveFile::checkAndReload() // broken somehow. Clear flags and return. if (mLastExists) { - loadFile(); // Load the file, even though it's missing to allow it to clear state. mLastExists = false; - return true; + return true; // no longer existing is a change! } return false; } @@ -68,7 +99,44 @@ bool LLLiveFile::checkAndReload() mLastExists = true; mLastModTime = stat_data.st_mtime; - loadFile(); return true; } +bool LLLiveFile::checkAndReload() +{ + bool changed = impl.check(); + if (changed) + { + loadFile(); + } + return changed; +} + +std::string LLLiveFile::filename() const +{ + return impl.mFilename; +} + +namespace +{ + class LiveFileEventTimer : public LLEventTimer + { + public: + LiveFileEventTimer(LLLiveFile& f, F32 refresh) + : LLEventTimer(refresh), mLiveFile(f) + { } + + void tick() + { mLiveFile.checkAndReload(); } + + private: + LLLiveFile& mLiveFile; + }; + +} + +void LLLiveFile::addToEventTimer() +{ + impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod); +} + diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h index 97c88a5c5c..a71844e5a9 100644 --- a/indra/llcommon/lllivefile.h +++ b/indra/llcommon/lllivefile.h @@ -9,7 +9,6 @@ #ifndef LL_LLLIVEFILE_H #define LL_LLLIVEFILE_H -#include "llframetimer.h" class LLLiveFile { @@ -17,18 +16,22 @@ public: LLLiveFile(const std::string &filename, const F32 refresh_period = 5.f); virtual ~LLLiveFile(); - bool checkAndReload(); // Returns true if the file changed in any way + bool checkAndReload(); + // Returns true if the file changed in any way + // Call this before using anything that was read & cached from the file + + std::string filename() const; + + void addToEventTimer(); + // Normally, just calling checkAndReload() is enough. In some cases + // though, you may need to let the live file periodically check itself. protected: virtual void loadFile() = 0; // Implement this to load your file if it changed - bool mForceCheck; - F32 mRefreshPeriod; - LLFrameTimer mRefreshTimer; - - std::string mFilename; - time_t mLastModTime; - bool mLastExists; +private: + class Impl; + Impl& impl; }; #endif //LL_LLLIVEFILE_H diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 25bd7ceac8..45d7acd417 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -10,6 +10,7 @@ #include <math.h> #include "../llmath/llmath.h" +#include "llformat.h" namespace { class ImplMap; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 0555231010..a381af74d0 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -11,6 +11,7 @@ #include "stdtypes.h" #include "llerror.h" +#include "llfile.h" #include <algorithm> #include <map> #include <stdio.h> diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index ad428ce565..20db115c6e 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -10,6 +10,8 @@ #ifndef LL_STRING_TABLE_H #define LL_STRING_TABLE_H +#include "lldefs.h" +#include "llformat.h" #include "llstl.h" #include <list> #include <set> |