Navigating The Exceptionless World of Go

Go does not have the concept of exceptions. Welp, I guess Exceptionless doesn't apply. Let's pack it up and head home, everyone.

Kidding, of course. While Go really doesn't have the concept of exceptions, errors still happen in Go codebases, and those errors need to be handled. Having recently built an Exceptionless client in Go, we had the opportunity to take a deep dive into the Go programming language, and we'd like to share some of what we discovered along the way.

No Scrubs, No Exceptions #

Gif of No Scrubs music video

Let's take a quick look at why the Go programming language doesn't have exceptions, and then we can dive into how we work around this. From the Go FAQ:

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

Essentially, Go doesn't want any scrubs (try/catch paradigm) hanging out the passenger side of their best friend's (that's you) ride trying to call things exceptions. Not everything is fatal. Not everything is an exception. Errors can happen without being so extraordinary to be considered an exception. As such, Go made the opinionated decision to simply not acknowledge exceptions as a construct.

For those instances where an error is truly catastrophic, Go has some build in mechanism to handle and recover. Those are documented here.

So with no exceptions, how should errors be handled when using Exceptionless?

No Exceptions != No Errors #

Just because there are no exceptions doesn't mean there are no errors. Fortunately, Go has a well-documented paradigm for handling errors without the need for something like an exception.

Every function you write in Go can have multiple return values. So, a well-written function will return both an error and the actual function's response. Let's take a look at a very basic example of this.

package main 

import (
"github.com/go-errors/errors"
)

func CombineStrings(string1 string, string2 string) (string, error) {
var errToReturn error

if string1 == "" || string2 == "" {
errToReturn = errors.New(fmt.Sprintf("Strings must have at least one character"))
}

returnValue := string1 + string2
return returnValue, errToReturn
}

In this example, we are using the go-errors module to build an error of the type error. We do a simple check to see if both of the string arguments to our function are not empty. If either is empty, we assign an error to the variable errToReturn. Then, in our return statement, we return both the expected return value of the function and the error.

When calling this function, the developer needs to first check for an error before moving on. That might look like this:

combinedString, err := CombineString("hello, ", "world")

if err != nil {
//Handle Error
}

fmt.Print(combinedString)

If an error is so bad that your code cannot execute any further, you may want to call Go's built in handler, panic, like this:

if err != nil {
panic(err)
}

Ok, So How Do We Use Exceptionless in an Exceptionless Environment? #

Exceptionless (that's us) is a way of thinking about your code. It does not actually rely on the concepts of exceptions. In fact, Exceptionless is so much more than error/exception handling.

So, with that framing, it becomes a lot more clear that Exceptionless can very easily be used within Go code to handle errors. In fact, the structure for using Exceptionless in Go is not all that different from other programming languages.

We have a (currently as of the writing of this post) beta version of a Go Exceptionless client. You can find that here. I'll show you how to use the client, but do note that the client API could change until it's out of beta. The goal here is less about showing off the Go client and more about showing you how to handle errors with an event monitoring service within Go.

The first thing you'll need to do is sign up for Exceptionless and get an API Key. You can sign up here. Once you've done that, you'll need to install the Exceptionless Go client in your project. To do so, simply run the following:

go get https://github.com/exceptionless/Exceptionless.Go

Once that's installed, import it into your project like this:

import (
"github.com/exceptionless/Exceptionless.Go"
)

Now, let's take a look at how we would use the client. On app startup (probably in your main function), you can initialize and configure the client like this:

func main() {
config := exceptionless.Exceptionless{
ApiKey: "YOUR API KEY"
}
exceptionless.Configure(config)
}

With the Exceptionless client configured, you can now use it to handle all sorts of events in your app. We're going to focus in on exception—or, not exceptions as the case might be with go 😉.

Let's use our example function from earlier as the example.

package main 

import (
"github.com/go-errors/errors"
"github.com/exceptionless/Exceptionless.Go"
)

func SomOtherFunction() {
combinedString, err := CombineString("", "world")

if err != nil {
exceptionless.SubmitError(err)
}

fmt.Print(combinedString)
}

func CombineStrings(string1 string, string2 string) (string, error) {
var errToReturn error

if string1 == "" || string2 == "" {
errToReturn = errors.New(fmt.Sprintf("Strings must have at least one character"))
}

returnValue := string1 + string2
return returnValue, errToReturn
}

If you remember the CombineStrings function will return an error if either string is empty. So in my mock function SomeOtherFunction, I am calling CombineStrings and passing in an empty string. When the error comes back, all we need to do is handle it with Exceptionless using exceptionless.SubmitError(). Simple as that.

Conclusion #

There is a lot more you could do. The Exceptionless Go client allows you to build custom events with more info. You could even bypass the client entirely and make an http request directly to the Exceptionless API in your error handler.

But the point is, despite Go being an exceptionless programming environment, you can catch errors and report them. So that means Exceptionless is actually a match made in heaven for the exceptionless environment of Go.

How To Monitor Events in Swift

Swift, best known as the programming language that enables iOS, iPadOS, and macOS developers to build apps, has been growing in popularity over the years. As of February of 2020, it had leaped into the top-10 of most popular programming languages. While Swift can be used for more than just Apple platform products, we're going to keep things simple today and focus on how to set up event monitoring with Swift in an iOS app.

For this tutorial, we're going to be using Exceptionless to collect events, so you'll need to sign up for a free account here. You're also going to need the following:

  • Mac computer (sorry Windows and Linux folks)
  • Xcode
  • Xcode command line tools

Getting Set Up #

If you don't already have Xcode installed on your Mac, do so now. You can grab an install here. If you already have it installed, you will need to make sure you are upgraded to Xcode 12. To check your version, open Xcode and check to see that you have version 12 or above. It'll look like this:

Xcode version

Once you're done installing it, you can check that everything was successful by either firing up Xcode or running the command I showed above. Now, we need to make sure you have the Xcode command line tools. These should be installed automatically when you install Xcode, but we want to be sure. To check if these were installed, run the following in Terminal:

xcode-select -p

If a path is printed out, you're all good. That's all you need. Open up Xcode, and we'll get started.

Creating a Project #

Once you've opened Xcode, you're going to create a new project. Choose iOS and choose App, then click Next.

iOS app creation in Xcode

Next, you'll give the app a name in the Product Name section. I'll call mine Exceptionless Events. Make sure your interface is SwiftUI, your lifecycle is SwiftUI App, and your language is Swift. Then, click Next.

You'll be asked where you want to store your code. Choose a folder that makes sense for you. When you've done that, your new project will load up. It should look like this:

Example iOS project

We're not going to spend a lot of time building an actual iOS app here, but we'll add enough functionality to test errors and test logging events. To set ourselves up, we're going to create two buttons in the UI. So, inside your ContentView.Swift file, replace the Text element with this:

VStack{
Spacer()
Button(action: {}) {
Text("Log Message")
}
Spacer()
Button(action: {}) {
Text("Log Error")
}
Spacer()
}

Note: the Spacer items are just there to make the screen a little more manageable (not necessarily prettier). Each button has a label and each has an action. However, the actions don't do anything just yet. We'll get that taken care of next.

Above your body declaration, add these two functions:

private func submitEvent() {
print("event")
}
private func submitError() {
print("error")
}

Then, back inside your body, change each button's action to call the associated function like this:

VStack{
Spacer()
Button(action: submitEvent) {
Text("Log Message")
}
Spacer()
Button(action: submitError) {
Text("Log Error")
}
Spacer()
}

Just to test that everything is working, click the play button at the top of the Xcode window. This will build the app and launch a simulator. Click each button and make sure "event" and "error" print out in the console. Pro tip: the console may be shrunk down and hidden. At the bottom of your Xcode window, hover over the section that looks like this and drag to increase the window size so you can see the console.

console example

Assuming all is working, we can move on to using Exceptionless!

Creating the Exceptionless Class #

We're going to handle all of the event processing and posting through a single class. You may want to refactor into multiple classes in a production project, but this will get the point across, I think.

Let's create a new file. In the file menu bar, click File, New, File, and be sure to choose a Swift file and not SwiftUI. Let's name the new file Exceptionless.swift.

Inside that file, we're going to create our Exceptionless class. Add the following so your file looks like this:

import Foundation

class Exceptionless {
var apiKey: String
init(apiKey: String) {
self.apiKey = apiKey
}

func submit(type: String, event: String) {
switch type {
case "log":
print("log")
case "error":
print("error")
default:
print("log")
}
}

private func logMessage(logMessage: String) {

}

private func logError(errorEvent: String) {

}
}

This class doesn't do anything besides accept the Exceptionless API key, but you can see we're starting to frame out how to build our event monitoring system. To keep things simple, the submit function accepts a type of event (in string form) and it accepts the event string. The event string for the purposes of this example should be a simple log message or error message.

Let's test what we have out so far. We want to connect our two buttons from earlier to the function, submit, that we created in the Exceptionless class. We should see the console print out "log" or "error" depending on what button we press.

Back inside your ContentView.swift file, replace the actions for each button like this:

var body: some View {
VStack{
Spacer()
Button(action: {Exceptionless.init(apiKey: "YOUR API KEY").submit(type: "log", event: "Log Message")}) {
Text("Log Message")
}
Spacer()
Button(action:{Exceptionless.init(apiKey: "YOUR API KEY").submit(type: "error", event: "Log Error")}) {
Text("Log Error")
}
Spacer()
}
}

This is a very simplified example. In reality, your buttons would be calling some other action and you'd be catching errors or logging events dependent on what that action does. Let's test this out, though. We don't actually need an API Key yet, so leave the apiKey string as is. Run your project on a simulator and click each button. You should see "log" and "error" printed in the console.

Congratulations! You just built a class within Swift that will act as the framework for submitting events.

Building Events #

We're going to keep things simple. The Exceptionless API has some great documentation and some simple examples that we'll be making use of. Let's start by updating out switch statement to actually call the functions specific to events or errors:

func submit(type: String, event: String) {
switch type {
case "log":
return logMessage(logMessage: event)
case "error":
return logError(errorEvent: event)
default:
print("log")
}
}

You can do whatever you'd like with your default case. I'm going to keep it as a print statement, though. What we've done is we've said if we call submit with a type of "log", we will call the logMessage function. If we call submit with a type of "error", we will call the logError function.

Let's build out the logMessage function fist. Replace the existing empty function with this:

private func logMessage(logMessage: String) {
let now = Date()
let formatter = ISO8601DateFormatter()
let datetime = formatter.string(from: now)
let eventDictionary : [String: Any] = [ "type": "log", "message":logMessage, "date": datetime ]
let jsonData = (try? JSONSerialization.data(withJSONObject: eventDictionary, options: []))!
let jsonString = String(data: jsonData, encoding: String.Encoding.ascii)!
print (jsonString)
}

Because the Exceptionless API takes JSON body payloads, we want to build our event into JSON. To do that, we first start with a dictionary. As you can see, we are creating an eventDictionary which is simply keyvalue pairs in string format. We add in the date in ISO format so we know when our events happen. The actual event message is what we passed in all the way back from when we clicked the button.

We take that dictionary and convert it to JSON with Swift's built-in JSONSerialization function. Before posting to the API, let's take a look at the output in the console and make sure we're happy with it.

Build and run the app, then click "Log Message". You should see the following printed in the console:

{"message":"Log Message","date":"2021-04-05T13:34:39Z","type":"log"}

Your date will, of course, be different.

Ok, we've gotten this far. Let's build our error function. Don't worry, we'll come back and think about how we want to actually post these events to Exceptionless.

Replace the empty logError function with this:

private func logError(errorEvent: String) {
let now = Date()
let formatter = ISO8601DateFormatter()
let datetime = formatter.string(from: now)

let errorDictionary : [String: Any] = ["message": errorEvent, "type": "System.Exception"]

let errorJson = (try? JSONSerialization.data(withJSONObject:errorDictionary, options: []))!
let errorJsonString = String(data: errorJson, encoding: String.Encoding.ascii)!
let eventDictionary : [String: Any] = [ "type": "error", "@simple_error":errorJsonString, "date": datetime ]
let jsonData = (try? JSONSerialization.data(withJSONObject: eventDictionary, options: []))!
let jsonString = String(data: jsonData, encoding: String.Encoding.ascii)!
print (jsonString)
}

You'll see this function is very similar to the logMessage function. However, we are nesting data in our JSON, so we need two dictionaries. We use one dictionary to house the "@simple_error" and another to house the entire event. In other words, nest the JSON version of our errorDictionary inside the JSON version of our eventDictionary.

Let's build and run the app and try both buttons. You should see the following printed out:

{"message":"Log Message","date":"2021-04-05T13:46:58Z","type":"log"}
{"type":"error","@simple_error":"{\"message\":\"Log Error\",\"type\":\"System.Exception\"}","date":"2021-04-05T13:46:59Z"}

Your dates will be different, but the rest should look about the same. We now have a Log Event AND an Error Event. I think we're ready to post to the Exceptionless API.

Remember, if you haven't done so, you'll need to sign up for a free Exceptionless account, create a project, and grab an API Key.

Posting To The API #

When we post to the Exceptionless API, we are posting JSON data. In the examples above, we are printing the JSON string version of what we will ultimately push to the API. Let's see how it will look when actually posting to the API.

Within our Exceptionless class, let's create a new private function called postToAPI.

private func postToApi(event: Data) {
let url = URL(string: "https://collector.exceptionless.com/api/v2/events")!

var request = URLRequest(url: url)
request.httpMethod = "POST"

// insert json data to the request
request.httpBody = event
request.setValue("application/json; charset=utf-8",
forHTTPHeaderField: "Content-Tye")
request.setValue("application/json; charset=utf-8",
forHTTPHeaderField: "Accept")
request.setValue("Bearer " + self.apiKey,
forHTTPHeaderField: "Authorization")

let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
}

task.resume()
}

Walking through this new function, let's take a look at what's going on. First, we are setting the URL for our API request. This is a hardcoded value and probably not something you'd want to do in a production application.

Next, we are creating a new request and assigning values to the request. You'll note that our postToAPI function accepts a Data value argument called event. That is what we will be posting to the API and it represents the JSON version of the event we build when we click Log Message or Log Error.

Then, we set header values for our request. Take note, that we are assigning the Authorization value by using our Exceptionless API key. We'll need to get that key from Exceptionless before actually sending events. \

Finally, we are posting the request using URLSession.

To actually call this function, we need to make two minor changes. First, back in your ContentView.swift file, we need to add our Exceptionless API key in where we left placeholder text. Again, this is not how you'd assign the API Key in a production app, but it's a simple example to get you going.

Now, back in the Exceptionless.swift file, let's update both the logMessage and logError functions. Instead of converting our event to a JSON string, we're going to leave it as JSON and pass it through to our postToAPI function like this:

private func logMessage(logMessage: String) {
let now = Date()
let formatter = ISO8601DateFormatter()
let datetime = formatter.string(from: now)
let eventDictionary : [String: Any] = [ "type": "log", "message":logMessage, "date": datetime ]
let jsonData = (try? JSONSerialization.data(withJSONObject: eventDictionary, options: []))!
postToApi(event: jsonData)
}

And here's the logError function:

private func logError(errorEvent: String) {
let now = Date()
let formatter = ISO8601DateFormatter()
let datetime = formatter.string(from: now)

let errorDictionary : [String: Any] = ["message": errorEvent, "type": "System.Exception"]

let errorJson = (try? JSONSerialization.data(withJSONObject:errorDictionary))!
let errorJsonString = String(data: errorJson, encoding: String.Encoding.ascii)!
let eventDictionary : [String: Any] = [ "type": "error", "@simple_error":errorJsonString, "date": datetime ]
let jsonData = (try? JSONSerialization.data(withJSONObject: eventDictionary))!

postToApi(event: jsonData)
}

Go ahead and build and run your app. Click the Log Message button and then the Log Error button. You won't see anything printed in the console, but if you go into your Exceptionless dashboard, you should see a new log and a new exception.

Exceptionless dashboard

Congratulations! You just built a simple event monitoring service in native Swift code. You can extend this pretty easily thanks to the Exceptionless class and leverage it in a production application.

Swift is a fun language, but when using it to build mobile applications, it can be difficult to debug and track events. Exceptionless can help, and hopefully this tutorial shows how to implement such event tracking in SwiftUI and Swift.

If you'd like to see the sample Xcode project, we have a repo for it here.

How to Build a Custom Go Client For a REST API

Picture of gophers working

Exceptionless is powered by a REST API. When you interact with the dashboard UI, when you use the .NET client, and when you use the JavaScript client, you are interacting with the REST API. It is well-documented, and it can be used without any client libraries. This paradigm makes it simple for developers to create their own wrappers around the API. In fact, we recently started work on building an official Go client for Exceptionless. Along the way, we learned some tips and tricks that may be helpful for others that want to build clients and SDKs in Go that wrap RESTful APIs.

First, a little about Go. Go is a statically typed language, built originally by the folks at Google. Go, while close in syntax to many other statically typed languages, differs in that it is no object oriented. Go is also very well suited for gRPC APIs, but that does not prevent it from being used with REST APIs, as we'll see here today.

Getting Started #

In order to build our Go client, we will need to have Go installed. Honestly, this can be the hardest step as it involves setting environment variables and updating your profile source PATH. So rather than risk confusing you with the steps to install Go and get started, I'm going to simply point you to Go's official install instructions.

You can find those instructions here.

Once you've installed Go, you will need to have a text editor handy so that we can write our new Go code. From the command line, create a new folder and call it "go-rest". Change into that directory, and let's start writing some code.

The Main File #

In Go, you will always have a main.go file which acts as the entry point for your source code. We need to set that up first, so let's do that now. In the root of your project folder, create your main.go file. Inside that file, let's start by declaring our package and importing a module. Add the following:

package main

import (
"fmt"
)

Your file won't do anything yet, but we're laying the groundwork. We have declared our package as main, and we have imported the built-in fmt library from Go for formatting.

Next, we need a main function, so let's create that. Add the following below your import statement:

func main() {
fmt.Println("Hello, world")
}

This is the example program Go's example docs show, so we might as well run it. From your command line, inside your project directory, run this command:

go run .

You should see Hello, world printed in the command line terminal window.

Now that we have the fundamentals down, let's talk about how Go works so that we can build our REST API client. You can include as many functions in your main.go file as you'd like and you can call those function from within other functions. But, like any other programming language, it's probably smart to separate code to make it easier to work with.

Creating an API Helper #

The nice thing about Go is that when you create a new file, that file is automatically available from any of your other files as long as they share the same main package.

Since we are building a REST client, it probably makes sense to create a file that would handle all our API routing request. So, create a file in the root of your project called api.go.

Inside that file, make sure to reference the main package at the top like this:

pacakage main

We are also going to import a couple packages here as well, so your file should look like this:

package main

import (
"bytes"
"log"
"net/http"
)

These packages are all built into Go itself. You can install external packages as well, and we'll explore that soon.

Now that we have the start of our API file, it's good to think about what our client needs to do. With a REST API, you may have the following request methods:

  • GET
  • POST
  • PUT
  • DELETE
  • PATCH

You may not need all of these for your client, but it's good to know that they exist. In our case, we are going to implement the GET and POST methods and with those as a template, you should be able to extend your code to implement PUT, PATCH, and DELETE.

Let's start by building the POST method since its the backbone of our client. In your api.go file, below the import statement, add the following:

//Post posts to the Exceptionless Server
func Post(endpoint string, postBody string, authorization string) string {
baseURL := "YOUR API URL/"
url := baseURL + endpoint
var jsonStr = []byte(postBody)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
req.Header.Set("Authorization", "Bearer "+authorization)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
return string(resp.Status)
}

In our real-world use case, we are making requests to the Exceptionless API, so we know the post body needs to be a JSON string. This is why the postBody is of type string. If your API is expecting a different format, make sure you type your variable properly here. The other two arguments in our Post function are pretty self explanatory. The endpoint string is the endpoint on your API you want to call. The authorization string is the token/API key needed to authenticate into the API. You could choose to handle the authorization differently, if you wanted. For example, if your API expected basic authentication, your authorization variable might be a string mapping of username and password.

One of the tricks here is if you are sending JSON to your REST API, you will need to convert the body into a format the http client library within Go can handle. We're doing that with the bytes.NewBuffer(jsonStr) call.

Now, let's put together our GET function:

//GET makes api GET requests
func Get(endpoint string, authorization string) map[string]interface{} {
baseURL := "YOUR API URL/"

url := baseURL + endpoint

httpClient := &http.Client{}
req, err := http.NewRequest("GET", url, nil)

if err != nil {
fmt.Println(err)
}

req.Header.Add("accept", "application/json")
req.Header.Add("Authorization", "Bearer "+authorization)

res, err := httpClient.Do(req)
if err != nil {
fmt.Println(err)
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
}

var result map[string]interface{}
json.Unmarshal([]byte(body), &result)
return result
}

Much like out POST request, our GET request takes in arguments. We only need the endpoint and the authorization arguments for this function. This function is pretty straight forward. However, if you want to read the response as JSON, you need to take an extra step as I've shown above.

You will want to a string mapping by unmarshaling the JSON returned by the API. Of course, your API may not return JSON, so use this accordingly. If you do need to unmarshal the JSON, you simply need to pass the response body into the json.Unmarshal() as shown above.

These two functions should help you build your other REST-related functions. Now, let's take a look at helper functions that will make your client easy to use while sending the correct data to your API.

Convenience Functions #

A good SDK or client API wrapper will include helper functions so the developer using it doesn't have to still manually build requests to your API. The best way to build helper functions is to start with your data model. Let's say, for example, your API expects a JSON payload like this:

{
"BookTitle": "The Great Gatsby",
"Author": "F. Scott Fitzgerald",
"Rating": 7
}

In this case, we'd probably want to create a struct type variable that we can use to build our payload for the reqest. That might look like this:

type BookRating struct {
BookTitle string
Author string
Rating uint
}

A quick note on Go variables and functions. If the variable or the function name is capitalized, it is exported and available throughout your program.

Now that we have a struct we can use, we can start to build a helper function that would build a payload for our API. In keeping with the example in the JSON and the struct above, let's pretend our API take a POST request to rate a specific book. For some reason, our API needs the string title and string author of the book, and it needs an interger for the rating. You might create a helper function like this:

func RateBook(title string, author string, rating uint): bool {
newRating := BookRating{
BookTitle: title,
Author: author,
Rating: rating
}

json, err := json.Marshal(newRating)
if err != nil {
fmt.Println(err)
return false
}

resp, err := Post("rateBook", string(json), "API KEY")
if err != nil {
fmt.Println(err)
return false
}

return true
}

In the RateBook function, we are allowing the developer to simply pass in the title, author, and the rating. We then build the JSON payload for the developer and send it to the Post function we created earlier. When we are building the JSON payload, we must use json.Marshal to convert our struct to a type that can be used with our REST API.

You'll note, the authorization argument in the above example is "API KEY", but a good SDK will have stored that API Key when the client was initialized. I'll leave it up to you on how you'd like to handle this, but it could be as simple as calling Configure function with the developer's API Key and storing the key in memory.

Wrapping Up #

This is a simple example of how you might build a Go client for a REST API. The concepts are general, but they hopefully help you if you find yourself needing to build your own client. Exceptionless will be launching its own Go client soon. If you haven't tried Exceptionless for your application's event monitoring, give it a shot now.

How to Debug Electron Apps

Electron is a great framework that makes developing cross-platform desktop applications easy. If you're a JavaScript developer, it is probably the first thing you'll reach for when you decide to build a desktop application. I know I did. In building my first and second desktop applications, I used Electron. In that process, I learned some tricks to help the development process go a little smoother. One such trick is how to better debug issues that may arise in the packaged, production version of your Electron app that you won't see in development.

If you're not familiar with Electron, it is a framework that allows you to write apps with web technologies and use them on the desktop. This is accomplished by packaging your app within its own dedicated Chromium-based application. Think of a web browser. All it is is a desktop application that allows you to explore web apps and web pages. That's what Electron does for your app. It creates a one off desktop browser. In doing so, you get access to native desktop functionalities that are not available to traditional web applications.

Like with many software projects, you might find that your local development experience doesn't exactly match what happens in production. When an app is minified, built, compiled, and packaged for production use, there can be subtle changes that can break the experience of the application or break the app entirely. This is especially true when dealing with desktop applications that have more access than you might be used to with web apps. Debugging problems when your application works locally but doesn't work in its production state can be frustrating. This becomes even more frustrating in Electron when you only have access to the web application's JavaScript output in production and not the underling Electron code's output. Fortunately, we can solve this by using an error monitoring service.

We're going to be making use of Exceptionless and the Exceptionless JavaScript client to debug and monitor our Electron application. Exceptionless is free to get started and totally open-source. Let's get started.

From within your Electron app's project directory run npm i exceptionless.

Now, we can configure the Exceptionless client and use it anywhere. This means we can use it in both the "front end" (web app) code and the "back end" Electron code. For the sake of this tutorial, we are only going to be focusing on the Electron code. Inside your main.js file, add the following below your other import/require statements:

const { ExceptionlessClient } = require("exceptionless")
const client = ExceptionlessClient.default.config.apiKey = "YOUR API KEY"

You can get your project API key in the Exceptionless project settings page.

Now, with the client configured, you can start using Exceptionless to log events. The cool thing is these don't need to just be errors. If you want to log when a particular function is called within your main Electron code, you can use client.submitLog("Function called") but with something more descriptive. By submitting log events for particular functions, you will know for sure the function is being called. Of course, you can and should also track errors. This is as simple as calling client.submitException(error) with your error.

This is all very abstract, though. So, let's look at a practical example. Let's say your Electron app is listening to some event in order to write some data to the computer's hard disk. We need a trigger to come from our "frontend" html/js code, and then we need to read that trigger and take some action. In Electron, we use ipcMain to listen for events from the frontend code. An example of this might look like:

ipcMain.on("Save File", async (event, message) => {
try {
await fs.writeFileSync("/path/to/where/you/want/to/store/the/file", message)
client.submitLog(`Wrote file successfully with the following content: ${message}`)
} catch(e) {
client.submitException(e)
}
});

I added a log event that is sent to Exceptionless in the try and I catch the error and send that to Exceptionless in the catch. The beauty of this is we know when the event is successful, which is comforting, but we also know when it fails and why. This is important, because a failure here would be a silent failure in your app.

Let's say the file path you think you're trying to write to does not exist after your Electron app is built and packaged (a common issue is that PATH variables exposed by default to applications can be different than what you use and have available in your development environment). If that path did not exist, the writeFileSync command would not work. You would have no idea why, and your users would only know it when they tried to fetch the file that should have been written.

Imagine trying to debug that without error and event monitoring. You'd fire it up locally on your machine, run some tests, try to replicate the steps exactly as the user did them. And everything would work. You wouldn't see the error because your development environment is just different enough from the production environment to keep you from realizing that the write path in production doesn't exist.

There are a million other ways your Electron app can fail silently. By adding error and event monitoring, you can quickly debug problems that would otherwise have you banging your head off your desk.

Saying Thanks to the Open Source Community Through Sponsorship

Exceptionless has always been committed to the open-source software ecosystem. In fact, Exceptionless is entirely open-source, and we try our best to make it easy for anyone to host their own instance of our software. Our main repository has nearly 2,000 stars on Github and has seen contributions from 26 different people. Across all our repositories, we've seen hundreds of issues opened, dozens of pull requests, and countless comments. And for all of that, we are so appreciative. But we wanted to show our appreciation by doing more than just saying thanks.

Thanking Open Source Contributions Through Sponsorship #

Today, we're excited to announce that we have sponsored Ben Adams through the Github Sponsors program. As you all know, we are huge fans of .NET, and Exceptionless is one of the only monitoring tools built around the .NET ecosystem first. Ben, like us, cares deeply about open-source software and the .NET community. We hope that by sponsoring him, we will be giving back in a tangible way, not just to Ben but to the sustainability of open-source.

Before diving into more about who Ben is and why we're sponsoring him specifically, it might be good to reflect on the importance of sustainable open-source development. Exceptionless survives through a hosted service, but many open-source projects don't have that option. This leaves maintainers in the unenviable position of having to write software, fix bugs, and respond to issues while receiving nothing more than "thanks" as payment.

"Thanks" doesn't pay the bills. Exposure doesn't pay the bills.

We don't want to see open-source developers shut down their projects. We want developers to have optionality. A quote from Kitze, the founder of Sizzy and other products, really illustrates this sentiment well:

Open source, writing blog posts, and playing with tweaking lint settings and editor themes all day are completely fine until your landlord knocks on your door or you’re at the checkout at the grocery store.

It's not just the viability of creating open-source software that becomes a problem, it's also the long-term sustainability when larger companies use that software. When we see companies like Amazon abuse open-source contributions, we see a door closing on the viability of software that is designed to survive through community and contribution.

So, while Exceptionless might not change the world by itself with its sponsorship, we want to lead by example. As Kitze said, Github stars don't pay the bills, but money does. So, we're opening our wallet and helping Ben Adams do what he's done so well for years.

Ben Adams #

Ben Admas Github Profile

Ben has been working on open-source projects for years. He has thousands of contributions across dozens of repositories. In fact, he is so prolific in his .NET contributions that everyone else with more contributions than him are Microsoft employees. It doesn't get more impressive than that. Here's a quick summary of Ben's open-source contributions:

  • His projects have 2,600 Github stars (which, again, don't pay the bills 😉)
  • He has over 20,000 commits
  • He has contributed to 25 different projects
  • He's opened over 1,500 pull requests

It should be clear by now that Ben knows his stuff. As mentioned before, Ben cares a whole lot about .NET, and so do we. So, this sponsorship makes perfect sense for us.

For the gamers and the game developers, Ben has you covered too. He is the CTO of Illyriad Games which makes Age of Ascent. Age of Ascent is a real-time MMO that puts players in the cockpits of fighter ships in space. As you might expect, the game's tech stack makes heavy use of .NET and other Microsoft-related services.

Other people recognize Ben's contributions too:

Since the beginning of the .NET Core journey we’ve had some amazing contributors but I seriously enjoy working with
@ben_a_adams!
-David Fowler

There's smart, then there's crazy. Then there's Crazy Smart. That's Ben (and he's a lovely chap, too)!
-Rich Turner

Junior .Net Developer
Senior .Net Developer
Ben Adams .Net Developer
😁 😁 😁
-Marco Rossignoli

How Does Sponsorship Work? #

Github Sponsors allows each developer to define what comes along with sponsorship at various levels. Ben was gracious enough to offer his own development time to Gold Level sponsors, and we simply could not pass that up. While we would be happy to simply sponsor Ben's work, having him contribute code to our open-source repositories will help Exceptionless take a major step forward.

Sponsoring open-source developers is not a new concept, but having it built into Github has help elivate the visibility of portential sponsorship opportunities. We hope that by having companies like Exceptionless sponsor (and publicize those sponsorships) developers, it will encourage others to do the same. Open-source sustainability still has a long way to go, but any step forward we can help the ecosystem take, we're willing to do it.

Thanks so much, Ben! We look forward to working with you and following your continued contributions across the open-source community.

How to Use React Hooks to Monitor Events in Your App

The introduction of Hooks in React was a game-changer. Developers now had options when it came to creating stateful components. It used to be that if you had a stateful component, you'd have to use a class component, and if you had a presentational component, you could choose between a class or a function component. Now, with hook, state can be managed inside function components. But that's not all Hooks did for React developers.

Hooks introduced a better way to reuse stateful functions. Today, we're going to explore how to build a custom React hook that will allow us to monitor events in our application. Those events can be errors, messages, feature usage, logs, or anything you want. There are plenty of error monitoring services out there, but we're going to make use of Exceptionless for three reasons:

  1. Real-time updates
  2. It's open-source
  3. The Exceptionless JavaScript client gives us a singleton option (which makes for a good example in this post)

Let's get started by starting a new React project. You'll need to have the following available on your machine:

  • Node >= 8.10
  • npm >= 5.6
  • Text editor

Assuming you have that ready, open up a terminal. To create a new React project, simply run:

npx create-react-app Monitoring-App

Replace Monitoring-App with whatever you'd like to call your project. When everything is done, change into your new application's directory (cd Monitoring-App). Now, we need to install the Exceptionless JavaScript client.

To do so, simple run:

npm install exceptionless

When that's done installing, start your app (npm start) and open your project directory in the text editor of your choice.

What are Hooks? #

React's docs have the simplest definition of Hooks and I think it's worth calling that out here:

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

The popularity of classes in JavaScript in general has oscillated greatly. However, within the React ecosystem, it was the ONLY option for creating components that could actually do anything. Because many JavaScript developers do not like and do not want to use classes, the option to write function components was a huge step forward. But outside of developer preferences, React Hooks open up new ways to write code in React apps, and hopefully help you reduce your code footprint in the process.

Hooks introduced a new way to handle lifecycle events in React as well. Gone are the componentDidMount and componentDidUpdate methods. In is the simple and elegant useEffect function.

You can read a lot more about the logic behind React's introduction of hooks here, but for the sake of this tutorial, I think that's enough of an intro.

Creating our first Hook #

A hook is just a function that returns a value. The big gotcha with Hooks, though, is that they can only be called within the body of a function component. Keep that in mind as we progress. It's still relatively early in the life of Hooks, but there are some standards and conventions in place. Convention is to house your custom hooks in a folder called something like hooks. Let's do that.

Create a folder within the src directory of your project called hooks. Inside that folder, we're going to create a file using another convention within the React Hooks community. Developers will generally name their hooks with a useFunctionPurpose type of pattern. For example, if you were building a Hook that determined whether a navigation bar should show up or not, you might name the Hook file useNavigation. For us, we're going to call our file useMonitoring.

Create that file inside the hooks directory, and then we can begin building the function. We'e going to go line by line and then I'll show the whole file in one snippet. Let's start by importing the necessary functions from React.

import { useState, useEffect } from 'react';

I briefly touched on useEffect. It can be thought of in the same context as componentDidMount or componentDidUpdate. We'll make use of this to handle some logic in the setup of our Exceptionless client. useState can be thought of like this.state and this.setState in React class components.

Next, we'll get access to the Exceptionless JavaScript client like this:

import { ExceptionlessClient } from "exceptionless/dist/exceptionless";

Note: it's important to reference the /dist/exceptionless file because Exceptionless targets multiple environments and we want to make sure to import the right one.

Next we're going to handle something I mentioned earlier. Exceptionless is a class-based client. This means we have the option of instantiating that class everytime we use it, or we can make use of a singleton provided to us out of the box by Exceptionless. We set up the singleton patter first and then we will use some logic to handle situations where that singleton may not need to be used. Go ahead and grab the default client from Exceptionless like this:

const defaultClient = ExceptionlessClient.default;

If you haven't done so, get yourself an API Key from Exceptionless. You can sign up for a free account here. Once you have that, we're going to provide that key to to the default Exceptionless client. I like to make use of environment variables even when using keys that are safe to expose in the browser. So mine looks like this:

defaultClient.config.apiKey = process.env.REACT_APP_EXCEPTIONLESS_API_KEY;

Now that we've handled all our imports and initial configuration, we can frame out our hook function. The bare bones function looks like this:

export const useMonitoring = ({ config, useDefault }) => {
// Code goes here
}

That's not much to look at, but there are two things I want to call out. The function name follows the convention I mentioned before, and this function takes two props: config and useDefault. If we are making use of the default singleton instance of the Exceptionless client, useDefault would be true and config would be null. However, if we are creating a custom instance of the Exceptionless client, config would be an object and useDefault would be either null or false.

Let's handle that logic. At the top of your function add the following:

const [client, setClient] = useState(defaultClient);

We are making use of the built-in useState hook here. We are simply defining a client state variable, defining a function that will be used to update that variable (setClient), and setting a defualt value for our variable. As you can see, we set the default value to use the default client from Exceptionless. Makes sense, right?

Next, we're going to use the built-in useEffect hook to determine what client should be returned for use in the particular part of the app that requested it.

useEffect(() => {
if(useDefault) {
return client;
} else if(config) {
config.apiKey = process.env.REACT_APP_EXCEPTIONLESS_API_KEY;
setClient(new ExceptionlessClient(config));
} else {
throw "Please pass useDefault as true or a config object in";
}
});

In this case, it's best to think of our useEffect function in the same way you think of componentDidUpdate. Any update to the component that called our useMonitoring Hook will trigger another event within our useEffect function.

If you wanted this useEffect method to operate more like componentDidMount, you would structure it like this:

useEffect(() => {

}, []) // <-- This array says only run this function once when the component mounts

The empty array in the above example can be filled with a list of dependencies. Say, for example, you want your useEffect method to run everytime some specific variable changes, you would just pass that variable name into the array.

Ok back to our useEffect method. You can see we are checking the useDefault prop I mentioned before. If it is truthy, we return the client state variable. We had already set that variable to use the singleton Exceptionless client, so all we have to do is return it. If useDefault is falsey, we then check for the config object. If it's there, we add the apiKey to whatever values were passed into the config object, and then we instantiate a new Exceptionless client.

If no config object is provided, we throw an error. You could take this a step further and check if the config prop is actually of type object, but, hey, that's what Typescript is for, right? We're living dangerously in this crazy, crazy dynamically typed world.

The final thing you need to do in your useMonitoring function is return the client. Just as easy as it sounds:

return client;

Here's the whole, complete file:

import { useState, useEffect } from 'react';
import { ExceptionlessClient } from "exceptionless/dist/exceptionless";
const defaultClient = ExceptionlessClient.default;
defaultClient.config.apiKey = process.env.REACT_APP_EXCEPTIONLESS_API_KEY;

export const useMonitoring = ({ config, useDefault }) => {
const [client, setClient] = useState(defaultClient);
useEffect(() => {
if(useDefault) {
return client;
} else if(config) {
config.apiKey = process.env.REACT_APP_EXCEPTIONLESS_API_KEY;
setClient(new ExceptionlessClient(config));
} else {
throw "Please pass useDefault as true or a config object in";
}
});
return client;
}

Ready to use this bad boy?

Using your custom Hook #

We created a basic React app, so let's just make use of what comes out of the box. Open your App.js file, and import your new custom Hook.

import { useMonitoring } from "./hooks/useMonitoring";

You'll also need to import the built-in useEffect Hook from React:

import { useEffect } from 'react';

Now, withing the main App function, you can use your new custom Hook:

const exceptionlessClient = useMonitoring({ useDefault: true });

How can we test this now? Well, let's make use of the useEffect function to throw an error as soon as the component mounts.

useEffect(() => {
throwException();
}, []);

As you remember, the empty array at the end of the function says this should only run once when the component mounts.

Now, define the actual throwException function like this:

const throwException = () => {
try {
throw new Error("Whoops, it broke");
} catch (error) {
exceptionlessClient.submitException(error);
}
}

Ready to test this out? If you previously started your React app, it probably already has worked. If you haven't started the app, go ahead and do that now.

You won't see much beyond the default starter React boiletplate. But that's OK. Open up your Exceptionless dashboard, and you should see an exception has, in fact, been captured.

Example exception from React app

If you click into that exception, you'll get a bunch of additional, useful, information. You can start to see how this can be helpful.

Now is the time where I remind you, our custom Hook can only be used within the body of your component functions. If you were to try to use your Hook in a Redux action function, you'd see an error. The beauty is, even if you're using Redux or something else, you can still throw errors back to your component and handle the error with your useMonitoring hook.

But Exceptionless is more than just errors. Let's build a quick, more practical example.

Usage Tracking #

In your App.js file, we're going to remove all the boilerplate and add some ugly buttons. No styling in this post. This is what your App.js file should look like now:

import { useMonitoring } from "./hooks/useMonitoring";

function App() {
const exceptionlessClient = useMonitoring({ useDefault: true });

const handleButtonClick = (planName) => {
exceptionlessClient.submitFeatureUsage(planName);
}
return (
<div>
<button onClick={() => handleButtonClick("Free")}>Free Plan - Learn More</button>
<button onClick={() => handleButtonClick("Starter")}>Starter Plan - Learn More</button>
<button onClick={() => handleButtonClick("Premium")}>Premium Plan - Learn More</button>
</div>
);
}

export default App;

You can see we have what might be a pricing page in an app. There are three buttons, one for each plan. We have wired up a button that will call a function that ultimately sends a feature usage event to Exceptionless. Go ahead and try clicking the buttons.

If you go to your Exceptionless dashboard and click the Feature Usage link on the left, you'll see these events have been captured in a dedicated section that makes it easy for you to track, well, feature usage. This may not replace your analytics provider, but it goes a long way.

Feature usage example

Wrapping up #

There is a lot more you can do with Exceptionless. For example, you may have noticed, we have only passed in the useDefault prop to our custom Hook. You can completely customize your Exceptionless configuration and pass in a config prop instead. I would encourage you to read up on the options here.

Exceptionless provides a lot of methods built into its JavaScript client that will make debugging, logging, and tracking easier.

Debugging a .NET Serverless App

Serverless development has become the hot thing in tech. Renting time on a virtual machine only when you need it saves money and resources. However, there are many gotchas that can make working with serverless technology difficult if you're not prepared. One such gotcha is event handling.

Unlike a Web Server applications, events in a serverless function act very similarly to console applications. Take AWS Lambda, for example. A Lambda function is going to spin up, execute, and spin down as quickly as it can. You want this. This is one of the key selling points of serverless, and how developers can ultimately save money. However, this can lead to problems when trying to process asynchronous events. We'll cover how to solve that in this post.

Today, we're going to build a simple .NET Hello World serverless application and we're going to implement event handling to log errors and other events. We're going to use the dotnet cli to install a new Lambda template, but first we need to make sure we have the Amazon Lambda Templates Package.

Once that's installed, create a new directory. I'm calling mine MyFunction, but you can call yours whatever you'd like. Change into that directory and run the following command:

dotnet new lambda.EmptyFunction --name MyFunction

This will create your new serverless function project in .NET. If you open up your project in Visual Studio or whatever IDE or code editor you prefer, you'll see your main file is Function.cs. You'll also have a very helpful README, and a json file with some default configuration. We're not going to be editing anything besides the Function.cs file, but it's good to know what's provided out of the box when generating a new lambda project.

We now need to add Exceptionless to the project. Exceptionless is an open-source event monitoring service available to all programming languages, but dedicated to .NET. Let's install it by running this command at the command line:

dotnet add package Exceptionless

This will install the most recent version of Exceptionless and is the foundation to you adding event handling in your serverless app. Once Exceptionless is installed, we can take a look at our code and see about where we might want to utilize Exceptionless.

Open up the Function.cs file again and add the Exceptionless namespace like this:

using Exceptionless;

Now, let's take a look at the function itself. It's a pretty simple function that returns a string in its uppercase form. We're going to change this around to instead return "Hello" plus the string passed in. Of course, that's not the focus of the tutorial, so we're going to also wire up error handling and event processing.

So, let's start with error handling. We can manually force an error that will fall into the catch block of a try/catch by not passing in a string to our function. Update your function to look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Exceptionless;

using Amazon.Lambda.Core;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace MyFunction
{
public class Function
{

public string FunctionHandler(string input, ILambdaContext context)
{
var client = new ExceptionlessClient(c =>
{
c.ApiKey = "YOUR EXCEPTIONLESS API KEY";
});

using var _ = client.ProcessQueueDeferred();

try
{
if (input == null)
{
throw new Exception("input value is required");
}
return "Hello, " + input + "!";
}
catch (Exception ex)
{
client.SubmitException(ex);
return ex.Message;
}
}
}
}

We've added the Exceptionless namespace, we've converted the function to take in a string and return a hello world string.

To test error handling, we have set up a try/catch that will throw if no string is passed into our function. If that happens, we send the exception to Exceptionless.

Because we need to make sure events are sent to Exceptionless before the function ends, we are using the Exceptionless method ProcessQueueDeferred().

Now, it's time to test. Fortunately, there's a super easy way to test our code before we deploy to AWS. In your root directory, find the test folder. Nested in that folder is a test file called MyFunction.Test.cs. We're going to edit this file a bit and run our tests from the command line.

Update the file to look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Xunit;
using Amazon.Lambda.Core;
using Amazon.Lambda.TestUtilities;

using MyFunction;

namespace MyFunction.Tests
{
public class FunctionTest
{
[Fact]
public void HelloWorldFunction()
{
var function = new Function();
var context = new TestLambdaContext();
var upperCase = function.FunctionHandler("John", context);

Assert.Equal("Hello, John!", upperCase);
}
}
}

This test should pass and we should not be sending anything to Exceptionless. To test it, switch into MyFunction/test/MyFunction.Tests and run dotnet test.

If your test passed, you're in great shape! But we don't just want to test our function. We want to make sure errors are sent to Exceptionless. So let's force an error.

Add the following to your test file:

 public class ErrorTest
{
[Fact]
public void ErrorFunctionTest()
{
var function = new Function();
var context = new TestLambdaContext();
var helloworld = function.FunctionHandler(null, context);

Assert.NotEqual("Hello, John!", helloworld);
}
}

The test should pass because we don't get a response of "Hello, John!". But what we want to do is check to see if the exception was sent to Exceptionless.

Open up your Exceptionless dashboard and you shoud see something like Exception in FunctionHandler input value is required. If you click on it, you'll see more details like this:

There are three tabs on the details page. In the above screenshot, I have selected the "Overview" tab. However, if you explore the other tabs, you'll see value data captured for you automatically.

Cool, so we created an exception. What about something a little more complex?

Let's say we want to track the usage of our new serverless function. We can do that pretty easily with Exceptionless. Let's change the current try/catch block to look like this:

 try
{
if (input == null)
{
throw new Exception("input value is required");
}
client.SubmitFeatureUsage("Serverless Function");
return "Hello, " + input + "!";
}
catch (Exception ex)
{
client.SubmitException(ex);
return ex.Message;
}

Here we are sending a feature usage event to Exceptionless as soon as the serverless function is triggered and we're sure the input is not null. The nice thing about this is that it doesn't rely on any errors being thrown and the rest of your function can continue to execute.

Let's run our tests again. We should get the feature usage event as well as an additional error because, remember, one of our tests forces a null exception.

In our dashboard, if we look at all event, we will see our feature usage event logged.

Feature usage event example

Conclusion #

These types of events (features, logs, errors) because especially useful in serverless environments. Often, default logging in a serverless environment is either non-existent or much more difficult to set up. By dropping Exceptionless into your serverless application, you can track just about anything you're interested in. If you'd like to see full examples of Exceptionless in Lambda functions, check out our examples here and here.

Why You Should be Practicing Exception Driven Development

You've heard of test-driven development. You've heard of behavior-driven development. Maybe you've even heard of acceptance-driven development. But you probably haven't heard much about exception-driven development. Let's take a look at what that type of development process is and why you should be practicing it.

Exception Driven Development #

Jeff Atwood, of Coding Horror, wrote a blog post almost 12 years ago about this topic. In those dozen years since his post, the need for exception-driven development has only increased, but developers and companies need a clear understanding of how and why to practice this type of development.

At a high-level, exception-driven development is simply the practice of monitoring for exceptions in your code and shipping fixes quickly. To ensure you are monitoring the right things and shipping the right fixes, all without burdening your users, you have to make sure you are collecting the right data.

Contextual data is the key to exception-driven development. Arbitrary data from various points of input may not be useful. Data that is highly contextual to user actions, user experiences, and points in time during the use of your app will allow you to target better solutions and ship faster.

With your contextual data in hand, you can start to fix issues. But here's the rub: You can't wait until you "have enough" fixed for a deployment. Part of the exception-driven development process is deploying often. Have a single bug that took one line to fix? Cool, ship it. Know that you'll have additional features to put out in a few days and you're considering holding your bug fix for that deployment? Don't. Ship the bug fix, then ship the features.

Exception-driven development isn't a mysterious new process for most developers, but it is a mindset shift within many organizations. Bug fixes, listening to the data, and responsiveness must take priority if you are to transition to an exception-driven development cycle. While it may not take much effort to change your company's internal process, it may still take some convincing. So, why should you and your company practice exception-driven development?

Why Exception Driven Development #

The first reason you should care is the most obvious reason. Your code has bugs. It does. No matter how good you are, you're going to ship bugs and you need to be ready to fix those bugs. Jeff Atwood probably said it best in his post about exception-driven development:

The question isn't how many bugs you will ship but how fast you can fix them.

Bugs are a part of code. Some never get fixed. But the ones that need to be fixed need to be fixed quickly. The trouble, of course, is that if you are relying on your users to tell you about these bugs, you've already taken too long to fix the issue.

Wait, what?

It's true. If your users have told you about bugs through email or Slack or Discord, you have already taken too long to resolve the issue. This is where the heart of exception-driven development comes in. You need to know about issues the second they arise.

You don't want to turn into this guy, right?

Picture of an angry cat acting as tech support

Think about it this way. How many lines of code does your application have? Got that number in mind? Ok, now divide it by 1000. Take your resulting number and multiply it by 15. That is possibly how many bugs you have in your software right now. Knowing this, don't you think it's important to try to find these bugs before your users have to tell you about them?

By practicing exception-driven development, you make your customers happier which protects your brand image. Do you want to be the brand that has a ton of bugs or do you want to be the brand that solves problems? You also make your developers happier. Would you rather fix a bug on a piece of code shortly after you were working on it, or do you want to try to fix it six months later after you've long forgotten about that code?

Customer retention, customer conversion, productivity, morale, and more are the reasons exception-driven development is important. Convinced yet? Good, now how do you do it?

How to Practice Exception Driven Development #

If you remember, the first step of exception-driven development is to collect contextual data. Contextual data means data more than the stack trace itself. You should be collecting those traces, but the stack trace is just part of the picture. Some of the questions your contextual data should be answering include:

  • What user had this problem?
  • Where in the application did the problem occur?
  • Has the problem occurred previously?
  • What actions did the user take leading up to the issue?

You can probably think of other questions you'd like answered to help you with the bugs that come in. But notice that the suggestion here is not to collect this information directly from your users. Don't wait for an email from them. Don't follow-up with questions about their operating system and browser. Collect that information upfront without the user having to take action, and you will have your contextual data. Services like Exceptionless make this easy.

But why use a service like Exceptionless? You're a developer, you can just build the solution yourself. Sure you can, but then you have to manage that solution. You have to fix bugs on that solution while also fixing bugs you've found from your actual production application. You're not in the business of running an error monitoring service. But Exceptionless is.

Once you've collected your contextual data, you should know what to fix, where to fix it, and why it needs to be fixed. So, the logical thing to do is—well—fix it. The trick, though, is not to allow code creep to slow your release down. It's easy (trust me we all do it) to say, "oh, I'll just add this little feature or change this one other thing" when you're trying to fix a bug. You have to fight that urge, fix the bug, commit the code, and ship it. Then, you can go back and work on the shiny thing that almost distracted you.

Once your code is shipped, if you are using Exceptionless, you can mark the version that fixed the bug right within your dashboard. By doing this, you'll automatically know if you have any regressions. Exceptionless will track new occurrences of the error and compare the occurrence against the version of software the user is using and the version you marked as solving the problem. If those versions match, it's a regression.

Conclusion #

The whole flow of collecting data, analyzing the data, writing code, and shipping should not be foreign to you. Yet, exception-driven development is still not practiced everywhere. Hopefully, this article helps you understand why you should change your ways and how to do it.

Comparing Monitoring Services

Error monitoring is vital to the success of any application. Console logs only get you so far. Once your app is out in the wild, asking your customers to open up the developer tools window and inspect it for errors is a deal-breaker. This is where error monitoring services come in. Let's take a look at three different services:

Each service, generally speaking, handles errors and logging for your applications, but not all services are created equally. Let's take a closer look at each and see how they stack up.

Log Rocket #

Log Rocket was founded in 2015 and is an error monitoring service that focuses on replaying the events that led up to the error. While some services focus on digging into the error details themselves, Log Rocket uses HTML from the user's session to reconstruct a playback of what the user experienced.

What LogRocket does is we capture a recording in real time of all the user activity so the developer on the other end can replay exactly what went wrong and troubleshoot issues faster.

Log Rocket example

Log Rocket is a venture-backed company that has raised $30 million in its history. It provides its tools exclusively as a for-profit, hosted solution. They do offer self-hosted options, but only as part of their enterprise, custom pricing.

Their focus is on user experience through the tracking of everything a user does on the site. While this is helpful, it is also a bit invasive in the fact that the default installation of Log Rocket captures just about everything a user does in the application.

Sentry #

Sentry was founded in 2012 and is focused on capturing errors at the code-level and sending them to a repository for review and organization. Sentry allows you to configure the groupings of errors and presents them in an interface designed to surface the most-pressing errors first.

In addition to surfacing errors, Sentry allows users to create custom alerts. For example, should a specific event occur, you can set up a trigger to send an email to your team. This is generally used for errors, but it could be used for just about any event sent through to Sentry.

Sentry Example

Sentry, like Log Rocket, is also a venture-backed company. They have raised over $66 million in their history. Unlike Log Rocket, Sentry provides an open-source and self-hosted solution for free. For users that do not want to pay for Sentry's hosted option, they can utilize Docker to run a self-hosted, containerized version of the Sentry platform.

One big differentiator for Sentry is its focus on providing services for a wide array of platforms.

Sentry differentiates from legacy application performance monitoring solutions by focusing on software that runs on devices its developers have no control over, including mobile and IoT devices and smart sensor networks.

Exceptionless #

Exceptionless has been around the longest of the three services reviewed here. Created as a software offering from the parent company CodeSmith Tools, Exceptionless was established in 2010 and is privately held. It is positioned as a tool to help first and a product second. This is why one of Exceptionless's main focuses is the open-source community.

Exceptionless lets developers capture logs, events, and errors. However, Exceptionless's real differentiator is its real-time delivery mechanisms. Rather than costly code deployments to change your error monitoring configurations, Exceptionless allows you to make changes from their user interface and those changes will apply instantly in your application. This is key when it becomes clear that additional errors need to be surfaced quickly.

From the user experience and customer experience front, Exceptionless works hard to stand out from the competition. In Exceptionless 7.0, customers are now able to mark stacks of errors and logs as "discarded", the events will no longer count against plan quotas. Combine that with their chat, email, and Discord support, and Exceptionless proves its focus on customers.

Digging into the product features, Exceptionless provides one of the cleanest views for understanding events and errors in your application.

Example error details

All the information you need about errors and events are captured in a simple, easy to read view. From your dashboard, you can consolidate events into stacks to help group relevant issues. You can also indicate the resolution of events based not just on the fact that the issue was resolve but based on the version of your software that fixed the event. Exceptionless will automatically recognize any new errors that come in with an older version and group them in the resolved bucket. However, should the error surface on the version of your software in which you marked the issue fixed, Exceptionless will classify this event as a regression without you having to manually do so.

So Which One is Right For You? #

The answer to this often comes down to what's most important for your application. Each service has its merits, but the value provided differs based on use-cases. Log Rocket offers full session replays, but may not be the right solution for digging deep into stack traces. Sentry offers full stack trace reviews and error categorization, but it may not be the right solution if you need the ability to update your error handling configuration in real-time. Exceptionless provides real-time error monitoring and configuration, and it is committed to the open-source community.

In the end, the choice is yours.