Custom Event Stacking in Exceptionless

custom event stacking with exceptionlessSometimes you just need things to be your way.

We get it... your morning coffee, folded towels, and how events stack (group) in your event reporting application should be controllable and customizable.

Well, thanks to a great suggestion by @adamzolotarev, now they are! Well, the events, at least.

Why Custom Event Stacking? #

We do our best to group your events into relevant and smartly-named stacks, but there are cases where you may want to specifically name a stack and attribute certain events to it for organization, reporting, troubleshooting, or other reasons.

To facilitate this need, we created SetManualStackingKey, which both .NET and JavaScript client users can set.

How Do I Create Custom Event Stacks? #

Adding your own custom stacking to events in Exceptionless is super easy. Below are examples for both .NET and JavaScript.

In these examples, we are using setManualStackingKey and naming the custom stack "MyCustomStackingKey".

So, any events you use the below for will be a part of the custom stack, and all other events, exceptiones, logs, feature usages, etc will still be stacked automatically, like normal, by the app.

.NET Custom Event Stack Example #

try {
throw new ApplicationException("Unable to create order from quote.");
} catch (Exception ex) {
ex.ToExceptionless().SetManualStackingKey("MyCustomStackingKey").Submit();
}

Alternatively, you can set the stacking directly on an event (e.g., inside a plugin).

event.SetManualStackingKey("MyCustomStackingKey");

JavaScript Custom Event Stack Example #

var client = exceptionless.ExceptionlessClient.default;
// Node.Js
// var client = require('exceptionless').ExceptionlessClient.default;

try {
throw new Error('Unable to create order from quote.');
} catch (error) {
client.createException(error).setManualStackingKey('MyCustomStackingKey').submit();
}

How Do We Stack Up? #

We're always interested in what you think of Exceptionless' features and functionality, so let us know if you find custom stacking useful, need help implementing it, or just want to chat over on GitHub.

Thanks for reading!

New Releases: Exceptionless 3.2.1, .NET Client 3.3.6, JavaScript Client 1.3.2, UI 2.3.1

![Exceptionless error logging](/assets/img/news/blog-header-image-3.2.1.jpg)

Since the last major release cycle, we've made several minor releases, including Exceptionless 3.2.1, Exceptionless.NET 3.3.5, and Exceptionless.UI 2.3.1.

Lets take a look at some of the highlights, and you can check out the full release notes on each at the provided links, below.

Exceptionless 3.2.1 #

We fixed a few minor bugs made a few improvements to the main platform. Check them out!

  • Free accounts can now look up events by reference ID (bug).
  • Improvements to posting events via GET.
  • Items can now be added and removed from project data (bug).
  • Users can log directly in to an existing account if they attempt to sign up with the same credentials now, rather than getting an error.
  • @VikzSharma fixed the "too many bad attempts" lockout feature. Thanks!
  • There is also a once-an-hour limit on user signups and email address changes thanks to @VikzSharma.

Upgrading: Only self-hosters need to worry about upgrading - see details on the full release notes.

Exceptionless.NET 3.3.3 - 3.3.6 #

We've pushed out several minor releases of the .NET client in the last few weeks. I'll cover the major stuff here, but you can view the full release notes and get the latest source on GitHub.

3.3.6 #

  • An issue causing query string params and cookies to be renamed when dictionary key names were being changed has been fixed.
  • client.Register() now respects your session setting.
  • Manual stacking now uses a model instead of a string, which lets us send a stack title and key value pairs telling the event how to be stacked. More info on the release page.

If you are using manual stacking, this is a required update. See the release page for the latest source.

3.3.5 #

  • New extension methods have been added for events, making it easier to set valid geo coordinates, tags, and more.
  • A few new variables and parameters have been added for session heartbeats and id setting. See release notes for details.
  • @InlineASM fixed an issue for geolocations that have different separators - thanks!

3.3.4 #

  • @ahmettaha added SubmitLog and CreateLog overloads without source parameters. Thanks!
  • ExceptionlessClient.Default.Configuration.UseDebugLogger() only worked in debug mode with client source, so we replaced it with ExceptionlessClient.Default.Configuration.UseInMemoryLogger()
  • The serializer wasn't always being passed through so it could get known event data helper methods, which was causing some silent failures - this has been fixed.

