Performance measurement decorator for Azure Application Insights on a Node.js app

Published on October 06, 2020

I’ve been writing lots of Node.js apps on Azure recently. Azure provides an instrumentation product called Application Insights. The App Insights client hooks in to Node.js requests and other popular parts of the Node.js ecosystem like postgres with almost no configuration.

However If you want to use the performance measurement api you have to actually call the app insights Node.js client api method. I figured since this was measuring some wrapped code anyway it would be a great candidate for an es6 decorator.

Adding application insights to your app

You need to install the library

yarn add applicationinsights

Add the env var with your connection string. You get the app insights connection string from the overview page on App insights in Azure.

APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=a-guid-key;IngestionEndpoint=https:/in.applicationinsights.azure.com/

Then import the library REALLY EARLY. You should import it as the first thing you import in the entire application. That’s usually the index.js for the app.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import AppInsights = require('applicationinsights')

Then optionally configure some of the parameters for the default client. The default client is what we will use later to measure metrics.

// These are all the library settings
AppInsights.setup()
  .setAutoDependencyCorrelation(true)
  .setAutoCollectRequests(true)
  .setAutoCollectPerformance(true, true)
  .setAutoCollectExceptions(true)
  .setAutoCollectDependencies(true)
  .setAutoCollectConsole(true)
  .setUseDiskRetryCaching(true)
  .setSendLiveMetrics(false)
  .setDistributedTracingMode(AppInsights.DistributedTracingModes.AI_AND_W3C)
  .start()
// It's a good idea to name the cloud role. This helps later when looking at the metrics on Azure.
AppInsights.defaultClient.context.tags[
  AppInsights.defaultClient.context.keys.cloudRole
] = 'My awesome app'

// If you use any kind of versioning you can set this for application insights also. Let's just pull the version out of the package.json file
AppInsights.defaultClient.context.tags['ai.application.ver'] =
  process.env.npm_package_version || '99.99.99'

Create the decorator

import appInsights = require('applicationinsights')

export type TimerOptions = {
  name: string
}

// start a decorator
export function PerformanceTimer(options: TimerOptions) {
  return (
    target: unknown,
    propertyKey: string,
    propertyDescriptor: PropertyDescriptor
  ): PropertyDescriptor => {
    // Get a ref to method we're wrapping
    const originalMethod = propertyDescriptor.value
    // Get the name the developer provided
    const timerName = options.name

    // eslint-disable-next-line unicorn/prevent-abbreviations
    propertyDescriptor.value = async function (...args: never[]) {
      // start a timer
      const t0 = process.hrtime.bigint()

      // call the method
      const result = await originalMethod.apply(this, args)

      // stop the timer
      const timerValue = (process.hrtime.bigint() - t0) / BigInt(1000000)

      // log the result to azure
      appInsights.defaultClient &&
        appInsights.defaultClient.trackMetric({
          name: timerName,
          value: Number(timerValue),
        })
      return result
    }
    return propertyDescriptor
  }
}

How to use

We just call it as a decorator. No need to import and libraries to service classes or anything.

    @PerformanceTimer({ name: "Measure LongRunningMethod" })
    public async someLongRunningMethod(): Promise<string> {
      ...
    }
Darragh ORiordan

Hi! I'm Darragh ORiordan.

I live and work in Sydney, Australia building and supporting happy teams that create high quality software for the web.

I also make tools for busy developers! Do you have a new M1 Mac to setup? Have you ever spent a week getting your dev environment just right?

My Universal DevShell tooling will save you 30+ hours of configuring your Windows or Mac dev environment with all the best, modern shell and dev tools.

Get DevShell here: ✨ https://usemiller.dev/dev-shell


Read more articles like this one...

List of article summaries

#devops

Extract user profile attributes from an Azure ADB2C tenant using the Microsoft Graph API

I had to retrieve a list of users from an Azure Active Directory B2C instance today. I thought I could just go through the Azure UI but that’s limited to short pages of data and limited attributes.

There is a CSV export provided on the UI but you won’t get the required identity objects in the csv output if you need a user’s signin email address.

I had to use the Microsoft Graph Api to get what I needed. This is a bit hacky but it does the trick!

#devops

Force restart your Azure App service site and host

Sometimes your Azure App service host will need to be restarted. You can do this but it’s hidden away in the Azure resource manager site. Here’s how to find it!

#devops

Scheduling a feature toggle using no-code with Azure Logic Apps

I use launch darkly to toggle features on an app. There is one third-party dependency that has regular scheduled maintenance and I need to toggle the feature on and off on schedule.

Launch Darkly has built in scheduling to handle this scenario but you have to be on the enterprise plan to use it. The enterprise plan is too expensive to upgrade to for scheduling alone so I needed to find a different way to automate this.

#frontend-development

Avoid rebuild of React App in every CI stage

If you have a react app you can use env vars like REACT_APP_MY_ENV_VAR in your application and React will automatically pull them in to your app when you build the production application.

This is very useful but if you have variables that change for each environment and your application build takes a long time, you might want to avoid building unnecessarily in CI. For example you might have a QA environment and a Staging environment that have different configuration.

We type-check our code on each build and that was taking 5 minutes+ to build each environment so we had to make it faster. We changed our app from using REACT_APP env vars to using a configuration file that we could quickly write to using CI.

Our CI system is Azure DevOops so the CI scripts here are specifically for Azure DevOps but they apply to most CI systems with small changes.

The real work happens in a Node.js script that would work anywhere.

Comments