As we mentioned last spring, Dynamics 365 now supports tracing messages from plugins without requiring an error to be thrown. However as plugins become more complex, we often find ourselves wishing for more granular control over the level of detail traced without requiring code recompilation. With these requirements in mind, we will build an elegant solution that has minimal impact on performance.
First, we'll define an enum that introduces the different levels of tracing we want to support.
This should start to look familiar if you use log4net or similar logging frameworks. The different levels (Debug, Info, Warn, Error, and Fatal) provide the granularity we are looking for when configuring how much detail we want in our Plugin Trace Logs. The other two values (All and Off) provide a more explicit way of completely enabling or disabling tracing.
Next we’ll add an internal class to our plugin assembly to wrap all of our trace calls.
The main constructor starts by getting the ITracingService from the passed in IServiceProvider and storing it in a field for later use. It then goes on to look for a shared variable on the IPluginExecutionContext which will define the minimum tracing level to trace. If that shared variable doesn’t exist, it defaults to the minimum level passed in to the constructor.
Now we’ll add a method that will actually perform the tracing.
The Trace method takes a level, a format, and an array of arguments. If the level is at or above the minimum, the format and arguments are combined and then passed to the previously saved _tracingService. We prefix the message with the trace level, to provide extra detail. This could be further enhanced to provide timestamps if you are investigating performance concerns.
Finally, we’ll add a few convenience properties and methods to our Tracer class just to make it easier to use.
The properties provide a quick way to check and see which trace levels are enabled. For simple messages there isn’t a need to check these properties, but some more detailed traces have to build up a complex messages. In these cases, it is worth it to check and see if the targeted level is enabled before building the message. The methods here are simple shortcuts for Trace with the level specified as the method name.
Now we’re ready to write a plugin that takes advantage of our new class.
While this plugin’s logic is very contrived, it demonstrates how to use the Tracer class. There are examples of tracing at different levels and checking which levels are enabled before composing a more complex message.
We can register this plugin to run during the Create of a contact using the following configuration:
If the plugin is left registered by itself, it will always be configured to run at the “Info” trace level (The simpler Tracer constructor it uses defaults to TraceLevel.Info for the defaultMinimumLevel). If we want to change the level without making code changes, we’ll need to introduce another plugin.
The TraceConfigurationPlugin uses the Unsecure Configuration value to set the TracingLevel shared variable on the execution context. As long as we register this plugin to run before any of our other plugins, it can specify the level the plugins following it should use. We could even register multiple steps for the same message with different Execution Order values if we wanted to have different trace levels for different plugins.
Here is an example of how we could register this plugin to run before the BusinessLogicPlugin and set the trace level to “Debug”:
Now we can use the Tracer in all of our plugins and feel comfortable adjusting the trace level as more details are needed during troubleshooting. No additional API calls are made to read the tracing configuration values, so this will have a minimal impact on performance.
To see how to enable tracing and where to read the logs, please reference our earlier blog post.