diff --git a/samples/react-appinsights-usage/README.md b/samples/react-appinsights-usage/README.md index 283833477..53c6954cb 100644 --- a/samples/react-appinsights-usage/README.md +++ b/samples/react-appinsights-usage/README.md @@ -1,10 +1,23 @@ -# app-insights-spfx-webparts +--- +page_type: sample +products: +- office-sp +languages: +- typescript +extensions: + contentType: samples + technologies: + - SharePoint Framework + platforms: + - react + createdDate: 5/26/2024 12:00:00 AM +--- + +# Application Insights usage ## Summary -Short summary on functionality and used technologies. - -[picture of the solution in action, if possible] +Application Insights provides telemetry data to monitor and improve application performance and user experience, while AB Testing, user flow analysis, and logging help in optimizing and debugging applications by comparing different versions, mapping user paths, and recording significant events. The 3 Sample Webpart demonstrates webpart functionalities to aid developers in integrating customizable components effectively. ## Used SharePoint Framework Version @@ -22,11 +35,10 @@ Short summary on functionality and used technologies. Application Insight Service on Azure Add the connection stirng of this Service to the the variable ```AIConnectionString``` at ```src/EnvProps.ts``` -## Solution -| Solution | Author(s) | -| ----------- | ------------------------------------------------------- | -| folder name | Author details (name, company, twitter alias with link) | +## Contributors + +* [Peter Paul Kirschner](https://github.com/petkir) ## Version history @@ -44,6 +56,8 @@ Add the connection stirng of this Service to the the variable ```AIConnectionStr - Clone this repository - Ensure that you are at the solution folder +- Create or use Existing Azure Application Insights +- Update ```src/EnvProps.ts``` and Set AIConnectionString - in the command-line run: - **npm install** - **gulp serve** @@ -63,8 +77,7 @@ This extension illustrates the following concepts: - Logging - PnP JS Logger - Logging with PnPJS -- Custom Logger - - Logging with PnPJS + # Sample Router WebPart @@ -115,6 +128,10 @@ customEvents ``` ![AB Evaluation](assets/ABEvaluation.png) +# PnPJS Logger WebPart + +![Logoutput with Browser Log Level Filter](assets/PNPJSLogger.png) + > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. @@ -126,3 +143,5 @@ customEvents - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development +- [PnPJS/Logging](https://pnp.github.io/pnpjs/logging/) +- [Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview) Application Insights provides many experiences to enhance the performance, reliability, and quality of your applications. \ No newline at end of file diff --git a/samples/react-appinsights-usage/assets/PNPJSLogger.png b/samples/react-appinsights-usage/assets/PNPJSLogger.png new file mode 100644 index 000000000..7ba2c445e Binary files /dev/null and b/samples/react-appinsights-usage/assets/PNPJSLogger.png differ diff --git a/samples/react-appinsights-usage/assets/sample.json b/samples/react-appinsights-usage/assets/sample.json new file mode 100644 index 000000000..020c350e7 --- /dev/null +++ b/samples/react-appinsights-usage/assets/sample.json @@ -0,0 +1,112 @@ +[ + { + "name": "pnp-sp-dev-spfx-web-parts-react-appinsights-usage", + "source": "pnp", + "title": "React AppInsights Usage WebParts", + "shortDescription": "This web parts shows different use cases for capuring data in your application and store it into the Azure Application Insights service.", + "url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-appinsights-usage", + "longDescription": [ + "This web parts shows different use cases for capuring data in your application. Azure Application Insights is more than only Logging and Tracing. It can be used to analyse data and display them in a graphical representation." + ], + "creationDateTime": "2024-05-26", + "updateDateTime": "2024-05-26", + "products": [ + "SharePoint" + ], + "metadata": [ + { + "key": "CLIENT-SIDE-DEV", + "value": "React" + }, + { + "key": "SPFX-VERSION", + "value": "1.18.2" + }, + { + "key": "SPFX-FULLPAGEAPP", + "value": "true" + }, + { + "key": "PNPCONTROLS", + "value": "" + } + ], + "thumbnails": [ + { + "type": "image", + "order": 100, + "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-appinsights-dashboard/assets/AppInsights_Dashboard.gif", + "alt": "React AppInsights Dashboard" + }, + { + "type": "image", + "order": 101, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/ABEvaluation.png?raw=true", + "alt": "AppInsights AB Test Chart" + }, + { + "type": "image", + "order": 102, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/ABTextUI.png?raw=true", + "alt": "AppInsights AB Test UI" + }, + { + "type": "image", + "order": 103, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/PNPJSLogger.png?raw=true", + "alt": "Logger in Terminal" + }, + { + "type": "image", + "order": 104, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/SampleRouterDurationEvaluation.png?raw=true", + "alt": "AppInsights Page visit average duration Chart" + }, + { + "type": "image", + "order": 105, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/SampleRouterEvaluation.png?raw=true", + "alt": "AppInsights Page visit count Chart" + }, + { + "type": "image", + "order": 105, + "url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-appinsights-usage/assets/SampleRouterUserFlow.png?raw=true", + "alt": "AppInsights User Flow" + } + + ], + "authors": [ + { + "gitHubAccount": "petkir", + "company": "ACP CUBIDO Digital Solutions GmbH", + "pictureUrl": "https://github.com/petkir.png", + "name": "Peter Paul Kirschner", + "twitter": "petkir_at" + } + ], + "references": [ + { + "name": "Build your first SharePoint client-side web part", + "description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.", + "url": "https://learn.microsoft.com/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part" + }, + { + "name": "Using single part app pages in SharePoint Online", + "description": "Single part app pages provide a capability to host SharePoint Framework web parts or Microsoft Teams applications in SharePoint Online with a locked layout. End users cannot modify or configure the page that is using the Single Part App Page layout.", + "url": "https://learn.microsoft.com/sharepoint/dev/spfx/web-parts/single-part-app-pages?tabs=pnpposh" + }, + { + "name": "PNPJS Understanding the Logging Framework", + "description": "The logging framework is centered on the Logger class to which any number of listeners can be subscribed. Each of these listeners will receive each of the messages logged. Each listener must implement the ILogListener interface", + "url": "https://pnp.github.io/pnpjs/logging/" + }, + { + "name": "Application Insights", + "description": "Application Insights provides many experiences to enhance the performance, reliability, and quality of your applications.", + "url": "https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview" + } + + ] + } +] diff --git a/samples/react-appinsights-usage/package-lock.json b/samples/react-appinsights-usage/package-lock.json index 7fd91b336..64f375cb8 100644 --- a/samples/react-appinsights-usage/package-lock.json +++ b/samples/react-appinsights-usage/package-lock.json @@ -17,6 +17,7 @@ "@microsoft/sp-office-ui-fabric-core": "1.18.2", "@microsoft/sp-property-pane": "1.18.2", "@microsoft/sp-webpart-base": "1.18.2", + "@pnp/logging": "^4.0.1", "react": "17.0.1", "react-dom": "17.0.1", "tslib": "2.3.1" @@ -4758,6 +4759,26 @@ "node": ">=8.0.0" } }, + "node_modules/@pnp/logging": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-4.0.1.tgz", + "integrity": "sha512-+HVb3EFvSuEis2Wn7kHVhReEyFsB8Vtb2PfRWGcHQiCXGuje9y2oJb8HLxpV5+IdS2qoqpktrZa6ADf8+EkOVQ==", + "dependencies": { + "tslib": "2.6.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/patrick-rodgers/" + } + }, + "node_modules/@pnp/logging/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@pnpm/crypto.base32-hash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@pnpm/crypto.base32-hash/-/crypto.base32-hash-2.0.0.tgz", diff --git a/samples/react-appinsights-usage/package.json b/samples/react-appinsights-usage/package.json index 6179099b9..8b16e6942 100644 --- a/samples/react-appinsights-usage/package.json +++ b/samples/react-appinsights-usage/package.json @@ -21,6 +21,7 @@ "@microsoft/sp-office-ui-fabric-core": "1.18.2", "@microsoft/sp-property-pane": "1.18.2", "@microsoft/sp-webpart-base": "1.18.2", + "@pnp/logging": "^4.0.1", "react": "17.0.1", "react-dom": "17.0.1", "tslib": "2.3.1" diff --git a/samples/react-appinsights-usage/src/webparts/pnPjsLogger/PnPjsLoggerWebPart.ts b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/PnPjsLoggerWebPart.ts index 5fdee22c1..57ffcb1cf 100644 --- a/samples/react-appinsights-usage/src/webparts/pnPjsLogger/PnPjsLoggerWebPart.ts +++ b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/PnPjsLoggerWebPart.ts @@ -11,6 +11,10 @@ import { IReadonlyTheme } from '@microsoft/sp-component-base'; import * as strings from 'PnPjsLoggerWebPartStrings'; import PnPjsLogger from './components/PnPjsLogger'; import { IPnPjsLoggerProps } from './components/IPnPjsLoggerProps'; +import { ApplicationInsights, DistributedTracingModes, ITelemetryItem } from '@microsoft/applicationinsights-web'; +import { ConsoleListener, LogLevel, Logger } from '@pnp/logging'; +import { AppInsightListener } from './listener/appinsight-loglistener'; +import { AIConnectionString } from '../../EnvProps'; export interface IPnPjsLoggerWebPartProps { description: string; @@ -20,7 +24,8 @@ export default class PnPjsLoggerWebPart extends BaseClientSideWebPart = React.createElement( PnPjsLogger, @@ -37,11 +42,57 @@ export default class PnPjsLoggerWebPart extends BaseClientSideWebPart { - return this._getEnvironmentMessage().then(message => { + const allAsynCalls = [] + allAsynCalls.push(this._getEnvironmentMessage().then(message => { this._environmentMessage = message; + }) + ) + + const userId: string = this.context.pageContext.user.loginName.replace(/([\\|:;=])/g, ''); + + // App Insights JS Documentation: https://github.com/microsoft/applicationinsights-js + this._appInsights = new ApplicationInsights({ + config: { + connectionString: AIConnectionString, + accountId: userId, + disableFetchTracking: false, + enableRequestHeaderTracking: true, + enableResponseHeaderTracking: true, + enableAjaxErrorStatusText: true, + enableAjaxPerfTracking: true, + enableUnhandledPromiseRejectionTracking: true, + enableCorsCorrelation: true, + disableExceptionTracking: false, + distributedTracingMode: DistributedTracingModes.AI + } + }); + + this._appInsights.loadAppInsights(); + this._appInsights.addTelemetryInitializer(this._appInsightsInitializer); + this._appInsights.setAuthenticatedUserContext(userId, userId, true); + this._appInsights.trackPageView(); + + Logger.subscribe(new AppInsightListener(this._appInsights)); + Logger.subscribe( ConsoleListener("pnpjs")); + Logger.activeLogLevel = LogLevel.Info; + + return Promise.all(allAsynCalls).then(() => { + return super.onInit(); }); } + private _appInsightsInitializer = (telemetryItem: ITelemetryItem): boolean | void => { + if (telemetryItem) { + if (!telemetryItem.tags) telemetryItem.tags = {}; + telemetryItem.tags['ai.cloud.role'] = "app-insights-spfx-webparts"; + telemetryItem.tags['ai.cloud.roleInstance'] = "PnPjsLoggerWebPart"; + + if (telemetryItem.baseType === 'RemoteDependencyData' && telemetryItem.baseData?.target) { + const isExcluded = telemetryItem.baseData.target.toLowerCase().indexOf('my_un_monitored_api ') !== -1; + if (isExcluded) return false; // don't track + } + } + } private _getEnvironmentMessage(): Promise { diff --git a/samples/react-appinsights-usage/src/webparts/pnPjsLogger/components/PnPjsLogger.tsx b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/components/PnPjsLogger.tsx index 9b5a18233..f9683efae 100644 --- a/samples/react-appinsights-usage/src/webparts/pnPjsLogger/components/PnPjsLogger.tsx +++ b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/components/PnPjsLogger.tsx @@ -2,9 +2,17 @@ import * as React from 'react'; import styles from './PnPjsLogger.module.scss'; import type { IPnPjsLoggerProps } from './IPnPjsLoggerProps'; import { escape } from '@microsoft/sp-lodash-subset'; +import { LogLevel, Logger } from '@pnp/logging'; +import { ActionButton } from '@fluentui/react'; export default class PnPjsLogger extends React.Component { + constructor(props: IPnPjsLoggerProps) { + super(props); + Logger.writeJSON(this.props, LogLevel.Info); + } public render(): React.ReactElement { + Logger.write("PnPjsLogger:Render", LogLevel.Info); + const { description, isDarkTheme, @@ -22,21 +30,23 @@ export default class PnPjsLogger extends React.Component
Web part property value: {escape(description)}
-

