Even if sometimes developers focus only on the CI/CD or automatic testing aspects, telemetry is one of the critical components to adopt a successful DevOps culture for your projects.
Being able to quickly build and deploy new versions of your application, in fact, is much less effective if you rely only on the willing of your users to report problems and issues. You must identify problems fast, before they start to affect all your users, and this is where telemetry, crash logging and analytics come into play.
Recently I helped a customer in integrating telemetry inside an Electron application and I faced some challenges in identifying the best way to do it by leveraging one of the available Microsoft services.
As Microsoft, in fact, we offer two platforms for telemetry:
- Visual Studio App Center, which is focused on client applications (mobile and desktop).
- Application Insights, which is part of Azure and it's focused on web applications.
Electron stands in the middle of these two worlds, being a platform to build desktop applications using web technologies. Under the hood, in fact, an Electron app is nothing more than a Node.js application with the UI rendered by an embedded instance of Chromium.
So, what is the right choice? The answer is... drum roll... Application Insights! App Center, in fact, being dedicated to client applications doesn't offer a real web-compliant SDK. The closer matches are the Apache Cordova and React Native SDKs, but we aren't talking about traditional web development here. Application Insights, instead, offers many web based SDKs, including one for Node.js.
Let's see how to integrate it, even if you'll see how the process is really straightforward!
Create an Application Insight resource on Azure
The first step is to login to your Azure account and create a new Application Insight project. Just click on Create a resource and look for Application Insights. You can search for the it using the built-in search tool, otherwise you will find it under the Developer Tools or DevOps categories.
The required information are very minimal. You just need to choose the Azure subscription where you want to create the resource, a resource group, a name and the region.
Once the deployment has been completed, go to the resource. The most important information you will see is the Instrumentation key, which we're going to specify in our Electron application to connect it to this resource.
Integrate the SDK in the application
Now that we have a resource, it's time to integrate it into our Electron application. The starting point is to install inside your project the SDK, which is available as a NPM package.
Note: In this blog post I will give for granted that you already have an Electron application and that your computer is already setup with all the required tools (Node.js, NPM, etc.). If it's your first time working with Electron, the official documentation will help you setting up your development environment.
Just run the following command in a console (like the new shiny Windows Terminal 😃 ) on the folder which hosts your Electron app:
npm install --save applicationinsights
Now you can initialize the SDK when the application starts. A good place where to do it is in the main.js file, inside the function hooked to the ready event that is triggered when Electron has finished the initialization process and it's ready to create browser windows. If you're using the standard Electron template as a starting point, the function will be called createWindow().
Just add the following code inside this function:
let appInsights = require('applicationinsights');
appInsights.setup("your-instrumentation-key").start();
You will need to replace the parameter passed to the setup() method (the one called your-instrumentation-key) with the Instrumentation Key you have retrieved from the Azure portal in the previous step.
That's it! Once you do this, the basic telemetry will be automatically sent to your Application Insights resource every time the application is connected to Internet. This includes information like the OS of the user, the geographical area, etc. Of course, all the information will be anonymous and there won't be any way to trace them back to the user who generated them.
These information will be displayed in the various sections under the Usage area of the Application Insights dashboard. For example, these are some of the information displayed in the Users section:
Keep in mind that Application Insights, even if it works great with our Electron application, it's born for traditional web application, served through a browser. As such, some of the collected information (like the browser of the user or the metrics around the response timings of the server) won't be available or will contain not useful data.
Tracking custom events
One of the most important features in the telemetry space is the ability to track events which are relevant to your application. It might be a page loaded, a button clicked, an option turned on or off, etc. Events are useful to understand how people are using the application and can give you meaningful insights to better understand how to move your application forward. For example, if you add a new option to your application, but the number of people who turn it on is really low, you may want to focus your efforts on something else; or if a section takes too much time to load, you may want to work on improving its performance.
Events must be tracked manually, since the SDK can't know how your application is designed and which kind of events you would like to track. To do this, you need to get a reference to the Application Insights client and then call one of the many available events which start with the track prefix. Here is a sample code to track a custom event:
let appInsights = require('applicationinsights');
appInsights.setup("your-instrumentation-key").start();
let client = appInsights.defaultClient;
client.trackEvent({name: 'app loaded'});
Once you get a reference to the client using the defaultClient object, you can leverage the trackEvent() function. The name parameter contains the name of the event, which will identify it unequivocally. If you reuse the same event name in multiple pages, it will be tracked under a single event. The trackEvent() function accepts also a second parameter, which is a collection of properties. It's helpful if you want to track not just the event, but also additional information to provide some context. For example, let's say you have a multi-language application and you want to track not only when the user has changed the language, but also which one he has selected. You could write some code like this:
let appInsights = require('applicationinsights');
appInsights.setup("your-instrumentation-key").start();
let client = appInsights.defaultClient;
client.trackEvent({name: "Language changed", properties: { language: "Italian"}});
Another useful helper to track custom metrics is the trackMetric() function. For example, let's say you want to track how many times the user has clicked on a specific option. This is the code you would use:
let appInsights = require('applicationinsights');
appInsights.setup("your-instrumentation-key").start();
let client = appInsights.defaultClient;
client.trackMetric({name: "Button clicked", value: 3});
These information will be reported in the Events section of the Application Insights dashboard:
Tracking errors
Errors can be tracked as well by using the trackException() method, which accepts an Error object as parameter. This method can be helpful to track custom errors, like with the following sample code:
let appInsights = require('applicationinsights');
appInsights.setup("9a5f20b9-3d27-4ad2-842a-45f9c9ebad1c").start();
let client = appInsights.defaultClient;
client.trackException({exception: new Error("This is a custom exception")});
However, even more helpful would be the ability to track every unhandled exception in the application. The Application Insights SDK is able to do this automatically with standard web applications, but Electron "hides" every exception which doesn't lead to a critical failure of the app, so they aren't automatically tracked. We can workaround this problem by installing a NPM library called Electron Unhandled, which provides a convenient way to capture all the unhandled exceptions in an Electron application.
First you have to install the package in your project, by running the following command:
npm install electron-unhandled
The basic usage is quite simple. Just invoke the following code when your application starts. You can leverage, like we did for the SDK initialization, the windowReady() function.
const unhandled = require('electron-unhandled');
unhandled();
However, this initialization isn't helpful for our scenario. With the default settings, in fact, exceptions will be logged:
- In the Node.js debug console.
- With an alert displayed to the user, if the Electron application has been compiled as a production build.
We can customize the behavior of the library and use a custom logger, like our Application Insights SDK. Here is a full example:
let appInsights = require('applicationinsights');
appInsights.setup("9a5f20b9-3d27-4ad2-842a-45f9c9ebad1c").start();
let client = appInsights.defaultClient;
async function createWindow () {
// window initialization
const unhandled = require('electron-unhandled');
unhandled({showDialog: true, logger: (error) => { client.trackException({exception: error})}});
var result = await fetch("http://www.foo.foo");
var json = await result.json();
console.log(json);
}
function logError(error) {
}
We invoke the unhandled() function passing two parameters. With the first one, showDialog, we force the display of the alert, even if we're running in development mode. The second one is the custom logger, which we specify with a lambda expression that holds, inside the argument, the Error object with the full stack. In our scenario the custom logger invokes the trackException() method of the Application Insights SDK, passing as exception the error caught by the library.
That's it! The next lines simulate an error. We're using the fetch APIs, which however we haven't referenced in the application. If we launch the application, we will see the error popping out, since we have set the showDialog property to true:
Additionally, thanks to the custom logger, the error will be logged also on the Application Insights dashboard. Move to the Failures section and click on Exceptions. You will see the error there:
Wrapping up
Adding telemetry to your application, no matter if it's a web or desktop one, is critical to address issues quickly and to understand usage patterns, so that you can focus on moving your application forwards in the right way. Electron applications aren't an exception to this best practice. Application Insights, the telemetry platform offered by Azure, is a great tool to achieve this goal and, thanks to the Node.js SDK, we can easily integrate it in our application, even if the SDK is originally born for traditional web apps.
Happy coding!