Power Platform Application Insights Instrumentation

Model-driven apps and Microsoft Dataverse diagnostics and performance data can be monitored in an Azure Application Insights resource. This enhancement allows developers and admins to gain a deep understanding of what's going on inside the platform. Out of the box, there are form-loads, plug-ins, and platform APIs logging error and performance data.

On a high level, you can report user activity with specific forms and it will allow product owners to understand adoption blockers and drivers.

Setup Application Insights

Activate Application Insights Data Export in PAC

Monitoring a Model-driven app

Once you have enabled data export you can immediately start to see monitoring events in your Application Insights resource. Activate Application Insights Monitoring

Add instrumentation code to your custom plugins

Knowing about timings in platform-level functionality is handy, but this feature really shines if you can extend it to your own plugins. While there has always been a way of utilizing the ITracingService for Plug-in Trace Logs there is now an additional ILogger service that can be obtained from the service provider.

var logger = (ILogger)serviceProvider.GetService(typeof(ILogger)); logger.LogInformation("log something useful");

The sample plugin below retrieves the share price from marketstack once a tickersymbol is set on an account and updates the stackexchange accordingly.

If we create an update on the account we can capture the Request Id from the response headers collection inside F12 developer tools network tab.

Application Insights Find RequestId

This Request Id (req_id) can be used to trace down the request along with the dependency graph of sub-components e.g. the HTTP Call to the marketstack API by searching for it under transaction search.

Transaction Search

When we identified the transaction we see all operations that happened inside that transaction including other plugins as well as connections to external dependencies like in this case the financial data service.

Transaction Search

Wrapping up

  • Capture telemetry over time for analysis and debugging.
  • Easily query events with Kusto Query Language (KQL).
  • Analyze if issue was caused by custom code or platform code.
  • Alerts based off performance thresholds for form-loads, APIs, and plug-ins

Sample Plugin to receive stock market prices for an account

public class GetStockExchangePlugin : IPlugin { // Get an API Key from https://marketstack.com/ private const string ApiKey = "<ENTER YOUR OWN API KEY>"; public void Execute(IServiceProvider serviceProvider) { var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); var logger = (ILogger)serviceProvider.GetService(typeof(ILogger)); Entity target; if (!context.InputParameters.TryGetValue("Target", out var targetParameter)) { throw new InvalidPluginExecutionException("Target not present."); } else { target = (Entity)targetParameter; } var tickersymbol = target.GetAttributeValue<string>("tickersymbol"); if (!string.IsNullOrWhiteSpace(tickersymbol)) { var stock = GetStockData(logger, tickersymbol); var symbol = stock?.data?.FirstOrDefault(); if (symbol != null) { target["stockexchange"] = $"{symbol.close:c} @{symbol.date:d}"; } } } private static Rootobject GetStockData(ILogger logger, string symbol) { using (logger.BeginScope("FetchStockData")) { logger.LogInformation("Outbound call to marketstack started."); using (HttpClient client = new HttpClient()) { client.Timeout = TimeSpan.FromMilliseconds(15000); client.DefaultRequestHeaders.ConnectionClose = true; logger.LogInformation("Getting Stock data for: {0}", symbol); var response = client .GetAsync($"http://api.marketstack.com/v1/eod?access_key={ApiKey}&symbols={symbol}&date_to={DateTime.UtcNow:yyyy-MM-dd)}&limit=1") .ConfigureAwait(false) .GetAwaiter() .GetResult(); if ((int)response.StatusCode == 422) { logger.LogError("'{0}' not found.", symbol); return null; } response.EnsureSuccessStatusCode(); var responseStream = response.Content .ReadAsStreamAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); return ParseResponse(logger, responseStream); } } } private static Rootobject ParseResponse(ILogger logger, Stream responseStream) { using (responseStream) using (logger.BeginScope("Parsing Response")) { var rootobject = (Rootobject)new DataContractJsonSerializer(typeof(Rootobject), new DataContractJsonSerializerSettings { DateTimeFormat = new DateTimeFormat("yyyy-MM-ddTHH:mm:ss+0000") }).ReadObject(responseStream); return rootobject; } } [DataContract] public class Rootobject { [DataMember] public StockData[] data { get; set; } } [DataContract] public class StockData { [DataMember] public decimal close { get; set; } [DataMember] public DateTime date { get; set; } } }

Blog post written by

Marius Pothmann

Marius Pothmann is a Microsoft certified solution architect focusing on the Power Platform and cloud-native application development on Azure. Working on the Microsoft stack for over a decade gave him the opportunity to deep dive into many building blocks of successful enterprise application architectures like Dynamics 365, SQL Server, ADFS, ASP.NET (Core), SPA-based Web Apps, WPF/WinForms. If you think he can assist in solving your challenges drop us an e-mail and we will get back to you right away.