Monitoring and Telemetry in Dynamics365 for Operations


It’s been a while since my last post, and let’s just say I haven’t been slacking the past few months! In October, I married my best friend and her and I have been back and forth from St. Thomas US Virgin Islands just a few weeks ago for our honeymoon.  What a blast!

In that time, I have also launched one customer on AX7 RTW, and steadily approaching another launch of Dynamics365 for Operations 1611 in May.  The pace and cadence of releases and launches with this version of the product is unprecedented, especially at Microsoft as they release Platform updates each month.  I envy their speed and productivity, and as a Microsoft Partner we are doing everything we can to keep up.

One thing that I’ve heard from our customer who is live in the cloud on AX7 RTW is that the means of monitoring their system is difficult.  This is because all of the telemetry, diagnostics, and downtime notifications are stored within Lifecycle Services/LCS.  LCS is the portal through which we deploy new environments, and can monitor with rich insight each machine in a given deployment.  However, the data is only available within LCS for that environment, and there are not any proactive notifications or email alerts when something goes wrong unexpectedly.

To mitigate this, I built a simple telemetry client of my own using Application Insights from Azure as a basis.  Application Insights is a very popular ( and FREE ) approach to monitoring your websites and desktop applications.  It works best with websites hosted by IIS, however it can be built in to any product.  Let’s start by building our own C# library which uses the latest nuget package for appInsights:
 

After you have your C# library setup and the nuget package installed, go ahead and create a simple class that can handle our various events.  I’ve called mine TelemetryClient.cs.  For simplicity, I’ve done nothing with the exceptions.  You may want to capture them in to AX table or other storage for analysis.

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using MSFT = Microsoft.ApplicationInsights;
  7.  
  8. namespace McaConnect.Integration.Telemetry
  9. {
  10. public class TelemetryClient
  11. {
  12. string applicationId;
  13. string apiKey;
  14. MSFT.TelemetryClient cli;
  15.  
  16. public TelemetryClient(string _appId, string _apiKey, string _userId, int _sessionId, Boolean _isBatchJob)
  17. {
  18. applicationId = _appId;
  19. apiKey = _apiKey;
  20.  
  21. cli = new MSFT.TelemetryClient();
  22. if (apiKey != String.Empty)
  23. {
  24. cli.InstrumentationKey = apiKey;
  25. }
  26. cli.Context.User.Id = _userId;
  27. cli.Context.Session.Id = String.Format("{0}", _sessionId);
  28. cli.Context.Properties["ExecutionMode"] = _isBatchJob ? "Batch" : "Interactive";
  29.  
  30. }
  31.  
  32. public void trackMetric(string _metricName, double _metricValue)
  33. {
  34. try
  35. {
  36. cli.TrackMetric(_metricName, _metricValue);
  37. }
  38. catch(Exception ex)
  39. {
  40. //do nothing
  41. }
  42. }
  43.  
  44. public void trackPageView(string _pageName)
  45. {
  46.  
  47. try
  48. {
  49. cli.TrackPageView(_pageName);
  50. }
  51. catch(Exception ex)
  52. {
  53. //do nothing
  54. }
  55. }
  56.  
  57. public void trackException(string _ex)
  58. {
  59. try
  60. {
  61. cli.TrackException(new MSFT.DataContracts.ExceptionTelemetry { Message = _ex });
  62. }
  63. catch (Exception ex)
  64. {
  65. //do nothing
  66. }
  67. }
  68.  
  69. public void trackEvent(string _eventName)
  70. {
  71. try
  72. {
  73. cli.TrackEvent(_eventName);
  74. }
  75. catch(Exception ex)
  76. {
  77. //do nothing
  78. }
  79. }
  80.  
  81.  
  82. }
  83. }

Next, create an X++ project in your solution and add a reference to your C# library:

