Custom Code Tracing & Exception Logging in Dynamics 365 v.9

As a Solution Architect I often review Microsoft Dynamics 365 custom server and client side code.
One of the most common rejects regards tracing and exception handling mechanisms, or their absence. Some code constructs may have empty Try/Catch blocks or none at all, other catch exceptions and just re-throw. As for tracing, code often contains debugging ‘aids’ such as alerts and debugger statements or no tracing notifications at all.

Why is this a reject?

  • Unhandled raw exceptions float to UI, confusing and frustrating users while exposing internal implementation details to potential attackers
  • System Administrator is unaware of custom code exceptions unless users decide to report an error message or a bug
  • Tracing mechanism help diagnose faults and sometimes allow resolving without debugging or recompiling code

Microsoft Dynamics 365 contains a Plug-in Trace repository which contains trace and exception entries originating from Plug-in and Custom Workflow Activity components.
While this is a good built in solution, it has some drawbacks:

  • It does not support common types of custom code such as client side JavaScript and external components consuming Microsoft Dynamics API directly
  • There is no straight forward method to filter Plug-in Trace notification to search for specific text in trace/exception messages
  • No option to trigger a notification to system administrator once an exception is logged
  • No option to accurately correlate a user reported error message to a specific exception entry

In this post I would like to suggest an updated implementation of a more wholesome infrastructure for tracing and exceptions logging. Using this  infrastructure, any developer can easily log trace entries and exceptions from any custom code interacting with Microsoft Dynamics 365 application, while admins can easily monitor the application health in that aspect.

WIIFM

Why using this infrastructure?

  • Easily log trace entries and exceptions from any client and server side custom code interacting with Microsoft Dynamics 365 application in a unified manner
  • You can add processes triggered by exception Log record creation to alert system administrator regarding a fault as it occurs
  • Easily search and filter specific exceptions/trace records according to unique Reference Token provided by users/clients using Quick/Advanced Find

In the following How To Use section you can download a un/managed solution and follow setup and usage instructions.  You can view the GitHub project here.

