Showing posts with label MS Enterprise Library. Show all posts
Showing posts with label MS Enterprise Library. Show all posts

Monday, 12 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins (using MSMQ) - part 4

In part one of this series I described how to use the enterprise library for trace logging in MS Dynamics CRM 2011 plug-ins (it should also work for MS Dynamics CRM 4.0) and in part two, I described how to use add the necessary changes to the various configuration files using Wix, in the third post, I provided a small code sample of how to use the enterprise library.

In this fourth and final post I describe a rather interesting use of the Enterprise Library trace logging capabilities, feel free to contact me if you want a sample.

From version 4.0 of the enterprise library, not sure whether present in any other versions, there is the MSMQ Distributor Service, which picks up queue messages and then logs them to any trace listener. This means that something like this is possible (A picture is worth a thousand words and all that):

Pilfered from here
In our environment we will have four application servers running MS Dynamics CRM 2011 and we want to log everything to a database on the SQL Cluster that hosts the Dynamics CRM databases. For the web services we are logging directly to the database, but for the plug-ins and code based workflows we will be using MSMQ trace listeners to log to a private queue on the SQL Cluster and then have the MSMQ distributor service do the final mile of logging to the database.

Although the queue is private, Everyone will be able to write messages to it, which means that the plug-ins that run under the user context will be able to write messages to the queue or in other words, trace logging should work for all users regardless of their permissions, which is the key. We could log to the database with a sql user but that is not what we wanted.

I will not post the configuration file logging section that I used again, as it's already been posted here.
  1. Install MSMQ on all servers (Application and Database), which can be done using the script posted here
  2. Use the configuration file logging section posted here for the CrmAsyncService.exe.config and web.config. 
  3. Install MSMQ Distributor Service on Database Server.
    Installutil -i MsmqDistributor.exe
  4. Change MSMQ Distributor Service configuration, see sample below.
  5. Start MSMQ Distributor Service, e.g.:
    net start "Enterprise Library Distributor Service"
MSMQ Distributor Service Sample Configuration File.
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <configSections>
     <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging" />
     <section name="msmqDistributorSettings" type="Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Configuration.MsmqDistributorSettings, MsmqDistributor"/>
   </configSections>
   <loggingConfiguration tracingEnabled="true" defaultCategory="Trace" logWarningsWhenNoCategoriesMatch="true">
     <logFilters/>
     <categorySources>
       <add name="Trace" switchValue="All">
         <listeners>
           <add name="Database Trace Listener"/>
         </listeners>
       </add>
       <add name="Error" switchValue="All">
         <listeners>
           <add name="Database Trace Listener"/>
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents name="allEvents" switchValue="All"/>
       <notProcessed name="notProcessed" switchValue="All"/>
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Formatted EventLog TraceListener" />
         </listeners>
       </errors>
     </specialSources>
     <listeners>
       <add name="Formatted EventLog TraceListener"
         type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"
         source="Enterprise Library Logging" formatter="Text Formatter"/>
       <add name="Database Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.Database.FormattedDatabaseTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Database.Configuration.FormattedDatabaseTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         databaseInstanceName="loggingdb" writeLogStoredProcName="WriteLog"
         addCategoryStoredProcName="AddCategory" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add name="Text Formatter"
         type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging"
         template="Timestamp: {timestamp}{newline}Message: {message}{newline}Category: {category}{newline}Priority: {priority}{newline}EventId: {eventid}{newline}Severity: {severity}{newline}Title:{title}{newline}Machine: {machine}{newline}App Domain: {appDomain}{newline}ProcessId: {processId}{newline}Process Name: {processName}{newline}Thread Name: {threadName}{newline}Win32 ThreadId:{win32ThreadId}{newline}Extended Properties: {dictionary({key} - {value}{newline})}" />
     </formatters>
   </loggingConfiguration>
   <connectionStrings>
     <add name="loggingdb" connectionString="Data Source=sql2k12a;Initial Catalog=Logging;Integrated Security=SSPI;"
       providerName="System.Data.SqlClient" />
   </connectionStrings>
   <msmqDistributorSettings
                 msmqPath=".\Private$\logging"
                 queueTimerInterval="1000"
                 serviceName="Enterprise Library Distributor Service" />
 </configuration>

Sunday, 11 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins - part 3

In part one of this series I described how to use the enterprise library for trace logging in MS Dynamics CRM 2011 plug-ins (it should also work for MS Dynamics CRM 4.0) and in part two, I described how to use add the necessary changes to the various configuration files using Wix.

In this post, I provide a simple sample of how to use the Enterprise Library for trace logging.

