Skip to content

Commit 79fdcc5

Browse files
committed
Merge pull request #94 from exceptionless/feature/heartbeats
Added the ability to update a sessions activity without submitting an event.
2 parents 48cc7ee + cdb260b commit 79fdcc5

23 files changed

+181
-218
lines changed

Source/Extras/Submission/SubmissionClient.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Net;
55
using System.Runtime.CompilerServices;
66
using Exceptionless.Configuration;
7+
using Exceptionless.Dependency;
78
using Exceptionless.Extensions;
89
using Exceptionless.Extras.Extensions;
910
using Exceptionless.Json.Linq;
@@ -23,7 +24,7 @@ public SubmissionResponse PostEvents(IEnumerable<Event> events, ExceptionlessCon
2324

2425
HttpWebResponse response;
2526
try {
26-
var request = CreateHttpWebRequest(config, "events");
27+
var request = CreateHttpWebRequest(config, String.Format("{0}/events", config.GetServiceEndPoint()));
2728
response = request.PostJsonAsyncWithCompression(data).Result as HttpWebResponse;
2829
} catch (AggregateException aex) {
2930
var ex = aex.GetInnermostException() as WebException;
@@ -47,7 +48,7 @@ public SubmissionResponse PostUserDescription(string referenceId, UserDescriptio
4748

4849
HttpWebResponse response;
4950
try {
50-
var request = CreateHttpWebRequest(config, String.Format("events/by-ref/{0}/user-description", referenceId));
51+
var request = CreateHttpWebRequest(config, String.Format("{0}/events/by-ref/{1}/user-description", config.GetServiceEndPoint(), referenceId));
5152
response = request.PostJsonAsyncWithCompression(data).Result as HttpWebResponse;
5253
} catch (AggregateException aex) {
5354
var ex = aex.GetInnermostException() as WebException;
@@ -69,7 +70,7 @@ public SubmissionResponse PostUserDescription(string referenceId, UserDescriptio
6970
public SettingsResponse GetSettings(ExceptionlessConfiguration config, IJsonSerializer serializer) {
7071
HttpWebResponse response;
7172
try {
72-
var request = CreateHttpWebRequest(config, "projects/config");
73+
var request = CreateHttpWebRequest(config, String.Format("{0}/projects/config", config.GetServiceEndPoint()));
7374
response = request.GetJsonAsync().Result as HttpWebResponse;
7475
} catch (Exception ex) {
7576
var message = String.Concat("Unable to retrieve configuration settings. Exception: ", ex.GetMessage());
@@ -86,6 +87,16 @@ public SettingsResponse GetSettings(ExceptionlessConfiguration config, IJsonSeri
8687
var settings = serializer.Deserialize<ClientConfiguration>(json);
8788
return new SettingsResponse(true, settings.Settings, settings.Version);
8889
}
90+
91+
public void SendHeartbeat(string sessionIdOrUserId, bool closeSession, ExceptionlessConfiguration config) {
92+
try {
93+
var request = CreateHttpWebRequest(config, String.Format("{0}/events/session/heartbeat?id={1}&close={2}", config.GetHeartbeatServiceEndPoint(), sessionIdOrUserId, closeSession));
94+
var response = request.GetResponseAsync().Result;
95+
} catch (Exception ex) {
96+
var log = config.Resolver.GetLog();
97+
log.Error(String.Concat("Error submitting heartbeat: ", ex.GetMessage()));
98+
}
99+
}
89100

90101
private static string GetResponseMessage(HttpWebResponse response) {
91102
if (response.IsSuccessful())
@@ -105,8 +116,8 @@ private static string GetResponseMessage(HttpWebResponse response) {
105116
return message;
106117
}
107118

108-
protected virtual HttpWebRequest CreateHttpWebRequest(ExceptionlessConfiguration config, string endPoint) {
109-
var request = (HttpWebRequest)WebRequest.Create(String.Concat(config.GetServiceEndPoint(), endPoint));
119+
protected virtual HttpWebRequest CreateHttpWebRequest(ExceptionlessConfiguration config, string url) {
120+
var request = (HttpWebRequest)WebRequest.Create(url);
110121
request.AddAuthorizationHeader(config);
111122
request.SetUserAgent(config.UserAgent);
112123
request.AllowAutoRedirect = true;

Source/Platforms/Console/ExceptionlessExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@ public static void Register(this ExceptionlessClient client) {
2626
public static void Unregister(this ExceptionlessClient client) {
2727
client.Shutdown();
2828
client.UnregisterOnProcessExitHandler();
29+
30+
client.ProcessQueue();
2931
if (client.Configuration.SessionsEnabled)
3032
client.SubmitSessionEnd();
31-
client.ProcessQueue();
3233
}
3334

3435
private static void RegisterOnProcessExitHandler(this ExceptionlessClient client) {
3536
if (_onProcessExit == null) {
3637
_onProcessExit = (sender, args) => {
37-
client.SubmitSessionEnd();
3838
client.ProcessQueue();
39+
client.SubmitSessionEnd();
3940
};
4041
}
4142

Source/Platforms/Web/ExceptionlessWebPlugin.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Web;
34
using Exceptionless.Dependency;
45
using Exceptionless.Plugins;
@@ -17,16 +18,13 @@ public void Run(EventPluginContext context) {
1718
// if the context is not passed in, try and grab it
1819
if (httpContext == null && HttpContext.Current != null)
1920
httpContext = HttpContext.Current.ToWrapped();
21+
22+
var serializer = context.Client.Configuration.Resolver.GetJsonSerializer();
23+
if (context.Client.Configuration.IncludePrivateInformation)
24+
AddUser(context, httpContext, serializer);
2025

2126
if (httpContext == null)
2227
return;
23-
24-
var serializer = context.Client.Configuration.Resolver.GetJsonSerializer();
25-
if (context.Client.Configuration.IncludePrivateInformation && httpContext.User != null && httpContext.User.Identity.IsAuthenticated) {
26-
var user = context.Event.GetUserIdentity(serializer);
27-
if (user == null)
28-
context.Event.SetUserIdentity(httpContext.User.Identity.Name);
29-
}
3028

3129
var tags = httpContext.Items[TAGS_HTTP_CONTEXT_NAME] as TagSet;
3230
if (tags != null)
@@ -56,5 +54,16 @@ public void Run(EventPluginContext context) {
5654

5755
context.Event.AddRequestInfo(ri);
5856
}
57+
58+
private static void AddUser(EventPluginContext context, HttpContextBase httpContext, IJsonSerializer serializer) {
59+
var user = context.Event.GetUserIdentity(serializer);
60+
if (user != null)
61+
return;
62+
63+
if (httpContext != null && httpContext.User != null && httpContext.User.Identity.IsAuthenticated)
64+
context.Event.SetUserIdentity(httpContext.User.Identity.Name);
65+
else if (Thread.CurrentPrincipal != null && Thread.CurrentPrincipal.Identity.IsAuthenticated)
66+
context.Event.SetUserIdentity(Thread.CurrentPrincipal.Identity.Name);
67+
}
5968
}
6069
}

Source/Platforms/WebApi/ExceptionlessWebApiPlugin.cs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Reflection;
66
using System.Security.Principal;
77
using System.Threading;
8+
using System.Web.Http.Controllers;
89
using Exceptionless.Dependency;
910
using Exceptionless.Plugins;
1011
using Exceptionless.Extensions;
@@ -15,23 +16,14 @@ namespace Exceptionless.WebApi {
1516
[Priority(90)]
1617
internal class ExceptionlessWebApiPlugin : IEventPlugin {
1718
public void Run(EventPluginContext context) {
18-
if (!context.ContextData.ContainsKey("HttpActionContext"))
19-
return;
20-
2119
var actionContext = context.ContextData.GetHttpActionContext();
20+
var serializer = context.Client.Configuration.Resolver.GetJsonSerializer();
21+
if (context.Client.Configuration.IncludePrivateInformation)
22+
AddUser(context, actionContext, serializer);
23+
2224
if (actionContext == null)
2325
return;
2426

25-
var serializer = context.Client.Configuration.Resolver.GetJsonSerializer();
26-
if (context.Client.Configuration.IncludePrivateInformation) {
27-
var user = context.Event.GetUserIdentity(serializer);
28-
if (user == null) {
29-
IPrincipal principal = GetPrincipal(actionContext.Request);
30-
if (principal != null && principal.Identity.IsAuthenticated)
31-
context.Event.SetUserIdentity(principal.Identity.Name);
32-
}
33-
}
34-
3527
var ri = context.Event.GetRequestInfo(serializer);
3628
if (ri != null)
3729
return;
@@ -54,9 +46,19 @@ public void Run(EventPluginContext context) {
5446
context.Event.AddRequestInfo(ri);
5547
}
5648

49+
private static void AddUser(EventPluginContext context, HttpActionContext actionContext, IJsonSerializer serializer) {
50+
var user = context.Event.GetUserIdentity(serializer);
51+
if (user != null)
52+
return;
53+
54+
var principal = GetPrincipal(actionContext != null ? actionContext.Request : null);
55+
if (principal != null && principal.Identity.IsAuthenticated)
56+
context.Event.SetUserIdentity(principal.Identity.Name);
57+
}
58+
5759
private static IPrincipal GetPrincipal(HttpRequestMessage request) {
5860
if (request == null)
59-
throw new ArgumentNullException("request");
61+
return Thread.CurrentPrincipal;
6062

6163
const string RequestContextKey = "MS_RequestContext";
6264

@@ -73,7 +75,6 @@ private static IPrincipal GetPrincipal(HttpRequestMessage request) {
7375
}
7476

7577
var principal = _principalGetAccessor(context) as IPrincipal;
76-
7778
return principal ?? Thread.CurrentPrincipal;
7879
}
7980

Source/Platforms/Windows/ExceptionlessWindowsExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ public static void Unregister(this ExceptionlessClient client) {
4444
client.UnregisterOnProcessExitHandler();
4545

4646
client.SubmittingEvent -= OnSubmittingEvent;
47-
47+
48+
client.ProcessQueue();
4849
if (client.Configuration.SessionsEnabled)
4950
client.SubmitSessionEnd();
50-
client.ProcessQueue();
5151
}
5252

5353
private static void OnSubmittingEvent(object sender, EventSubmittingEventArgs e) {
@@ -63,10 +63,10 @@ private static void OnSubmittingEvent(object sender, EventSubmittingEventArgs e)
6363
private static void RegisterOnProcessExitHandler(this ExceptionlessClient client) {
6464
if (_onProcessExit == null) {
6565
_onProcessExit = (sender, args) => {
66+
client.ProcessQueue();
67+
6668
if (client.Configuration.SessionsEnabled)
6769
client.SubmitSessionEnd();
68-
69-
client.ProcessQueue();
7070
};
7171
}
7272

Source/Platforms/Wpf/ExceptionlessWpfExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public static void Unregister(this ExceptionlessClient client) {
4747
client.UnregisterOnProcessExitHandler();
4848

4949
client.SubmittingEvent -= OnSubmittingEvent;
50-
51-
client.SubmitSessionEnd();
50+
5251
client.ProcessQueue();
52+
client.SubmitSessionEnd();
5353
}
5454

5555
private static void OnSubmittingEvent(object sender, EventSubmittingEventArgs e) {
@@ -72,10 +72,10 @@ private static bool ShowDialog(EventSubmittingEventArgs e) {
7272
private static void RegisterOnProcessExitHandler(this ExceptionlessClient client) {
7373
if (_onProcessExit == null) {
7474
_onProcessExit = (sender, args) => {
75+
client.ProcessQueue();
76+
7577
if (client.Configuration.SessionsEnabled)
7678
client.SubmitSessionEnd();
77-
78-
client.ProcessQueue();
7979
};
8080
}
8181

Source/Samples/SampleConsole/Program.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ private static void Main() {
8686
} else if (keyInfo.Key == ConsoleKey.D5) {
8787
ExceptionlessClient.Default.Configuration.UseSessions(false, null, true);
8888
ExceptionlessClient.Default.SubmitSessionStart();
89-
} else if (keyInfo.Key == ConsoleKey.D6)
90-
ExceptionlessClient.Default.SubmitSessionHeartbeat();
91-
else if (keyInfo.Key == ConsoleKey.D7)
89+
} else if (keyInfo.Key == ConsoleKey.D7)
9290
ExceptionlessClient.Default.SubmitSessionEnd();
9391
else if (keyInfo.Key == ConsoleKey.D8)
9492
ExceptionlessClient.Default.Configuration.SetUserIdentity(Guid.NewGuid().ToString("N"));

Source/Samples/SampleConsole/Submission/InMemorySubmissionClient.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,34 @@
44
using Exceptionless.Models.Data;
55
using Exceptionless.Submission;
66

7-
namespace Exceptionless.SampleConsole
8-
{
9-
public class InMemorySubmissionClient: ISubmissionClient {
10-
private static readonly Dictionary<string, object> _eventRepository = new Dictionary<string, object>();
11-
private static readonly Dictionary<string, object> _userDescriptionRepository = new Dictionary<string, object>();
7+
namespace Exceptionless.SampleConsole {
8+
public class InMemorySubmissionClient : ISubmissionClient {
9+
private readonly Dictionary<string, object> _eventContainer = new Dictionary<string, object>();
10+
private readonly Dictionary<string, object> _userDescriptionContainer = new Dictionary<string, object>();
11+
private readonly Dictionary<string, DateTime> _heartbeatContainer = new Dictionary<string, DateTime>();
1212

1313
public SubmissionResponse PostEvents(IEnumerable<Event> events, ExceptionlessConfiguration config, IJsonSerializer serializer) {
14-
foreach (Event e in events)
15-
{
14+
foreach (Event e in events) {
1615
string data = serializer.Serialize(e);
17-
string referenceId = !string.IsNullOrWhiteSpace(e.ReferenceId)
18-
? e.ReferenceId
19-
: Guid.NewGuid().ToString("D");
20-
_eventRepository[referenceId] = data;
16+
string referenceId = !string.IsNullOrWhiteSpace(e.ReferenceId) ? e.ReferenceId : Guid.NewGuid().ToString("D");
17+
_eventContainer[referenceId] = data;
2118
}
22-
return new SubmissionResponse(200);
2319

20+
return new SubmissionResponse(200);
2421
}
2522

2623
public SubmissionResponse PostUserDescription(string referenceId, UserDescription description, ExceptionlessConfiguration config, IJsonSerializer serializer) {
2724
string data = serializer.Serialize(description);
28-
_userDescriptionRepository[referenceId] = data;
25+
_userDescriptionContainer[referenceId] = data;
2926
return new SubmissionResponse(200);
3027
}
3128

3229
public SettingsResponse GetSettings(ExceptionlessConfiguration config, IJsonSerializer serializer) {
3330
return new SettingsResponse(true);
3431
}
32+
33+
public void SendHeartbeat(string sessionIdOrUserId, bool closeSession, ExceptionlessConfiguration config) {
34+
_heartbeatContainer[sessionIdOrUserId] = DateTime.UtcNow;
35+
}
3536
}
36-
}
37+
}

Source/Samples/SampleWebApi/Controllers/ValuesController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Web.Http;
4+
using Exceptionless.Logging;
45

56
namespace Exceptionless.SampleWebApi.Controllers {
67
public class ValuesController : ApiController {
78
// GET api/values
89
public IEnumerable<string> Get() {
10+
ExceptionlessClient.Default.SubmitLog("ValuesController", "Getting results", LogLevel.Info);
911
throw new ApplicationException("WebApi GET error");
1012
}
1113

Source/Shared/Configuration/ExceptionlessConfiguration.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
namespace Exceptionless {
1212
public class ExceptionlessConfiguration {
1313
private const string DEFAULT_SERVER_URL = "https://collector.exceptionless.io";
14+
private const string DEFAULT_HEARTBEAT_SERVER_URL = "https://heartbeat.exceptionless.io";
1415
private const string DEFAULT_USER_AGENT = "exceptionless/" + ThisAssembly.AssemblyFileVersion;
1516
private const int DEFAULT_SUBMISSION_BATCH_SIZE = 50;
1617

1718
private readonly IDependencyResolver _resolver;
1819
private bool _configLocked;
1920
private string _apiKey;
21+
private string _heartbeatServerUrl;
2022
private string _serverUrl;
2123
private int _submissionBatchSize;
2224
private ValidationResult _validationResult;
@@ -28,6 +30,7 @@ public ExceptionlessConfiguration(IDependencyResolver resolver) {
2830
throw new ArgumentNullException("resolver");
2931

3032
ServerUrl = DEFAULT_SERVER_URL;
33+
HeartbeatServerUrl = DEFAULT_HEARTBEAT_SERVER_URL;
3134
UserAgent = DEFAULT_USER_AGENT;
3235
SubmissionBatchSize = DEFAULT_SUBMISSION_BATCH_SIZE;
3336
Enabled = true;
@@ -69,6 +72,24 @@ public string ServerUrl {
6972

7073
_validationResult = null;
7174
_serverUrl = value;
75+
_heartbeatServerUrl = value;
76+
}
77+
}
78+
79+
/// <summary>
80+
/// The server url that all events will be sent to.
81+
/// </summary>
82+
public string HeartbeatServerUrl {
83+
get { return _heartbeatServerUrl; }
84+
set {
85+
if (_heartbeatServerUrl == value)
86+
return;
87+
88+
if (_configLocked)
89+
throw new ArgumentException("HeartbeatServerUrl can't be changed after the client has been initialized.");
90+
91+
_validationResult = null;
92+
_heartbeatServerUrl = value;
7293
}
7394
}
7495

@@ -143,6 +164,8 @@ public string ApiKey {
143164
/// </summary>
144165
public bool SessionsEnabled { get; set; }
145166

167+
internal string CurrentSessionIdentifier { get; set; }
168+
146169
/// <summary>
147170
/// Maximum number of events that should be sent to the server together in a batch. (Defaults to 50)
148171
/// </summary>
@@ -358,6 +381,9 @@ public ValidationResult Validate() {
358381
if (String.IsNullOrEmpty(ServerUrl))
359382
result.Messages.Add("ServerUrl is not set.");
360383

384+
if (String.IsNullOrEmpty(HeartbeatServerUrl))
385+
result.Messages.Add("HeartbeatServerUrl is not set.");
386+
361387
return result;
362388
}
363389

0 commit comments

Comments
 (0)