3.3.3 #

  • @adamzolotarev added the ability to take complete control over how an event is stacked (be careful) by adding the ability to do manual stacking by calling EventBuilder.SetManualStackingKey("MyStackingKey")
  • You can now ignore events by user agent bot filter when you call (ExceptionlessClient.Default.Configuration.AddUserAgentBotPatterns("*bot*")) on the client side or edit project settings server side.
  • The default max size limit of RequestInfo has been increased.
  • Extra nesting has been reduced by merging exception.Data properties into the Error.Data dictionary.
  • Bug Fix: AggregatedExceptiones that contain more than one inner exception are no longer discarded.
  • Bug Fix: Machines with a Non-English locale will not process events when SetGeo is used.
  • Bug Fix: ArgumentNullException will no longer be thrown if post data or cookies contain a null key.

Exceptionless.JavaScript 1.3.2 #

  • @frankebersoll contributed by adding support for offline storage, which can be enabled by calling client.config.useLocalStorage(). Thanks!
  • User agent bots can be ignored via a filter now with (`client.config.addUserAgentBotPatterns("bot")) on the client side or via project settings on the server side.
  • @frankebersoll also added support for manual stacking (be careful! grants complete control). See release notes for instructions.
  • The implementation of the angular $stateChangeError has also been improved.

Full release notes

Exceptionless.UI 2.3.1 #

There's nothing major to report with the UI, just a few tweaks.

  • The project settings pages has been reworked by adding the ability to specify user namespaces, and user agents that the clients will ignore. @VikzSharma also fixed an issue where the single page app could be clickjacked - thanks again!

Full release notes and latest release download.

Questions? Let Us Know! #

If you've got any questions about any of the release notes above, please don't hesitate to let us know by commenting below or submitting an issue to the respective GitHub repo, above.

Thanks for checking out our release notes.

Add Reverse Geocoding to Your App

Reverse Geocoding

We recently introduced reverse geocoding into Exceptionless and are now adding features to make full use of it.

What we'd like to do in this blog article is walk any interested developers through the process of adding it to their own app.

We'll talk about the resources and services we're using to pull it off, why we chose them, and give you code snippets for implementation. It's all open source, so we've also included links to all the relevant code in hopes it will make your life easier!

Lets check it out.

What is Reverse Geocoding? #

It’s the process of taking geo coordinates or an IP Address and resolving it to a physical address (E.G., city, county, state/province, country).

Why You Need It #

Reverse Geocoding - User Location

Wouldn’t it be nice if you could provide location services to your users automatically? Maybe help them fill in a shipping form from a zip code or there current location?

With the launch of Exceptionless 2.0 we added a geo property to all events. This allows us to translate valid latitude and longitude coordinates into a physical location. Our goal was to begin capturing the data then and enable different scenarios and uses later. This also allowed us to show end users where their customers events are being submitted from.

What does it cost? #

One of our primary goals with Exceptionless is to be completely open source and easy to use (both in setting up self hosting and using the product). We had to take this into account when picking any library or service, because we want a painless setup and no additional costs for self hosters, all while adding additional value!

Please note that if you love the services we use, you should look into using one of their paid plans or at least promoting them with a positive review, shout out, etc (everyone needs to eat at the end of the day, right?).

After researching many different services, we ended up goin

g with GeoLite2's free, offline, downloadable databases. These databases are free and updated once a month, but if you require a more accurate and up-to-date database they offer a paid subscription. We also use their open source library for interacting with the database in memory.

Automating the GeoIP Database Download #

We use our very own Foundatio Jobs to download the most up-to-date database. Foundatio Jobs allows us to run the job in process or out of process on a schedule in Azure.

Alternatively, you could use the PowerShell script we created for downloading the database.  <a href="https://github.com/exceptionless/Exceptionless/blob/master/src/Exceptionless.Core/Jobs/DownloadGeoIPDatabaseJob.cs" target="_blank">DownloadGeoIPDatabaseJob</a> downloads the database over http and extracts the file contents to disk using Foundatio Storage.

Please feel free to take a look out our job for a complete sample including logging and error handling:

var client = new HttpClient();
var file = await client.GetAsync("http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz", context.CancellationToken);
if (!file.IsSuccessStatusCode)
throw new Exception("Unable to download GeoIP database.");

using (GZipStream decompressionStream = new GZipStream(await file.Content.ReadAsStreamAsync(), CompressionMode.Decompress))
await _storage.SaveFileAsync("GeoLite2-City.mmdb", decompressionStream, context.CancellationToken);

Looking up a Physical Address from an IP Address #

Resolving the geo coordinates into a location

After we automate the database download, the next step involves loading the database in memory using the open source library provided by MaxMind and querying by the IP address. The code below will do very basic IP validation and lookup the records using the API.

private DatabaseReader _database;
public async Task<GeoResult> ResolveIpAsync(string ip, CancellationToken cancellationToken = new CancellationToken()) {
if (String.IsNullOrWhiteSpace(ip) || (!ip.Contains(".") && !ip.Contains(":")))
return null;

var database = await GetDatabaseAsync(cancellationToken);
if (database == null)
return null;

try {
var city = database.City(ip);
if (city?.Location != null) {
return new GeoResult {
Latitude = city.Location.Latitude,
Longitude = city.Location.Longitude,
Country = city.Country.IsoCode,
Level1 = city.MostSpecificSubdivision.IsoCode,
Locality = city.City.Name
};
}
} catch (Exception) {
// The result was not found in the database
}

return null;
}

private async Task<DatabaseReader> GetDatabaseAsync(CancellationToken cancellationToken) {
if (_database != null)
return _database;

if (!await _storage.ExistsAsync("GeoLite2-City.mmdb")) {
Logger.Warn().Message("No GeoIP database was found.").Write();
return null;
}

try {
using (var stream = await _storage.GetFileStreamAsync("GeoLite2-City.mmdb", cancellationToken))
_database = new DatabaseReader(stream);
} catch (Exception) {
// Unable to open GeoIP database.
}

return _database;
}

Then, just call the ResolveIPAsync method with an IP address to look up the location details.

var location = await ResolveIPAsync("YOUR_IP_ADDRESS_HERE");

Feel free to take a look at <a href="https://github.com/exceptionless/Exceptionless/blob/master/src/Exceptionless.Insulation/Geo/MaxMindGeoIpService.cs" target="_blank">MaxMindGeoIPService</a> for a complete sample that includes logging, error handling, caching of the results, and IP validation for higher lookup throughput. We’ve spent the time writing tests and optimizing it to ensure its rock solid and works great. So feel free to grab our IGeoIPService interfaces and models and use them in your app.

It’s worth noting that in our app, we use the IP address provided in the event. This could come from a server request or the actual machine's IP address. We also fall back to the API consumer's client IP address.

Looking up a Physical Address from Geo Coordinates #

As stated previously, every event submitted to Exceptionless has a geo property that can be set. If it’s set, we will attempt to look up your location by the geo coordinates using a third party service. We used the open source Geocoding.net library, which abstracts the major different third party reverse geocode services into an easy to use API (options are always good!).

After we decided on the library, we evaluated a few API/lookup services based on cost and accuracy. We ended up going with the Google Maps GeoCoding API. They offer 2500 free requests per day and are one of the most used location services in the world.

Next, let’s write the code that will look up our location from a latitude and longitude. You can find our complete example here.

Remember to get your free api key from Google before running the code below.

public async Task<GeoResult> ReverseGeocodeAsync(double latitude, double longitude, CancellationToken cancellationToken = new CancellationToken()) {
var geocoder = new GoogleGeocoder("YOUR_API_KEY");
var addresses = await geocoder.ReverseGeocodeAsync(latitude, longitude, cancellationToken);
var address = addresses.FirstOrDefault();
if (address == null)
return null;

return new GeoResult {
Country = address[GoogleAddressType.Country]?.ShortName,
Level1 = address[GoogleAddressType.AdministrativeAreaLevel1]?.ShortName,
Level2 = address[GoogleAddressType.AdministrativeAreaLevel2]?.ShortName,
Locality = address[GoogleAddressType.Locality]?.ShortName,
Latitude = latitude,
Longitude = longitude
};
}

Finally, just call the ReverseGeocodeAsync method with a latitude and longitude to look up the location details.

var location = await ResolveGeocodeAsync(44.5241, -87.9056);

Final Thoughts on Reverse Geocoding #

It took us a bit of work and research initially to get everything working flawlessly for location services. We hope you grab our code off of GitHub to save yourself all that work. Also, it’s worth noting that we use Foundatio Caching to cache the results of location lookups. It drastically increased the performance and cut down on our limited number of requests to rate-limited third party services!

We also queue work items to look up the geo location since that can be an expensive operation. So, please take this into consideration anytime you are interacting with a third party service.

Feedback? Questions? #

Get in touch on GitHub or leave a comment below to let us know your thoughts or questions. We're always open to suggestions and will do what we can to help you out if you're having issues with implementation!

Replacing DIY Exception Logging with Exceptionless - Case Study

referral-rock-logoToday we bring you a case study from the founder of Referral Rock and serial entrepreneur, Joshua Ho.

Referral Rock is a referral platform for small businesses that Josh created after he "... realized small businesses lacked the tools to make a customer referral program work." The app allows businesses to easily and effectively create, implement, and administer a rock-solid referral program to help grow their business.

Exceptionless recently became a part of Referral Rock's exception reporting and logging toolkit, replacing Joshua's home-grown exception logging solutions, and here are his thoughts!

"


I've always done my own exception logging. Very basic stuff, where I would just log exceptions to my local drive. This gave me a nice place to just look at errors in my ASP.NET code. As with most code, it ended up in production deployments. At one point, I even built some web pages to view the logs remotely. Those little exception logging code snippets also made it into about 3-5 other projects as the years went by. I knew there was software out there that could do this, but I more or less had it solved for myself. **But that changed recently.**

"One huge benefit Exceptionless adds to my business is giving me the ability to provide better customer support."

Enter Exceptionless #

As I've been growing my own SaaS business for referral software, called Referral Rock, I realized there were times my old solution wasn't effectively capturing all the exceptions and I would have to venture into the Event Log to find out what happened. Also, I liked being able to view the production server remotely, but my little web pages got lost somewhere and I wasn't excited about coding them again. Who likes to code the same thing more than once? Not I.

"I could see details on the requests, browser cookies, user identity... all much more than I could see using my old solution."

So that led me to look for other solutions to view my exceptions remotely, which is when I found Exceptionless. With the help of support, I got it up and running fairly quickly. The guys at Exceptionless were very responsive and helpful in getting me setup.

Usage & Evaluation #

Being a startup, I was initially using the free version for about 2 weeks and was blown away. The UI was great and I love the AngularJS based responsiveness. Soon I had a great pulse on my production server. I could see details on the requests, browser cookies, user identity... all much more than I could see using my old solution. Once I had it set up, I started to see the benefits using other types of events in Exceptionless, such as logging. I started adding some logs when I was debugging an issue with a customer, and it worked great.

"With the help of support, I got it up and running fairly quickly. The guys at Exceptionless were very responsive and helpful in getting me setup."

One huge benefit Exceptionless adds to my business is giving me the ability to provide better customer support. Not only do I know when errors are happening, but also who is seeing them. This allows me to have an opportunity to reach out to that specific customer, once the issue is fixed, and say something like "I saw you had and error when you did XYZ, I wanted to let you know it is fixed now so you can try it again". Taking opportunities to provide that level of service has helped my business.

We are now running a paid version of Exceptionless with multiple projects and I look forward to adding more logs and playing with other features to give me even greater visibility into my web app. Thanks guys!

- Joshua Ho // Founder, Referral Rock


No - Thank You, Josh! #

We love to see people enjoying Exceptionless - it's our baby, and we've put a lot of blood, sweat, and tears (I blame Blake) into it. Keep rocking it with Referral Rock!

Track and View User Session Data - New Exceptionless Feature!

app user session logging

To many, this feature may be the missing piece... that connection you've always wanted to make between users, bugs, exceptions, app events, etc. I'm talking about, of course, user session tracking!

That's right, you can now use Exceptionless to track users as they use your app, which of course will come in super handy when you want to know exactly what they did to stumble upon or cause an error or trigger an event.

Continue reading to learn more about sessions and how you can enable them for your apps.

Session Overview #

First, you must have a paid (premium) Exceptionless plan to report on sessions if you are hosting with us. This is mainly because of the added resource requirements the feature puts on our infrastructure. We think it's definitely worth it, though!

Sessions can be searched/filtered like all other events - they are just an event type like exceptions or logs.

What's in a User Session Event? #

Each user session records how long they were active and what they did. For instance, the average user session on be.exceptionless.io the first week we monitored it using the feature was two hours.

With that, each user session event has a "Session Events" tab that displays all the relevant events that occurred during that session and allows you to see exactly what that user did. This is especially helpful, of course, if that session lead to an exception or noteworthy event in your app.

![App User Session Reporting](/assets/img/sessions-event-tab-user-footsteps-300x142.jpg)

All unique data that remains constant throughout the user session is also stored in the event, such as browser and environment information.

![app user session unique data](/assets/img/sessions-unique-user-data-300x155.jpg)

Sounds Good. How do I Set it Up?

sessions-dashboard-nav First, you'll need to update to the latest client versions to enable sessions, then you'll have to follow the below steps to begin tracking them. Once you've got that set up, visit the new Sessions section under the Reports option on your main dashboard, or navigate directly to https://be.exceptionless.io/session/dashboard. If you are self hosting, make sure you update to Exceptionless 3.2 first.

Turning On Session Tracking #

For Exceptionless to track a user for your app, you need to send a user identity with each event. To do so, you need to set the default user identity via the following client methods:

C#
using Exceptionless;
ExceptionlessClient.Default.Configuration.SetUserIdentity("UNIQUE_ID_OR_EMAIL_ADDRESS", "Display Name");
JavaScript
exceptionless.ExceptionlessClient.default.config.setUserIdentity('UNIQUE_ID_OR_EMAIL_ADDRESS', 'Display Name');

Once the user is set on the config object, it will be applied to all future events.

Please Note: In WinForms and WPF applications, a plugin will automatically set the default user to the **Environment.UserName** if the default user hasn't been already set. Likewise, if you are in a web environment, we will set the default user to the request principal's identity if the default user hasn't already been set.

If you are using WinForms, WPF, or a Browser App, you can enable sessions by calling the EnableSessions extension method.

C#
using Exceptionless;
ExceptionlessClient.Default.Configuration.UseSessions();
JavaScript
exceptionless.ExceptionlessClient.default.config.useSessions();

How do Sessions get Created? #

Sessions are created in two different ways. Either the client can send a session start event, or we can create it automatically on the server side when an event is processed.

We have a server-side plugin that runs as part of our pipeline process for every event - its sole purpose is to manage sessions by using a hash on the user's identity as a lookup for the session id.

If the session doesnt' exist or the current event is a session event type, a new session id will be created. If we receive a sessionend event, we close that session and update the end time on the sessionstart event.

We also have a CloseInactiveSessionsJob event that runs every few minutes to close sessions that haven't been updated in a set period of time. This allows you to efficiently show who is online and offline during a time window.

How do I Enable Near Real-Time Online/Offline Then? #

We do this by default in our JavaScript, WinForms, and WPF clients when you call the UseSessions() method.

In the background, we send a heartbeat event every 30 seconds if no other events have been sent in the last 30 seconds.

You can disable this heartbeat from being sent by passing false as an argument to the UseSessions() method.

The WinForms and WPF clients will also send a SessionEnd event when the process exits.

Can I Manually Send SessionStart, SessionEnd, and heartbeat Events? #

Sure! You can send these events manually via our client API to start, update, or end a session. Please remember, though, that a user identity must be set.

C# #

using Exceptionless;
ExceptionlessClient.Default.SubmitSessionStart();
ExceptionlessClient.Default.SubmitSessionHeartbeat();
ExceptionlessClient.Default.SubmitSessionEnd();

JavaScript #

exceptionless.ExceptionlessClient.default.submitSessionStart();
exceptionless.ExceptionlessClient.default.submitSessionHeartbeat();
exceptionless.ExceptionlessClient.default.submitSessionEnd();

Source

Tell Us What You Think #

As always, please send us your feedback. You can post it here in the comments or submit a GitHub Issue and we will get back to you as soon as possible! We're always looking for contributors, as well, so don't be afraid to jump in and be the hero the code needs. Contributors get Exceptionless perks!

New Releases for ALL the Codes! Exceptionless 3.2

Exceptionless 3.2 HighlightsThat's right folks - we've gone and released Exceptionless 3.2, which includes releases for Exceptionless.NET, Exceptionless.JavaScript, and Exceptionless.UI! Awe yeah.

We're kind of excited, in case you couldn't tell. Big stuff in here, like session tracking (#BOOM), licensing changes (less confusion - it's a good thing), and posting via HTTP GET (such easy, much wow)!

Lets get into some of the details...

Exceptionless 3.2.0 #

#

Sessions! #

Track and automatically manage user sessions for much more visibility into their user experience, how they interact with your app, and, of course, any errors or events that occur related to that user. This answers the age-old question, "What the hell was this guy doing when stuff blew up!?"

Check out the User Sessions post for more details and instructions!

Exceptionless Event Sessions

HTTP GET! #

Now it's even easier to integrate with Exceptionless from any environment, because you can post event or meta data via HTTP GET! More info coming soon (blog post).

License Change #

The server and all Exceptionless projects are now using the Apache License, so there should be much less confusion on how things are licensed moving forward. Boring stuff, we know... but important.

User Location #

User locations are now resolved from geographic coordinates or the IP address. We look at the geo property for coordinates or an IP, then we inspect the IP. If no IP or geo coordinates present themsevles, we fall back to the client IP that the event was submitted from.

More Speed Improvements #

As always, we keep speed improvements in mind with each release. With 3.2, we've been able to make more massive improvements in processing time for events (over 250% per 1000 events!) and further reduce app startup times and elastic query execution times. #alwaysoptimizing!

Hourly Throttling #

The hourly event-throttling threshold has been increased from 5x to 10x the plan limit. The way we calculate it is by taking the plan limit and dividing it by the hours in the month, then multiplying it by 10.

Signup Experience #

The signup experience has been improved when inviting users, as well. Thanks @mcquaiga and @theit8514 for your contribution!

Upgrading (Self Hosters) #

Self hoster? Need to upgrade? The latest code can be downloaded from the release page. All other users: No action required.


Exceptionless.UI 2.3.0 #

User experience was the primary focus of this UI release, along with the new sessions feature. More details below, including other improvements and a few bug fix details.

Adding a New Project #

When adding a new project, users will now have a much better experience, and we added a JavaScript configuration section for JS projects. Check it out!

Reference id Lookup #

Support for looking up reference ids was added, so you can now navigate to /event/by-ref/YOUR_REFERENCE_ID to look up an event.

Other Improvements #

  • Better messages and a loading mask has been added to data grids to improve user experience when filtering and loading data.
  • Escaping of strack traces containing HTML or CSS has also been improved.
  • You can now sort extended data items alphabetically.
  • The request and environment info tabs for events now show additional extended data.

Bug Fixes #

  • You can now create an organization or project that ends with a period or whitespace.
  • Sometimes an incorrect time range would be set when users used the history chart/graph to select a period of time to drill down to.

Check out the Exceptionless.UI Changelog for all the code changes (87 files / 75 commits).


Exceptionless.NET 3.3.2 #

Users on desktop applications can now opt-in to sessions by setting a default user and calling the below:

ExceptionlessClient.Default.Configuration.UseSessions();

Also, module info was not being included in some error reports, which was incorrect. That has now been fixed.

The full change log can be viewed on GitHub.


Exceptionless.JavaScript 1.3.1 #

Besides integrating with the above, the only major change in the JavaScript client, like the .NET client, was that users can now op-in to sessions. To do so, set a default user and call the below:

exceptionless.ExceptionlessClient.default.config.useSessions();

Check out the full change log for all the dirty details.

Elasticsearch Case Study - By Exceptionless

Elasticsearch case study

We recently wrote a case study regarding how Elasticsearch has helped us improve speed and ease of scale as we've continued to improve Exceptionless.

Last week, Elasticsearch published that case study on their blog, so we wanted to make sure our readers had a chance to check it out!

We've written about our use of Elasticsearch in numerous articles before, including:

In the case study, we discuss how Elasticsearch let us improve our real-time dashboard, work with time zones more easily, index data much faster, not have to worry so much about pre-aggregating data, maintain much less code, improve backup and restoration, shrink disk space usage, and, perhaps most importantly, drastically improve scalability.

Basically, we really like Elasticsearch and you should check it out if you haven't already.

Do you use Elasticsearch? #

Let us know how it's helped you, or if you have any questions we might be able to answer, by commenting below!

Using Reference Ids for Better Customer Service

Exceptionless Reference IDs

This week we want to talk about reference Ids and how we use them to improve customer service for Exceptionless. And how we think you can do the same for your users.

What's a Reference Id? #

A reference id is a unique identifier, generated by the user, that allows you to look up a submitted event at a later time. This is important because event Ids are not created until after the event is processed, so there is no other way to look up an event. We also use Reference Ids to help deduplicate events server side, which is another reason why it’s important that they are unique.

Using Reference Ids for the Greater Good #

One of the ways we use Reference Ids is to support end users, something that is very important to us. There is nothing more frustrating than being unable to help a user that is experiencing a problem because you don’t know what they are seeing (when there could be thousands of errors in your app).

To combat this issue, we always try to include a reference Id with every error message shown to the user. This allows end users to contact us with a Reference Id and receive help immediately for the error they are seeing because we can easily track down and view it.

Reference Id Example #

The Exceptionless clients have built in support to generate and track unique Reference Ids for every error event. Behind the scenes, we register a default Reference Id plugin that sets a Reference Id on submitted error events and stores the Reference Id in an implementation of ILastReferenceIdManager. The Reference Id manager’s goal is just to persist and retrieve the last used Reference Id.

Since this is a default plugin, we can enable this behavior by calling the UseReferenceIds() method on the configuration object to register the default Reference Id on every error event.

C# Example #

using Exceptionless;
ExceptionlessClient.Default.Configuration.UseReferenceIds();

JavaScript Example #

exceptionless.ExceptionlessClient.default.config.useReferenceIds();

Please note that you can create your own plugin to create your very own Reference Id(s).

To get the the last used Reference Id, you can call the GetLastReferenceId() helper method on the the ExceptionlessClient instance.

C#
using Exceptionless;
// Get the last created Reference Id
ExceptionlessClient.Default.GetLastReferenceId();
JavaScript
// Get the last created Reference Id
exceptionless.ExceptionlessClient.default.getLastReferenceId();

You might have noticed how easy it is to get or add Reference Id’s to your events automatically. This makes it a breeze to let your developers track down user-facing issues by displaying the Reference Id to your end users.

We display Reference Ids to all of our end users anytime an error occurs in our ASP.NET WebAPI application. We accomplish this by adding a custom <a href="http://www.asp.net/web-api/overview/error-handling/web-api-global-error-handling" target="_blank">IExceptionHandler</a> and return a new error response to include the Reference Id as shown below:

public class ExceptionlessReferenceIdExceptionHandler : IExceptionHandler {
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken) {
if (context == null)
throw new ArgumentNullException(nameof(context));

var exceptionContext = context.ExceptionContext;
var request = exceptionContext.Request;
if (request == null)
throw new ArgumentException($"{typeof(ExceptionContext).Name}.{"Request"} must not be null", nameof(context));

context.Result = new ResponseMessageResult(CreateErrorResponse(request, exceptionContext.Exception, HttpStatusCode.InternalServerError));
return TaskHelper.Completed();
}

private HttpResponseMessage CreateErrorResponse(HttpRequestMessage request, Exception ex, HttpStatusCode statusCode) {
HttpConfiguration configuration = request.GetConfiguration();
HttpError error = new HttpError(ex, request.ShouldIncludeErrorDetail());

string lastId = ExceptionlessClient.Default.GetLastReferenceId();
if (!String.IsNullOrEmpty(lastId))
error.Add("Reference", lastId);

// CreateErrorResponse should never fail, even if there is no configuration associated with the request
// In that case, use the default HttpConfiguration to con-neg the response media type
if (configuration == null) {
using (HttpConfiguration defaultConfig = new HttpConfiguration()) {
return request.CreateResponse(statusCode, error, defaultConfig);
}
}

return request.CreateResponse(statusCode, error, configuration);
}
}

The next step is to replace the existing IExceptionFilter with the one above.

Config.Services.Replace(typeof(IExceptionHandler), new ExceptionlessReferenceIdExceptionHandler());

Finally, when an error occurs in your app, you’ll get a more user friendly error response that contains a Reference Id!

{
"message": "An error has occurred.",
"reference": "411085622e"
}

We’ve found that this allows consumers of our API to quickly contact us with a reference id and get up and running quickly!

Looking up Events by Reference Id #

You might be thinking: "Reference Ids are great, but what do I do with them now that I have one." Well, you can view the event that they reference on our site or via our API. This can be accomplished two different ways:

  • Hotlinking: You can link directly to a submitted event by outputting a link in your UI or logs (e.g. https://be.exceptionless.io/event/by-ref/YOUR_REFERENCE_ID)
  • Search: You can search via our api/ui with reference:YOUR_REFERENCE_ID

Your Turn! #

We hope this article was helpful, and we'd love to know if you're using Reference Ids and how they've helped you help users, solve internal issues, etc. Post a comment below!

A Better Approach to Running Azure WebJobs

Azure Webjobs

Lets talk about jobs in the Exceptionless world for a minute and how you can use our methods to improve your Azure WebJobs.

A job is a specific task/process that runs and does something like send a mail message, etc.

Out with the Old #

Prior to version 3.1, we used an early version of the Foundatio Jobs system to run our out-of-process jobs via Azure WebJobs. We found it to be quite a pain to figure out which jobs were running or eating up system resources because every job was titled Job.exe (just like figuring out the w3wp IIS process is running). Also, just to run an out-of-process job, one would have to compile the source, copy dependencies to a common bin folder, and then run an executable (Job.exe) with parameters that specify the job type.

These tedious and error-prone tasks that had to be completed just to get a job to run are a thing of the past.

In with the New #

In Exceptionless 3.1 we focused on refining and improving jobs. To do so, we created a new console application for each job and specified settings in the code versus error prone command line options as shown here.

Now, with Foundatio, our open source app building block solution used in Exceptionless, you just define a new Job that runs (via the run method) and you can use the Foundatio Jobs API to run the job in process, out of process, continuous, or one time without changing the implementation.

This new approach also gave us a great deployment strategy, for free. Simply copy the job executable and bin folders and run it anywhere!

Jobs (processes) running in Azure as an Azure web job #

Exceptionless Jobs and Processes

How you can implement a better Azure WebJob #

Foundatio Jobs allows you to run a long running process (in process or out of process) with out worrying about it being terminated prematurely. By using Foundatio Jobs you gain all of the following features without changing your job implementation:

  • Run job in process
  • Run job out of process
  • Run job with a start up delay
  • Run job in an continuous loop with an optional interval delay.

In this sample we'll just define a new class called HelloWorldJob that will hold our job that increments a counter and derives from JobBase. Please note that there are a few different base classes you can derive from based on your use case.

using Foundatio.Jobs;

public class HelloWorldJob : JobBase {
public int RunCount { get; set; }

protected override Task<JobResult> RunInternalAsync(JobRunContext context) {
RunCount++;
return Task.FromResult(JobResult.Success);
}
}

Now that we have our job defined we can run our job in process with a few different options:

var job = new HelloWorldJob();
await job.RunAsync(); // job.RunCount = 1;
await job.RunContinuousAsync(iterationLimit: 2); // job.RunCount = 3;
await job.RunContinuousAsync(cancellationToken: new CancellationTokenSource(TimeSpan.FromMilliseconds(10)).Token); // job.RunCount > 10;

But our goal is to run this out of process in an Azure WebJob (this also works if you want to run this as a service or from the desktop).

The first step is to create a new console application and reference the Foundatio NuGet Package and the project that contains our HelloWorldJob. We are going to call our console application HelloWorldJob. Inside of the Program class, we'll update the main method to run our job.

using System;
using System.IO;
using JobSample;
using Foundatio.Jobs;
using Foundatio.ServiceProviders;

namespace HelloWorldJob {
public class Program {
public static int Main(string[] args) {
// NOTE: This should be the path to your App_Data folder of your website.
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\..\Api\App_Data");
if (Directory.Exists(path))
AppDomain.CurrentDomain.SetData("DataDirectory", path);

// Get a service provider so we can create an instance of our job.
var serviceProvider = ServiceProvider.GetServiceProvider("JobSample.JobBootstrappedServiceProvider,JobSample");

var job = serviceProvider.GetService<JobSample.HelloWorldJob>();
return new JobRunner(job, initialDelay: TimeSpan.FromSeconds(2), interval: TimeSpan.Zero).RunInConsole();
}
}
}

The last steps are to simply compile the project and deploy it to your Azure website!

Questions? #

If you have any questions please feel free to contact us via our contact page, in app message, GitHub issues or Discord.

Exceptionless.JavaScript 1.2 Release Notes

Exceptionless JavaScript ClientWe've got a quick release for the Exceptionless JavaScript Client that includes a few new additions to what it supports and a quick bug fix or two.

Shout out to @frankebersoll for his help!

Please download and update to the source code here, and view the full change log here.

JavaScript Client v1.2 Notes #

Errors #

@frankebersoll added support for deduplicating JavaScript errors. Thanks!

Data Collection #

Frank's back at it, adding support for collecting extra exception data that was already in the .NET client like module info, custom exception properties, and everything else that was already displayed in the UI when using the .NET client.

Node Info #

Support for collecting Node module info has also been added (thanks again, @frankebersoll).

Bug Fix - Invalid States #

An issue where Data Exclusions could cause events to be submitted in an invalid state has been fixed.

As always, let us know if you have any questions or feedback! #