/** 
 * @file llerror.cpp
 * @date   December 2006
 * @brief error message system
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * 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
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llerror.h"
#include "llerrorcontrol.h"

#include <cctype>
#ifdef __GNUC__
# include <cxxabi.h>
#endif // __GNUC__
#include <sstream>
#if !LL_WINDOWS
# include <syslog.h>
# include <unistd.h>
#endif // !LL_WINDOWS
#include <vector>

#include "llapp.h"
#include "llapr.h"
#include "llfile.h"
#include "lllivefile.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "llstl.h"
#include "lltimer.h"

#include "aithreadsafe.h"

namespace {
#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(utf8str_to_utf16str(s).c_str());
				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();
		}
		
		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(), std::ios_base::out | std::ios_base::app);
			if (!mFile)
			{
				LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
			}
			mWantsTime = true;
			mWantsTags = true;
		}
		
		~RecordToFile()
		{
			mFile.close();
		}
		
		bool okay() { return mFile.good(); }
		
		virtual void recordMessage(LLError::ELevel level,
									const std::string& message)
		{
			mFile << message << std::endl;
		}
	
	private:
		llofstream mFile;
	};
	
	
	class RecordToStderr : public LLError::Recorder
	{
	public:
		RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE) 
		{
			mWantsTime = timestamp;
		}
		
		virtual void recordMessage(LLError::ELevel level,
					   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;
				}
			}
#ifdef CWDEBUG
			// Include normal logging in libcwd's message processing.
			// This takes care of prefixing with thread ID's, locking
			// and allows us to (temporarily) turn off normal logging
			// output.
			Dout(dc::viewer, message);
#else
			fprintf(stderr, "%s\n", message.c_str());
#endif
#if LL_WINDOWS
			fflush(stderr); //Now using a buffer. flush is required.
#endif
			if (ANSI_YES == mUseANSI) colorANSI("0"); // reset
		}
	
	private:
		enum ANSIState 
		{
			ANSI_PROBE, 
			ANSI_YES, 
			ANSI_NO
		}					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(LLLineBuffer* buffer) : mBuffer(buffer) { }
		
		virtual void recordMessage(LLError::ELevel level,
								   const std::string& message)
		{
			mBuffer->addLine(message);
		}
	
	private:
		LLLineBuffer* mBuffer;
	};

#if LL_WINDOWS
	class RecordToWinDebug: public LLError::Recorder
	{
	public:
		RecordToWinDebug()
		{}

		virtual void recordMessage(LLError::ELevel level,
								   const std::string& message)
		{
			debugger_print(message);
		}
	};
#endif
}


namespace
{
	std::string className(const std::type_info& type)
	{
#ifdef __GNUC__
		// 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(type.name(), nullptr, 0, &status);
        std::string result(name ? name : type.name());
        free(name);
        return result;
#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 bool 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, &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
	}
	
	bool LogControlFile::loadFile()
	{
		LLSD configuration;

		{
			llifstream file(filename().c_str());
			if (file.is_open())
			{
				LLSDSerialize::fromXML(configuration, file);
			}

			if (configuration.isUndefined())
			{
				LL_WARNS() << filename() << " missing, ill-formed,"
							" or simply undefined; not changing configuration"
						<< LL_ENDL;
				return false;
			}
		}
		
		LLError::configure(configuration);
		LL_INFOS() << "logging reconfigured from " << filename() << LL_ENDL;
		return true;
	}


	typedef std::map<std::string, LLError::ELevel> LevelMap;
	typedef std::vector<LLError::RecorderPtr> Recorders;
	typedef std::vector<LLError::CallSite*> CallSiteVector;

	class Globals
	{
	public:

		std::ostringstream messageStream;
		bool messageStreamInUse;

		void addCallSite(LLError::CallSite&);
		void invalidateCallSites();

		static AIThreadSafeSimple<Globals>& get();
			// return the one instance of the globals

	private:
		CallSiteVector callSites;
		
		friend class AIThreadSafeSimpleDC<Globals>;		// Calls constructor.
		friend class AIThreadSafeSimple<Globals>;		// Calls destructor.
		
		Globals();
	};

	Globals::Globals()
		: messageStream(),
		messageStreamInUse(false),
		callSites()
	{
	}

	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();
	}
	
	AIThreadSafeSimple<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 AIThreadSafeSimpleDCRootPool<Globals>* ts_globals_ptr = new AIThreadSafeSimpleDCRootPool<Globals>;
		return *ts_globals_ptr;
	}
}

namespace LLError
{
	class SettingsConfig : public LLRefCount
	{
		friend class Settings;

	public:
		virtual ~SettingsConfig();

		bool                                mPrintLocation;

		LLError::ELevel                     mDefaultLevel;
		
		LevelMap                            mFunctionLevelMap;
		LevelMap                            mClassLevelMap;
		LevelMap                            mFileLevelMap;
		LevelMap                            mTagLevelMap;
		std::map<std::string, unsigned int> mUniqueLogMessages;
		
		LLError::FatalFunction              mCrashFunction;
		LLError::TimeFunction               mTimeFunction;
		
		Recorders                           mRecorders;
		RecorderPtr                         mFileRecorder;
		RecorderPtr                         mFixedBufferRecorder;
		std::string                         mFileRecorderFileName;
		
		int									mShouldLogCallCounter;
		
	private:
		SettingsConfig();
	};

	typedef LLPointer<SettingsConfig> SettingsConfigPtr;

	class Settings
	{
	public:
		static AIThreadSafeSimple<Settings>& get();

		SettingsConfigPtr getSettingsConfig();
		
		void reset();
		SettingsStoragePtr saveAndReset();
		void restore(SettingsStoragePtr pSettingsStorage);
		
	private:
		friend class AIThreadSafeBits<Settings>;		// Calls destructor.
		friend class AIThreadSafeSimpleDC<Settings>;	// Calls constructor.
		Settings();
		
		SettingsConfigPtr mSettingsConfig;
	};
	
	SettingsConfig::SettingsConfig()
		: LLRefCount(),
		mPrintLocation(false),
		mDefaultLevel(LLError::LEVEL_DEBUG),
		mFunctionLevelMap(),
		mClassLevelMap(),
		mFileLevelMap(),
		mTagLevelMap(),
		mUniqueLogMessages(),
		mCrashFunction(NULL),
		mTimeFunction(NULL),
		mRecorders(),
		mFileRecorder(),
		mFixedBufferRecorder(),
		mFileRecorderFileName(),
		mShouldLogCallCounter(0)
	{
	}

	SettingsConfig::~SettingsConfig()
	{
		mRecorders.clear();
	}

	Settings::Settings()
		: mSettingsConfig(new SettingsConfig())
	{
	}
	
	AIThreadSafeSimple<Settings>& Settings::get()
	{
		static AIThreadSafeSimpleDCRootPool<Settings>* ts_globals_ptr = new AIThreadSafeSimpleDCRootPool<Settings>;
		return *ts_globals_ptr;
	}
	
	SettingsConfigPtr Settings::getSettingsConfig()
	{
		return mSettingsConfig;
	}
	
	void Settings::reset()
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		mSettingsConfig = new SettingsConfig();
	}
	
	SettingsStoragePtr Settings::saveAndReset()
	{
		SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get());
		reset();
		return oldSettingsConfig;
	}
	
	void Settings::restore(SettingsStoragePtr pSettingsStorage)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get()));
		mSettingsConfig = newSettingsConfig;
	}
}

namespace LLError
{
	CallSite::CallSite(ELevel level,
		const char* file,
		int line,
		const std::type_info& class_info,
		const char* function,
		bool printOnce,
		const char** tags,
		size_t tag_count)
		: mLevel(level),
		mFile(file),
		mLine(line),
		mClassInfo(class_info),
		mFunction(function),
		mCached(false),
		mShouldLog(false),
		mPrintOnce(printOnce),
		mTags(new const char*[tag_count]),
		mTagCount(tag_count)
	{
		for (size_t i = 0; i < tag_count; i++)
		{
			mTags[i] = tags[i];
		}

		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;
		};

		mLocationString = llformat("%s(%d) :", abbreviateFile(mFile).c_str(), mLine);
		if (mFunction)
		{
#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()))
#else
			if (mClassInfo != typeid(NoClassInfo))
#endif // LL_LINUX
			{
				mFunctionString = className(mClassInfo) + "::";
		}
#endif
			mFunctionString += std::string(mFunction) + ":";
			for (size_t i = 0; i < mTagCount; i++)
			{
				mTagString += std::string("#") + mTags[i] + ((i == mTagCount - 1) ? "" : ",");
			}
		}
	}

	CallSite::~CallSite()
	{
		delete []mTags;
	}

	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, bool log_to_stderr = true)
	{
		AIAccess<LLError::Settings>(LLError::Settings::get())->reset();
		
		LLError::setDefaultLevel(LLError::LEVEL_INFO);
		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::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
			LLError::addRecorder(recordToStdErr);
		}
		
#if LL_WINDOWS
		LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
		LLError::addRecorder(recordToWinDebug);
#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();
	}
}

namespace LLError
{
	void initForServer(const std::string& identity)
	{
		std::string dir = "/opt/linden/etc";
		if (LLApp::instance())
		{
			dir = LLApp::instance()->getOption("configdir").asString();
		}
		commonInit(dir);
#if !LL_WINDOWS
		LLError::RecorderPtr recordToSyslog(new RecordToSyslog(identity));
		addRecorder(recordToSyslog);
#endif
	}

	void initForApplication(const std::string& dir, bool log_to_stderr)
	{
		commonInit(dir, log_to_stderr);
	}

	void setPrintLocation(AIAccess<Settings> const& settings_w, bool print)
	{
		settings_w->getSettingsConfig()->mPrintLocation = print;
	}

	void setPrintLocation(bool print)
	{
		setPrintLocation(AIAccess<Settings>(Settings::get()), print);
	}

	void setFatalFunction(const FatalFunction& f)
	{
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->mCrashFunction = f;
	}

    FatalFunction getFatalFunction()
    {
		return AIAccess<Settings>(Settings::get())->getSettingsConfig()->mCrashFunction;
    }

	void setTimeFunction(TimeFunction f)
	{
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->mTimeFunction = f;
	}

	void setDefaultLevel(AIAccess<Settings> const& settings_w, ELevel level)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		settings_w->getSettingsConfig()->mDefaultLevel = level;
	}

	void setDefaultLevel(ELevel level)
	{
		setDefaultLevel(AIAccess<Settings>(Settings::get()), level);
	}

	void setFunctionLevel(const std::string& function_name, ELevel level)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->mFunctionLevelMap[function_name] = level;
	}

	void setClassLevel(const std::string& class_name, ELevel level)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->mClassLevelMap[class_name] = level;
	}

	void setFileLevel(const std::string& file_name, ELevel level)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->mFileLevelMap[file_name] = level;
	}

	void setTagLevel(const std::string& tag_name, ELevel level)
	{
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		AIAccess<Settings>(Settings::get())->getSettingsConfig()->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())
		{
			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)
	{
		AIAccess<Settings> settings_w(Settings::get());
		AIAccess<Globals>(Globals::get())->invalidateCallSites();
		SettingsConfigPtr s = settings_w->getSettingsConfig();

		s->mFunctionLevelMap.clear();
		s->mClassLevelMap.clear();
		s->mFileLevelMap.clear();
		s->mTagLevelMap.clear();
		s->mUniqueLogMessages.clear();
		
		setPrintLocation(settings_w, config["print-location"]);
		setDefaultLevel(settings_w, 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->mFunctionLevelMap,	entry["functions"],	level);
			setLevels(s->mClassLevelMap,	entry["classes"],	level);
			setLevels(s->mFileLevelMap,		entry["files"],		level);
			setLevels(s->mTagLevelMap,		entry["tags"],		level);
		}
	}
}


namespace LLError
{
	Recorder::Recorder()
	:	mWantsTime(false),
		mWantsTags(false),
		mWantsLevel(true),
		mWantsLocation(false),
		mWantsFunctionName(true)
	{
	}

	Recorder::~Recorder()
	{
	}

	bool Recorder::wantsTime()
	{ 
		return mWantsTime; 
	}

	// virtual
	bool Recorder::wantsTags()
	{
		return mWantsTags;
	}

	// virtual 
	bool Recorder::wantsLevel() 
	{ 
		return mWantsLevel;
	}

	// virtual 
	bool Recorder::wantsLocation() 
	{ 
		return mWantsLocation;
	}

	// virtual 
	bool Recorder::wantsFunctionName() 
	{ 
		return mWantsFunctionName;
	}

	void addRecorder(AIAccess<Settings> const& settings_w, RecorderPtr recorder)
	{
		if (!recorder)
		{
			return;
		}
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		s->mRecorders.push_back(recorder);
	}
	
	void addRecorder(RecorderPtr recorder)
	{
		addRecorder(AIAccess<Settings>(Settings::get()), recorder);
	}

	void removeRecorder(AIAccess<Settings> const& settings_w, RecorderPtr recorder)
	{
		if (!recorder)
		{
			return;
		}
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
							s->mRecorders.end());
	}

	void removeRecorder(RecorderPtr recorder)
	{
		removeRecorder(AIAccess<Settings>(Settings::get()), recorder);
	}
}

namespace LLError
{
	void logToFile(const std::string& file_name)
	{
		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();

		removeRecorder(s->mFileRecorder);
		s->mFileRecorder.reset();
		s->mFileRecorderFileName.clear();
		
		if (file_name.empty())
		{
			return;
		}
		
		RecorderPtr recordToFile(new RecordToFile(file_name));
		if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay())
		{
			s->mFileRecorderFileName = file_name;
			s->mFileRecorder = recordToFile;
			addRecorder(recordToFile);
		}
	}
	
	void logToFixedBuffer(LLLineBuffer* fixedBuffer)
	{
		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();

		removeRecorder(s->mFixedBufferRecorder);
		s->mFixedBufferRecorder.reset();
		
		if (!fixedBuffer)
		{
			return;
		}
		
		RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
		s->mFixedBufferRecorder = recordToFixedBuffer;
		addRecorder(recordToFixedBuffer);
	}

	std::string logFileName()
	{
		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		return s->mFileRecorderFileName;
	}
}

namespace
{
	void writeToRecorders(AIAccess<LLError::Settings>& settings_w, const LLError::CallSite& site, const std::string& message, bool show_location = true, bool show_time = true, bool show_tags = true, bool show_level = true, bool show_function = true)
	{
		LLError::ELevel level = site.mLevel;
		LLError::SettingsConfigPtr s = settings_w->getSettingsConfig();
	
		for (Recorders::const_iterator i = s->mRecorders.begin();
			i != s->mRecorders.end();
			++i)
		{
			LLError::RecorderPtr r = *i;
			
			std::ostringstream message_stream;

			if (show_location && (r->wantsLocation() || level == LLError::LEVEL_ERROR || s->mPrintLocation))
			{
				message_stream << site.mLocationString << " ";
			}

			if (show_time && r->wantsTime() && s->mTimeFunction != NULL)
			{
				message_stream << s->mTimeFunction() << " ";
			}

			if (show_level && r->wantsLevel())
			{
				message_stream << site.mLevelString;
			}
				
			if (show_tags && r->wantsTags())
			{
				message_stream << site.mTagString;
			}

			if ((show_level && r->wantsLevel())||
				(show_tags && r->wantsTags()))
			{
				message_stream << " ";
			}

			if (show_function && r->wantsFunctionName())
			{
				message_stream << site.mFunctionString << " ";
			}

			message_stream << message;

			r->recordMessage(level, message_stream.str());
		}
	}
}


LLGlobalMutex gLogMutex;
LLGlobalMutex gCallStacksLogMutex;

namespace {
	bool checkLevelMap(const LevelMap& map, const std::string& key,
						LLError::ELevel& level)
	{
		bool stop_checking;
		LevelMap::const_iterator i = map.find(key);
		if (i == map.end())
		{
			return stop_checking = false;
		}
		
			level = i->second;
		return stop_checking = true;
	}
	
	bool checkLevelMap(	const LevelMap& map, 
						const char *const * keys, 
						size_t count,
						LLError::ELevel& level)
	{
		bool found_level = false;

		LLError::ELevel tag_level = LLError::LEVEL_NONE;

		for (size_t i = 0; i < count; i++)
		{
			LevelMap::const_iterator it = map.find(keys[i]);
			if (it != map.end())
			{
				found_level = true;
				tag_level = llmin(tag_level, it->second);
			}
		}

		if (found_level)
		{
			level = tag_level;
		}
		return found_level;
	}
	
	class LogLock
	{
	public:
		LogLock();
		~LogLock();
		bool ok() const { return mOK; }
	private:
		bool mLocked;
		bool mOK;
	};
	
	LogLock::LogLock()
		: mLocked(false), mOK(false)
	{
		if (!gLogMutex.isInitalized())
		{
			mOK = true;
			return;
		}
		
		const int MAX_RETRIES = 5;
		for (int attempts = 0; attempts < MAX_RETRIES; ++attempts)
		{
			if (gLogMutex.try_lock())
			{
				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)
		{
			gLogMutex.unlock();
		}
	}
}

namespace LLError
{
	bool Log::shouldLog(CallSite& site)
	{
		LogLock lock;
		if (!lock.ok())
		{
			return false;
		}
		
		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		
		s->mShouldLogCallCounter++;
		
		const std::string& class_name = className(site.mClassInfo);
		std::string function_name;
		if (site.mFunction)
		{
			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->mDefaultLevel;

		// The most specific match found will be used as the log level,
		// since the computation short circuits.
		// So, in increasing order of importance:
		// Default < Tags < File < Class < Function
		checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel)
		|| checkLevelMap(s->mClassLevelMap, class_name, compareLevel)
		|| checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel)
		|| (site.mTagCount > 0
			? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel) 
			: false);

		site.mCached = true;
		AIAccess<Globals>(Globals::get())->addCallSite(site);
		return site.mShouldLog = site.mLevel >= compareLevel;
	}


	std::ostringstream* Log::out()
	{
		LogLock lock;
		if (lock.ok())
		{
			AIAccess<Globals> g(Globals::get());

			if (!g->messageStreamInUse)
			{
				g->messageStreamInUse = true;
				return &g->messageStream;
			}
		}
		
		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' ;
	   }
	   
	   AIAccess<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)
	{
		LogLock lock;
		if (!lock.ok())
		{
			return;
		}
		
		std::string message = out->str();
		{
			AIAccess<Globals> g(Globals::get());
			if (out == &g->messageStream)
			{
				g->messageStream.clear();
				g->messageStream.str("");
				g->messageStreamInUse = false;
			}
			else
			{
				delete out;
			}
		}

		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		
		if (site.mLevel == LEVEL_ERROR)
		{
			writeToRecorders(settings_w, site, "error", true, true, true, false, false);
		}
		
		std::ostringstream message_stream;

		bool need_function = site.mFunction;
		if (need_function && !site.mTagString.empty())
		{
#if LL_DEBUG
			// Suppress printing mFunction if mBroadTag is set, starts with
			// "Plugin " and ends with "child": a debug message from a plugin.
			size_t taglen = site.mTagString.length();
			if (taglen >= 12 && strncmp(site.mTagString.c_str(), "Plugin ", 7) == 0 &&
				strcmp(site.mTagString.c_str() + taglen - 5, "child") == 0)
			{
				need_function = false;
			}
#endif
		}

		if (site.mPrintOnce)
		{
			std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);
			if (messageIter != s->mUniqueLogMessages.end())
			{
				messageIter->second++;
				unsigned int num_messages = messageIter->second;
				if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
				{
					message_stream << "ONCE (" << num_messages << "th time seen): ";
				} 
				else
				{
					return;
				}
			}
			else 
			{
				message_stream << "ONCE: ";
				s->mUniqueLogMessages[message] = 1;
			}
		}
		
		message_stream << message;
		
		writeToRecorders(settings_w, site, message_stream.str(), true, true, true, true, need_function);
		
		if (site.mLevel == LEVEL_ERROR  &&  s->mCrashFunction)
		{
			s->mCrashFunction(message_stream.str());
		}
	}
}

namespace LLError
{
	SettingsStoragePtr saveAndResetSettings()
	{
		return AIAccess<Settings>(Settings::get())->saveAndReset();
	}
	
	void restoreSettings(SettingsStoragePtr pSettingsStorage)
	{
		return AIAccess<Settings>(Settings::get())->restore(pSettingsStorage);
	}

	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());
	}
	
	void replaceChar(std::string& s, char old, char replacement)
	{
		std::string::size_type i = 0;
		std::string::size_type len = s.length();
		for ( ; i < len; i++ )
		{
			if (s[i] == old)
			{
				s[i] = replacement;
			}
		}
	}

	std::string abbreviateFile(const std::string& filePath)
	{
		std::string f = filePath;
#if LL_WINDOWS
		replaceChar(f, '\\', '/');
#endif
		static std::string indra_prefix = "indra/";
		f = removePrefix(f, indra_prefix);

#if LL_DARWIN
		static std::string newview_prefix = "newview/../";
		f = removePrefix(f, newview_prefix);
#endif

		return f;
	}

	int shouldLogCallCount()
	{
		AIAccess<Settings> settings_w(Settings::get());
		SettingsConfigPtr s = settings_w->getSettingsConfig();
		return s->mShouldLogCallCounter;
	}

#if LL_WINDOWS
		// MSVC is optimizing the error away.
		#pragma optimize("", off)
#endif
	void crashAndLoop(const std::string& message)
	{
#ifdef CWDEBUG
		DoutFatal(dc::core, message);
#else
		// Now, we go kaboom!
		int* make_me_crash = nullptr;
		*make_me_crash = 0xDEADBEEF;

		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);
#endif
	}
#if LL_WINDOWS
		#pragma optimize("", on)
#endif

	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";
	}
}

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 (!gCallStacksLogMutex.isInitalized())
		{
			mOK = true;
			return;
		}
		
		const int MAX_RETRIES = 5;
		for (int attempts = 0; attempts < MAX_RETRIES; ++attempts)
		{
			if (gCallStacksLogMutex.try_lock())
			{
				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)
		{
			gCallStacksLogMutex.unlock();
		}
	}
#endif

	//static
   void LLCallStacks::allocateStackBuffer()
   {
	   if(sBuffer == NULL)
	   {
		   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 ;
	   }
   }

   void LLCallStacks::freeStackBuffer()
   {
	   if(sBuffer != NULL)
	   {
		   delete [] sBuffer[0] ;
		   delete [] sBuffer ;
		   sBuffer = NULL ;
	   }
   }

   //static
   void LLCallStacks::push(const char* function, const int line)
   {
	   CallStacksLogLock lock;
       if (!lock.ok())
       {
           return;
       }

	   if(sBuffer == NULL)
	   {
		   allocateStackBuffer();
	   }

	   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 == NULL)
	   {
		   allocateStackBuffer();
	   }

	   if(sIndex > 511)
	   {
		   clear() ;
	   }

	   LLError::Log::flush(_out, sBuffer[sIndex++]) ;	   
   }

   //static
   void LLCallStacks::print()
   {
	   CallStacksLogLock lock;
       if (!lock.ok())
       {
           return;
       }

       if(sIndex > 0)
       {
           LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
           while(sIndex > 0)
           {                  
			   sIndex-- ;
               LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
           }
           LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
       }

	   if(sBuffer != NULL)
	   {
		   freeStackBuffer();
	   }
   }

   //static
   void LLCallStacks::clear()
   {
       sIndex = 0 ;
   }

   //static
   void LLCallStacks::cleanup()
   {
	   freeStackBuffer();
   }
}