Ensure that you add a reference to Enterprise Logging Application Block to your project, if this is not showing up, you probably don't have the Enterprise Library installed.

Sample Code:
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Diagnostics;
 using Microsoft.Practices.EnterpriseLibrary.Logging;
 using System.IO;
 
 namespace ELSample
 {
     class Program
     {
         static void Main(string[] args)
         {
             string file = @"c:\myfile.txt";
             
             try
             {
                 WriteLogEntry("Trace",string.Format("Start to read file {0}.", file)); 
 
                 using (StreamReader sr = new StreamReader(file))
                 {
                     //do something here.
                 }
 
                 WriteLogEntry("Trace", string.Format("Finished reading file {0}.", file)); 
             }
             catch (Exception ex)
             {
                 WriteLogEntry("Error", "Exception Occurred.");
                 WriteLogEntry("Error", string.Format("Source: {0}.", ex.Source));
                 WriteLogEntry("Error", string.Format("Type: {0}.", ex.GetType()));
                 WriteLogEntry("Error", string.Format("Message: {0}.", ex.Message));
 
                 if (ex.InnerException != null)
                 {
                     WriteLogEntry("Error", string.Format("Inner Exception: {0}.", ex.InnerException.Message));
                 }
             }
 
         }
 
         /// <summary>
         /// Write a log entry using the Enterprise Library
         /// </summary>
         /// <param name="LogCategory">This is the name of the Trace Listener, e.g. Trace, Error</param>
         /// <param name="LogMessage">Log Entry to write</param>
         public static void WriteLogEntry(string LogCategory, string LogMessage)
         {
             LogEntry entry = new LogEntry();
             entry.Categories.Add(LogCategory);
             entry.Message = LogMessage;
 
             StackTrace trace = new StackTrace();
 
             entry.Title = string.Format("{0}.{1}",
                 trace.GetFrame(1).GetMethod().ReflectedType.Name, trace.GetFrame(1).GetMethod().Name);
 
             Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(entry);
         }
 
   
     }
 }
A few things to note:
  1. I know that the sample is not from a plug-in.
  2. Same config section, the logging part, as in the first post.
  3. If you turn a trace listener off, the WriteLogEntry method will not write any entries, but there will be some processing done, so it will run slower than without it, as with everything in life it's a trade off.
  4. It probably makes sense to have a separate method for logging exceptions.
  5. I normally only bother with two trace listeners, Trace and Error. The former is normally equivalent to Verbose logging, the latter to ... Error logging.

Saturday, 10 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins - part 2

In part one of this series I described the changes needed to use the trace logging capabilities of the enterprise library, in this post I shall describe how to add these changes using Wix.

The more eagled eyed readers, will notice that the listeners are different from the last time, this is because we are actually using a private queue on the database server to log the Trace messages to. These are then processed by the Enterprise Library MSMQ Distributor service, I shall discuss this in detail in an upcoming post.

