Files
UnrealEngineUWP/Engine/Source/Programs/CrashReport/AutoReporter/Program.cs
2014-03-14 14:13:41 -04:00

519 lines
18 KiB
C#

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
using System;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
namespace AutoReporter
{
class OutputLogFile
{
private StreamWriter MyStreamWriter;
private string FileName;
public OutputLogFile(string filename)
{
try
{
MyStreamWriter = new StreamWriter(filename);
this.FileName = filename;
}
catch(Exception)
{
MyStreamWriter = null;
}
}
public void WriteLine(string line)
{
if(MyStreamWriter != null)
{
try
{
MyStreamWriter.WriteLine(line);
MyStreamWriter.Flush();
}
catch(Exception)
{
}
}
#if DEBUG
Console.WriteLine(line);
#endif
}
public void Close()
{
if(MyStreamWriter != null)
{
MyStreamWriter.Close();
}
}
};
static class Program
{
private static EpicBalloon NotifyObject = null;
private const int BalloonTimeInMs = 2000;
/**
* Code for calling SendMessage.
*/
#region Win32 glue
// import the SendMessage function so we can send windows messages to the UnrealConsole
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private static extern int RegisterWindowMessage(string lpString);
// Constants from the Platform SDK.
private const int HWND_BROADCAST = 0xffff;
#endregion
/**
* AutoReporter is a program for sending crash data to a web service.
* First it opens a log file on the local machine. Then it parses the crash dump.
* Next the ReportService is used to create a new report from data extracted from the crash dump.
* The log file and ini dump are uploaded to the server. If all this succeeds, the user is prompted
* to enter a description of the crash, which is then sent to the ReportService.
* Finally, if no errors occur the dumps and log are deleted.
*
* 3 arguments expected: AutoReport Dump file name, Log file name and Ini dump file name
*/
[STAThread]
static void Main (string[] args)
{
Application.EnableVisualStyles ();
Application.SetCompatibleTextRenderingDefault (false);
string logFileName;
if (args.Length >= 2) {
string logDirectory;
int endOfLogPath = args [2].LastIndexOf ('/');
logDirectory = args [2].Substring (0, endOfLogPath + 1);
logFileName = logDirectory + "AutoReportLog.txt";
} else {
logFileName = "AutoReportLog.txt";
}
OutputLogFile LogFile = new OutputLogFile (logFileName);
LogFile.WriteLine ("Log opened: " + logFileName);
LogFile.WriteLine ("");
LogFile.WriteLine ("Current Time = " + DateTime.Now.ToString ());
LogFile.WriteLine ("");
LogFile.WriteLine ("Arguments:");
foreach (string arg in args) {
LogFile.WriteLine (arg);
}
if (args.Length < 6) {
LogFile.WriteLine ("Expected 5 arguments: AutoReport Dump file name, Log file name, Ini dump file name, Mini dump file name, Crash video file name and ProcessID");
LogFile.Close ();
return;
}
Int32 ProcessId = Convert.ToInt32 (args [0]);
Thread.Sleep (1000);
// Check for additional options
bool bForceUnattended = false;
bool bShowBalloon = false;
for (int ExtraArgIndex = 6; ExtraArgIndex < args.Length; ++ExtraArgIndex) {
string CurArgString = args [ExtraArgIndex];
// -Unattended : forces unattended mode regardless of command line string
if (CurArgString.Equals ("-Unattended", StringComparison.OrdinalIgnoreCase)) {
bForceUnattended = true;
}
// -Balloon : displays a system tray notify icon (balloon) and forces unattended mode
else if (CurArgString.Equals ("-Balloon", StringComparison.OrdinalIgnoreCase)) {
// balloon not possible on Mac
if(Environment.OSVersion.Platform != PlatformID.Unix)
bShowBalloon = true;
// Unattended mode is implied with -Balloon
bForceUnattended = true;
} else {
LogFile.WriteLine (String.Format ("Unrecognized parameter: {0}", CurArgString));
LogFile.Close ();
return;
}
}
ReportFile rFile = new ReportFile ();
ReportFileData reportData = new ReportFileData ();
LogFile.WriteLine ("Parsing report file: " + args [1]);
if (!rFile.ParseReportFile (args [1], reportData, LogFile)) {
LogFile.WriteLine ("Failed to parse report file: " + args [1]);
LogFile.Close ();
return;
}
string ProcessName = reportData.GameName;
Int32 SpaceIndex = ProcessName.IndexOf (' ');
if (SpaceIndex != -1) {
// This is likely UE4 UDK... parse off the game name
ProcessName = ProcessName.Substring (0, SpaceIndex);
}
bool bIsUnattended = reportData.CommandLine.Contains ("unattended") || bForceUnattended || Environment.OSVersion.Platform == PlatformID.Unix;
string CrashDescription;
string Summary = CrashDescription = bShowBalloon ? "Handled error" : "Unattended mode";
if (bShowBalloon)
{
String MsgText = "An unexpected error has occurred but the application has recovered. A report will be submitted to the QA database.";
// If the error occurred in the editor then we'll remind them to save their changes
if (reportData.EngineMode.Contains("Editor"))
{
MsgText += " Remember to save your work often.";
}
// Notify the user of the error by launching the NotifyObject.
NotifyObject = new EpicBalloon();
NotifyObject.Show(MsgText, BalloonTimeInMs);
}
if (!bIsUnattended)
{
LogFile.WriteLine ("Attempting to create a new crash description form...");
Form1 crashDescForm = new Form1 ();
crashDescForm.summary = Summary;
crashDescForm.crashDesc = CrashDescription;
LogFile.WriteLine ("Running attended...");
crashDescForm.SetCallStack (reportData.CallStack);
LogFile.WriteLine ("Running the application...");
Application.Run (crashDescForm);
Summary = crashDescForm.summary;
CrashDescription = crashDescForm.crashDesc;
}
LogFile.WriteLine ("Crash Summary = " + Summary);
LogFile.WriteLine ("Crash Description = " + CrashDescription);
LogFile.WriteLine ("Registering report service...");
// Create an instance of AutoReportService handler to make a web request to the AutoReportService to create a Crash Report in the database. Once this is complete we'll upload the associated files.
ReportService.RegisterReport reportService = new ReportService.RegisterReport ();
Exception serviceException = new Exception ("");
bool serviceError = false;
LogFile.WriteLine ("Attempting to create a new crash...");
int uniqueIndex = -1;
// Make the web request
try {
uniqueIndex = reportService.CreateNewCrash (-1, reportData.ComputerName, reportData.UserName, reportData.GameName, reportData.PlatformName,
reportData.LanguageExt, reportData.TimeOfCrash, reportData.BuildVer, reportData.ChangelistVer, reportData.CommandLine,
reportData.BaseDir, reportData.CallStack, reportData.EngineMode);
} catch (Exception e) {
LogFile.WriteLine ("AutoReporter had an exception in accessing the reportService! --> " + e.ToString ());
serviceException = e;
serviceError = true;
LogFile.WriteLine (e.Message);
}
LogFile.WriteLine ("");
LogFile.WriteLine ("uniqueIndex = " + uniqueIndex.ToString ());
LogFile.WriteLine ("");
if (uniqueIndex == -1) {
LogFile.WriteLine ("The service failed to create a new Crash!");
serviceError = true;
serviceException = new Exception ("The service failed to create a new Crash!");
}
LogFile.WriteLine ("Attempting to create a new UpdateReportFiles instance...");
// Create handler to upload the files.
UploadReportFiles reportUploader = new UploadReportFiles ();
bool fileUploadSuccess = false;
if (!serviceError) {
LogFile.WriteLine ("Attempting to upload files...");
try {
fileUploadSuccess = reportUploader.UploadFiles (ProcessName, bShowBalloon, ProcessId, args [2], args [3], args [4], args [5], uniqueIndex, LogFile);
} catch (NullReferenceException nre) {
LogFile.WriteLine (nre.Source + " Caused Null Reference Exception");
// Try again:
try {
LogFile.WriteLine ("Process Name: " + ProcessName);
LogFile.WriteLine ("bShowBalloon: " + bShowBalloon);
LogFile.WriteLine ("ProcessId: " + ProcessId);
LogFile.WriteLine ("args[2]: " + args [2]);
LogFile.WriteLine ("args[3]: " + args [3]);
LogFile.WriteLine ("args[4]: " + args [4]);
LogFile.WriteLine ("args[5] ; " + args [5]);
LogFile.WriteLine ("uniqueIndex " + uniqueIndex);
LogFile.WriteLine ("LogFile " + LogFile);
fileUploadSuccess = reportUploader.UploadFiles (ProcessName, bShowBalloon, ProcessId, args [2], args [3], args [4], args [5], uniqueIndex, LogFile);
} catch (NullReferenceException nre2) {
LogFile.WriteLine (nre2.Source + " Caused Null Reference Exception Two Times");
serviceError = true;
}
} catch (Exception e) {
LogFile.WriteLine ("AutoReporter had an exception uploading files! --> " + e.ToString ());
serviceException = e;
serviceError = true;
}
if (fileUploadSuccess) {
LogFile.WriteLine ("Successfully uploaded files");
LogFile.WriteLine ("Saved: " + args [5]);
} else {
LogFile.WriteLine ("Failed to upload some files!");
}
}
//Update Crash with Summary and Description Info
bool updateSuccess = false;
string logDirectory2;
int endOfLogPath2 = args [2].LastIndexOf ('/');
logDirectory2 = args [2].Substring (0, endOfLogPath2 + 1);
logFileName = logDirectory2 + "AutoReportLog.txt";
LogFile.WriteLine ("Attempting to add the crash description...");
try {
updateSuccess = reportService.AddCrashDescription (uniqueIndex, CrashDescription, Summary);
} catch (Exception e) {
LogFile.WriteLine ("AutoReporter had an exception adding crash description! --> " + e.ToString ());
serviceException = e;
serviceError = true;
updateSuccess = false;
LogFile.WriteLine (e.Message);
}
if (uniqueIndex != -1) {
LogFile.WriteLine ("Attempting to write the crash report URL...");
#if DEBUG
string strCrashURL = Properties.Settings.Default.CrashURL_Debug + uniqueIndex;
#else
string strCrashURL = Properties.Settings.Default.CrashURL + uniqueIndex;
#endif
LogFile.WriteLine ("CrashReport url = " + strCrashURL);
LogFile.WriteLine ("");
if (NotifyObject != null) {
// Set up text to inform the user of the report submission
String MsgText = "Your crash report was submitted, and the URL was copied to your clipboard. Click this balloon to display the crash report.";
// setup crash report URL ready for use by the notification
Clipboard.SetText (strCrashURL);
m_CrashReportUrl = strCrashURL;
// launch a notification that when clicked will link
// to the crash report url.
NotifyObject.SetEvents(OnBalloonClicked);
NotifyObject.Show(MsgText, BalloonTimeInMs);
}
string str = reportData.PlatformName.ToLower ();
if (str == "pc" || str == "mac") {
LogFile.WriteLine ("Attempting to send the crash report Id to the game log file...");
// On PC, so try just writing to the log.
string AppLogFileName = Path.Combine (reportData.BaseDir, "..", reportData.GameName + "Game", "Logs", reportData.GameName + ".log");
if (!File.Exists (AppLogFileName)) {
// It's also possible the log name is Engine.log (default game name from BaseEngine.ini)
AppLogFileName = Path.Combine (reportData.BaseDir, "..", reportData.GameName + "Game", "Logs", "Engine.log");
if (!File.Exists (AppLogFileName))
// Default to hardcoded UE4.log
AppLogFileName = Path.Combine (reportData.BaseDir, "..", reportData.GameName + "Game", "Logs", "UE4.log");
}
LogFile.WriteLine ("\n");
LogFile.WriteLine ("Attempting to open log file: " + AppLogFileName);
try {
using (StreamWriter AppLogWriter = new StreamWriter(AppLogFileName, true)) {
AppLogWriter.WriteLine ("");
AppLogWriter.WriteLine ("CrashReport url = " + strCrashURL);
AppLogWriter.WriteLine ("");
AppLogWriter.Flush ();
}
} catch (System.Exception e) {
LogFile.WriteLine ("AutoReporter had an exception creating a stream writer! --> " + e.ToString ());
}
}
}
if (updateSuccess) {
LogFile.WriteLine ("Successfully added crash description");
} else {
LogFile.WriteLine ("Service failed to add crash description!");
LogFile.Close ();
return;
}
LogFile.WriteLine ("Closing the AutoReporter log file...");
LogFile.Close ();
try {
//everything was successful, so clean up dump and log files on client
System.IO.File.Delete (args [1]);
System.IO.File.Delete (args [3]);
//todo: need to handle partial failure cases (some files didn't upload, etc) to know if we should delete the log
//System.IO.File.Delete(logFileName);
} catch (Exception e) {
string ExcStr = "AutoReporter had an exception deleting the temp files!\n" + e.ToString ();
MessageBox.Show (ExcStr, "AutoReporter Status", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
// Make a copy of the log file w/ the CrashReport # in it...
// This is to help track down the issue where empty log files are being uploaded to the crash reporter.
string CopyFileName = logFileName.Replace (".txt", "_" + uniqueIndex + ".txt");
System.IO.File.Copy (logFileName, CopyFileName);
if (NotifyObject != null) {
// spin until the NotifyObject has closed
while (NotifyObject.IsOpen) {
// sleep while the notify balloon is fading
System.Threading.Thread.Sleep (1000);
// force check the events.
Application.DoEvents ();
}
}
}
/// <summary>
/// Internal static variable used to hold the url of the crash report
/// once it is generated.
/// </summary>
static private String m_CrashReportUrl = "";
/// <summary>
/// Event handler which launches the url contained in m_CrashReportURL
/// when called. Designed to be called when a balloon is clicked on.
/// </summary>
/// <param name="sender">Object which sent event</param>
/// <param name="e">event arguments</param>
static void OnBalloonClicked(object sender, EventArgs e)
{
// attempts to launch crash report website
try
{
if (m_CrashReportUrl != null)
{
System.Diagnostics.Process.Start(m_CrashReportUrl);
}
}
finally
{
}
}
/// <summary>
/// EpicBalloon is a subtle yet interactive user notification method.
/// It should be used where the user does not need to be interrupted,
/// but may require confirmation of a task completing.
/// </summary>
class EpicBalloon
{
/// <summary>
/// Constructor: initializes values and sets up all required
/// internal events.
/// </summary>
public EpicBalloon()
{
// initialize balloon
balloon = new NotifyIcon();
balloon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Warning;
// create a new icon, (balloons require tray icons to run).
var iconResource = AutoReporter.Properties.Resources.BalloonTrayIcon;
balloon.Icon = new System.Drawing.Icon(iconResource, iconResource.Size);
// set default title and text
balloon.BalloonTipTitle = "Unreal AutoReporter";
balloon.Text = "";
// set on closed to exposed events, these are expected to close the window
balloon.BalloonTipClicked += new System.EventHandler(OnClosed);
balloon.BalloonTipClosed += new System.EventHandler(OnClosed);
// set open flag
bOpen = false;
}
/// <summary>
/// Displays the NotifyIcon, with the desired text for the desired time.
/// </summary>
/// <param name="Text">The text to show in the notification</param>
/// <param name="TimeDisplayed">The time the notification is shown for. NOTE: Opperating system may override this.</param>
public void Show(String Text, int TimeDisplayed)
{
// launch the notification
balloon.BalloonTipText = Text;
balloon.Visible = true;
balloon.ShowBalloonTip(TimeDisplayed);
// Set internal flag to show the notification is now open.
bOpen = true;
}
/// <summary>
/// Allows for custom events to be set, all functions must
/// for fill the EvtHandler delegate requirements.
/// </summary>
/// <param name="OnClick">Called when notification is clicked</param>
/// <param name="OnFinish">Called when notification is closed (manually or through timeout)</param>
public void SetEvents(EvtHandler OnClick = null, EvtHandler OnFinish = null)
{
if (OnClick != null)
{
balloon.BalloonTipClicked += new System.EventHandler(OnClick);
}
if (OnFinish != null)
{
balloon.BalloonTipClosed += new System.EventHandler(OnFinish);
}
}
/// <summary>
/// Used for internal event, sets a value allowing the notification to tell
/// when it has been closed.
/// </summary>
/// <param name="sender">Object which sent event</param>
/// <param name="e">event arguments</param>
void OnClosed(object sender, EventArgs e)
{
bOpen = false;
}
/// <summary>
/// Read Only property: Returns TRUE when the notification is being shown.
/// </summary>
public bool IsOpen { get { return bOpen; } }
/// <summary>
/// Internal flag, true when notification is open
/// </summary>
bool bOpen;
/// <summary>
/// .Net Notification icon.
/// </summary>
NotifyIcon balloon;
/// <summary>
/// Delegate users must for fill to use notification icons events.
/// </summary>
/// <param name="sender">Object which sent event</param>
/// <param name="e">event arguments</param>
public delegate void EvtHandler(object sender, EventArgs e);
}
}
}