diff --git a/src/Exceptionless.Tests/Plugins/PluginTests.cs b/src/Exceptionless.Tests/Plugins/PluginTests.cs index 8554b676..6897226d 100644 --- a/src/Exceptionless.Tests/Plugins/PluginTests.cs +++ b/src/Exceptionless.Tests/Plugins/PluginTests.cs @@ -726,7 +726,7 @@ public void VerifyDeduplication() { var errorPlugin = new ErrorPlugin(); EventPluginContext mergedContext = null; - using (var duplicateCheckerPlugin = new DuplicateCheckerPlugin(TimeSpan.FromMilliseconds(20))) { + using (var duplicateCheckerPlugin = new DuplicateCheckerPlugin(TimeSpan.FromMilliseconds(40))) { for (int index = 0; index < 10; index++) { var builder = GetException().ToExceptionless(); var context = new EventPluginContext(client, builder.Target, builder.PluginContextData); @@ -745,7 +745,7 @@ public void VerifyDeduplication() { } } - Thread.Sleep(50); + Thread.Sleep(100); Assert.Equal(9, mergedContext.Event.Count.GetValueOrDefault()); } diff --git a/src/Exceptionless.Tests/project.json b/src/Exceptionless.Tests/project.json index dedbde4e..f0cee9db 100644 --- a/src/Exceptionless.Tests/project.json +++ b/src/Exceptionless.Tests/project.json @@ -8,7 +8,7 @@ } }, "dependencies": { - "BenchmarkDotNet": "0.9.8", + "BenchmarkDotNet": "0.9.9", "Exceptionless": { "target": "project" }, diff --git a/src/Exceptionless/Configuration/ExceptionlessConfiguration.cs b/src/Exceptionless/Configuration/ExceptionlessConfiguration.cs index d7d3bc75..301cb17a 100644 --- a/src/Exceptionless/Configuration/ExceptionlessConfiguration.cs +++ b/src/Exceptionless/Configuration/ExceptionlessConfiguration.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Net; using System.Reflection; using Exceptionless.Dependency; using Exceptionless.Plugins; @@ -99,11 +100,6 @@ public string HeartbeatServerUrl { } } - /// - /// Used to identify the client that sent the events to the server. - /// - public string UserAgent { get; set; } - /// /// The API key that will be used when sending events to the server. /// @@ -122,6 +118,16 @@ public string ApiKey { } } + /// + /// Used to identify the client that sent the events to the server. + /// + public string UserAgent { get; set; } + + /// + /// Ability to set a custom proxy. By default, .NET will use any system or configuration defined proxy settings. + /// + public IWebProxy Proxy { get; set; } + /// /// Whether the client is currently enabled or not. If it is disabled, submitted errors will be discarded and no data will be sent to the server. /// diff --git a/src/Exceptionless/Configuration/SettingsManager.cs b/src/Exceptionless/Configuration/SettingsManager.cs index 0ee4384d..e0800fde 100644 --- a/src/Exceptionless/Configuration/SettingsManager.cs +++ b/src/Exceptionless/Configuration/SettingsManager.cs @@ -71,7 +71,7 @@ public static void CheckVersion(int version, ExceptionlessConfiguration config) public static void UpdateSettings(ExceptionlessConfiguration config, int? version = null) { if (config == null || !config.IsValid || !config.Enabled) return; - + try { if (!version.HasValue || version < 0) version = GetVersion(config); diff --git a/src/Exceptionless/ExceptionlessClient.cs b/src/Exceptionless/ExceptionlessClient.cs index 737d5e91..38a7ee3a 100644 --- a/src/Exceptionless/ExceptionlessClient.cs +++ b/src/Exceptionless/ExceptionlessClient.cs @@ -203,7 +203,7 @@ public void SubmitEvent(Event ev, ContextData pluginContextData = null) { _queue.Value.Enqueue(ev); if (!String.IsNullOrEmpty(ev.ReferenceId)) { - _log.Value.FormattedTrace(typeof(ExceptionlessClient), "Setting last reference id '{0}'", ev.ReferenceId); + _log.Value.FormattedTrace(typeof(ExceptionlessClient), "Setting last reference id: {0}", ev.ReferenceId); _lastReferenceIdManager.Value.SetLast(ev.ReferenceId); } diff --git a/src/Exceptionless/Logging/ExceptionlessLogExtensions.cs b/src/Exceptionless/Logging/ExceptionlessLogExtensions.cs index 3e2ca691..f9fd11c5 100644 --- a/src/Exceptionless/Logging/ExceptionlessLogExtensions.cs +++ b/src/Exceptionless/Logging/ExceptionlessLogExtensions.cs @@ -11,11 +11,11 @@ public static void Error(this IExceptionlessLog log, Type source, Exception exce } public static void FormattedError(this IExceptionlessLog log, Type source, Exception exception, string format, params object[] args) { - log.Error(String.Format(format, args), GetSourceName(source), exception); + log.Error(GetMessage(format, args), GetSourceName(source), exception); } public static void FormattedError(this IExceptionlessLog log, Type source, string format, params object[] args) { - log.Error(String.Format(format, args), GetSourceName(source)); + log.Error(GetMessage(format, args), GetSourceName(source)); } public static void Info(this IExceptionlessLog log, Type source, string message) { @@ -23,7 +23,7 @@ public static void Info(this IExceptionlessLog log, Type source, string message) } public static void FormattedInfo(this IExceptionlessLog log, Type source, string format, params object[] args) { - log.Info(String.Format(format, args), GetSourceName(source)); + log.Info(GetMessage(format, args), GetSourceName(source)); } public static void Debug(this IExceptionlessLog log, Type source, string message) { @@ -31,7 +31,7 @@ public static void Debug(this IExceptionlessLog log, Type source, string message } public static void FormattedDebug(this IExceptionlessLog log, Type source, string format, params object[] args) { - log.Debug(String.Format(format, args), GetSourceName(source)); + log.Debug(GetMessage(format, args), GetSourceName(source)); } public static void Warn(this IExceptionlessLog log, Type source, string message) { @@ -39,7 +39,7 @@ public static void Warn(this IExceptionlessLog log, Type source, string message) } public static void FormattedWarn(this IExceptionlessLog log, Type source, string format, params object[] args) { - log.Warn(String.Format(format, args), GetSourceName(source)); + log.Warn(GetMessage(format, args), GetSourceName(source)); } public static void Trace(this IExceptionlessLog log, Type source, string message) { @@ -47,7 +47,7 @@ public static void Trace(this IExceptionlessLog log, Type source, string message } public static void FormattedTrace(this IExceptionlessLog log, Type source, string format, params object[] args) { - log.Trace(String.Format(format, args), GetSourceName(source)); + log.Trace(GetMessage(format, args), GetSourceName(source)); } public static void Error(this IExceptionlessLog log, Exception exception, string message) { @@ -55,27 +55,35 @@ public static void Error(this IExceptionlessLog log, Exception exception, string } public static void FormattedError(this IExceptionlessLog log, Exception exception, string format, params object[] args) { - log.Error(String.Format(format, args), exception: exception); + log.Error(GetMessage(format, args), exception: exception); } public static void FormattedError(this IExceptionlessLog log, string format, params object[] args) { - log.Error(String.Format(format, args)); + log.Error(GetMessage(format, args)); } public static void FormattedInfo(this IExceptionlessLog log, string format, params object[] args) { - log.Info(String.Format(format, args)); + log.Info(GetMessage(format, args)); } public static void FormattedDebug(this IExceptionlessLog log, string format, params object[] args) { - log.Debug(String.Format(format, args)); + log.Debug(GetMessage(format, args)); } public static void FormattedWarn(this IExceptionlessLog log, string format, params object[] args) { - log.Warn(String.Format(format, args)); + log.Warn(GetMessage(format, args)); } private static string GetSourceName(Type type) { return type.Name; } + + private static string GetMessage(string format, params object[] args) { + try { + return String.Format(format, args); + } catch (Exception) { + return format; + } + } } } \ No newline at end of file diff --git a/src/Exceptionless/Plugins/Default/1010_DuplicateCheckerPlugin.cs b/src/Exceptionless/Plugins/Default/1010_DuplicateCheckerPlugin.cs index f6fd0cdc..d42ce3e4 100644 --- a/src/Exceptionless/Plugins/Default/1010_DuplicateCheckerPlugin.cs +++ b/src/Exceptionless/Plugins/Default/1010_DuplicateCheckerPlugin.cs @@ -32,7 +32,7 @@ public DuplicateCheckerPlugin(TimeSpan? interval) { public void Run(EventPluginContext context) { int hashCode = context.Event.GetHashCode(); int count = context.Event.Count ?? 1; - context.Log.FormattedTrace(typeof(DuplicateCheckerPlugin), String.Concat("Checking event: ", context.Event.Message, " with hash: ", hashCode)); + context.Log.FormattedTrace(typeof(DuplicateCheckerPlugin), "Checking event: {0} with hash: {1}", context.Event.Message, hashCode); lock (_lock) { // Increment the occurrence count if the event is already queued for submission. @@ -40,21 +40,21 @@ public void Run(EventPluginContext context) { if (merged != null) { merged.IncrementCount(count); merged.UpdateDate(context.Event.Date); - context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), String.Concat("Ignoring duplicate event with hash:", hashCode)); + context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), "Ignoring duplicate event with hash: {0}", hashCode); context.Cancel = true; return; } DateTimeOffset repeatWindow = DateTimeOffset.UtcNow.Subtract(_interval); if (_processed.Any(s => s.Item1 == hashCode && s.Item2 >= repeatWindow)) { - context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), String.Concat("Adding event with hash:", hashCode, " to cache.")); + context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), "Adding event with hash: {0} to cache.", hashCode); // This event is a duplicate for the first time, lets save it so we can delay it while keeping count _mergedEvents.Enqueue(new MergedEvent(hashCode, context, count)); context.Cancel = true; return; } - context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), String.Concat("Enqueueing event with hash:", hashCode, " to cache.")); + context.Log.FormattedInfo(typeof(DuplicateCheckerPlugin), "Enqueueing event with hash: {0} to cache.", hashCode); _processed.Enqueue(Tuple.Create(hashCode, DateTimeOffset.UtcNow)); while (_processed.Count > 50) @@ -116,7 +116,7 @@ public void Resubmit() { _context.Resolver.GetEventQueue().Enqueue(_context.Event); if (!String.IsNullOrEmpty(_context.Event.ReferenceId)) { - _context.Log.FormattedTrace(typeof(DuplicateCheckerPlugin), "Setting last reference id '{0}'", _context.Event.ReferenceId); + _context.Log.FormattedTrace(typeof(DuplicateCheckerPlugin), "Setting last reference id: {0}", _context.Event.ReferenceId); _context.Resolver.GetLastReferenceIdManager().SetLast(_context.Event.ReferenceId); } diff --git a/src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs b/src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs index d2f09ee6..6dd7633e 100644 --- a/src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs +++ b/src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs @@ -24,7 +24,7 @@ public DefaultEnvironmentInfoCollector(IExceptionlessLog log) { public EnvironmentInfo GetEnvironmentInfo() { if (_environmentInfo != null) { PopulateThreadInfo(_environmentInfo); - PopulateMemoryInfo(_environmentInfo); + PopulateMemoryInfo(_environmentInfo); return _environmentInfo; } @@ -178,7 +178,7 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) { #if NETSTANDARD computerInfo = Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default; #elif NET45 - computerInfo = new Microsoft.VisualBasic.Devices.ComputerInfo(); + computerInfo = new Microsoft.VisualBasic.Devices.ComputerInfo(); #endif } catch (Exception ex) { _log.FormattedInfo(typeof(DefaultEnvironmentInfoCollector), "Unable to get computer info. Error message: {0}", ex.Message); diff --git a/src/Exceptionless/Submission/DefaultSubmissionClient.cs b/src/Exceptionless/Submission/DefaultSubmissionClient.cs index 3fef0167..d9d36cba 100644 --- a/src/Exceptionless/Submission/DefaultSubmissionClient.cs +++ b/src/Exceptionless/Submission/DefaultSubmissionClient.cs @@ -15,10 +15,10 @@ namespace Exceptionless.Submission { public class DefaultSubmissionClient : ISubmissionClient, IDisposable { - private readonly HttpClient _client; + private readonly Lazy _client; public DefaultSubmissionClient(ExceptionlessConfiguration config) { - _client = CreateHttpClient(config.UserAgent); + _client = new Lazy(() => CreateHttpClient(config)); } public SubmissionResponse PostEvents(IEnumerable events, ExceptionlessConfiguration config, IJsonSerializer serializer) { @@ -36,8 +36,8 @@ public SubmissionResponse PostEvents(IEnumerable events, ExceptionlessCon if (data.Length > 1024 * 4) content = new GzipContent(content); - _client.AddAuthorizationHeader(config.ApiKey); - response = _client.PostAsync(url, content).ConfigureAwait(false).GetAwaiter().GetResult(); + _client.Value.AddAuthorizationHeader(config.ApiKey); + response = _client.Value.PostAsync(url, content).ConfigureAwait(false).GetAwaiter().GetResult(); } catch (Exception ex) { return new SubmissionResponse(500, message: ex.Message); } @@ -48,7 +48,7 @@ public SubmissionResponse PostEvents(IEnumerable events, ExceptionlessCon return new SubmissionResponse((int)response.StatusCode, GetResponseMessage(response)); } - + public SubmissionResponse PostUserDescription(string referenceId, UserDescription description, ExceptionlessConfiguration config, IJsonSerializer serializer) { if (!config.IsValid) return new SubmissionResponse(500, message: "Invalid client configuration settings."); @@ -64,8 +64,8 @@ public SubmissionResponse PostUserDescription(string referenceId, UserDescriptio if (data.Length > 1024 * 4) content = new GzipContent(content); - _client.AddAuthorizationHeader(config.ApiKey); - response = _client.PostAsync(url, content).ConfigureAwait(false).GetAwaiter().GetResult(); + _client.Value.AddAuthorizationHeader(config.ApiKey); + response = _client.Value.PostAsync(url, content).ConfigureAwait(false).GetAwaiter().GetResult(); } catch (Exception ex) { return new SubmissionResponse(500, message: ex.Message); } @@ -85,8 +85,8 @@ public SettingsResponse GetSettings(ExceptionlessConfiguration config, int versi HttpResponseMessage response; try { - _client.AddAuthorizationHeader(config.ApiKey); - response = _client.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); + _client.Value.AddAuthorizationHeader(config.ApiKey); + response = _client.Value.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); } catch (Exception ex) { var message = String.Concat("Unable to retrieve configuration settings. Exception: ", ex.GetMessage()); return new SettingsResponse(false, message: message); @@ -105,22 +105,22 @@ public SettingsResponse GetSettings(ExceptionlessConfiguration config, int versi var settings = serializer.Deserialize(json); return new SettingsResponse(true, settings.Settings, settings.Version); } - + public void SendHeartbeat(string sessionIdOrUserId, bool closeSession, ExceptionlessConfiguration config) { if (!config.IsValid) return; string url = String.Format("{0}/events/session/heartbeat?id={1}&close={2}", GetHeartbeatServiceEndPoint(config), sessionIdOrUserId, closeSession); try { - _client.AddAuthorizationHeader(config.ApiKey); - _client.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); + _client.Value.AddAuthorizationHeader(config.ApiKey); + _client.Value.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult(); } catch (Exception ex) { var log = config.Resolver.GetLog(); log.Error(String.Concat("Error submitting heartbeat: ", ex.GetMessage())); } } - private HttpClient CreateHttpClient(string userAgent) { + protected virtual HttpClient CreateHttpClient(ExceptionlessConfiguration config) { #if NET45 var handler = new WebRequestHandler { UseDefaultCredentials = true }; handler.ServerCertificateValidationCallback = delegate { return true; }; @@ -130,17 +130,19 @@ private HttpClient CreateHttpClient(string userAgent) { //handler.ServerCertificateCustomValidationCallback = delegate { return true; }; #endif #endif - if (handler.SupportsAutomaticDecompression) handler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip | DecompressionMethods.None; if (handler.SupportsRedirectConfiguration) handler.AllowAutoRedirect = true; - + + if (handler.SupportsProxy && config.Proxy != null) + handler.Proxy = config.Proxy; + var client = new HttpClient(handler, true); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.ExpectContinue = false; - client.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent); + client.DefaultRequestHeaders.UserAgent.ParseAdd(config.UserAgent); return client; } @@ -205,7 +207,8 @@ private Uri GetHeartbeatServiceEndPoint(ExceptionlessConfiguration config) { } public void Dispose() { - _client.Dispose(); + if (_client.IsValueCreated) + _client.Value.Dispose(); } } } \ No newline at end of file diff --git a/src/Platforms/Exceptionless.NLog/project.json b/src/Platforms/Exceptionless.NLog/project.json index e2ecdc4a..da3c93eb 100644 --- a/src/Platforms/Exceptionless.NLog/project.json +++ b/src/Platforms/Exceptionless.NLog/project.json @@ -48,7 +48,7 @@ "define": [ "NET45" ] }, "dependencies": { - "NLog": "4.3.6" + "NLog": "4.3.8" } } }