Welcome to SharePoint Framework!

-

- The SharePoint Framework (SPFx) is a extensibility model for Microsoft Viva, Microsoft Teams and SharePoint. It's the easiest way to extend Microsoft 365 with automatic Single Sign On, automatic hosting and industry standard tooling. -

-

Learn more about SPFx development:

-
+ ); } diff --git a/samples/react-appinsights-usage/src/webparts/pnPjsLogger/listener/appinsight-loglistener.ts b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/listener/appinsight-loglistener.ts new file mode 100644 index 000000000..8c2ebcc6a --- /dev/null +++ b/samples/react-appinsights-usage/src/webparts/pnPjsLogger/listener/appinsight-loglistener.ts @@ -0,0 +1,22 @@ +import { ApplicationInsights, SeverityLevel } from "@microsoft/applicationinsights-web"; +import { ILogEntry, ILogListener, LogLevel } from "@pnp/logging"; + +export class AppInsightListener implements ILogListener { + private appInsights: ApplicationInsights; + constructor(appInsights: ApplicationInsights) { + this.appInsights = appInsights; + } + + log(entry: ILogEntry): void { + if (entry.level === LogLevel.Error) + this.appInsights.trackException({ error: new Error(entry.message), severityLevel: SeverityLevel.Error }); + else if (entry.level === LogLevel.Warning) + this.appInsights.trackException({ error: new Error(entry.message), severityLevel: SeverityLevel.Warning }); + else if (entry.level === LogLevel.Info) + this.appInsights.trackException({ error: new Error(entry.message), severityLevel: SeverityLevel.Information }); + else + this.appInsights.trackException({ error: new Error(entry.message), severityLevel: SeverityLevel.Verbose }); + } + + +}