Skip to content

Commit cc54750

Browse files
committed
Merge pull request #81 from exceptionless/feature/sessions
Session Improvements
2 parents 549276b + 1a4dcf5 commit cc54750

File tree

8 files changed

+134
-45
lines changed

8 files changed

+134
-45
lines changed

Source/Shared/Configuration/ExceptionlessConfiguration.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,16 @@ public IEnumerable<PluginRegistration> Plugins {
225225
get { return _plugins.Values.OrderBy(e => e.Priority).ToList(); }
226226
}
227227

228+
/// <summary>
229+
/// Register an plugin to be used in this configuration.
230+
/// </summary>
231+
/// <typeparam name="T">The plugin type to be added.</typeparam>
232+
/// <param name="plugin">The plugin instance to be added.</param>
233+
public void AddPlugin<T>(T plugin) where T : IEventPlugin {
234+
string key = typeof(T).FullName;
235+
_plugins[key] = new PluginRegistration(key, GetPriority(typeof(T)), new Lazy<IEventPlugin>(() => plugin));
236+
}
237+
228238
/// <summary>
229239
/// Register an plugin to be used in this configuration.
230240
/// </summary>

Source/Shared/EventBuilder.cs

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Globalization;
4-
using System.Linq;
53
using Exceptionless.Plugins;
6-
using Exceptionless.Extensions;
74
using Exceptionless.Models;
85