How to use

  1. Download and import solution

    You can download a managed solution here.
    If you want to make any changes to this solution, download the un-managed version. In that case, open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area. Finally, publish the solution.

    open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area

  2. Using in an entity form JavaScript code

    Add the dyn_utils.js Web Resource to the target entity form libraries collection.
    Call the LogTrace and LogException functions in your custom code.

    Add the dyn_utils.js Web Resource to the target entity form Libraries collection

        //perform some business logic including tracing and exception handling 
        ns.DoSomeBusinessLogic = function () {
            //define verbose trace entry
            var traceEntry = {
                //title
                "title": "loggingSample.DoSomeBusinessLogic",
                //description
                "description": "Start execution",
                //related business record id 
                "relatedBusinessRecordId": "2514FC63-9E58-4D1E-8226-69256D0197E3",
                //related business record URL
                "relatedBusinessRecordURL": "https://xxx.crm.dynamics.com/main.aspx?etc=8&extraqs=&histKey=520872127&id=%7b2514FC63-9E58-4D1E-8226-69256D0197E3%7d&newWindow=true&pagetype=entityrecord#923042",
                //set current user id as related user id 
                "relatedUserId": getContext().userSettings.userId
            };
    
            //trace function execution start asynchronously
            Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);
    
            try {
                //some business logic including some exception 
                NonExistingFunctionCall();
            }
            catch (err) {
                //define verbose exception entry
                var exceptionEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": Constants.MSG_GENERAL_FAILURE,
                    //related business record id 
                    "errorMessage": err.message,
                    //related business record URL
                    "stackTrace": err.stack
                };
    
                //log exception and return reference token
                var referenceToken = Utils.LogException(exceptionEntry, false);
    
                //notify user with a reference token to report back to admin
                alert(Constants.MSG_GENERAL_FAILURE + referenceToken);
            }
            finally {
                //define thin trace entry 
                var traceEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": "Start execution",
                };
    
                //trace function exectuion end asynchronously
                Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);    
            }
        }
    
  3. Using in Web Resource JavaScript code

    You can view the code in action by launching the dyn_loggingSample.htm Web Resource (replace organization base address)
    https://YOUR_ORG_NAME.crm.dynamics.com//WebResources/dyn_loggingSample.htm

    You can view the code in action by launching the dyn_loggingSample.htm Web

    Make sure you include the dyn_utils.js as a referenced resource:

    
    
        
        Logging Sample
        
        
        
        
    
    

    The sample code in the dyn_loggingSample.js demonstrates tracing and exception handling, same as in the code sample above.

  4. Using in Plug-in/Custom Workflow Activity/external SDK client

    The following code sample represents usage in Plug-in and is similar to CWA and external SDK client usage:

        public class LogTraceAndException : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                // Obtain the execution context from the service provider
                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                // Get a reference to the organization service
                IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService organizationService = factory.CreateOrganizationService(context.UserId);
    
                //define a trace entry, all attributes are optional except title 
                Log traceEntry1 = new Log
                {
                    title = "ServerSideLogging: Execute",
                    description = "Execution end",
                    //replace user id with an valid one
                    relatedUser = new EntityReference("systemuser", 
                        Guid.Parse("37ACCC73-0FCE-474B-A206-19C08163438B")),
                    relatedBusinessRecordURL = "https://YOURORGNAME.crm.dynamics.com/main.aspx?etc=1&extraqs=&histKey=87055256&id=%7b381FA757-60D7-E811-A97E-000D3AB20035%7d",
                };
    
                //log trace entry, get reference token back 
                string refToken1 = GenericLogging.Log(traceEntry1, 
                    GenericLogging.LogType.Trace, organizationService);
    
                try
                {
                    //some exception raising code.
                    //If transaction is rolled back, Log entries will persist
                    throw new Exception("An error occurred, please notify the System Administrator");
                }
                catch (Exception ex)
                {
                    //define an exception entry 
                    Log excptionEntry = new Log
                    {
                        title = "ServerSideLogging: Execute",
                        errorMessage = ex.Message,
                        stackTrace = ex.StackTrace
                    };
    
                    //log exception entry, get reference token back
                    string refToken2 = GenericLogging.Log(excptionEntry,
                        GenericLogging.LogType.Exception,
                        organizationService);
    
                    throw new InvalidPluginExecutionException(
                        string.Format("An error occurred, please notify the System Administrator. Reference token: {0}", 
                        refToken2));
                }
            }
        }
    

    A resulting exception raised from the Plug-in code includes the reference token. Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    A resulting exception includes the reference token

    Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    View Log details

Implementation Notes

I have written about the subject in the past and the described approach has not changed much. The implementation has been updated to leverage version 9 features (currently available on for Online implementations)

What has changed since last version:

  • Using Action definition to activate, deactivate and control logging level
  • Using built-in auto-numbering attribute to generate reference token, so a designated CWA component is no longer required
  • Optional usage of client side asynchronous logging operation to keep UX uninterrupted

In order to support logging from transactional components (such as Plugin registered to pre/post operation stages), the Log method in GenericLogging.cs file is using the ExecuteMultipleRequest class to execute the dyn_Log request.
As the ExecuteMultipleRequest instance is external to the Plugin transaction, it manages to create a Log record without the Plugin transaction rolling it back.
Using the Execute method directly with the dyn_LogRequest request instead, would have executed the Log Action, but the created Exception record would have been deleted by the Plugin transaction rollback, leaving no trace.

Custom Trace & Exception Logging diagram

 

Advertisement

Logging and Handling Microsoft Dynamics CRM 2013 Exceptions – Part 3

In the previous post I described a solution to the business problem of logging & handling implementation level exceptions (presented in the first post of this series).
In this post, I will supply an actual solution, demonstrate common usage scenarios and other solution features.

Before I walkthrough usage scenarios, some implementation notes:

