Newer
Older
* @date December 2006
* @brief error message system
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
#include "llsdutil.h"
Aaron Brashears
committed
#ifdef __GNUC__
# include <cxxabi.h>
#endif // __GNUC__
# include <syslog.h>
# include <unistd.h>
# include <sys/stat.h>
#else
# include <io.h>

Rye Mutt
committed
#include <string_view>
#include <absl/strings/str_format.h>
Aaron Brashears
committed
#include "llapp.h"
#include "llapr.h"
#include "llfile.h"
#include "lllivefile.h"
#include "llsd.h"
#include "llsdserialize.h"
Stinson Linden
committed
#include "llsingleton.h"
Aaron Brashears
committed
#include "llstl.h"
Steven Bennetts
committed
#include "lltimer.h"
#include <boost/make_shared.hpp>
// On Mac, got:
// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
// _Unwind_Backtrace is available without `_GNU_SOURCE`."
#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
#if LL_WINDOWS
// On Windows, header-only implementation causes macro collisions -- use
// prebuilt library
#define BOOST_STACKTRACE_LINK
#endif // LL_WINDOWS
#include <boost/stacktrace.hpp>
Richard Linden
committed
#if LL_WINDOWS
void debugger_print(const std::string& s)
{
// Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
// which works just fine under the windows debugger, but can cause users who
// have enabled SEHOP exception chain validation to crash due to interactions
// between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
//
if (IsDebuggerPresent())
{
// Need UTF16 for Unicode OutputDebugString
//
if (s.size())
{
OutputDebugString(ll_convert_string_to_wide(s).c_str());
Richard Linden
committed
OutputDebugString(TEXT("\n"));
}
}
}
#else
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();
}
Brad Payne (Vir Linden)
committed
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x01;
}
virtual void recordMessage(LLError::ELevel level,
Brad Payne (Vir Linden)
committed
const std::string& message) override
{
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):
mName(filename)
mFile.open(filename, std::ios_base::out | std::ios_base::app);
Richard Linden
committed
LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
else
{
if (!LLError::getAlwaysFlush())
{
mFile.sync_with_stdio(false);
}
~RecordToFile()
{
mFile.close();
}
Brad Payne (Vir Linden)
committed
virtual bool enabled() override
{
#ifdef LL_RELEASE_FOR_DOWNLOAD
return 1;
#else
return LLError::getEnabledLogTypesMask() & 0x02;
#endif
}
bool okay() const { return mFile.good(); }
std::string getFilename() const { return mName; }
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
{
Brad Payne (Vir Linden)
committed
if (LLError::getAlwaysFlush())
{
mFile << message << std::endl;
}
else
{
mFile << message << "\n";
}
const std::string mName;
llofstream mFile;
};
class RecordToStderr : public LLError::Recorder
{
public:
RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
Richard Linden
committed
{
Oz Linden
committed
this->showMultiline(true);
Richard Linden
committed
}
Brad Payne (Vir Linden)
committed
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x04;
}
LL_FORCE_INLINE std::string createANSI(const std::string& color)
{
std::string ansi_code;
ansi_code += '\033';
ansi_code += "[";
ansi_code += color;
ansi_code += "m";
return ansi_code;
}
virtual void recordMessage(LLError::ELevel level,
Brad Payne (Vir Linden)
committed
const std::string& message) override
static std::string s_ansi_error = createANSI("31"); // red
static std::string s_ansi_warn = createANSI("34"); // blue
static std::string s_ansi_debug = createANSI("35"); // magenta
if (mUseANSI)
writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
(level == LLError::LEVEL_WARN) ? s_ansi_warn :
s_ansi_debug, message);
else
{
fprintf(stderr, "%s\n", message.c_str());
}
bool mUseANSI;
Richard Linden
committed
LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
static std::string s_ansi_bold = createANSI("1"); // bold
static std::string s_ansi_reset = createANSI("0"); // reset
// ANSI color code escape sequence, message, and reset in one fprintf call
// Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
}
Richard Linden
committed
static bool checkANSI(void)
{
// Check whether it's okay to use ANSI; if stderr is
// a tty then we assume yes. Can be turned off with
// the LL_NO_ANSI_COLOR env var.
return (0 != isatty(2)) &&
(NULL == getenv("LL_NO_ANSI_COLOR"));
}
class RecordToFixedBuffer : public LLError::Recorder
{
public:
Oz Linden
committed
RecordToFixedBuffer(LLLineBuffer* buffer)
: mBuffer(buffer)
{
this->showMultiline(true);
this->showTags(false);
this->showLocation(false);
}
Brad Payne (Vir Linden)
committed
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x08;
}
virtual void recordMessage(LLError::ELevel level,
Brad Payne (Vir Linden)
committed
const std::string& message) override
Steven Bennetts
committed
mBuffer->addLine(message);
Steven Bennetts
committed
LLLineBuffer* mBuffer;
};
#if LL_WINDOWS
class RecordToWinDebug: public LLError::Recorder
{
public:
Richard Linden
committed
RecordToWinDebug()
Oz Linden
committed
{
this->showMultiline(true);
this->showTags(false);
this->showLocation(false);
}
Richard Linden
committed
Brad Payne (Vir Linden)
committed
virtual bool enabled() override
{
return LLError::getEnabledLogTypesMask() & 0x10;
}
virtual void recordMessage(LLError::ELevel level,
Brad Payne (Vir Linden)
committed
const std::string& message) override
Richard Linden
committed
debugger_print(message);
std::string className(const std::type_info& type)
{
return LLError::Log::demangle(type.name());
}
} // anonymous
namespace LLError
{

Rye Mutt
committed
std::string Log::demangle(const std::string_view mangled)
Richard Linden
committed
// GCC: type_info::name() returns a mangled class name,st demangle
// passing nullptr, 0 forces allocation of a unique buffer we can free
// fixing MAINT-8724 on OSX 10.14
int status = -1;
char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
std::string result(name ? name : mangled);
free(name);
return result;
#elif LL_WINDOWS

Rye Mutt
committed
using namespace std::literals;
// Visual Studio: type_info::name() includes the text "class " at the start

Rye Mutt
committed
static constexpr auto class_prefix = "class "sv;
static constexpr auto struct_prefix = "struct "sv;
if (0 == mangled.compare(0, class_prefix.length(), class_prefix))

Rye Mutt
committed
return std::string(mangled.substr(class_prefix.length()));

Rye Mutt
committed
else if (0 == mangled.compare(0, struct_prefix.length(), struct_prefix))
{
return std::string(mangled.substr(struct_prefix.length()));
}
// huh, that's odd, we should see one or the other prefix -- but don't
// try to log unless logging is already initialized
// in Python, " or ".join(vector) -- but in C++, a PITB
LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"

Rye Mutt
committed
<< mangled << "'" << LL_ENDL;
return std::string(mangled);
#else // neither GCC nor Visual Studio
return mangled;
} // LLError
namespace
{
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& user_dir, const std::string& app_dir);
private:
LogControlFile(const std::string &filename)
: LLLiveFile(filename)
{ }
};
LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_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 = user_dir + "/logcontrol-dev.xml";
if (LLFile::stat(file, &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 = app_dir + "/logcontrol.xml";
Josh Bell
committed
return * new LogControlFile(file);
// NB: This instance is never freed
bool LogControlFile::loadFile()
Oz Linden
committed
llifstream file(filename().c_str());
if (!file.is_open())
LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL;
return false;
}
if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE)
{
LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL;
return false;
if (! configuration || !configuration.isMap())
LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
" content; not changing configuration"
}
}
LLError::configure(configuration);
LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL;
}
typedef std::map<std::string, LLError::ELevel> LevelMap;
typedef std::vector<LLError::RecorderPtr> Recorders;
typedef std::vector<LLError::CallSite*> CallSiteVector;
class Globals
public:
static Globals* getInstance();
protected:
Globals();
public:
std::ostringstream messageStream;
bool messageStreamInUse;
std::string mFatalMessage;
void addCallSite(LLError::CallSite&);
void invalidateCallSites();
private:
CallSiteVector callSites;
};
Stinson Linden
committed
Globals::Globals()
: messageStream(),
messageStreamInUse(false),
callSites()
{
}
Globals* Globals::getInstance()
{
// According to C++11 Function-Local Initialization
// of static variables is supposed to be thread safe
// without risk of deadlocks.
static Globals inst;
return &inst;
}
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();
}
}
namespace LLError
{
Stinson Linden
committed
class SettingsConfig : public LLRefCount
Stinson Linden
committed
friend class Settings;
Stinson Linden
committed
virtual ~SettingsConfig();
Richard Linden
committed
LLError::ELevel mDefaultLevel;
Brad Payne (Vir Linden)
committed
bool mLogAlwaysFlush;
U32 mEnabledLogTypesMask;
Richard Linden
committed
LevelMap mFunctionLevelMap;
LevelMap mClassLevelMap;
LevelMap mFileLevelMap;
LevelMap mTagLevelMap;
absl::flat_hash_map<std::string, unsigned int> mUniqueLogMessages;
Richard Linden
committed
LLError::FatalFunction mCrashFunction;
LLError::TimeFunction mTimeFunction;
Richard Linden
committed
Recorders mRecorders;
int mShouldLogCallCounter;
Stinson Linden
committed
private:
SettingsConfig();
};
typedef LLPointer<SettingsConfig> SettingsConfigPtr;
class Settings
Stinson Linden
committed
{
public:
static Settings* getInstance();
protected:
Settings();
Stinson Linden
committed
public:
SettingsConfigPtr getSettingsConfig();
Stinson Linden
committed
void reset();
SettingsStoragePtr saveAndReset();
Stinson Linden
committed
void restore(SettingsStoragePtr pSettingsStorage);
Stinson Linden
committed
SettingsConfigPtr mSettingsConfig;
Stinson Linden
committed
SettingsConfig::SettingsConfig()
: LLRefCount(),
mDefaultLevel(LLError::LEVEL_DEBUG),
Brad Payne (Vir Linden)
committed
mLogAlwaysFlush(true),
mEnabledLogTypesMask(255),
Stinson Linden
committed
mFunctionLevelMap(),
mClassLevelMap(),
mFileLevelMap(),
mTagLevelMap(),
mUniqueLogMessages(),
mCrashFunction(NULL),
mTimeFunction(NULL),
mRecorders(),
mShouldLogCallCounter(0)
Stinson Linden
committed
SettingsConfig::~SettingsConfig()
Stinson Linden
committed
mRecorders.clear();
}
Stinson Linden
committed
mSettingsConfig(new SettingsConfig())
{
}
Settings* Settings::getInstance()
{
// According to C++11 Function-Local Initialization
// of static variables is supposed to be thread safe
// without risk of deadlocks.
static Settings inst;
return &inst;
}
Stinson Linden
committed
SettingsConfigPtr Settings::getSettingsConfig()
{
return mSettingsConfig;
Stinson Linden
committed
void Settings::reset()
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
mSettingsConfig = new SettingsConfig();
Stinson Linden
committed
SettingsStoragePtr Settings::saveAndReset()
Stinson Linden
committed
SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get());
reset();
return oldSettingsConfig;
Stinson Linden
committed
void Settings::restore(SettingsStoragePtr pSettingsStorage)
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get()));
mSettingsConfig = std::move(newSettingsConfig);
const char* file,
int line,
const std::type_info& class_info,
const char* function,
Richard Linden
committed
bool printOnce,
Richard Linden
committed
const char** tags,
size_t tag_count)
Richard Linden
committed
: mLevel(level),
mFile(file),
mLine(line),
mClassInfo(class_info),
mFunction(function),
mCached(false),
mShouldLog(false),
mPrintOnce(printOnce),
Richard Linden
committed
mTags(new const char* [tag_count]),
mTagCount(tag_count)
Richard Linden
committed
{
Richard Linden
committed
switch (mLevel)
{
case LEVEL_DEBUG: mLevelString = "DEBUG"; break;
case LEVEL_INFO: mLevelString = "INFO"; break;
case LEVEL_WARN: mLevelString = "WARNING"; break;
case LEVEL_ERROR: mLevelString = "ERROR"; break;
default: mLevelString = "XXX"; break;
Richard Linden
committed
};
mLocationString = absl::StrFormat("%s(%d)", abbreviateFile(mFile), mLine);
Richard Linden
committed
#if LL_WINDOWS
// DevStudio: __FUNCTION__ already includes the full class name
#else
#if LL_LINUX
// gross, but typeid comparison seems to always fail here with gcc4.1
if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name()))
Richard Linden
committed
#else
Richard Linden
committed
#endif // LL_LINUX
{
Richard Linden
committed
}
#endif
mFunctionString += std::string(mFunction);
for (int i = 0; i < tag_count; i++)
{
if (strchr(tags[i], ' '))
{
LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL;
}
mTags[i] = tags[i];
}
mTagString.append("#");
// always construct a tag sequence; will be just a single # if no tag
Richard Linden
committed
for (size_t i = 0; i < mTagCount; i++)
{
Richard Linden
committed
}
Richard Linden
committed
}
Richard Linden
committed
CallSite::~CallSite()
{
delete []mTags;
}
Richard Linden
committed
{
mCached = false;
}
bool shouldLogToStderr()
{
// 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. That scenario can be detected by noticing that stderr is a
// character device (S_IFCHR).
// If stderr is a tty or a pipe, assume the user launched from the
// command line or debugger and therefore wants to see stderr.
if (isatty(STDERR_FILENO))
return true;
// not a tty, but might still be a pipe -- check
struct stat st;
if (fstat(STDERR_FILENO, &st) < 0)
{
// capture errno right away, before engaging any other operations
auto errno_save = errno;
// this gets called during log-system setup -- can't log yet!
std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
<< errno_save << std::endl;
// if we can't tell, err on the safe side and don't write stderr
return false;
}
// fstat() worked: return true only if stderr is a pipe
return ((st.st_mode & S_IFMT) == S_IFIFO);
return true;
bool stderrLogWantsTime()
{
#if LL_WINDOWS
return false;
#else
return true;
#endif
}
void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
Stinson Linden
committed
LLError::Settings::getInstance()->reset();
LLError::setDefaultLevel(LLError::LEVEL_INFO);
LLError::setAlwaysFlush(true);
LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setFatalFunction(LLError::crashAndLoop);
LLError::setTimeFunction(LLError::utcTime);
// log_to_stderr is only false in the unit and integration tests to keep builds quieter
if (log_to_stderr && shouldLogToStderr())
LLError::logToStderr();
LLError::RecorderPtr recordToWinDebug = boost::make_shared<RecordToWinDebug>();
LLError::addRecorder(std::move(recordToWinDebug));
LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir);
// NOTE: We want to explicitly load the file before we add it to the event timer
// that checks for changes to the file. Else, we're not actually loading the file yet,
// and most of the initialization happens without any attention being paid to the
// log control file. Not to mention that when it finally gets checked later,
// all log statements that have been evaluated already become dirty and need to be
// evaluated for printing again. So, make sure to call checkAndReload()
// before addToEventTimer().
e.checkAndReload();
Josh Bell
committed
e.addToEventTimer();
void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr)
commonInit(user_dir, app_dir, log_to_stderr);
void setFatalFunction(const FatalFunction& f)
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mCrashFunction = f;
FatalFunction getFatalFunction()
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
return s->mCrashFunction;
std::string getFatalMessage()
{
return Globals::getInstance()->mFatalMessage;
}
void setTimeFunction(TimeFunction f)
{
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mTimeFunction = f;
}
void setDefaultLevel(ELevel level)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mDefaultLevel = level;
ELevel getDefaultLevel()
{
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
return s->mDefaultLevel;
}
Brad Payne (Vir Linden)
committed
void setAlwaysFlush(bool flush)
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mLogAlwaysFlush = flush;
}
bool getAlwaysFlush()
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
return s->mLogAlwaysFlush;
}
void setEnabledLogTypesMask(U32 mask)
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mEnabledLogTypesMask = mask;
}
U32 getEnabledLogTypesMask()
{
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
return s->mEnabledLogTypesMask;
}
void setFunctionLevel(const std::string& function_name, ELevel level)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mFunctionLevelMap[function_name] = level;
}
void setClassLevel(const std::string& class_name, ELevel level)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mClassLevelMap[class_name] = level;
}
void setFileLevel(const std::string& file_name, ELevel level)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mFileLevelMap[file_name] = level;
void setTagLevel(const std::string& tag_name, ELevel level)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
s->mTagLevelMap[tag_name] = level;
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())
{
Richard Linden
committed
LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL;
return LLError::LEVEL_INFO;
}
return i->second;
}
}
namespace {
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)
{
Stinson Linden
committed
Globals::getInstance()->invalidateCallSites();
Stinson Linden
committed
SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
Stinson Linden
committed
s->mFunctionLevelMap.clear();
s->mClassLevelMap.clear();
s->mFileLevelMap.clear();
s->mTagLevelMap.clear();
s->mUniqueLogMessages.clear();
setDefaultLevel(decodeLevel(config["default-level"]));
Brad Payne (Vir Linden)
committed
if (config.has("log-always-flush"))
{
setAlwaysFlush(config["log-always-flush"]);
}
if (config.has("enabled-log-types-mask"))
{
setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger());
}
if (config.has("settings") && config["settings"].isArray())
{
LLSD sets = config["settings"];
LLSD::array_const_iterator a, end;
for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a)
{
const LLSD& entry = *a;
if (entry.isMap() && entry.size() != 0)
{
ELevel level = decodeLevel(entry["level"]);
setLevels(s->mFunctionLevelMap, entry["functions"], level);
setLevels(s->mClassLevelMap, entry["classes"], level);
setLevels(s->mFileLevelMap, entry["files"], level);
setLevels(s->mTagLevelMap, entry["tags"], level);
}
}
}
Richard Linden
committed
Recorder::Recorder()
Oz Linden
committed
: mWantsTime(true)
, mWantsTags(true)
, mWantsLevel(true)
, mWantsLocation(true)
, mWantsFunctionName(true)
, mWantsMultiline(false)
Stinson Linden
committed
{
}
Richard Linden
committed
Richard Linden
committed
{
Richard Linden
committed
return mWantsTime;
Richard Linden
committed
}
Richard Linden
committed
// virtual
bool Recorder::wantsTags()
{
Richard Linden
committed
return mWantsTags;
}
Richard Linden
committed
// virtual
bool Recorder::wantsLevel()
{
return mWantsLevel;
}
// virtual
bool Recorder::wantsLocation()
{
return mWantsLocation;
}
// virtual
bool Recorder::wantsFunctionName()
{
return mWantsFunctionName;
Richard Linden
committed
}