Blog Post

Microsoft Mission Critical Blog
3 MIN READ

Seamless blend of ILogger with ITracingService inside D365 CE Plugins

PravinT's avatar
PravinT
Icon for Microsoft rankMicrosoft
Aug 25, 2025

👋 Introduction

This document introduces a practical approach for integrating Application Insights logging into Dynamics 365 plugins using the ILogger interface alongside the existing ITracingService. The key advantage of this method is its minimal impact on the existing codebase, allowing developers to adopt enhanced logging capabilities without the need to refactor or modify each plugin individually. By wrapping the tracing logic, this pattern promotes maintainability and simplifies the transition to modern observability practices with a single, centralized change.

🧱 The Classic Plugin Base Pattern

Most customers already use a base class pattern for plugins. This pattern wraps the IPlugin interface and provides a LocalPluginContext object that encapsulates services like IOrganizationService, ITracingService, and IPluginExecutionContext.

✅ Benefits:

  • Reduces boilerplate
  • Encourages separation of concerns
  • Makes plugins easier to test and maintain

🧩 Base Class with try-catch in Execute

public abstract class PluginBase : IPlugin
{
    protected internal class LocalPluginContext
    {
        public IOrganizationService OrganizationService { get; }
        public ITracingService TracingService { get; }
        public IPluginExecutionContext PluginExecutionContext { get; }

        public LocalPluginContext(IServiceProvider serviceProvider)
        {
            PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            OrganizationService = ((IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)))
                .CreateOrganizationService(PluginExecutionContext.UserId);
        }
    }

    public void Execute(IServiceProvider serviceProvider)
    {
        try
        {
            var localContext = new LocalPluginContext(serviceProvider);
            localContext.TracingService.Trace("Plugin execution started.");
            ExecutePlugin(localContext);
            localContext.TracingService.Trace("Plugin execution completed.");
        }
        catch (Exception ex)
        {
            var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            tracingService.Trace($"Unhandled exception: {ex}");
            throw new InvalidPluginExecutionException("An error occurred in the plugin.", ex);
        }
    }

    protected abstract void ExecutePlugin(LocalPluginContext localContext);
}
    

 

📈 Seamless Application Insights Logging with a Tracing Adapter

💡 The Problem

Many customers want to adopt Application Insights for plugin telemetry—but hesitate due to the need to refactor hundreds of TracingService.Trace(...) calls across their plugin codebase.

💡 The Innovation

The following decorator pattern wraps ITracingService and forwards trace messages to both the original tracing service and an ILogger implementation (e.g., Application Insights). The only change required is in the base class constructor—no changes to existing trace calls.

🧩 Tracing Adapter

public class LoggerTracingServiceDecorator : ITracingService
{
    private readonly ITracingService _tracingService;
    private readonly ILogger _logger;

    public LoggerTracingServiceDecorator(ITracingService tracingService, ILogger logger)
    {
        _tracingService = tracingService;
        _logger = logger;
    }

    public void Trace(string format, params object[] args)
    {
        _tracingService.Trace(format, args);
        _logger?.LogInformation(format, args);
    }
}

 

🧩 Updated Base Class Constructor

public LocalPluginContext(IServiceProvider serviceProvider)
{
    PluginExecutionContext = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
    var standardTracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    var logger = (ILogger)serviceProvider.GetService(typeof(ILogger));
    TracingService = new LoggerTracingServiceDecorator(standardTracingService, logger);
    OrganizationService = ((IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)))
        .CreateOrganizationService(PluginExecutionContext.UserId);
}

This enables all trace calls to be logged both to Plugin Trace Logs and Application Insights. AppInsights Traces allows for easier troubleshooting using Kusto Query Language and enables alerting based on custom trace messages.

🧩 Using ILogger inside a plugin without base class

This approach is exactly the same as implemented in plugins with a base class. However, in this case, we make the wrapping assignment once at the beginning of each plugin. This is far better compared to modifying every line that uses tracingService.Trace.

public class ExistingPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var originalTracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
        var logger = (ILogger)serviceProvider.GetService(typeof(ILogger));

        // Wrap the original tracing service with the decorator
        var tracingService = new LoggerTracingServiceDecorator(originalTracingService, logger);

        tracingService.Trace("Plugin execution started.");

        try
        {
            // Existing plugin logic
            tracingService.Trace("Processing business logic...");
        }
        catch (Exception ex)
        {
            tracingService.Trace($"Exception occurred: {ex}");
            throw;
        }

        tracingService.Trace("Plugin execution completed.");
    }
}
    

 

📣 Final Thoughts

These patterns weren’t invented in a vacuum—they were shaped by real customer needs and constraints. Whether you're modernizing legacy plugins or building new ones, this approach helps you deliver supportable solutions with minimal disruption to your existing codebase.

Updated Sep 11, 2025
Version 2.0
No CommentsBe the first to comment