In order to support exceptions raised from transactional components (such as Plugin registered to pre/post operation stages), the LogException method in ExceptionManagement.cs file is using the ExecuteMultipleRequest class to execute the e4d_LogExceptionRequest request. As the ExecuteMultipleRequest instance is external to the Plugin transaction, it manages to create the Exception record without the Plugin transaction rolling it back. Using the Execute method directly with the e4d_LogExceptionRequest request instead, would have executed the LogException Action, but the created Exception record would have been deleted by the Plugin transaction rollback, leaving no trace.

The provided Exception Management solution is unmanaged. This allows you to modify the solution freely but note that unmanaged solution can not be uninstalled.

The provided Exception Management solution is exported as MSCRM2013 6.0 compatible.

As an Exception record is created on behalf of a user, all users must have the Create, Read and Write privileges for the Exception entity.
The solution contains a custom Security Role named Exception Logging Authorized which includes these privileges for the Exception entity. Make sure you add this role to your users or add the privilege to their existing roles.

Ok, let’s get some work done:

Step 1: import and configure the Exception Management solution:

1. Download and extract the E4D MSCRM2013 Excreption Management.zip package.
2. Import the solution ExceptionManagement_1_0_0_0_target_CRM_6.0.zip.  Leave the ‘Enable any SDK message…’ checkbox checked.
3. Add the Exception entity to the Settings area.
4. Publish the solution.
5. Add the Exception Logging Authorized Security Role to all users.

Step 2: Follow the relevant scenario below to use the Exception Management functionality from your custom code:

Scenario 1: Logging Exceptions from JavaScript code running in Entity form

1. Open the target entity Form Properties window.
2. In the Events tab, under Form Libraries, add the ExceptionManagement.js library to the form libraries.
3. If the JavaScript Web Resource that contains your custom code does not appear in the form libraries, add it. Make sure it appears below the e4d_ExceptionManagement.js library.

image_thumb5_thumb

4. In your custom JavaScript code (someFunction in my example), wrap the target code with Try-Catch blocks.
5. In the Catch block, add the following line of code. You can add your own error handling code in the Catch block.

Sdk.logException(error, Sdk.e4d_LogExceptionRequest.PriorityLevel.LOW);

Your code should look similar to this:

function someFunction()
{
try
{
//your custom business logic here
}
catch(error)
{
//your custom exception handling here
//log exception. This is the only line of code required in your custom code
//You can set the Priority level to MEDIUM, HIGH or CRITICAL instead of LOW
Sdk.logException(error, Sdk.e4d_LogExceptionRequest.PriorityLevel.LOW);
}
}

6. Save and publish the entity form.

Scenario 2: Logging Exceptions from JavaScript code running in HTML Web Resource

1. In your HTML Web Resource, add the following references in that order:

    <script src=”ClientGlobalContext.js.aspx” type=”text/javascript”></script>
<script src=”e4d_exceptionManagement.js” type=”text/javascript”></script>

2. Follow steps 4,5 in the Scenario 1. 3.
3. Save and publish the Web Resource

Scenario 3: Logging Exceptions from c# code running in Plugin/Custom Workflow Activity/.net application

1. Add the ExceptionManagement.cs file to your Visual Studio project
2. In your code, add the following using statement

   using ExceptionMgmtServices;

3. wrap the target code with Try-Catch blocks
4. In the Catch block, add the following line of code. You can add your own error handling code in the Catch block

string friendlyMessage = ExceptionManagement.LogException(ex, service, PriorityLevel.LOW);

Your code should look something like this:

try
{
//your custom business logic here
}
catch (Exception ex)
{
//your custom exception handling here
//You can set the Priority level to MEDIUM, HIGH, CRITICAL instead of LOW
string friendlyMessage = ExceptionManagement.LogException(ex, service, PriorityLevel.LOW);
//Optionally, throw out the exception to return friendly message to the client
throw new Exception(friendlyMessage);
}

I advise you test the relevant scenarios by generating an intentional exception, such as calling a non-existing function. Make sure an error message is displayed, an Exception record is created and that the Exception details are correct.

The ExceptionManagement_1_0_0_0_target_CRM_6.0.zip contains a Web Resource named ClientSideExceptionDemo.htm which demonstrate scenario 2.

