summaryrefslogtreecommitdiff
path: root/indra/llcommon/llerror.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llerror.cpp')
-rw-r--r--indra/llcommon/llerror.cpp439
1 files changed, 378 insertions, 61 deletions
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 22b2c9db82..bb64152407 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -3,30 +3,25 @@
* @date December 2006
* @brief error message system
*
- * $LicenseInfo:firstyear=2006&license=viewergpl$
- *
- * Copyright (c) 2006-2007, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlife.com/developers/opensource/gplv2
+ * Copyright (C) 2010, Linden Research, Inc.
*
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at http://secondlife.com/developers/opensource/flossexception
+ * 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.
*
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
+ * 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.
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * 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
* $/LicenseInfo$
*/
@@ -37,26 +32,23 @@
#include <cctype>
#ifdef __GNUC__
-#include <cxxabi.h>
-#endif
+# include <cxxabi.h>
+#endif // __GNUC__
#include <sstream>
#if !LL_WINDOWS
-#include <syslog.h>
-#endif
-#if LL_WINDOWS
-#include <windows.h>
-#endif
+# include <syslog.h>
+# include <unistd.h>
+#endif // !LL_WINDOWS
#include <vector>
#include "llapp.h"
#include "llapr.h"
#include "llfile.h"
-#include "llfixedbuffer.h"
#include "lllivefile.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "llstl.h"
-
+#include "lltimer.h"
namespace {
#if !LL_WINDOWS
@@ -101,7 +93,7 @@ namespace {
public:
RecordToFile(const std::string& filename)
{
- mFile.open(filename.c_str(), llofstream::out | llofstream::app);
+ mFile.open(filename, llofstream::out | llofstream::app);
if (!mFile)
{
llinfos << "Error setting log file to " << filename << llendl;
@@ -133,33 +125,73 @@ namespace {
class RecordToStderr : public LLError::Recorder
{
public:
- RecordToStderr(bool timestamp) : mTimestamp(timestamp) { }
+ RecordToStderr(bool timestamp) : mTimestamp(timestamp), mUseANSI(ANSI_PROBE) { }
virtual bool wantsTime() { return mTimestamp; }
virtual void recordMessage(LLError::ELevel level,
- const std::string& message)
+ const std::string& message)
{
+ if (ANSI_PROBE == mUseANSI)
+ mUseANSI = (checkANSI() ? ANSI_YES : ANSI_NO);
+
+ if (ANSI_YES == mUseANSI)
+ {
+ // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
+ colorANSI("1"); // bold
+ switch (level) {
+ case LLError::LEVEL_ERROR:
+ colorANSI("31"); // red
+ break;
+ case LLError::LEVEL_WARN:
+ colorANSI("34"); // blue
+ break;
+ case LLError::LEVEL_DEBUG:
+ colorANSI("35"); // magenta
+ break;
+ default:
+ break;
+ }
+ }
fprintf(stderr, "%s\n", message.c_str());
+ if (ANSI_YES == mUseANSI) colorANSI("0"); // reset
}
private:
bool mTimestamp;
+ enum ANSIState {ANSI_PROBE, ANSI_YES, ANSI_NO};
+ ANSIState mUseANSI;
+ void colorANSI(const std::string color)
+ {
+ // ANSI color code escape sequence
+ fprintf(stderr, "\033[%sm", color.c_str() );
+ };
+ bool checkANSI(void)
+ {
+#if LL_LINUX || LL_DARWIN
+ // 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"));
+#endif // LL_LINUX
+ return false;
+ };
};
class RecordToFixedBuffer : public LLError::Recorder
{
public:
- RecordToFixedBuffer(LLFixedBuffer& buffer) : mBuffer(buffer) { }
+ RecordToFixedBuffer(LLLineBuffer* buffer) : mBuffer(buffer) { }
virtual void recordMessage(LLError::ELevel level,
- const std::string& message)
+ const std::string& message)
{
- mBuffer.addLine(message.c_str());
+ mBuffer->addLine(message);
}
private:
- LLFixedBuffer& mBuffer;
+ LLLineBuffer* mBuffer;
};
#if LL_WINDOWS
@@ -167,7 +199,7 @@ namespace {
{
public:
virtual void recordMessage(LLError::ELevel level,
- const std::string& message)
+ const std::string& message)
{
llutf16string utf16str =
wstring_to_utf16str(utf8str_to_wstring(message));
@@ -247,7 +279,7 @@ namespace
public:
static LogControlFile& fromDirectory(const std::string& dir);
- virtual void loadFile();
+ virtual bool loadFile();
private:
LogControlFile(const std::string &filename)
@@ -264,7 +296,7 @@ namespace
std::string file = dirBase + "logcontrol-dev.xml";
llstat stat_info;
- if (LLFile::stat(file.c_str(), &stat_info)) {
+ 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.
@@ -275,12 +307,12 @@ namespace
// NB: This instance is never freed
}
- void LogControlFile::loadFile()
+ bool LogControlFile::loadFile()
{
LLSD configuration;
{
- llifstream file(filename().c_str());
+ llifstream file(filename());
if (file.is_open())
{
LLSDSerialize::fromXML(configuration, file);
@@ -291,12 +323,13 @@ namespace
llwarns << filename() << " missing, ill-formed,"
" or simply undefined; not changing configuration"
<< llendl;
- return;
+ return false;
}
}
LLError::configure(configuration);
llinfos << "logging reconfigured from " << filename() << llendl;
+ return true;
}
@@ -367,6 +400,8 @@ namespace LLError
LevelMap functionLevelMap;
LevelMap classLevelMap;
LevelMap fileLevelMap;
+ LevelMap tagLevelMap;
+ std::map<std::string, unsigned int> uniqueLogMessages;
LLError::FatalFunction crashFunction;
LLError::TimeFunction timeFunction;
@@ -388,7 +423,7 @@ namespace LLError
Settings()
: printLocation(false),
defaultLevel(LLError::LEVEL_DEBUG),
- crashFunction(NULL),
+ crashFunction(),
timeFunction(NULL),
fileRecorder(NULL),
fixedBufferRecorder(NULL),
@@ -453,11 +488,17 @@ namespace LLError
namespace LLError
{
CallSite::CallSite(ELevel level,
- const char* file, int line,
- const std::type_info& class_info, const char* function)
+ const char* file,
+ int line,
+ const std::type_info& class_info,
+ const char* function,
+ const char* broadTag,
+ const char* narrowTag,
+ bool printOnce)
: mLevel(level), mFile(file), mLine(line),
mClassInfo(class_info), mFunction(function),
- mCached(false), mShouldLog(false)
+ mCached(false), mShouldLog(false),
+ mBroadTag(broadTag), mNarrowTag(narrowTag), mPrintOnce(printOnce)
{ }
@@ -511,6 +552,15 @@ namespace
#endif
LogControlFile& e = LogControlFile::fromDirectory(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();
e.addToEventTimer();
}
}
@@ -541,12 +591,18 @@ namespace LLError
s.printLocation = print;
}
- void setFatalFunction(FatalFunction f)
+ void setFatalFunction(const FatalFunction& f)
{
Settings& s = Settings::get();
s.crashFunction = f;
}
+ FatalFunction getFatalFunction()
+ {
+ Settings& s = Settings::get();
+ return s.crashFunction;
+ }
+
void setTimeFunction(TimeFunction f)
{
Settings& s = Settings::get();
@@ -584,6 +640,14 @@ namespace LLError
g.invalidateCallSites();
s.fileLevelMap[file_name] = level;
}
+
+ void setTagLevel(const std::string& tag_name, ELevel level)
+ {
+ Globals& g = Globals::get();
+ Settings& s = Settings::get();
+ g.invalidateCallSites();
+ s.tagLevelMap[tag_name] = level;
+ }
}
namespace {
@@ -633,6 +697,8 @@ namespace LLError
s.functionLevelMap.clear();
s.classLevelMap.clear();
s.fileLevelMap.clear();
+ s.tagLevelMap.clear();
+ s.uniqueLogMessages.clear();
setPrintLocation(config["print-location"]);
setDefaultLevel(decodeLevel(config["default-level"]));
@@ -648,6 +714,7 @@ namespace LLError
setLevels(s.functionLevelMap, entry["functions"], level);
setLevels(s.classLevelMap, entry["classes"], level);
setLevels(s.fileLevelMap, entry["files"], level);
+ setLevels(s.tagLevelMap, entry["tags"], level);
}
}
}
@@ -715,7 +782,7 @@ namespace LLError
addRecorder(f);
}
- void logToFixedBuffer(LLFixedBuffer* fixedBuffer)
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
{
LLError::Settings& s = LLError::Settings::get();
@@ -728,7 +795,7 @@ namespace LLError
return;
}
- s.fixedBufferRecorder = new RecordToFixedBuffer(*fixedBuffer);
+ s.fixedBufferRecorder = new RecordToFixedBuffer(fixedBuffer);
addRecorder(s.fixedBufferRecorder);
}
@@ -809,7 +876,7 @@ namespace {
return false;
}
- level = i->second;
+ level = i->second;
return true;
}
@@ -881,16 +948,27 @@ namespace LLError
std::string class_name = className(site.mClassInfo);
std::string function_name = functionName(site.mFunction);
+#if LL_LINUX
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
+#else
if (site.mClassInfo != typeid(NoClassInfo))
+#endif // LL_LINUX
{
function_name = class_name + "::" + function_name;
}
ELevel compareLevel = s.defaultLevel;
- checkLevelMap(s.functionLevelMap, function_name, compareLevel)
+ // The most specific match found will be used as the log level,
+ // since the computation short circuits.
+ // So, in increasing order of importance:
+ // Default < Broad Tag < File < Class < Function < Narrow Tag
+ ((site.mNarrowTag != NULL) ? checkLevelMap(s.tagLevelMap, site.mNarrowTag, compareLevel) : false)
+ || checkLevelMap(s.functionLevelMap, function_name, compareLevel)
|| checkLevelMap(s.classLevelMap, class_name, compareLevel)
- || checkLevelMap(s.fileLevelMap, abbreviateFile(site.mFile), compareLevel);
+ || checkLevelMap(s.fileLevelMap, abbreviateFile(site.mFile), compareLevel)
+ || ((site.mBroadTag != NULL) ? checkLevelMap(s.tagLevelMap, site.mBroadTag, compareLevel) : false);
site.mCached = true;
g.addCallSite(site);
@@ -914,6 +992,38 @@ namespace LLError
return new std::ostringstream;
}
+
+ void Log::flush(std::ostringstream* out, char* message)
+ {
+ LogLock lock;
+ if (!lock.ok())
+ {
+ return;
+ }
+
+ if(strlen(out->str().c_str()) < 128)
+ {
+ strcpy(message, out->str().c_str());
+ }
+ else
+ {
+ strncpy(message, out->str().c_str(), 127);
+ message[127] = '\0' ;
+ }
+
+ Globals& g = Globals::get();
+ if (out == &g.messageStream)
+ {
+ g.messageStream.clear();
+ g.messageStream.str("");
+ g.messageStreamInUse = false;
+ }
+ else
+ {
+ delete out;
+ }
+ return ;
+ }
void Log::flush(std::ostringstream* out, const CallSite& site)
{
@@ -965,17 +1075,42 @@ namespace LLError
<< "(" << site.mLine << ") : ";
}
- if (message.find(functionName(site.mFunction)) == std::string::npos)
- {
#if LL_WINDOWS
- // DevStudio: __FUNCTION__ already includes the full class name
+ // DevStudio: __FUNCTION__ already includes the full class name
#else
- if (site.mClassInfo != typeid(NoClassInfo))
+ #if LL_LINUX
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
+ #else
+ if (site.mClassInfo != typeid(NoClassInfo))
+ #endif // LL_LINUX
+ {
+ prefix << className(site.mClassInfo) << "::";
+ }
+ #endif
+ prefix << site.mFunction << ": ";
+
+ if (site.mPrintOnce)
+ {
+ std::map<std::string, unsigned int>::iterator messageIter = s.uniqueLogMessages.find(message);
+ if (messageIter != s.uniqueLogMessages.end())
{
- prefix << className(site.mClassInfo) << "::";
+ messageIter->second++;
+ unsigned int num_messages = messageIter->second;
+ if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
+ {
+ prefix << "ONCE (" << num_messages << "th time seen): ";
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ prefix << "ONCE: ";
+ s.uniqueLogMessages[message] = 1;
}
- #endif
- prefix << site.mFunction << ": ";
}
prefix << message;
@@ -1052,18 +1187,28 @@ namespace LLError
return s.shouldLogCallCounter;
}
+#if LL_WINDOWS
+ // VC80 was optimizing the error away.
+ #pragma optimize("", off)
+#endif
void crashAndLoop(const std::string& message)
{
// Now, we go kaboom!
- int* crash = NULL;
+ int* make_me_crash = NULL;
- *crash = 0;
+ *make_me_crash = 0;
while(true)
{
// Loop forever, in case the crash didn't work?
}
+
+ // this is an attempt to let Coverity and other semantic scanners know that this function won't be returning ever.
+ exit(EXIT_FAILURE);
}
+#if LL_WINDOWS
+ #pragma optimize("", on)
+#endif
std::string utcTime()
{
@@ -1079,3 +1224,175 @@ namespace LLError
}
}
+namespace LLError
+{
+ char** LLCallStacks::sBuffer = NULL ;
+ S32 LLCallStacks::sIndex = 0 ;
+
+#define SINGLE_THREADED 1
+
+ class CallStacksLogLock
+ {
+ public:
+ CallStacksLogLock();
+ ~CallStacksLogLock();
+
+#if SINGLE_THREADED
+ bool ok() const { return true; }
+#else
+ bool ok() const { return mOK; }
+ private:
+ bool mLocked;
+ bool mOK;
+#endif
+ };
+
+#if SINGLE_THREADED
+ CallStacksLogLock::CallStacksLogLock()
+ {
+ }
+ CallStacksLogLock::~CallStacksLogLock()
+ {
+ }
+#else
+ CallStacksLogLock::CallStacksLogLock()
+ : mLocked(false), mOK(false)
+ {
+ if (!gCallStacksLogMutexp)
+ {
+ mOK = true;
+ return;
+ }
+
+ const int MAX_RETRIES = 5;
+ for (int attempts = 0; attempts < MAX_RETRIES; ++attempts)
+ {
+ apr_status_t s = apr_thread_mutex_trylock(gCallStacksLogMutexp);
+ if (!APR_STATUS_IS_EBUSY(s))
+ {
+ mLocked = true;
+ mOK = true;
+ return;
+ }
+
+ ms_sleep(1);
+ }
+
+ // We're hosed, we can't get the mutex. Blah.
+ std::cerr << "CallStacksLogLock::CallStacksLogLock: failed to get mutex for log"
+ << std::endl;
+ }
+
+ CallStacksLogLock::~CallStacksLogLock()
+ {
+ if (mLocked)
+ {
+ apr_thread_mutex_unlock(gCallStacksLogMutexp);
+ }
+ }
+#endif
+
+ //static
+ void LLCallStacks::push(const char* function, const int line)
+ {
+ CallStacksLogLock lock;
+ if (!lock.ok())
+ {
+ return;
+ }
+
+ if(!sBuffer)
+ {
+ sBuffer = new char*[512] ;
+ sBuffer[0] = new char[512 * 128] ;
+ for(S32 i = 1 ; i < 512 ; i++)
+ {
+ sBuffer[i] = sBuffer[i-1] + 128 ;
+ }
+ sIndex = 0 ;
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ strcpy(sBuffer[sIndex], function) ;
+ sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
+ sIndex++ ;
+
+ return ;
+ }
+
+ //static
+ std::ostringstream* LLCallStacks::insert(const char* function, const int line)
+ {
+ std::ostringstream* _out = LLError::Log::out();
+ *_out << function << " line " << line << " " ;
+
+ return _out ;
+ }
+
+ //static
+ void LLCallStacks::end(std::ostringstream* _out)
+ {
+ CallStacksLogLock lock;
+ if (!lock.ok())
+ {
+ return;
+ }
+
+ if(!sBuffer)
+ {
+ sBuffer = new char*[512] ;
+ sBuffer[0] = new char[512 * 128] ;
+ for(S32 i = 1 ; i < 512 ; i++)
+ {
+ sBuffer[i] = sBuffer[i-1] + 128 ;
+ }
+ sIndex = 0 ;
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ LLError::Log::flush(_out, sBuffer[sIndex++]) ;
+ }
+
+ //static
+ void LLCallStacks::print()
+ {
+ CallStacksLogLock lock;
+ if (!lock.ok())
+ {
+ return;
+ }
+
+ if(sIndex > 0)
+ {
+ llinfos << " ************* PRINT OUT LL CALL STACKS ************* " << llendl ;
+ while(sIndex > 0)
+ {
+ sIndex-- ;
+ llinfos << sBuffer[sIndex] << llendl ;
+ }
+ llinfos << " *************** END OF LL CALL STACKS *************** " << llendl ;
+ }
+
+ if(sBuffer)
+ {
+ delete[] sBuffer[0] ;
+ delete[] sBuffer ;
+ sBuffer = NULL ;
+ }
+ }
+
+ //static
+ void LLCallStacks::clear()
+ {
+ sIndex = 0 ;
+ }
+}
+