Newer
Older
* @file llcrashlogger.cpp
* @brief Crash logger implementation
*
* $LicenseInfo:firstyear=2003&license=viewergpl$
*
* Copyright (c) 2003-2009, Linden Research, Inc.
*
* 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://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* 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://secondlifegrid.net/programs/open_source/licensing/flossexception
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
*
* 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.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <map>
#include "llcrashlogger.h"
#include "linden_common.h"
#include "llstring.h"
#include "indra_constants.h" // CRASH_BEHAVIOR_ASK, CRASH_SETTING_NAME
#include "llerror.h"
#include "lltimer.h"
#include "lldir.h"
#include "llsdserialize.h"
#include "lliopipe.h"
#include "llpumpio.h"
#include "llhttpclient.h"
#include "llsdserialize.h"
LLPumpIO* gServicePump;
BOOL gBreak = false;
BOOL gSent = false;
class LLCrashLoggerResponder : public LLHTTPClient::Responder
{
public:
LLCrashLoggerResponder()
{
}
virtual void error(U32 status, const std::string& reason)
{
gBreak = true;
}
virtual void result(const LLSD& content)
{
gBreak = true;
gSent = true;
}
};
bool LLCrashLoggerText::mainLoop()
{
std::cout << "Entering main loop" << std::endl;
sendCrashLogs();
return true;
}
void LLCrashLoggerText::updateApplication(const std::string& message)
{
LLCrashLogger::updateApplication(message);
std::cout << message << std::endl;
}
LLCrashLogger::LLCrashLogger() :
mCrashBehavior(CRASH_BEHAVIOR_ASK),
mCrashInPreviousExec(false),
mCrashSettings("CrashSettings"),
mSentCrashLogs(false),
mCrashHost("")
// Set up generic error handling
setupErrorHandling();
}
LLCrashLogger::~LLCrashLogger()
{
}
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// 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());
}
}
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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;
}
void LLCrashLogger::gatherFiles()
{
/*
//TODO:This function needs to be reimplemented somewhere in here...
if(!previous_crash && is_crash_log)
{
// Make sure the file isn't too old.
double age = difftime(gLaunchTime, stat_data.st_mtimespec.tv_sec);
// llinfos << "age is " << age << llendl;
if(age > 60.0)
{
// The file was last modified more than 60 seconds before the crash reporter was launched. Assume it's stale.
llwarns << "File " << mFilename << " is too old!" << llendl;
return;
}
}
*/
updateApplication("Gathering logs...");
// Figure out the filename of the debug log
std::string db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log");
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(mDebugLog, debug_log_file);
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());
}
llinfos << "Using log file from debug log " << mFileMap["SecondLifeLog"] << llendl;
llinfos << "Using settings file from debug log " << mFileMap["SettingsXml"] << llendl;
}
else
{
// Figure out the filename of the second life log
mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log");
mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml");
}
#if !LL_DARWIN
#else
#endif
{
// 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();
//Use the debug log to reconstruct the URL to send the crash report to
if(mDebugLog.has("CurrentSimHost"))
mCrashHost = "https://";
mCrashHost += mDebugLog["CurrentSimHost"].asString();
mCrashHost += ":12043/crash/report";
else if(mDebugLog.has("GridName"))
{
// This is a 'little' hacky, but its the best simple solution.
std::string grid_host = mDebugLog["GridName"].asString();
LLStringUtil::toLower(grid_host);
mCrashHost = "https://login.";
mCrashHost += grid_host;
mCrashHost += ".lindenlab.com:12043/crash/report";
}
// Use login servers as the alternate, since they are already load balanced and have a known name
mAltCrashHost = "https://login.agni.lindenlab.com:12043/crash/report";
mCrashInfo["DebugLog"] = mDebugLog;
mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log");
mFileMap["StackTrace"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log");
updateApplication("Encoding files...");
for(std::map<std::string, std::string>::iterator itr = mFileMap.begin(); itr != mFileMap.end(); ++itr)
{
std::ifstream f((*itr).second.c_str());
if(!f.is_open())
{
std::cout << "Can't find file " << (*itr).second << std::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);
}
mCrashInfo[(*itr).first] = rawstr_to_utf8(crash_info);
}
}
LLSD LLCrashLogger::constructPostData()
{
LLSD ret;
return mCrashInfo;
}
S32 LLCrashLogger::loadCrashBehaviorSetting()
{
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
mCrashSettings.loadFromFile(filename);
S32 value = mCrashSettings.getS32(CRASH_BEHAVIOR_SETTING);
if (value < CRASH_BEHAVIOR_ASK || CRASH_BEHAVIOR_NEVER_SEND < value) return CRASH_BEHAVIOR_ASK;
return value;
}
bool LLCrashLogger::saveCrashBehaviorSetting(S32 crash_behavior)
{
if (crash_behavior != CRASH_BEHAVIOR_ASK && crash_behavior != CRASH_BEHAVIOR_ALWAYS_SEND) return false;
mCrashSettings.setS32(CRASH_BEHAVIOR_SETTING, crash_behavior);
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
mCrashSettings.saveToFile(filename, FALSE);
return true;
}
bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg, int retries, int timeout)
{
gBreak = false;
std::string status_message;
for(int i = 0; i < retries; ++i)
{
status_message = llformat("%s, try %d...", msg.c_str(), i+1);
LLHTTPClient::post(host, data, new LLCrashLoggerResponder(), timeout);
while(!gBreak)
{
updateApplication(status_message);
}
if(gSent)
{
return gSent;
}
}
return gSent;
}
bool LLCrashLogger::sendCrashLogs()
{
gatherFiles();
LLSD post_data;
post_data = constructPostData();
updateApplication("Sending reports...");
std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
"SecondLifeCrashReport");
std::string report_file = dump_path + ".log";
std::ofstream out_file(report_file.c_str());
LLSDSerialize::toPrettyXML(post_data, out_file);
out_file.close();
bool sent = false;
//*TODO: Translate
if(mCrashHost != "")
sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3, 5);
sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3, 5);
mSentCrashLogs = sent;
void LLCrashLogger::updateApplication(const std::string& message)
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
{
gServicePump->pump();
gServicePump->callback();
}
bool LLCrashLogger::init()
{
// We assume that all the logs we're looking for reside on the current drive
gDirUtilp->initAppDirs("SecondLife");
// Default to the product name "Second Life" (this is overridden by the -name argument)
mProductName = "Second Life";
mCrashSettings.declareS32(CRASH_BEHAVIOR_SETTING, CRASH_BEHAVIOR_ASK, "Controls behavior when viewer crashes "
"(0 = ask before sending crash report, 1 = always send crash report, 2 = never send crash report)");
llinfos << "Loading crash behavior setting" << llendl;
mCrashBehavior = loadCrashBehaviorSetting();
// If user doesn't want to send, bail out
if (mCrashBehavior == CRASH_BEHAVIOR_NEVER_SEND)
{
llinfos << "Crash behavior is never_send, quitting" << llendl;
return false;
}
gServicePump = new LLPumpIO(gAPRPoolp);
gServicePump->prime(gAPRPoolp);
LLHTTPClient::setPump(*gServicePump);
//If we've opened the crash logger, assume we can delete the marker file if it exists
if( gDirUtilp )
{
std::string marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.exec_marker");
LLAPRFile::remove( marker_file );