The E4D MSCRM2013 Excreption Management package contains a sample Plugin project called E4D.CRM.ExceptionMgmtServices.ClientPlugin aimed to demonstrate scenario 3.

Managing Exceptions tools:

Navigate to the Exceptions node in the Settings area to view the Exceptions list. You can feed the unique token displayed in the error message in the Quick Find search box to quickly find the relevant Exception record.
You can export list of Exception records to Excel in order to send out to be reviewed by external developer.

image_thumb14_thumb[3][4]

image_thumb11_thumb[1][4]

image_thumb2_thumb[1]

 

As this solutions is unmanaged, you can can customize and extend it as you wish, here are some ideas:

  • Create a Workflow Rule that will add exceptions to a designated Queue from which Exceptions can be pulled and handled by developers or system administrators.
  • Create a Workflow Rule that will send email to administrators group when critical Exception record is created.
  • Create a Workflow Rule that will send an acknowledgment email to the end user when Exception record is created or handled (status change).
  • Develop a Custom Workflow Activity or Plugin component that will integrate with your ALM application to automatically send exceptions to be handled by the development team.
  • Add JavaScript code to display a nice error message using setFormNotification when error is displayed in entity forms, instead of displaying an alert.

I would like to hear your ideas about extending and improving this solution.

Logging and Handling Microsoft Dynamics CRM 2013 Exceptions – Part 2

In the previous post, I described the business problem of logging and handling implementation level exceptions. In this post, I would like to suggest a solution.

Before you continue reading, note that logging implementation level exceptions is up to your code developer. Although the solution supplies easy methods to log exceptions (to be demonstrated in part 3), nothing will be logged If your custom code does not use the solution components.

Let’s start by defining some business requirements for the suggested solution:

  • Support Microsoft Dynamics CRM 2013 of any deployment type
  • Expose minimum exception details to the user, maximum to the System Administrator
  • Supply the client (user or software component) with a unique correlation code to id each exception instance
  • Require minimum effort from software developers to log exceptions
  • Allow logging and handling client side & server side exceptions in a similar manner
  • Supply System Administrator with an efficient way to search and view exceptions
  • Allow flexible handling when exception is logged: send email notification, move to queue, assign to user/team, send acknowledgment to user etc.

Next, here are the suggested solution major components along with some architecture consideration:

  1. Exception Entity
    This custom entity describes an exception instance details and will be created whenever an exception is logged. Using a custom Exception entity will allow using Microsoft Dynamics CRM UI to easily search and view exception records. A custom entity will also allow great flexibility for handling exceptions by using all Microsoft Dynamics CRM magic: Processes, Queues, Advanced Search, Dashboards, Charts, export to Excel etc.

    The Exception entity will have the following attributes:

    • Name – describes the exception’s raw error message  
    • Created By – reference to the user who initiated the process from which the exception was raised
    • Created On – Date & time on which the exception was logged
    • Friendly Message – the error message that was displayed to the user
    • Unique Token – a unique id assigned to the exception instance and returned to the client along with the friendly message. This token will be used by the System Administrator to correlate the logged exception instance to the error message displayed to the user
    • Priority – describes the exception handling priority
    • Stack Trace – describes the exception’s stack trace which supplies contextual information regarding the calling source function, file etc.
  2. LogException Action
    A custom Action will be used to create an Exception record according to the specified details, generate and assign a unique token and return a friendly message to the client.

    Why Action?

    • Compared to writing custom code, Actions simplify the task of creating and updating an Exception record as it harness the powerful Process tools
    • Action is the only MSCRM mechanism that can receive input parameters and return output parameters in a straight forward manner. Sort of a web service
    • As Action can be accessed from client side (JavaScript code) and server side (Plugins, Custom Workflow Activities, external applications), it provide a single point of implementation
    • Actions allow defining business logic in a declarative manner, so Actions can be easily created and maintained by non developers, as long as the Action signature (parameters and name) is not changed

  3. Unique Token Generator Custom Workflow Activity
    This component will be used by the LogException Action to generate the unique token value. The unique token will be saved to the Exception record and also displayed to the user as part of the managed error message. This will allow the System Administrator to quickly locate the exact exception instance the user has reported.
    As the Process basic tools do not allow generating a unique token, a custom component is required. I prefer a Custom Workflow Activity component as it can be embedded in Actions natural (and transactional) flow, rather than Plugin which is external to the Action. It can also be reused in Workflow Rules and Dialogs.
  4. Sdk.Soap.js
    The Sdk.Soap.js package was recently released by Microsoft as part of the 6.1 SDK. It supplies a comprehensive and intuitive framework for MSCRM JavaScript developers and I recommend it completely.
    One of the package’s tools is the Action Message Generator which automatically generates a JavaScript API for Actions. This API simplifies the execution of Actions from JavaScript and the solution will use it to execute the aforementioned LogException Action from JavaScript Code.
     
  5. ExceptionManagement.js
    This JavaScript Web Resource is used to wrap the functions required to log exception from JavaScript code. 
    In order to use the exception logging functionality, a developer is required to reference this Web Resource in forms event handlers or other Web Resources. This file includes the Sdk.Soap.js library in order to prevent the need for additional reference.
  6. ExceptionManagement.cs
    This C# class is used to wrap the methods required to log exception from c# code written in Plugins, Custom Workflow Activities, external .net applications etc.
    In order to use the exception logging functionality, the developer is required to add this class to his project or compile it into a DLL and reference it.
     
  7. Exception Management Dashboard
    This dashboard is aimed to help the System Administrator manage exceptions with the following components:
    • Exceptions by priority and creation day chart
    • Exceptions by priority and user chart
    • Exceptions by priority chart
    • Critical & High priority exceptions view

 

