DEV Community

Cover image for Application Insights SDK for Node.js part 1 : Basic usage
Kenichiro Nakamura
Kenichiro Nakamura

Posted on • Edited on

Application Insights SDK for Node.js part 1 : Basic usage

As a developer (for Azure), I use Application Insights quite heavily for any type of applications including Node.js.

However, I notice some behavior or option differences for Node.js from dotnet which is somehow expected.

How to setup Application Insights for Node.js

It's so simply and all the complexity is well hidden. Basically,

1. Create Application Insights resource and get instrumentation key.
2. Add npm package.

npm install applicationinsights --save

3. Add following code.

const appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>");
appInsights.start();

or for mode detail configuration, you can also do this.

const appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>")
    .setAutoDependencyCorrelation(true)
    .setAutoCollectRequests(true)
    .setAutoCollectPerformance(true)
    .setAutoCollectExceptions(true)
    .setAutoCollectDependencies(true)
    .setAutoCollectConsole(true)
    .setUseDiskRetryCaching(true)
    .start();

Refer to docs:Monitor your Node.js services and apps with Application Insights for more detail.

But this doesn't explain everything for sure.

Config

The very first thing we all should know is "Config". As some options are configurable, whereas others aren't.

GitHub: Config.ts

As I am pointing master branch, some information shall be changed in the future.

You can access it under defaultClient.

appInsights.defaultClient.config

Default settings

There is the constructor.

constructor(instrumentationKey?: string) {
    this.instrumentationKey = instrumentationKey || Config._getInstrumentationKey();
    this.endpointUrl = `${this.endpointBase}/v2/track`;
    this.maxBatchSize = 250;
    this.maxBatchIntervalMs = 15000;
    this.disableAppInsights = false;
    this.samplingPercentage = 100;
    this.correlationIdRetryIntervalMs = 30 * 1000;
    this.correlationHeaderExcludedDomains = [
        "*.core.windows.net",
        "*.core.chinacloudapi.cn",
        "*.core.cloudapi.de",
        "*.core.usgovcloudapi.net"];

    this.setCorrelationId = (correlationId) => this.correlationId = correlationId;

    this.profileQueryEndpoint = process.env[Config.ENV_profileQueryEndpoint] || this.endpointBase;
    this.proxyHttpUrl = process.env[Config.ENV_http_proxy] || undefined;
    this.proxyHttpsUrl = process.env[Config.ENV_https_proxy] || undefined;
    this.httpAgent = undefined;
    this.httpsAgent = undefined;
    this._quickPulseHost = process.env[Config.ENV_quickPulseHost] || "rt.services.visualstudio.com";
}

There are several interesting settings here.

samplingPercentage

Depending on your scenario, you don't need all the logs from the application to understand the user or system behavior. In that case, you want to decrease the amount of data you store. By changing samplingPercentage, you can tweak the behavior.

This settings is used by SamplingTelemetryProcessor which takes care of sampling data.

GitHub: SamplingTelemetryProcessor.ts

disableAppInsights

Even though you configure and start the application insights, you may need to stop it time to time depending on your scenario. You can tweak the option to disable the application.

The settings is used by Channel.ts which takes care of sending data.

GitHub: Channel.ts

maxBatchSize and this.maxBatchIntervalMs

As many data will be generated by the application, SDK doesn't send each data immediately, rather sending them as batch requests.

These settings are used in Channel.ts again.

GitHub: Channel.ts

The send function pass the data to "Sender" to actually sends the data to Application Insight servers, but it first store the data in memory buffer. Then, SDK sends the data when either condition met.

  • When buffer exceeds batch size (default to 250)
  • When timeout period exceeded (default to 15,000 milliseconds)
public send(envelope: Contracts.Envelope) {

    // if master off switch is set, don't send any data
    if (this._isDisabled()) {
        // Do not send/save data
        return;
    }

    // validate input
    if (!envelope) {
        Logging.warn("Cannot send null/undefined telemetry");
        return;
    }

    // check if the incoming payload is too large, truncate if necessary
    var payload: string = this._stringify(envelope);
    if (typeof payload !== "string") {
        return;
    }

    // enqueue the payload
    this._buffer.push(payload);

    // flush if we would exceed the max-size limit by adding this item
    if (this._buffer.length >= this._getBatchSize()) {
        this.triggerSend(false);
        return;
    }

    // ensure an invocation timeout is set if anything is in the buffer
    if (!this._timeoutHandle && this._buffer.length > 0) {
        this._timeoutHandle = setTimeout(() => {
            this._timeoutHandle = null;
            this.triggerSend(false);
        }, this._getBatchIntervalMs());
    }
}

Sender

When you call trackXXXX function of SDK client, the metric or log data go through several places to verify, modify and batching the data before sending it to Application Insights servers.

But eventually it goes to Sender class, which sends the data to Application Insights Servers.

GitHub: Sender.ts

There are several important points to understand.

Offline Mode

Even though Microsoft cloud is quite stable, it sometimes has difficulties to reach out to Application Insights server due to local connection failure, network failure, or actually the service is down.

In such situation, Application Insights SDK still does it's best to preserve the metric and log data by caching them to local disk.

First of all, it checks if offline mode can be enabled. For Linux, it simply works. But for Windows, to ensure security, SDK uses icacls.exe tool, therefore it firstly checks the tool exists, then decide if it can support offline or not.

