PageRenderTime 38ms CodeModel.GetById 10ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/llcrashlogger/llcrashlogger.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 437 lines | 312 code | 70 blank | 55 comment | 38 complexity | 9f38b1894c00cd545df54a6d2a7c7bb2 MD5 | raw file
  1 /** 
  2* @file llcrashlogger.cpp
  3* @brief Crash logger implementation
  4*
  5* $LicenseInfo:firstyear=2003&license=viewerlgpl$
  6* Second Life Viewer Source Code
  7* Copyright (C) 2010, Linden Research, Inc.
  8* 
  9* This library is free software; you can redistribute it and/or
 10* modify it under the terms of the GNU Lesser General Public
 11* License as published by the Free Software Foundation;
 12* version 2.1 of the License only.
 13* 
 14* This library is distributed in the hope that it will be useful,
 15* but WITHOUT ANY WARRANTY; without even the implied warranty of
 16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17* Lesser General Public License for more details.
 18* 
 19* You should have received a copy of the GNU Lesser General Public
 20* License along with this library; if not, write to the Free Software
 21* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22* 
 23* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24* $/LicenseInfo$
 25*/
 26#include <cstdio>
 27#include <cstdlib>
 28#include <sstream>
 29#include <map>
 30
 31#include "llcrashlogger.h"
 32#include "linden_common.h"
 33#include "llstring.h"
 34#include "indra_constants.h"	// CRASH_BEHAVIOR_...
 35#include "llerror.h"
 36#include "llerrorcontrol.h"
 37#include "lltimer.h"
 38#include "lldir.h"
 39#include "llfile.h"
 40#include "llsdserialize.h"
 41#include "lliopipe.h"
 42#include "llpumpio.h"
 43#include "llhttpclient.h"
 44#include "llsdserialize.h"
 45#include "llproxy.h"
 46
 47LLPumpIO* gServicePump;
 48BOOL gBreak = false;
 49BOOL gSent = false;
 50
 51class LLCrashLoggerResponder : public LLHTTPClient::Responder
 52{
 53public:
 54	LLCrashLoggerResponder() 
 55	{
 56	}
 57
 58	virtual void error(U32 status, const std::string& reason)
 59	{
 60		gBreak = true;
 61	}
 62
 63	virtual void result(const LLSD& content)
 64	{	
 65		gBreak = true;
 66		gSent = true;
 67	}
 68};
 69
 70LLCrashLogger::LLCrashLogger() :
 71	mCrashBehavior(CRASH_BEHAVIOR_ALWAYS_SEND),
 72	mCrashInPreviousExec(false),
 73	mCrashSettings("CrashSettings"),
 74	mSentCrashLogs(false),
 75	mCrashHost("")
 76{
 77	// Set up generic error handling
 78	setupErrorHandling();
 79}
 80
 81LLCrashLogger::~LLCrashLogger()
 82{
 83
 84}
 85
 86// TRIM_SIZE must remain larger than LINE_SEARCH_SIZE.
 87const int TRIM_SIZE = 128000;
 88const int LINE_SEARCH_DIST = 500;
 89const std::string SKIP_TEXT = "\n ...Skipping... \n";
 90void trimSLLog(std::string& sllog)
 91{
 92	if(sllog.length() > TRIM_SIZE * 2)
 93	{
 94		std::string::iterator head = sllog.begin() + TRIM_SIZE;
 95		std::string::iterator tail = sllog.begin() + sllog.length() - TRIM_SIZE;
 96		std::string::iterator new_head = std::find(head, head - LINE_SEARCH_DIST, '\n');
 97		if(new_head != head - LINE_SEARCH_DIST)
 98		{
 99			head = new_head;
100		}
101
102		std::string::iterator new_tail = std::find(tail, tail + LINE_SEARCH_DIST, '\n');
103		if(new_tail != tail + LINE_SEARCH_DIST)
104		{
105			tail = new_tail;
106		}
107
108		sllog.erase(head, tail);
109		sllog.insert(head, SKIP_TEXT.begin(), SKIP_TEXT.end());
110	}
111}
112
113std::string getStartupStateFromLog(std::string& sllog)
114{
115	std::string startup_state = "STATE_FIRST";
116	std::string startup_token = "Startup state changing from ";
117
118	int index = sllog.rfind(startup_token);
119	if (index < 0 || index + startup_token.length() > sllog.length()) {
120		return startup_state;
121	}
122
123	// find new line
124	char cur_char = sllog[index + startup_token.length()];
125	std::string::size_type newline_loc = index + startup_token.length();
126	while(cur_char != '\n' && newline_loc < sllog.length())
127	{
128		newline_loc++;
129		cur_char = sllog[newline_loc];
130	}
131	
132	// get substring and find location of " to "
133	std::string state_line = sllog.substr(index, newline_loc - index);
134	std::string::size_type state_index = state_line.find(" to ");
135	startup_state = state_line.substr(state_index + 4, state_line.length() - state_index - 4);
136
137	return startup_state;
138}
139
140void LLCrashLogger::gatherFiles()
141{
142	updateApplication("Gathering logs...");
143
144	// Figure out the filename of the debug log
145	std::string db_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"debug_info.log");
146	std::ifstream debug_log_file(db_file_name.c_str());
147
148	// Look for it in the debug_info.log file
149	if (debug_log_file.is_open())
150	{		
151		LLSDSerialize::fromXML(mDebugLog, debug_log_file);
152
153		mCrashInPreviousExec = mDebugLog["CrashNotHandled"].asBoolean();
154
155		mFileMap["SecondLifeLog"] = mDebugLog["SLLog"].asString();
156		mFileMap["SettingsXml"] = mDebugLog["SettingsFilename"].asString();
157		if(mDebugLog.has("CAFilename"))
158		{
159			LLCurl::setCAFile(mDebugLog["CAFilename"].asString());
160		}
161		else
162		{
163			LLCurl::setCAFile(gDirUtilp->getCAFile());
164		}
165
166		llinfos << "Using log file from debug log " << mFileMap["SecondLifeLog"] << llendl;
167		llinfos << "Using settings file from debug log " << mFileMap["SettingsXml"] << llendl;
168	}
169	else
170	{
171		// Figure out the filename of the second life log
172		LLCurl::setCAFile(gDirUtilp->getCAFile());
173		mFileMap["SecondLifeLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log");
174		mFileMap["SettingsXml"] = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,"settings.xml");
175	}
176
177	if(mCrashInPreviousExec)
178	{
179		// Restarting after freeze.
180		// Replace the log file ext with .old, since the 
181		// instance that launched this process has overwritten
182		// SecondLife.log
183		std::string log_filename = mFileMap["SecondLifeLog"];
184		log_filename.replace(log_filename.size() - 4, 4, ".old");
185		mFileMap["SecondLifeLog"] = log_filename;
186	}
187
188	gatherPlatformSpecificFiles();
189
190	//Use the debug log to reconstruct the URL to send the crash report to
191	if(mDebugLog.has("CrashHostUrl"))
192	{
193		// Crash log receiver has been manually configured.
194		mCrashHost = mDebugLog["CrashHostUrl"].asString();
195	}
196	else if(mDebugLog.has("CurrentSimHost"))
197	{
198		mCrashHost = "https://";
199		mCrashHost += mDebugLog["CurrentSimHost"].asString();
200		mCrashHost += ":12043/crash/report";
201	}
202	else if(mDebugLog.has("GridName"))
203	{
204		// This is a 'little' hacky, but its the best simple solution.
205		std::string grid_host = mDebugLog["GridName"].asString();
206		LLStringUtil::toLower(grid_host);
207
208		mCrashHost = "https://login.";
209		mCrashHost += grid_host;
210		mCrashHost += ".lindenlab.com:12043/crash/report";
211	}
212
213	// Use login servers as the alternate, since they are already load balanced and have a known name
214	mAltCrashHost = "https://login.agni.lindenlab.com:12043/crash/report";
215
216	mCrashInfo["DebugLog"] = mDebugLog;
217	mFileMap["StatsLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stats.log");
218	
219	updateApplication("Encoding files...");
220
221	for(std::map<std::string, std::string>::iterator itr = mFileMap.begin(); itr != mFileMap.end(); ++itr)
222	{
223		std::ifstream f((*itr).second.c_str());
224		if(!f.is_open())
225		{
226			std::cout << "Can't find file " << (*itr).second << std::endl;
227			continue;
228		}
229		std::stringstream s;
230		s << f.rdbuf();
231
232		std::string crash_info = s.str();
233		if(itr->first == "SecondLifeLog")
234		{
235			if(!mCrashInfo["DebugLog"].has("StartupState"))
236			{
237				mCrashInfo["DebugLog"]["StartupState"] = getStartupStateFromLog(crash_info);
238			}
239			trimSLLog(crash_info);
240		}
241
242		mCrashInfo[(*itr).first] = LLStringFn::strip_invalid_xml(rawstr_to_utf8(crash_info));
243	}
244	
245	// Add minidump as binary.
246	std::string minidump_path = mDebugLog["MinidumpPath"];
247	if(minidump_path != "")
248	{
249		std::ifstream minidump_stream(minidump_path.c_str(), std::ios_base::in | std::ios_base::binary);
250		if(minidump_stream.is_open())
251		{
252			minidump_stream.seekg(0, std::ios::end);
253			size_t length = minidump_stream.tellg();
254			minidump_stream.seekg(0, std::ios::beg);
255			
256			LLSD::Binary data;
257			data.resize(length);
258			
259			minidump_stream.read(reinterpret_cast<char *>(&(data[0])),length);
260			minidump_stream.close();
261			
262			mCrashInfo["Minidump"] = data;
263		}
264	}
265	mCrashInfo["DebugLog"].erase("MinidumpPath");
266}
267
268LLSD LLCrashLogger::constructPostData()
269{
270	LLSD ret;
271	return mCrashInfo;
272}
273
274const char* const CRASH_SETTINGS_FILE = "settings_crash_behavior.xml";
275
276S32 LLCrashLogger::loadCrashBehaviorSetting()
277{
278	// First check user_settings (in the user's home dir)
279	std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
280	if (! mCrashSettings.loadFromFile(filename))
281	{
282		// Next check app_settings (in the SL program dir)
283		std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, CRASH_SETTINGS_FILE);
284		mCrashSettings.loadFromFile(filename);
285	}
286
287	// If we didn't load any files above, this will return the default
288	S32 value = mCrashSettings.getS32("CrashSubmitBehavior");
289
290	// Whatever value we got, make sure it's valid
291	switch (value)
292	{
293	case CRASH_BEHAVIOR_NEVER_SEND:
294		return CRASH_BEHAVIOR_NEVER_SEND;
295	case CRASH_BEHAVIOR_ALWAYS_SEND:
296		return CRASH_BEHAVIOR_ALWAYS_SEND;
297	}
298
299	return CRASH_BEHAVIOR_ASK;
300}
301
302bool LLCrashLogger::saveCrashBehaviorSetting(S32 crash_behavior)
303{
304	switch (crash_behavior)
305	{
306	case CRASH_BEHAVIOR_ASK:
307	case CRASH_BEHAVIOR_NEVER_SEND:
308	case CRASH_BEHAVIOR_ALWAYS_SEND:
309		break;
310	default:
311		return false;
312	}
313
314	mCrashSettings.setS32("CrashSubmitBehavior", crash_behavior);
315	std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE);
316	mCrashSettings.saveToFile(filename, FALSE);
317
318	return true;
319}
320
321bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg, int retries, int timeout)
322{
323	gBreak = false;
324	for(int i = 0; i < retries; ++i)
325	{
326		updateApplication(llformat("%s, try %d...", msg.c_str(), i+1));
327		LLHTTPClient::post(host, data, new LLCrashLoggerResponder(), timeout);
328		while(!gBreak)
329		{
330			updateApplication(); // No new message, just pump the IO
331		}
332		if(gSent)
333		{
334			return gSent;
335		}
336	}
337	return gSent;
338}
339
340bool LLCrashLogger::sendCrashLogs()
341{
342	gatherFiles();
343
344	LLSD post_data;
345	post_data = constructPostData();
346
347	updateApplication("Sending reports...");
348
349	std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
350														   "SecondLifeCrashReport");
351	std::string report_file = dump_path + ".log";
352
353	std::ofstream out_file(report_file.c_str());
354	LLSDSerialize::toPrettyXML(post_data, out_file);
355	out_file.close();
356
357	bool sent = false;
358
359	//*TODO: Translate
360	if(mCrashHost != "")
361	{
362		sent = runCrashLogPost(mCrashHost, post_data, std::string("Sending to server"), 3, 5);
363	}
364
365	if(!sent)
366	{
367		sent = runCrashLogPost(mAltCrashHost, post_data, std::string("Sending to alternate server"), 3, 5);
368	}
369	
370	mSentCrashLogs = sent;
371
372	return true;
373}
374
375void LLCrashLogger::updateApplication(const std::string& message)
376{
377	gServicePump->pump();
378    gServicePump->callback();
379	if (!message.empty()) llinfos << message << llendl;
380}
381
382bool LLCrashLogger::init()
383{
384	LLCurl::initClass(false);
385
386	// We assume that all the logs we're looking for reside on the current drive
387	gDirUtilp->initAppDirs("SecondLife");
388
389	LLError::initForApplication(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
390
391	// Default to the product name "Second Life" (this is overridden by the -name argument)
392	mProductName = "Second Life";
393
394	// Rename current log file to ".old"
395	std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log.old");
396	std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "crashreport.log");
397	LLFile::rename(log_file.c_str(), old_log_file.c_str());
398
399	// Set the log file to crashreport.log
400	LLError::logToFile(log_file);
401	
402	mCrashSettings.declareS32("CrashSubmitBehavior", CRASH_BEHAVIOR_ALWAYS_SEND,
403							  "Controls behavior when viewer crashes "
404							  "(0 = ask before sending crash report, "
405							  "1 = always send crash report, "
406							  "2 = never send crash report)");
407
408	// llinfos << "Loading crash behavior setting" << llendl;
409	// mCrashBehavior = loadCrashBehaviorSetting();
410
411	// If user doesn't want to send, bail out
412	if (mCrashBehavior == CRASH_BEHAVIOR_NEVER_SEND)
413	{
414		llinfos << "Crash behavior is never_send, quitting" << llendl;
415		return false;
416	}
417
418	gServicePump = new LLPumpIO(gAPRPoolp);
419	gServicePump->prime(gAPRPoolp);
420	LLHTTPClient::setPump(*gServicePump);
421
422	//If we've opened the crash logger, assume we can delete the marker file if it exists
423	if( gDirUtilp )
424	{
425		std::string marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
426																 "SecondLife.exec_marker");
427		LLAPRFile::remove( marker_file );
428	}
429	
430	return true;
431}
432
433// For cleanup code common to all platforms.
434void LLCrashLogger::commonCleanup()
435{
436	LLProxy::cleanupClass();
437}