Finally,  a nice diagram of the solution’s major components:

Solution Components Diagram

Feel free to comment on the architecture and the planned Implementation, I welcome your feedback.

In the next post I will describe the actual Solution and will demonstrate common usage scenarios.

Logging and Handling Microsoft Dynamics CRM 2013 Exceptions – Part 1

In this 3-parts post I would like to suggest a general approach and solution for logging and handling exceptions in Microsoft Dynamics CRM 2013 implementations.

By exceptions, I don’t mean Microsoft Dynamics CRM product core exceptions which occur from time to time. These are logged by various designated repositories (such as Event Viewer and CRM Trace) and beyond our reach anyway, certainly in Online deployments. I do mean unexpected events that arise from custom code written in both server and client side in most Microsoft Dynamics CRM 2013 implementations. These events are usually related to poorly written code, unexpected customization changes or external resources that are beyond your control.

Here are some examples for such exceptions:

  • Custom JavaScript code accessing an attribute of a an object with value of null
  • Custom JavaScript code accessing a field which was removed from the entity form
  • Custom Workflow Activity trying to access an external web service which does not respond or returns an error
  • Plugin code, triggered by an Update event, accessing an attribute which was not updated

Why is it important to log and handle such exceptions? Here are some of the reasons:

  • Bad user experience: When a custom code exception is not handled, it often bubbles to the user interface. This means a vague message is displayed to the end user, a message that he probably can’t handle or tell if and how it affect the business process he is attempting. 
  • Potential business damage: When a user encounters an exception he usually does one of two: ignore it and try again or report the system administrator.
    The first option is bad. It means your application is not healthy and you, as system administrator, are not aware. Beside your users getting frustrated, this may cause data integrity problems, junk data, missed business opportunities or other business related problems.
    It can cost you money!
  • Interrupting your users work: If a user does report an exception to the system administrator, he usually supplies minimum details, less than required to solve the problem without interrupting him to try and reproduce the exception. While some exception details are available to the user (approximate time, attempted business process, error message etc.), other critical details (Internal exception, Stack trace, source component etc.) are hidden from him. 
  • Security breach: Some unmanaged exceptions messages can expose internal mechanisms and supply hackers with details that can help them damage your organization.

Unfortunately, exceptions are part of any business application and cannot be completely avoided. The purpose of the suggested approach is to improve exception handling experience.

So, here is the business problem. In the next post I will describe the suggested solution approach, while the third post will discuss the actual implementation details.

Stay tuned.