summaryrefslogtreecommitdiff
path: root/indra/llcommon/llerror.cpp
diff options
context:
space:
mode:
authorMark Lentczner <markl@lindenlab.com>2007-02-06 00:57:33 +0000
committerMark Lentczner <markl@lindenlab.com>2007-02-06 00:57:33 +0000
commitd0d4670f4941dcf7430fb1269c6613140ecf3ff7 (patch)
treee3d6b59c19cac6bc172ec5fb0131ffc8f4923b75 /indra/llcommon/llerror.cpp
parent77f04c74eb1603bf2fadc30127d05378bfc7a48a (diff)
merge in of error-refactor-3
concludes (fixes) SL-31187 pair programmed and reviewed by markl and karen
Diffstat (limited to 'indra/llcommon/llerror.cpp')
-rw-r--r--indra/llcommon/llerror.cpp1031
1 files changed, 1016 insertions, 15 deletions
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";
+ }
+}
+