diff options
Diffstat (limited to 'indra/llcommon/llerror.cpp')
-rw-r--r-- | indra/llcommon/llerror.cpp | 439 |
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 ; + } +} + |