Thereafter, let’s create an X++ class which makes use of our telemetry library:

  1. using McaConnect.Integration.Telemetry;
  2.  
  3. /// <summary>
  4. /// Class used to log telemetry events to Application Insights in Azure
  5. /// Lane Swenka | mcaConnect, LLC.
  6. /// </summary>
  7. public class McaGlobalTelemetry
  8. {
  9. private static TelemetryClient newClient()
  10. {
  11. TelemetryClient cli;
  12.  
  13. McaAppURI locator = McaEnvironmentUtil::getLocationByAppName(McaIntParameters::Find().TelemetryAppName);
  14. if(locator)
  15. {
  16. cli = new McaConnect.Integration.Telemetry.TelemetryClient('', locator, curUserId(), sessionId(), BatchHeader::isExecutingInBatch());
  17. }
  18. else
  19. {
  20. cli = new McaConnect.Integration.Telemetry.TelemetryClient('', '', curUserId(), sessionId(), BatchHeader::isExecutingInBatch());
  21. exceptionTextFallThrough();
  22. }
  23. return cli;
  24. }
  25.  
  26. /// <summary>
  27. /// Logs errors to AppInsights
  28. /// </summary>
  29. /// <param name="args"></param>
  30. [PreHandlerFor(classStr(Global), staticMethodStr(Global, error))]
  31. public static void Global_Pre_error(XppPrePostArgs args)
  32. {
  33. str message = Args.getArg('txt');
  34. str exception = message + con2Str(xSession::xppCallStack());
  35.  
  36. TelemetryClient cli = McaGlobalTelemetry::newClient();
  37. cli.trackException(exception);
  38.  
  39. }
  40.  
  41. /// <summary>
  42. /// Log page views to AppInsights
  43. /// </summary>
  44. /// <param name="_formInstance"></param>
  45. [SubscribesTo(classStr(FormRun), staticDelegateStr(FormRun, onFormRun))]
  46. public static void FormRun_onFormRun(FormRun _formInstance)
  47. {
  48. TelemetryClient cli = McaGlobalTelemetry::newClient();
  49. if(_formInstance.args() && _formInstance.args().menuItemName())
  50. {
  51. cli.trackPageView(_formInstance.args().menuItemName());
  52. }
  53. }
  54.  
  55. /// <summary>
  56. /// Logs custom events to AppInsights
  57. /// </summary>
  58. /// <param name = "_eventName">ClassName.MethodName as a strFmt</param>
  59. public static void logEvent(str _eventName)
  60. {
  61. TelemetryClient cli = McaGlobalTelemetry::newClient();
  62. cli.trackEvent(_eventName);
  63. }
  64.  
  65. /// <summary>
  66. /// Logs custom metrics to AppInsights
  67. /// </summary>
  68. /// <param name = "_metricName">Metric Name</param>
  69. /// <param name = "_metricValue">Metric Value as REAL</param>
  70. public static void logMetric(str _metricName, real _metricValue)
  71. {
  72. TelemetryClient cli = McaGlobalTelemetry::newClient();
  73. cli.trackMetric(_metricName, _metricValue);
  74. }
  75.  
  76. }
  77.  
  78.  

You’ll notice I am using a locator in the constructor, feel free to implement any mechanism you wish to pull in the appropriate application insights GUID. 

Next, let’s head to the Azure Portal and setup a new ( FREE ) Application Insights resource:

Next, let’s grab our AppInsights GUID:

Plug that in to your application – whether that is storing it in a table, or some other web service registry.  Then, start seeing the data roll in!

By using this simple telemetry client, you’ll get rich insight in to what areas of the system are being used ( page views for every form ), which users are using the system ( including concurrent session management ), can monitor any custom class or batch job, and also have a capture of every Error / Exception with full stack trace.

This allowed me to build the following Azure Dashboard in 15 minutes:

Everything is cross-correlated as well, so you can see for a given user what page views and exceptions they’ve raised.  For a given exception, you can see the related user or page.  You can also see if an error was interactive or in batch mode. 

In addition to the data pumping out of your environment, you can setup ping availability alerts.  This allows the system to determine, proactively, the availability of your instance and can optionally send you an email when things go down.  This has added value on top of LCS because it is proactive, and not just for planned maintenance.  You can setup ping locations from any Azure region – such as West US, Europe, Asia, and more.  This gives you a deeper understanding of not only the availability in a primary region, but also across the globe.

It should be noted that while this is for Dynamics365, this same approach could be taken for AX 2012 given the appropriate event handlers.  Happy monitoring!