public static USE_ICACLS = os.type() === "Windows_NT";
...
constructor(config: Config, onSuccess?: (response: string) => void, onError?: (error: Error) => void) {
    ...
    if (!Sender.OS_PROVIDES_FILE_PROTECTION) {
        // Node's chmod levels do not appropriately restrict file access on Windows
        // Use the built-in command line tool ICACLS on Windows to properly restrict
        // access to the temporary directory used for disk retry mode.
        if (Sender.USE_ICACLS) {
            // This should be async - but it's currently safer to have this synchronous
            // This guarantees we can immediately fail setDiskRetryMode if we need to
            try {
                Sender.OS_PROVIDES_FILE_PROTECTION = fs.existsSync(Sender.ICACLS_PATH);
            } catch (e) {}
            if (!Sender.OS_PROVIDES_FILE_PROTECTION) {
                Logging.warn(Sender.TAG, "Could not find ICACLS in expected location! This is necessary to use disk retry mode on Windows.")
            }
        } else {
            // chmod works everywhere else
            Sender.OS_PROVIDES_FILE_PROTECTION = true;
        }
    }
}
...

public setDiskRetryMode(value: boolean, resendInterval?: number, maxBytesOnDisk?: number) {
    this._enableDiskRetryMode = Sender.OS_PROVIDES_FILE_PROTECTION && value;
    if (typeof resendInterval === 'number' && resendInterval >= 0) {
        this._resendInterval = Math.floor(resendInterval);
    }
    if (typeof maxBytesOnDisk === 'number' && maxBytesOnDisk >= 0) {
        this._maxBytesOnDisk = Math.floor(maxBytesOnDisk);
    }

    if (value && !Sender.OS_PROVIDES_FILE_PROTECTION) {
        this._enableDiskRetryMode = false;
        Logging.warn(Sender.TAG, "Ignoring request to enable disk retry mode. Sufficient file protection capabilities were not detected.")
    }
}

When SDK failed to send data, then it caches the data locally. Inside send function, it checks _enableDiskRetryMode and take appropriate action.

if (this._enableDiskRetryMode) {
    // try to send any cached events if the user is back online
    if (res.statusCode === 200) {
        setTimeout(() => this._sendFirstFileOnDisk(), this._resendInterval).unref();
        // store to disk in case of burst throttling
    } else if (
        res.statusCode === 408 || // Timeout
        res.statusCode === 429 || // Throttle
        res.statusCode === 439 || // Quota
        res.statusCode === 500 || // Server Error
        res.statusCode === 503) { // Service unavailable

        // TODO: Do not support partial success (206) until _sendFirstFileOnDisk checks payload age
        this._storeToDisk(payload);
    }
}

The sending retry interval is 60 seconds by default, which means if it has several cached files in the cache folder, each file will be sent to the server every minutes.

public static WAIT_BETWEEN_RESEND = 60 * 1000;

The temp folder is named after prefix and instrumentation key so that you can check if there is any file remains whenever you wonder what happens when failed to send data.

var tempDir = path.join(os.tmpdir(), Sender.TEMPDIR_PREFIX + this._config.instrumentationKey);

StoreToDisk

As part of offline support, Sender class provides StoreToDisk functions. There are two versions of the function.

  • Async : _storeToDisk
  • Sync : _storeToDiskSync

By using sync function, it ensures the data is flushed out to disk before it loses the data. The function is used by Exceptions.

The enable function set handler when throw exception to call flush function of the client.

this._client.flush({ isAppCrashing: true });

Then TelemetryClient calls the sync function.

public flush(options?: FlushOptions) {
    this.channel.triggerSend(
        options ? !!options.isAppCrashing : false,
        options ? options.callback : undefined);
}

Channel

As you already see several usage of Channel, this is a bridge between TelemetryClient and Sender class.

GitHub: Channel.ts

It offers send related functions such as:

  • setUseDiskRetryCaching
  • send
  • triggerSend

Even though send function sounds like it sends data to Application Insights servers, as you saw already, it just passes the data to TriggerSend which ask Sender to send the data.

TelemetryClient

This is the client you, as a developer, play with the most of the time.

GitHub: TelemetryClient.ts

It takes care of all incoming metrics and logs and convert them into "Envelope" by using EnvelopeFactory

The envelope contains many additional data which will be used to analyze by Application Insights. You can also setup your own telemetry processors here.

See Filtering and preprocessing telemetry in the Application Insights SDK for more detail about telemetry processors.

ApplicationInsights

Of course we need to look into ApplicationInsights to understand what it is, as this is the top level class for everything.

GitHub:applicationinsights.ts

Basically, it contains bunch of default configurations as well as functions to override them under Configuration class. For example, setUseDiskRetryCaching function calls setUseDiskRetryCaching function of Channel class so that you don't need to directly accessing the class.

The start method enables all the configured collections. As you can see, it returns the Configuration so that you can chain the call to setup everything at once.

export function start() {
    if(!!defaultClient) {
        _isStarted = true;
        _console.enable(_isConsole, _isConsoleLog);
        _exceptions.enable(_isExceptions);
        _performance.enable(_isPerformance);
        _nativePerformance.enable(_isNativePerformance, _disabledExtendedMetrics);
        _serverRequests.useAutoCorrelation(_isCorrelating, _forceClsHooked);
        _serverRequests.enable(_isRequests);
        _clientRequests.enable(_isDependencies);
        if (liveMetricsClient && _isSendingLiveMetrics) {
            liveMetricsClient.enable(_isSendingLiveMetrics);
        }
    } else {
        Logging.warn("Start cannot be called before setup");
    }

    return Configuration;
}

Summary

I hope I could provide some useful information about Application Insights SDK for node.js. Of course there are more interesting topics inside but most of them are common between languages.

In the next article, I demonstrate how it actually works.

Go to next article

Top comments (0)