How to Log SPFx React WebPart using Azure Application Insights and @pnp/logging

Ahamed Fazil Buhari
 
Senior Developer
May 21, 2020
 
Rate this article
 
Views
2615

Lots can be achieved through SPFx webpart. Well, at the same time developers have huge responsibility to keep track on the solution, how it behaves and quick way to find the root cause in case of bug or failure. In this article lets see how we can include Azure Application Insights in your SPFx solution. You can also include App Insight in application customizer SPFx solution to monitor SharePoint pages but here I added it in WebPart since I need to monitor only that particular webpart.

All we need from azure is Instrumental key from Application Insights,

app insight

Quickly I jump into the code by considering, we already have Application Insights created in azure. To keep it simple I just added new webpart in my existing SPFx solution,

webpart

Add the following packages,

npm i @microsoft/applicationinsights-react-js @microsoft/applicationinsights-web
We created a class called AILogListenerService which initializes ApplicationInsights for your webpart (please make sure it implements ILogListener, so we can include it as custom listener for @pnp/logging). In that we make some configuration settings for Application Insights along with that we pass the InstrumentationKey. Please refer MS doc1 MS doc2 to know more about all configuration for App Insights for npm based setup
import {
    LogLevel,
    ILogListener,
    ILogEntry
} from "@pnp/logging";
import { ApplicationInsights, IEventTelemetry, SeverityLevel, Telemetry } from '@microsoft/applicationinsights-web';
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
import { createBrowserHistory } from "history";
import { CONST } from "../utils/Const";
import { _logMessageFormat, _logEventFormat, _hashUser } from "../utils/Utilities";

export class AILogListenerService implements ILogListener {
    private static _appInsightsInstance: ApplicationInsights;
    private static _reactPluginInstance: ReactPlugin;
    constructor(currentUser: string) {
        if (!AILogListenerService._appInsightsInstance)
            AILogListenerService._appInsightsInstance = AILogListenerService._initializeAI(currentUser);
    }
    private static _initializeAI(currentUser?: string): ApplicationInsights {
        const browserHistory = createBrowserHistory({ basename: '' });
        AILogListenerService._reactPluginInstance = new ReactPlugin();
        const appInsights = new ApplicationInsights({
            config: {
                maxBatchInterval: 0,
                instrumentationKey: AZURE_APPINSIGHTS_INSTRUMENTATIONKEY_ENVV,
                namePrefix: WEBPART_NAME_ENVV, // Used as Postfix for cookie and localStorage 
                disableFetchTracking: false,  // To avoid tracking on all fetch
                disableAjaxTracking: true,    // Not to autocollect Ajax calls
                extensions: [AILogListenerService._reactPluginInstance],
                extensionConfig: {
                    [AILogListenerService._reactPluginInstance.identifier]: { history: browserHistory }
                }
            }
        });

        appInsights.loadAppInsights();
        appInsights.context.application.ver = WEBPART_VERSION_ENVV; // application_Version
        appInsights.setAuthenticatedUserContext(_hashUser(currentUser)); // user_AuthenticateId
        return appInsights;
    }

    public static getReactPluginInstance(): ReactPlugin {
        if (!AILogListenerService._reactPluginInstance) {
            AILogListenerService._reactPluginInstance = new ReactPlugin();
        }
        return AILogListenerService._reactPluginInstance;
    }

    public trackEvent(name: string): void {
        if (AILogListenerService._appInsightsInstance)
            AILogListenerService._appInsightsInstance.trackEvent(_logEventFormat(name), CONST.ApplicationInsights.CustomProps);
    }

    public log(entry: ILogEntry): void {
        const msg = _logMessageFormat(entry);
        if (entry.level === LogLevel.Off) {
            // No log required since the level is Off
            return;
        }

        if (AILogListenerService._appInsightsInstance)
            switch (entry.level) {
                case LogLevel.Verbose:
                    AILogListenerService._appInsightsInstance.trackTrace({ message: msg, severityLevel: SeverityLevel.Verbose }, CONST.ApplicationInsights.CustomProps);
                    console.log({ ...CONST.ApplicationInsights.CustomProps, Message: msg });
                    break;
                case LogLevel.Info:
                    AILogListenerService._appInsightsInstance.trackTrace({ message: msg, severityLevel: SeverityLevel.Information }, CONST.ApplicationInsights.CustomProps);
                    console.log({ ...CONST.ApplicationInsights.CustomProps, Message: msg });
                    break;
                case LogLevel.Warning:
                    AILogListenerService._appInsightsInstance.trackTrace({ message: msg, severityLevel: SeverityLevel.Warning }, CONST.ApplicationInsights.CustomProps);
                    console.warn({ ...CONST.ApplicationInsights.CustomProps, Message: msg });
                    break;
                case LogLevel.Error:
                    AILogListenerService._appInsightsInstance.trackException({ error: new Error(msg), severityLevel: SeverityLevel.Error });
                    console.error({ ...CONST.ApplicationInsights.CustomProps, Message: msg });
                    break;
            }
    }
}
I just post only the main functions and classes, at the end of the article you can find link to github for full code. Once we initialize app insights, in onInit of my webpart class, we need to subscibed to my AILogListenerService.
export default class MyApplicationInsightsWebPart extends BaseClientSideWebPart<IMyApplicationInsightsWebPartProps> {
  public onInit(): Promise<void> {
    Logger.subscribe(new AILogListenerService(this.context.pageContext.user.email));
    if (DEBUG)
      Logger.activeLogLevel = LogLevel.Verbose;
    return Promise.resolve<void>();
  }
That is all, now we can use our @pnp/logging in our webpart and all the logging will be traced in Application Insights. I don’t want application insights to log my webpart during debug mode, that’s why  in gulpfile.js we mapped App Insight Instrumental key as 0’s for Debug mode. DEBUG (this is available by default in SPFx) from define plugin variable. I also used “webpack-definePlugin-variables.d.ts” and added some custom variables for instrument key and other stuff and skip application insights logging while we work with local work bench. You can find more about “webpack-definePlugin-variables.d” in my other article – How To Handle Env Variable Using DefinePlugin In SPFx Solutions.
github repo refer to “myApplicationInsights” webpart
pnp log
ai_output
Happy Coding
Fazil

Author Info

Ahamed Fazil Buhari
 
Senior Developer
 
Rate this article
 
Ahamed is a Senior Developer and he has very good experience in the field of Microsoft Technologies, especially SharePoint, Azure, M365, SPFx, .NET and client side scripting - JavaScript, TypeScript, ...read more
 

Leave a comment