I've not managed to insert the configSections element at the beginning of CrmAsyncService.exe.config file, which is where it needs to be, so I just deleted what it was there, added the configSections element and re-added what was originally there. It feels too much like a hack but ...

 Wix Sample
 <?xml version="1.0" encoding="UTF-8"?>
 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
   <Fragment >
 
     <Component Id="pluginlogging" Guid="BA77335E-ADF1-4037-9881-68E116BA543D" Directory="dirB50C76F5A59FB10334E162992F65AAAA" KeyPath="yes">
       <Condition>DYNAMICS</Condition>
       <util:XmlConfig Id="web.logging"  File="[DYNAMICSCRMWEB]\web.config" Action="create"
               ElementPath="/configuration/configSections" On="install"  Node="document" Sequence="1" VerifyPath="/configuration/configSections/section[\[]@name='loggingConfiguration'[\]]" >
         <![CDATA[<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> ]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="webloggingConfiguration" Action="create" ElementPath="/configuration"   File="[DYNAMICSCRMWEB]\web.config" Node="document" On="install" Sequence="2" VerifyPath="/configuration/loggingConfiguration/listeners">
         <![CDATA[<loggingConfiguration name="" tracingEnabled="true" defaultCategory="Trace">
     <listeners>
       <add name="Message Queuing Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.MsmqTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.MsmqTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         queuePath="FormatName:Direct=OS:[SQLHOSTNAME]\private$\logging" formatter="Binary Log Message Formatter"
         timeToReachQueue="10.00:00:00" timeToBeReceived="10.00:00:00"
         recoverable="true" useDeadLetterQueue="true" traceOutputOptions="None" />
        <add name="Event Log Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         source="Enterprise Library Logging" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
         name="Text Formatter" />
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.BinaryLogFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         name="Binary Log Message Formatter" />
     </formatters>
     <categorySources>
       <add switchValue="All" name="Trace">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
       <add switchValue="All" name="Error">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents switchValue="All" name="All Events" />
       <notProcessed switchValue="All" name="Unprocessed Category" />
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Event Log Trace Listener" />
         </listeners>
       </errors>
     </specialSources>
   </loggingConfiguration>]]>
       </util:XmlConfig>
 
 
       <util:XmlConfig Id="asyncloggingConfiguratiodn" Action="delete" ElementPath="/configuration"   File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="element"
                  On="install" Sequence="3" VerifyPath="runtime" />
 
       <util:XmlConfig Id="asynclogging" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="4" VerifyPath="/configuration/configSections/section[\[]@name='loggingConfiguration'[\]]" >
         <![CDATA[<configSections><section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /></configSections>]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="asyncloggingmlarky" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="5" VerifyPath="/configuration/runtime" >
         <![CDATA[<runtime><gcServer enabled="true"/>
         <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
           <dependentAssembly>
             <assemblyIdentity name="Microsoft.Crm.Sdk" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
             <bindingRedirect oldVersion="4.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
           </dependentAssembly>
         </assemblyBinding>
       </runtime>]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="asyncloggingConfiguration" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="6" VerifyPath="/configuration/loggingConfiguration/listeners" >
         <![CDATA[<loggingConfiguration name="" tracingEnabled="true" defaultCategory="Trace">
     <listeners>
       <add name="Message Queuing Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.MsmqTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.MsmqTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         queuePath="FormatName:Direct=OS:[SQLHOSTNAME]\private$\logging" formatter="Binary Log Message Formatter"
         timeToReachQueue="10.00:00:00" timeToBeReceived="10.00:00:00"
         recoverable="true" useDeadLetterQueue="true" traceOutputOptions="None" />
        <add name="Event Log Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         source="Enterprise Library Logging" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
         name="Text Formatter" />
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.BinaryLogFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         name="Binary Log Message Formatter" />
     </formatters>
     <categorySources>
       <add switchValue="All" name="Trace">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
       <add switchValue="All" name="Error">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents switchValue="All" name="All Events" />
       <notProcessed switchValue="All" name="Unprocessed Category" />
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Event Log Trace Listener" />
         </listeners>
       </errors>
     </specialSources>
   </loggingConfiguration>]]>
       </util:XmlConfig>
       <File Id="fileA8FAD71029831AC3655E19B9142ABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Common.dll" >
         <CopyFile Id="cfile1" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilea" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA6A5ADE82FC3926C6591208B146ABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Data.dll" >
         <CopyFile Id="cfile2" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfileb" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA87A152C5D90176A5F35A9ED31AABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Logging.Database.dll" >
         <CopyFile Id="cfile3" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilec" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA83ADD8942E77F370200E658EAEABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Logging.dll" >
         <CopyFile Id="cfile4" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfiled" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileAE5A8C078462AB1F516A8987676ABCD"  Source="..\..\bin\Microsoft.Practices.ServiceLocation.dll" >
         <CopyFile Id="cfile5" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilee" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA5CDA0533279F89ACC36D7BDCD6ABCD"  Source="..\..\bin\Microsoft.Practices.Unity.dll" >
         <CopyFile Id="cfile6" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilef" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileAC309FAF27707DCE67A3664FA1FABCD"  Source="..\..\bin\Microsoft.Practices.Unity.Interception.dll" >
         <CopyFile Id="cfile7" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfileg" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
     </Component>
   </Fragment>
   <Fragment>
     <DirectoryRef  Id="INSTALLLOCATION">
       <Directory Id="dirB50C76F5A59FB10334E162992F65AAAA" />
     </DirectoryRef>
   </Fragment>
 </Wix>
Property Definition Sample
 <Property Id="DYNAMICSCRMWEB" Value="C:\Program Files\Microsoft Dynamics CRM\CRMWeb">
   <RegistrySearch Id="Dynamicscrmweb_RegKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="WebSitePath" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICSCRM" >
   <RegistrySearch Id="Dynamics_RegKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="CRM_Server_InstallDir" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICS" >
   <RegistrySearch Id="DynamicsgKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="roles" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICSWEBBIN"/>
 
 <SetProperty Id="DYNAMICSWEBBIN" Value="[DYNAMICSCRMWEB]\bin" After="CostFinalize"/>
 
 <Property Id="DYNAMICSBIN"/>
 
 <SetProperty Id="DYNAMICSBIN" Value="[DYNAMICSCRM]\Server\bin" After="CostFinalize"/>
The next post of the series can be found here