/** * @file llcrashlogger.cpp * @brief Crash logger implementation * * $LicenseInfo:firstyear=2003&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 "llviewerprecompiledheaders.h" #include "llcrashlogger.h" #include "linden_common.h" #include "llstring.h" #include "indra_constants.h" // CRASH_BEHAVIOR_... #include "llerror.h" #include "llerrorcontrol.h" #include "lltimer.h" #include "lldir.h" #include "llfile.h" #include "llsdserialize.h" #include "lliopipe.h" #include "llpumpio.h" #include "llhttpclient.h" #include "llsdserialize.h" #include "llproxy.h" #include "llwindow.h" #include "lltrans.h" #include "aistatemachine.h" #include "boost/filesystem.hpp" class AIHTTPTimeoutPolicy; extern AIHTTPTimeoutPolicy crashLoggerResponder_timeout; extern const std::string OLD_LOG_FILE; class LLCrashLoggerResponder : public LLHTTPClient::ResponderWithResult { public: LLCrashLoggerResponder() { } virtual void httpFailure(void) { LL_WARNS() << "Crash report sending failed: " << mReason << LL_ENDL; } virtual void httpSuccess(void) { std::string msg = "Crash report successfully sent"; if (mContent.has("message")) { msg += ": " + mContent["message"].asString(); } LL_INFOS() << msg << LL_ENDL; if (mContent.has("report_id")) { gSavedSettings.setS32("CrashReportID", mContent["report_id"].asInteger()); } } virtual AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return crashLoggerResponder_timeout; } virtual char const* getName(void) const { return "LLCrashLoggerResponder"; } }; LLCrashLogger::LLCrashLogger() : mCrashBehavior(CRASH_BEHAVIOR_ALWAYS_SEND), mCrashInPreviousExec(false), mCrashHost("") { } LLCrashLogger::~LLCrashLogger() { } // TRIM_SIZE must remain larger than LINE_SEARCH_SIZE. const int TRIM_SIZE = 128000; const int LINE_SEARCH_DIST = 500; const std::string SKIP_TEXT = "\n ...Skipping... \n"; void trimSLLog(std::string& sllog) { if(sllog.length() > TRIM_SIZE * 2) { std::string::iterator head = sllog.begin() + TRIM_SIZE; std::string::iterator tail = sllog.begin() + sllog.length() - TRIM_SIZE; std::string::iterator new_head = std::find(head, head - LINE_SEARCH_DIST, '\n'); if(new_head != head - LINE_SEARCH_DIST) { head = new_head; } std::string::iterator new_tail = std::find(tail, tail + LINE_SEARCH_DIST, '\n'); if(new_tail != tail + LINE_SEARCH_DIST) { tail = new_tail; } sllog.erase(head, tail); sllog.insert(head, SKIP_TEXT.begin(), SKIP_TEXT.end()); } } std::string getStartupStateFromLog(std::string& sllog) { std::string startup_state = "STATE_FIRST"; std::string startup_token = "Startup state changing from "; int index = sllog.rfind(startup_token); if (index < 0 || index + startup_token.length() > sllog.length()) { return startup_state; } // find new line char cur_char = sllog[index + startup_token.length()]; std::string::size_type newline_loc = index + startup_token.length(); while(cur_char != '\n' && newline_loc < sllog.length()) { newline_loc++; cur_char = sllog[newline_loc]; } // get substring and find location of " to " std::string state_line = sllog.substr(index, newline_loc - index); std::string::size_type state_index = state_line.find(" to "); startup_state = state_line.substr(state_index + 4, state_line.length() - state_index - 4); return startup_state; } bool miniDumpExists(const std::string& dumpDir) { bool found = false; try { if (!boost::filesystem::exists(dumpDir)) { return false; } boost::filesystem::directory_iterator end_itr; for (boost::filesystem::directory_iterator i(dumpDir); i != end_itr; ++i) { if (!boost::filesystem::is_regular_file(i->status())) continue; if (".dmp" == i->path().extension()) { found = true; break; } } } catch (const boost::filesystem::filesystem_error& e) { LL_WARNS() << "Failed to determine existance of the minidump file: '" + e.code().message() +"'" << LL_ENDL; } return found; } bool LLCrashLogger::readDebugFromXML(LLSD& dest, const std::string& filename ) { std::string db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,filename); std::ifstream debug_log_file(db_file_name.c_str()); // Look for it in the debug_info.log file if (debug_log_file.is_open()) { LLSDSerialize::fromXML(dest, debug_log_file); debug_log_file.close(); return true; } return false; } void LLCrashLogger::mergeLogs( LLSD src_sd ) { LLSD::map_iterator iter = src_sd.beginMap(); LLSD::map_iterator end = src_sd.endMap(); for( ; iter != end; ++iter) { mDebugLog[iter->first] = iter->second; } } bool LLCrashLogger::readMinidump(std::string minidump_path) { size_t length=0; std::ifstream minidump_stream(minidump_path.c_str(), std::ios_base::in | std::ios_base::binary); if(minidump_stream.is_open()) { minidump_stream.seekg(0, std::ios::end); length = (size_t)minidump_stream.tellg(); minidump_stream.seekg(0, std::ios::beg); LLSD::Binary data; data.resize(length); minidump_stream.read(reinterpret_cast(&(data[0])),length); minidump_stream.close(); mCrashInfo["Minidump"] = data; } return (length>0?true:false); } void LLCrashLogger::gatherFiles() { LL_INFOS() << "Gathering logs..." << LL_ENDL; LLSD static_sd; LLSD dynamic_sd; bool has_logs = readDebugFromXML( static_sd, "static_debug_info.log" ); has_logs |= readDebugFromXML( dynamic_sd, "dynamic_debug_info.log" ); if ( has_logs ) { mDebugLog = static_sd; mergeLogs(dynamic_sd); mCrashInPreviousExec = mDebugLog["CrashNotHandled"].asBoolean(); mFileMap["SecondLifeLog"] = mDebugLog["SLLog"].asString(); mFileMap["SettingsXml"] = mDebugLog["SettingsFilename"].asString(); if(mDebugLog.has("CAFilename")) { LLCurl::setCAFile(mDebugLog["CAFilename"].asString()); } else { LLCurl::setCAFile(gDirUtilp->getCAFile()); } LL_INFOS() << "Using log file from debug log " << mFileMap["SecondLifeLog"] << LL_ENDL; LL_INFOS() << "Using settings file from debug log " << mFileMap["SettingsXml"] << LL_ENDL; } else { // Figure out the filename of the second life log LLCurl::setCAFile(gDirUtilp->getCAFile()); mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml"); } if(mCrashInPreviousExec) { // Restarting after freeze. // Replace the log file ext with .old, since the // instance that launched this process has overwritten // SecondLife.log std::string log_filename = mFileMap["SecondLifeLog"]; log_filename.replace(log_filename.size() - 4, 4, ".old"); mFileMap["SecondLifeLog"] = log_filename; } gatherPlatformSpecificFiles(); mCrashInfo["DebugLog"] = mDebugLog; mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"stats.log"); // Singu Note: we have just started again, log has been renamed mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, OLD_LOG_FILE); LL_INFOS() << "Encoding files..." << LL_ENDL; for(std::map::iterator itr = mFileMap.begin(); itr != mFileMap.end(); ++itr) { std::ifstream f((*itr).second.c_str()); if(!f.is_open()) { LL_INFOS() << "Can't find file " << (*itr).second << LL_ENDL; continue; } std::stringstream s; s << f.rdbuf(); std::string crash_info = s.str(); if(itr->first == "SecondLifeLog") { if(!mCrashInfo["DebugLog"].has("StartupState")) { mCrashInfo["DebugLog"]["StartupState"] = getStartupStateFromLog(crash_info); } trimSLLog(crash_info); } mCrashInfo[(*itr).first] = LLStringFn::strip_invalid_xml(rawstr_to_utf8(crash_info)); } std::string minidump_path; // Add minidump as binary. bool has_minidump = mDebugLog.has("MinidumpPath"); if (has_minidump) minidump_path = mDebugLog["MinidumpPath"].asString(); if (has_minidump) { has_minidump = readMinidump(minidump_path); } if (!has_minidump) //Viewer was probably so hosed it couldn't write remaining data. Try brute force. { //Look for a filename at least 30 characters long in the dump dir which contains the characters MDMP as the first 4 characters in the file. typedef std::vector vec; std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,""); vec file_vec = gDirUtilp->getFilesInDir(pathname); for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) { if ( ( iter->length() > 30 ) && (iter->rfind(".log") != (iter->length()-4) ) ) { std::string fullname = pathname + *iter; std::ifstream fdat( fullname.c_str(), std::ifstream::binary); if (fdat) { char buf[5]; fdat.read(buf,4); fdat.close(); if (!strncmp(buf,"MDMP",4)) { minidump_path = *iter; has_minidump = readMinidump(fullname); mDebugLog["MinidumpPath"] = fullname; } } } } } } LLSD LLCrashLogger::constructPostData() { return mCrashInfo; } bool LLCrashLogger::sendCrashLog(std::string dump_dir) { gDirUtilp->setDumpDir( dump_dir ); std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SingularityCrashReport"); std::string report_file = dump_path + ".log"; gatherFiles(); LLSD post_data; post_data = constructPostData(); LL_INFOS() << "Sending reports..." << LL_ENDL; std::ofstream out_file(report_file.c_str()); LLSDSerialize::toPrettyXML(post_data, out_file); out_file.close(); LLHTTPClient::post(mCrashHost, post_data, new LLCrashLoggerResponder()); return true; } void LLCrashLogger::checkCrashDump() { #if LL_SEND_CRASH_REPORTS // 0 - ask, 1 - always send, 2 - never send S32 pref = gSavedSettings.getS32("CrashSubmitBehavior"); if (pref == 2) return; //never send mCrashHost = gSavedSettings.getString("CrashHostUrl"); std::string dumpDir = gDirUtilp->getDumpDir(); // Do we have something to send, and somewhere to send it if (!mCrashHost.empty() && miniDumpExists(dumpDir)) { if (pref == 1) // always send { sendCrashLog(dumpDir); } else // ask { U32 response = OSMessageBox(LLTrans::getString("MBFrozenCrashed"), LLTrans::getString("MBAlert"), OSMB_YESNO); if (response == OSBTN_YES) { sendCrashLog(dumpDir); } } } #endif }