Implement Logging Using Azure Cosmos DB In Dynamics CRM Plugin

If your organization got connected with lots of source systems, the most common request from management is to log the requests and check the result from the source system so we can analyze the log/provide the log for checking process. So today, we will learn how to create a simple logging function using Azure Cosmos DB and use it in our plugin.

Azure Cosmos DB

Go ahead to your azure.portal.com > you can search Azure Cosmos DB service > Click Create > Fill in all the mandatory information (SubscriptionResource GroupAccount NameLocation) > Review + Create and wait until the resource is ready.

Create Azure Cosmos DB

Go to Data Explorer > New Container > Fill in your Database Id, Container Id, and Partition Key like in the below picture > Press Ok button.

Create Container

With this step, our resource is ready to use.

Create Azure Logic App

Usually, I will prefer to install the Nuget package directly into the plugin compare to using the Azure Logic App. But unfortunately, the Nuget version of Microsoft.Azure.Cosmos was built by a different Target Framework (.NET Standard). Meanwhile, our plugin uses .NET Framework 4.6.2. That is why we can’t merge the DLL and the only way that I can think to make it work for now is via Azure Logic App.

Create Azure Logic App > Fill in SubscriptionResource GroupLogic App Name, and Region > Then you can hit Review + create.

Create Logic App

Then in logic app designer, you can create these steps:

Azure Logic App Flow

Choose HTTP: When a HTTP request is received action > click Use sample payload to generate schema and fill it using below value:

{
  "id": "b9cbead1-762f-47b1-b45c-64feafc9f195",
  "userId": "1e2ae189-c9d0-eb11-8235-0022481d77c1",
  "name": "Get Customers",
  "response": "\"Customer 475be983-e8d0-eb11-8235-00224823acd5: someone1@example.com.,Customer 495be983-e8d0-eb11-8235-00224823acd5: someone2@example.com.,Customer 4b5be983-e8d0-eb11-8235-00224823acd5: someone3@example.com.,Customer 4d5be983-e8d0-eb11-8235-00224823acd5: someone4@example.com.,Customer 4f5be983-e8d0-eb11-8235-00224823acd5: someone5@example.com.,Customer 515be983-e8d0-eb11-8235-00224823acd5: someone6@example.com.,Customer 535be983-e8d0-eb11-8235-00224823acd5: someone7@example.com.,Customer 555be983-e8d0-eb11-8235-00224823acd5: someone8@example.com.,Customer 575be983-e8d0-eb11-8235-00224823acd5: someone9@example.com.,Customer 595be983-e8d0-eb11-8235-00224823acd5: someone10@example.com.\"",
  "dateStart": "2021-07-15T09:27:15.6195506Z",
  "dateEnd": "2021-07-15T09:27:15.6664062Z",
  "totalSeconds": 0.0468556
}

The next action you can choose Azure Cosmos: Create or Update document (V2)(Preview) action > Create the connection to your Azure Cosmos DB > fill in Database ID, Collection ID, and fill Document = Body (from the previous step) > Click save and our flow is ready to be used.

Once you save it, you can keep the connection string to be use in the next step.

Plugin Code

For the plugin project, you can install RestSharp (my favorite Nuget Package project to invoke HTTP) and Niam.Xrm.Framework for the plugin framework. In this demo, I’ll just make a simple RetrieveMultiple action and log it into our Azure Cosmos DB:

GetAccount.cs

using Demo.Entities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Niam.XRM.Framework;
using Niam.XRM.Framework.Data;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
using RestSharp;
using System;
using System.Linq;
namespace Demo.Plugin.Business
{
    public class GetAccount : OperationBase
    {
        public GetAccount(ITransactionContext<Entity> context) :
            base(context)
        {
        }
        protected override void HandleExecute()
        {
            var customers = new Logger<string>("https://prod-24.eastasia.logic.azure.com:443/workflows/<your connection string>")
                .Do(Context.PluginExecutionContext, "Get Customers", "State Active & Email Address Not Null", () =>
                {
                    var data = GetCustomers();
                    return string.Join(",",
                        data.Select(customer => $"Customer {customer.Id}: {customer.Get(e => e.EMailAddress1)}."));
                });
            Set("ins_description", customers);
        }
        private Account[] GetCustomers()
        {
            var query = new QueryExpression(Account.EntityLogicalName)
            {
                ColumnSet = new ColumnSet<Account>(e => e.Id, e => e.EMailAddress1)
            };
            query.Criteria.AddCondition<Account>(e => e.StateCode, ConditionOperator.Equal, (int)Account.Options.StateCode.Active);
            query.Criteria.AddCondition<Account>(e => e.EMailAddress1, ConditionOperator.NotNull);
            var result = Service.RetrieveMultiple(query);
            return result.Entities?.Select(e => e.ToEntity<Account>()).ToArray();
        }
    }
    public class Logger<T>
    {
        private readonly string _url;
        public Logger(string url)
        {
            _url = url;
        }
        public T Do(IPluginExecutionContext context, string name, string parameter, Func<T> action)
        {
            var client = new RestClient(_url);
            var start = DateTime.Now;
            var response = "";
            try
            {
                var result = action.Invoke();
                response = result.ToJson();
                return result;
            }
            catch (Exception ex)
            {
                response = ex.ToString();
                throw;
            }
            finally
            {
                var end = DateTime.Now;
                var log = new
                {
                    id = Guid.NewGuid(),
                    userId = context.InitiatingUserId,
                    name = name,
                    response = response,
                    dateStart = start,
                    dateEnd = end,
                    totalSeconds = (end - start).TotalSeconds
                };
                System.Threading.Tasks.Task.Run(() =>
                {
                    var req = new RestRequest(Method.POST);
                    req.AddJsonBody(log);
                    client.ExecuteAsync(req);
                });
            }
        }
    }
}

PreCacheOperation.cs:

using System;
using Demo.Plugin.Business;
using Microsoft.Xrm.Sdk;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
namespace Demo.Plugin
{
    public class PreCacheOperation: PluginBase, IPlugin
    {
        public PreCacheOperation(string unsecure, string secure) : base(unsecure, secure)
        {
        }
        protected override void ExecuteCrmPlugin(IPluginContext<Entity> context)
        {
            new GetAccount(context).Execute();
        }
    }
}

Logger class receives the Flow URL. For simplicity, I hardcoded this URL in this class. But you can move this into the unsecure config or you can create Environment Variable. The class just have 1 method name Do and receives some parameter to be saved in the database. The last parameter is the action that will be triggered. We can put all the logic to create the record in the Do method. But in this demonstration, I just add Start, End time and the running seconds to be saved in the database. The Task.Run will execute the function without waiting for the result to be done.

With this, the code is ready to be used. You just need to merge your plugin, Niam.Xrm.Framework.dll, and RestSharp.dll. Once you already merge it, you just need to register the assembly and register the step. Then you can test it and here is the result in the Azure Cosmos DB Data Explorer:

Querying the log from Data Explorer in Azure Cosmos DB

What do you think?

One thought on “Implement Logging Using Azure Cosmos DB In Dynamics CRM Plugin

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.