96
namespace Exceptionless {
@@ -47,10 +44,7 @@ public EventBuilder SetSource(string source) {
4744
/// </summary>
4845
/// <param name="referenceId">The event reference id.</param>
4946
public EventBuilder SetReferenceId(string referenceId) {
50-
if (!IsValidIdentifier(referenceId))
51-
throw new ArgumentException("ReferenceId must contain between 8 and 100 alphanumeric or '-' characters.", "referenceId");
52-
53-
Target.ReferenceId = referenceId;
47+
Target.SetReferenceId(referenceId);
5448
return this;
5549
}
5650

@@ -60,26 +54,10 @@ public EventBuilder SetReferenceId(string referenceId) {
6054
/// <param name="name">Reference name</param>
6155
/// <param name="id">The reference id that points to a specific event</param>
6256
public EventBuilder SetEventReference(string name, string id) {
63-
if (String.IsNullOrEmpty(name))
64-
throw new ArgumentNullException("name");
65-
66-
if (!IsValidIdentifier(id) || String.IsNullOrEmpty(id))
67-
throw new ArgumentException("Id must contain between 8 and 100 alphanumeric or '-' characters.", "id");
68-
69-
Target.SetProperty(String.Format("@ref:{0}", name), id);
57+
Target.SetEventReference(name, id);
7058
return this;
7159
}
72-
73-
private bool IsValidIdentifier(string value) {
74-
if (value == null)
75-
return true;
76-
77-
if (value.Length < 8 || value.Length > 100)
78-
return false;
79-
80-
return value.IsValidIdentifier();
81-
}
82-
60+
8361
/// <summary>
8462
/// Sets the event message.
8563
/// </summary>
@@ -104,12 +82,7 @@ public EventBuilder SetGeo(string coordinates) {
10482
/// <param name="latitude">The event latitude.</param>
10583
/// <param name="longitude">The event longitude.</param>
10684
public EventBuilder SetGeo(double latitude, double longitude) {
107-
if (latitude < -90.0 || latitude > 90.0)
108-
throw new ArgumentOutOfRangeException("latitude", "Must be a valid latitude value between -90.0 and 90.0.");
109-
if (longitude < -180.0 || longitude > 180.0)
110-
throw new ArgumentOutOfRangeException("longitude", "Must be a valid longitude value between -180.0 and 180.0.");
111-
112-
Target.Geo = latitude.ToString("#0.0#######", CultureInfo.InvariantCulture) + "," + longitude.ToString("#0.0#######", CultureInfo.InvariantCulture);
85+
Target.SetGeo(latitude, longitude);
11386
return this;
11487
}
11588

@@ -127,10 +100,7 @@ public EventBuilder SetValue(decimal value) {
127100
/// </summary>
128101
/// <param name="tags">The tags to be added to the event.</param>
129102
public EventBuilder AddTags(params string[] tags) {
130-
if (tags == null || tags.Length == 0)
131-
return this;
132-
133-
Target.Tags.AddRange(tags.Where(t => !String.IsNullOrWhiteSpace(t)).Select(t => t.Trim()));
103+
Target.AddTags(tags);
134104
return this;
135105
}
136106

Source/Shared/Exceptionless.Portable.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<Compile Include="Dependency\TinyIoC.cs" />
6161
<Compile Include="Events\EventSubmissionEventArgsBase.cs" />
6262
<Compile Include="Events\EventSubmittedEventArgs.cs" />
63+
<Compile Include="Plugins\Default\025_SessionIdManagementPlugin.cs" />
6364
<Compile Include="Plugins\Default\030_DuplicateCheckerPlugin.cs" />
6465
<Compile Include="Plugins\ContextData.cs" />
6566
<Compile Include="Plugins\Default\1000_CancelSessionsWithNoUserPlugin.cs" />

Source/Shared/Extensions/EventExtensions.cs

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using Exceptionless.Extensions;
36
using Exceptionless.Models;
47
using Exceptionless.Models.Data;
58

@@ -185,6 +188,7 @@ public static void SetUserDescription(this Event ev, UserDescription description
185188
/// <summary>
186189
/// Sets the event geo coordinates. Can be either "lat,lon" or an IP address that will be used to auto detect the geo coordinates.
187190
/// </summary>
191+
/// <param name="ev">The event.</param>
188192
/// <param name="coordinates">The event coordinates.</param>
189193
public static void SetGeo(this Event ev, string coordinates) {
190194
if (String.IsNullOrWhiteSpace(coordinates)) {
@@ -197,11 +201,11 @@ public static void SetGeo(this Event ev, string coordinates) {
197201
else
198202
throw new ArgumentException("Must be either lat,lon or an IP address.", "coordinates");
199203
}
200-
201-
204+
202205
/// <summary>
203206
/// Sets the event geo coordinates.
204207
/// </summary>
208+
/// <param name="ev">The event.</param>
205209
/// <param name="latitude">The event latitude.</param>
206210
/// <param name="longitude">The event longitude.</param>
207211
public static void SetGeo(this Event ev, double latitude, double longitude) {
@@ -210,7 +214,70 @@ public static void SetGeo(this Event ev, double latitude, double longitude) {
210214
if (longitude < -180.0 || longitude > 180.0)
211215
throw new ArgumentOutOfRangeException("longitude", "Must be a valid longitude value between -180.0 and 180.0.");
212216

213-
ev.Geo = latitude.ToString("#0.0#######") + "," + longitude.ToString("#0.0#######");
217+
ev.Geo = latitude.ToString("#0.0#######", CultureInfo.InvariantCulture) + "," + longitude.ToString("#0.0#######", CultureInfo.InvariantCulture);
218+
}
219+
220+
/// <summary>
221+
/// Adds one or more tags to the event.
222+
/// </summary>
223+
/// <param name="ev">The event.</param>
224+
/// <param name="tags">The tags to be added to the event.</param>
225+
public static void AddTags(this Event ev, params string[] tags) {
226+
if (tags == null || tags.Length == 0)
227+
return;
228+
229+
ev.Tags.AddRange(tags.Where(t => !String.IsNullOrWhiteSpace(t)).Select(t => t.Trim()));
230+
}
231+
232+
/// <summary>
233+
/// Sets the event reference id.
234+
/// </summary>
235+
/// <param name="ev">The event.</param>
236+
/// <param name="referenceId">The event reference id.</param>
237+
public static void SetReferenceId(this Event ev, string referenceId) {
238+
if (!IsValidIdentifier(referenceId))
239+
throw new ArgumentException("ReferenceId must contain between 8 and 100 alphanumeric or '-' characters.", "referenceId");
240+
241+
ev.ReferenceId = referenceId;
242+
}
243+
244+
/// <summary>
245+
/// Returns the event reference id.
246+
/// </summary>
247+
/// <param name="ev">The event.</param>
248+
/// <param name="name">Reference name</param>
249+
/// <returns></returns>
250+
public static string GetEventReference(this Event ev, string name) {
251+
if (ev == null || String.IsNullOrEmpty(name))
252+
return null;
253+
254+
return ev.Data.GetString($"@ref:{name}");
255+
}
256+
257+
/// <summary>
258+
/// Allows you to reference a parent event by its <seealso cref="Event.ReferenceId" /> property. This allows you to have parent and child relationships.
259+
/// </summary>
260+
/// <param name="ev">The event.</param>
261+
/// <param name="name">Reference name</param>
262+
/// <param name="id">The reference id that points to a specific event</param>
263+
public static void SetEventReference(this Event ev, string name, string id) {
264+
if (String.IsNullOrEmpty(name))
265+
throw new ArgumentNullException("name");
266+
267+
if (!IsValidIdentifier(id) || String.IsNullOrEmpty(id))
268+
throw new ArgumentException("Id must contain between 8 and 100 alphanumeric or '-' characters.", "id");
269+
270+
ev.SetProperty(String.Format("@ref:{0}", name), id);
271+
}
272+
273+
private static bool IsValidIdentifier(string value) {
274+
if (value == null)
275+
return true;
276+
277+
if (value.Length < 8 || value.Length > 100)
278+
return false;
279+
280+
return value.IsValidIdentifier();
214281
}
215282

216283
/// <summary>

Source/Shared/Extensions/ExceptionlessConfigurationExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Exceptionless.Logging;
99
using Exceptionless.Models;
1010
using Exceptionless.Models.Data;
11+
using Exceptionless.Plugins;
1112
using Exceptionless.Storage;
1213

1314
namespace Exceptionless {
@@ -86,11 +87,21 @@ public static string GetInstallId(this ExceptionlessConfiguration config) {
8687
return persistedClientData[INSTALL_ID_KEY];
8788
}
8889

89-
public static void UseSessions(this ExceptionlessConfiguration config, bool sendHeartbeats = true) {
90+
/// <summary>
91+
/// Automatically send session start, session heartbeats and session end events.
92+
/// </summary>
93+
/// <param name="config">Exceptionless configuration</param>
94+
/// <param name="sendHeartbeats">Controls whether heartbeat events are sent on an interval.</param>
95+
/// <param name="heartbeatInterval">The interval at which heartbeats are sent after the last sent event. The default is 30 seconds.</param>
96+
/// <param name="useSessionIdManagement">Allows you to manually control the session id. This is only recommended for single user desktop environments.</param>
97+
public static void UseSessions(this ExceptionlessConfiguration config, bool sendHeartbeats = true, TimeSpan? heartbeatInterval = null, bool useSessionIdManagement = false) {
9098
config.SessionsEnabled = true;
9199

100+
if (useSessionIdManagement)
101+
config.AddPlugin<SessionIdManagementPlugin>();
102+
92103
if (sendHeartbeats)
93-
config.AddPlugin<HeartbeatPlugin>();
104+
config.AddPlugin(new HeartbeatPlugin(heartbeatInterval));
94105
}
95106

96107
public static InMemoryExceptionlessLog UseInMemoryLogger(this ExceptionlessConfiguration config, LogLevel minLogLevel = LogLevel.Info) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
3+
namespace Exceptionless.Plugins {
4+
[Priority(25)]
5+
public class SessionIdManagementPlugin : IEventPlugin {
6+
private string _sessionId;
7+
public void Run(EventPluginContext context) {
8+
if (context.Event.IsSessionStart() || String.IsNullOrEmpty(_sessionId))
9+
_sessionId = Guid.NewGuid().ToString("N");
10+
11+
if (context.Event.IsSessionStart())
12+
context.Event.ReferenceId = _sessionId;
13+
else
14+
context.Event.SetEventReference("session", _sessionId);
15+
16+
if (context.Event.IsSessionEnd())
17+
_sessionId = null;
18+
}
19+
}
20+
}

Source/Shared/Plugins/Default/100_HeartbeatPlugin.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ namespace Exceptionless.Plugins.Default {
77
[Priority(100)]
88
public class HeartbeatPlugin : IEventPlugin, IDisposable {
99
private SessionHeartbeat _heartbeat;
10+
private readonly TimeSpan _interval;
11+
12+
/// <summary>
13+
/// Controls whether session heartbeats are sent.
14+
/// </summary>
15+
/// <param name="interval">The interval at which heartbeats are sent after the last sent event. The default is 30 seconds.</param>
16+
public HeartbeatPlugin(TimeSpan? interval = null) {
17+
_interval = interval.HasValue && interval.Value.Ticks > 0 ? interval.Value : TimeSpan.FromSeconds(30);
18+
}
1019

1120
public void Run(EventPluginContext context) {
1221
if (context.Event.IsSessionHeartbeat())
@@ -26,12 +35,12 @@ public void Run(EventPluginContext context) {
2635
return;
2736

2837
if (_heartbeat == null) {
29-
_heartbeat = new SessionHeartbeat(user, context.Client);
38+
_heartbeat = new SessionHeartbeat(user, _interval, context.Client);
3039
} else if (_heartbeat.User.Identity != user.Identity) {
3140
if (_heartbeat != null)
3241
_heartbeat.Dispose();
3342

34-
_heartbeat = new SessionHeartbeat(user, context.Client);
43+
_heartbeat = new SessionHeartbeat(user, _interval, context.Client);
3544
} else {
3645
if (_heartbeat != null)
3746
_heartbeat.DelayNext();
@@ -48,11 +57,12 @@ public void Dispose() {
4857

4958
public class SessionHeartbeat : IDisposable {
5059
private readonly Timer _timer;
51-
private readonly int _interval = 30 * 1000;
60+
private readonly TimeSpan _interval;
5261
private readonly ExceptionlessClient _client;
5362

54-
public SessionHeartbeat(UserInfo user, ExceptionlessClient client) {
63+
public SessionHeartbeat(UserInfo user, TimeSpan interval, ExceptionlessClient client) {
5564
User = user;
65+
_interval = interval;
5666
_client = client;
5767
_timer = new Timer(SendHeartbeat, null, _interval, _interval);
5868
}

Source/Tests/Plugins/PluginTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ public void VerifyPriority() {
508508
Assert.Equal(0, config.Plugins.Count());
509509
config.AddPlugin<EnvironmentInfoPlugin>();
510510
config.AddPlugin<PluginWithPriority11>();
511-
config.AddPlugin<PluginWithNoPriority>();
511+
config.AddPlugin(new PluginWithNoPriority());
512512
config.AddPlugin("version", 1, ctx => ctx.Event.SetVersion("1.0.0.0"));
513513
config.AddPlugin("version2", 2, ctx => ctx.Event.SetVersion("1.0.0.0"));
514514
config.AddPlugin("version3", 3, ctx => ctx.Event.SetVersion("1.0.0.0"));

0 commit comments

Comments
 (0)