2
// LogReportingService.cs
5
// Alan McGovern <alan@xamarin.com>
7
// Copyright (c) 2011 Alan McGovern
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
using System.Threading;
31
using System.IO.Compression;
33
namespace MonoDevelop.Core.LogReporting
35
public static class LogReportingService
37
const string ServiceVersion = "1";
38
public static readonly FilePath CrashLogDirectory = UserProfile.Current.LogDir.Combine ("LogAgent");
40
const string ReportCrashesKey = "MonoDevelop.LogAgent.ReportCrashes";
41
const string ReportUsageKey = "MonoDevelop.LogAgent.ReportUsage";
44
static int Processing;
46
// Return value is the new value for 'ReportCrashes'
47
// First parameter is the current value of 'ReportCrashes
48
// Second parameter is the exception
49
// Thirdparameter shows if the exception is fatal or not
50
public static Func<bool?, Exception, bool, bool?> UnhandledErrorOccured;
52
public static bool? ReportCrashes {
53
get { return PropertyService.Get<bool?> (ReportCrashesKey); }
54
set { PropertyService.Set (ReportCrashesKey, value); }
57
public static bool? ReportUsage {
58
get { return PropertyService.Get<bool?> (ReportUsageKey); }
59
set { PropertyService.Set (ReportUsageKey, value); }
62
public static void ReportUnhandledException (Exception ex, bool willShutDown)
64
var oldReportCrashes = ReportCrashes;
66
if (UnhandledErrorOccured != null)
67
ReportCrashes = UnhandledErrorOccured (ReportCrashes, ex, willShutDown);
69
// If crash reporting has been explicitly disabled, disregard this crash
70
if (ReportCrashes.HasValue && !ReportCrashes.Value)
74
using (var stream = new MemoryStream ()) {
75
using (var writer = System.Xml.XmlWriter.Create (stream)) {
76
writer.WriteStartElement ("CrashLog");
77
writer.WriteAttributeString ("version", ServiceVersion);
79
writer.WriteElementString ("SystemInformation", SystemInformation.ToText ());
80
writer.WriteElementString ("Exception", ex.ToString ());
82
writer.WriteEndElement ();
84
data = stream.ToArray ();
87
// Log to disk only if uploading fails.
88
var filename = string.Format ("{0}.{1}.crashlog", SystemInformation.SessionUuid, Interlocked.Increment (ref CrashId));
89
ThreadPool.QueueUserWorkItem (delegate {
90
if (!TryUploadReport (filename, data)) {
91
if (!Directory.Exists (CrashLogDirectory))
92
Directory.CreateDirectory (CrashLogDirectory);
94
File.WriteAllBytes (CrashLogDirectory.Combine (filename), data);
98
//ensure we don't lose the setting
99
if (ReportCrashes != oldReportCrashes) {
100
PropertyService.SaveProperties ();
104
public static void ProcessCache ()
108
// Ensure only 1 thread at a time attempts to upload cached reports
109
origValue = Interlocked.CompareExchange (ref Processing, 1, 0);
113
// Uploading is not enabled, so bail out
114
if (!ReportCrashes.GetValueOrDefault ())
117
// Definitely no crash reports if this doesn't exist
118
if (!Directory.Exists (CrashLogDirectory))
121
foreach (var file in Directory.GetFiles (CrashLogDirectory)) {
122
if (TryUploadReport (file, File.ReadAllBytes (file)))
125
} catch (Exception ex) {
126
LoggingService.LogError ("Exception processing cached crashes", ex);
129
Interlocked.CompareExchange (ref Processing, 0, 1);
133
static bool TryUploadReport (string filename, byte[] data)
136
var server = Environment.GetEnvironmentVariable ("MONODEVELOP_CRASHREPORT_SERVER");
137
if (string.IsNullOrEmpty (server))
138
server = "monodevlog.xamarin.com:35162";
140
var request = (HttpWebRequest) WebRequest.Create (string.Format ("http://{0}/logagentreport/", server));
141
request.Headers.Add ("LogAgentVersion", ServiceVersion);
142
request.Headers.Add ("LogAgent_Filename", Path.GetFileName (filename));
143
request.Headers.Add ("Content-Encoding", "gzip");
144
request.Method = "POST";
146
// Compress the data and then use the compressed length in ContentLength
147
var compressed = new MemoryStream ();
148
using (var zipper = new GZipStream (compressed, CompressionMode.Compress))
149
zipper.Write (data, 0, data.Length);
150
data = compressed.ToArray ();
152
request.ContentLength = data.Length;
153
using (var requestStream = request.GetRequestStream ())
154
requestStream.Write (data, 0, data.Length);
156
LoggingService.LogDebug ("CrashReport sent to server, awaiting response...");
158
// Ensure the server has correctly processed everything.
159
using (var response = (HttpWebResponse) request.GetResponse ()) {
160
if (response.StatusCode != HttpStatusCode.OK) {
161
LoggingService.LogError ("Server responded with status code {1} and error: {0}", response.StatusDescription, response.StatusCode);
165
} catch (Exception ex) {
166
LoggingService.LogError ("Failed to upload report to the server", ex);
170
LoggingService.LogDebug ("Successfully uploaded crash report");