December 21, 2014

Weak References with Compiled Transforms

There is a good description of this generic WeakReference<> class here.
Here is an example of using WeakReferences for caching XslCompiledTransform's:
private static Dictionary<string, WeakReference<XslCompiledTransform>> xsltLookupTable = 
  new Dictionary<string, WeakReference<XslCompiledTransform>>();

public XslCompiledTransform GetCompiledTransform(string xslFileName)
{
    XslCompiledTransform xct = null;
    bool found = xsltLookupTable.ContainsKey(xslFileName);
    if (found) // IF the transform is already cached
    {   // Try and get it
        WeakReference<XslCompiledTransform> xctWr = xsltLookupTable[xslFileName] 
           as WeakReference<XslCompiledTransform>;
        xctWr.TryGetTarget(out xct); // Try and get it from the WeakReference
        m_logger.WriteTrace("Found XslCompiledTransform entry for \'" + 
           xslFileName + "\' in the cache");
        // Note the entry maybe null (if the weak reference expired)
    }
        
    if (xct == null) // IF the compiled transform was not already cached
    {
        // Create it
        xct = new XslCompiledTransform();
        xct.Load(xslFileName, 
          new XsltSettings { EnableDocumentFunction = true }, 
          new XmlUrlResolver());
        // Insert it into a WeakReference
        WeakReference<XslCompiledTransform> wr = new 
          WeakReference<XslCompiledTransform>(xct);
        if (found)
        {
          m_logger.WriteTrace("Removing XslCompiledTransform entry for \'" + 
              xslFileName + "\' as it was null in the cache");
          xsltLookupTable.Remove(xslFileName);
        }
        xsltLookupTable.Add(xslFileName, wr); // Add the WeakReference to the cache
        m_logger.WriteTrace("Adding XslCompiledTransform entry for \'" + 
          xslFileName + "\'");
    }
    return xct;
}
It uses the new generic WeakReference<> class (available in .NET 4.5?). This class could be further refactored into a generic caching class if it was required.

December 12, 2014

Configuring log4net for specific classes or namespaces

Here is some sample xml that goes in the application config file.
<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
    ...
  </configSections>
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
      <mapping>
        <level value="ERROR" />
        <foreColor value="Red, HighIntensity" />
      </mapping>
      <mapping>
        <level value="WARN" />
        <foreColor value="Yellow" />
      </mapping>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%utcdate [%t] %-5p [] - %m%n"/>
      </layout>
    </appender>
    <appender name="OutputDebugStringAppender" type="log4net.Appender.OutputDebugStringAppender">
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%utcdate [%t] %-5p %c [] - %m%n"/>
      </layout>
    </appender>
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
      <file value="FilePath.txt" />
      <appendToFile value="true" />
      <maximumFileSize value="1000KB" />
      <maxSizeRollBackups value="20" />
      <param name="RollingStyle" value="Size" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%utcdate [%t] %-5p %c [] - %m%n" />
      </layout>
    </appender>
    <root>
      <level value="INFO"/>
      <appender-ref ref="RollingFile"/>
      <appender-ref ref="ConsoleAppender"/>
      <appender-ref ref="OutputDebugStringAppender"/>
    </root>
    <!--Level values are DEBUG, INFO, WARN, ERROR -->
    <!--Entries for "top level" server module classes, to allow tracing of method calls -->
    <logger name="Some.Name.Space.ClassA.">
      <level value="INFO"/>
    </logger>
    ...
    <!--Entries for namespaces, to allow full tracing inside modules -->
    <logger name="Some.Particular.Namespace">
      <level value="INFO"/>
    </logger>
    ...
    <!--Entries for certain individual classes -->
    <logger name="Some.Deep.Level.NameSpace.SpecificClass.">
      <level value="DEBUG"/>
    </logger>
    ...
  </log4net>
...  
</configuration>

December 5, 2014

Simple Logger using Caller Info Attributes

Since .NET 4.5 There are 3 caller info attributes that are filled in at compile time by the compiler CallerMemberName, CallerFilePath, and CallerLineNumber These strings are inserted at compile time so they are much faster than using reflection.

Here is an example of how to use it to make the simplest logger (only 1 method + 1 property):
using System.Runtime.CompilerServices;

public enum LoggingLevelEnum
{
    Debug = 1,
    Info = 2,
    Warning = 3,
    Error = 4,
    Fatal = 5
}

public interface ISimpleLogger
{
    LoggingLevelEnum LoggingLevel { get; set; }

    void Log(
        Func<string> message,
        LoggingLevelEnum level = LoggingLevelEnum.Debug,
        [CallerMemberName] string member = "",
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = -1)
}

public class SimpleLogger : ISimpleLogger
{
    public LoggingLevelEnum LoggingLevel { get; set; } = 
        LoggingLevelEnum.Debug;

    public void Log(
        Func<string> message,
        LoggingLevelEnum level = LoggingLevelEnum.Debug,
        [CallerMemberName] string member = "",
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = -1)
    {
        if (level >= LoggingLevel)
        {
            Trace.WriteLine($"{level.ToString().ToUpper()} Member: {member}, File: {file}, Line: {line} - {message()}");
        }
    }
}
and invoking it:
ISimpleLogger logger = new SimpleLogger();
...
catch(Exception ex)
{
  // The attributed parameters are inserted by the compiler at compile time
  logger.Log(() => { return " Exception caught " + ex.ToString(); }, LogLevelEnum.Error); 
}

Note: Why log using a function returning a string? So that if you have a complex expression for creating the message, it only gets evaluated if the logging level is sufficient. If there is a lot of logging and the logging level is high, it saves a lot of unecessary string concatenation occurring.