Enterprise Library Logging tracelistener extension issue with resolving ILogFormatter

Topics: Logging Application Block
Jun 17, 2011 at 9:13 AM

I have been sitting with a problem for quite a while now and I just can't seem to find what I'm missing. I have written a custom trace listener component for Enterprise Library 5.0 for the Logging application block which works but the configured ILogFormatter just won't resolve and so I always end up with the basic string text when it gets handled by my component. I saw in the enterprise library source code that they use the "Container.ResolvedIfNotNull()" method. It doesn't seem to work for me. I need it to write out a custom formatted string for my component to use. You know, not just the message but the timestamp, machinename, threadId, etc.

I wrote this in a new custom assembly, not as part of the Entlib source code.

Does anyone have any ideas on how to fix this?

Thanks in advance.

Jun 17, 2011 at 9:56 AM

Hi,

Can you please be more specific with your issue? Have you encountered exception when resolving? Is the out of the box Text Formatter not sufficient since it has also timestamp, machinename and threadId included?

 

Noel Angelo Bolasoc
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jun 17, 2011 at 10:01 AM
Edited Jun 17, 2011 at 10:33 AM

Hi

That's the thing, I don't get any exceptions, and the Log Formatter isn't resolving at all although its configured (I want to use the configured one, not my own one). It just takes the message being logged and when I try to use the Log Formatter it is resolved by the container as 'null' and so I can only write out the message string.

I hope I'm clear enough.

In the Entlib source the EmailTraceListenerData has the GetCreationExpression() method.

protected override Expression<Func<TraceListener>> GetCreationExpression()
        {
            return () =>
                    new EmailTraceListener(
                        this.ToAddress,
                        this.FromAddress,
                        this.SubjectLineStarter,
                        this.SubjectLineEnder,
                        this.SmtpServer,
                        this.SmtpPort,
                        Container.ResolvedIfNotNull<ILogFormatter>(this.Formatter),
                        this.AuthenticationMode,
                        this.UserName,
                        this.Password,
                        this.UseSSL);
        }

In my case the ResolvedIfNotNull comes back with null.
Jun 17, 2011 at 11:43 AM

Ok, to clarify, you are extending the Email Trace Listener is this correct? And you are using the default Text Formatter? I tried to step through the code of Email Trace Listner and unfortunately, I can't repro your issue. Can you post your config here and your custom trace listener?  

 

Noel Angelo Bolasoc
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jun 17, 2011 at 11:57 AM

You are indeed correct, I wrote another version of the Email Trace Listener.

It is very much the same as the entlib version except for some differences in how entries are logged.

I'll try to post some code without bloating this discussion.

Here is my Formatter property for the Data class:

/// <summary>
/// Log formatter to use
/// </summary>
[ConfigurationProperty(FORMATTER_NAME, IsRequired = false)]
[Reference(typeof(NameTypeConfigurationElementCollection<FormatterDataCustomFormatterData>), typeof(FormatterData))]
public string Formatter
{
get { return (string)base[FORMATTER_NAME]; }
    set { base[FORMATTER_NAME] = value; }
}

The constructor (one and only):

public BatchedEmailTraceListenerData()
            : base(typeof(BatchedEmailTraceListener))
        {
            ListenerDataType = typeof(BatchedEmailTraceListenerData);
        }


The creation expression:

protected override Expression<Func<TraceListener>> GetCreationExpression()
        {
            return () =>
                   new BatchedEmailTraceListener(
                       new BatchedEmailConfiguration
                           {
                               AllowDuplicates = this.AllowDuplicates,
                               AuthenticationMode = this.AuthenticationMode,
                               BatchSize = this.BatchSize,
                               FlushInterval = this.FlushInterval,
                               Formatter = Container.ResolvedIfNotNull<ILogFormatter>(this.Formatter),
                               FromAddress = this.FromAddress,
                               MailSubject = this.MailSubject,
                               Password = this.Password,
                               SmtpPort = this.SmtpPort,
                               SmtpServer = this.SmtpServer,
                               ToAddress = this.ToAddress,
                               Username = this.UserName,
                               UseSSL = this.UseSSL
                           });
        }

My actual trace listener class also has only 1 constructor.
Overrides the:
void Write(string message)
void WriteLine(string message)
void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)

When I populate the mail body I use these lines:
message.AppendLine(this.Formatter != null
                                  ? this.Formatter.Format(logEntry)
                                  : logEntry.Message);

The app.config is straight forward. Its almost exactly the same as the original email trace listener except for a few custom attributes and the new assembly paths.

<listeners>
<
add name="Email Trace Listener"
                 type="CustomExtensions.BatchedEmailTraceListener, CustomExtensions" 
                 listenerDataType="CustomExtensions.BatchedEmailTraceListenerData, CustomExtensions" 
                 toAddress="NOTIFICATION_ADDRESS" 
                 fromAddress="NOTIFICATION_SENDER_ADDRESS"
                 mailSubject="Errors" 
                 smtpServer="SMTP_ADDRESS" 
                 smtpPort="25" 
                 formatter="Email Text Formatter" 
                 traceOutputOptions="None"
                 allowDuplicates="false"
                 flushInterval="15"
                 batchSize="10"/>
</
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} {severity} {machine} Thread: {win32ThreadId} {message} Duplicate Count: {keyvalue(DuplicateCount)}"
                 name="Email Text Formatter"/>
</formatters>

I really hope this would be enough for you to see.
Jun 17, 2011 at 12:24 PM

Ok I think I've solved it.

It wasn't easy to spot and understand but I went through the entlib source code and I saw that some fancy Lambda magic was done on those creation expressions (which I still don't understand).

So instead of having like I had above with the "GetCreationExpression()" method, I now did the following:

 

protected override Expression<Func<TraceListener>> GetCreationExpression()
        { 
            return () =>
                   new BatchedEmailTraceListener(this.MailSubject,
                       this.AuthenticationMode,
                       Container.ResolvedIfNotNull<ILogFormatter>(this.Formatter),
                       this.UserName,
                       this.Password, this.SmtpServer, this.SmtpPort, this.ToAddress, this.FromAddress, this.BatchSize, this.FlushInterval, this.UseSSL, this.AllowDuplicates);
        }

 

Instead of initializing the "BatchedEmailConfiguration" class first and passing that on, I'm initializing the "BatchedEmailTraceListener" directly and that level reduction seemed to have made it to work.

Maybe this is something to keep in mind or something for documentation purposes? (unless it's already there and I haven't read it)

Thanks for your help anyway.

Jun 17, 2011 at 12:49 PM

I just noticed that you already posted the right implementation on your earliest posts :). Honestly, I also didn't noticed the difference at first glance. Anyways, thanks also for sharing this out :)

 

Noel Angelo Bolasoc
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jun 17, 2011 at 12:50 PM
Edited Jun 17, 2011 at 12:51 PM

That was the entlib implementation. However, I'll try to remember in the future not to deviate from "standards". :-D

Jul 25, 2012 at 8:56 PM

Is it possible to write the following lamba statement in vb.net?

return () => new obect();

I tried:

return function() new object()

which does create a lamba expression tree but does not appear to be the correct NodeType.

I receive error "the creation expression for this type registration must be a Linq New or MemberInit Expression' in the Enterprise Library function 'GetRegistrations' when i step out of the GetCreationExpression function.

Jul 26, 2012 at 6:50 AM

I'm not sure I understand what you are trying to do.  Are you creating a trace listener?  Can you elaborate?

For example this C# code:

        protected override Expression<Func<TraceListener>> GetCreationExpression()
        {
            return () =>
                new FlatFileTraceListener(
                    this.FileName,
                    this.Header,
                    this.Footer,
                    Container.ResolvedIfNotNull<ILogFormatter>(this.Formatter));
        }

Looks like this in VB.NET:  

Protected Overrides Function GetCreationExpression() As Expression(Of Func(Of TraceListener))
	Return Function() New FlatFileTraceListener(Me.FileName, Me.Header, Me.Footer, Container.ResolvedIfNotNull(Of ILogFormatter)(Me.Formatter))
End Function

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jul 26, 2012 at 3:50 PM
    Thankyou for the VB.Net example.  I am actually updating my existing trace listener (written in vb and extending older version of the EL) to use Enterprise Library 5.0.
    I have a working c# example of an extended trace listener but cannot get the function GetCreationExpression() to work properly in VB.
    One thing that led me to believe my lambda syntax was incorrect was the following test I tried (see comment and error message below).
    The cast to NewExpression does work when applied to the C# example.  
    Protected Overrides Function GetCreationExpression() As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener))

        Dim expr As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener)) = Function() New ExtendedFormattedDatabaseTraceListener(Container.Resolved(Of Microsoft.Practices.EnterpriseLibrary.Data.Database)("PHRSDataAccess"), "writestoredproc", "addCategoryStoredProc", Container.ResolvedIfNotNull(Of Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.ILogFormatter)("Default Formatter"))

        'Test to see if this expression can be converted to new expression as is done in EL 5.0 function 'protected TypeRegistration GetTraceListenerTypeRegistration()'
        'Throws error, "Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.NewExpression'.” when casting my expr.Body to NewExpression"
        Dim creationExpression As System.Linq.Expressions.Expression = expr.Body
        Dim expression As System.Linq.Expressions.NewExpression = CType(creationExpression, System.Linq.Expressions.NewExpression)

        Return expr
    End Function
Jul 27, 2012 at 7:23 AM

It looks like VB.NET creates a different lambda expression than C# in this case.  I think the code that does what you want is:

    Public Function GetCreationExpression() As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener))

        Dim expr As Expression(Of Func(Of TraceListener)) = _
            Function() New ExtendedFormattedDatabaseTraceListener( _
                Container.Resolved(Of Database)("PHRSDataAccess"), _
                "writestoredproc", _
                "addCategoryStoredProc", _
                Container.ResolvedIfNotNull(Of ILogFormatter)("Default Formatter"))

        Dim creationExpression As System.Linq.Expressions.Expression

        If (expr.Body.GetType() = GetType(UnaryExpression)) Then
            creationExpression = CType(CType(expr.Body, UnaryExpression).Operand, Expression)
        Else
            ' Not actually called in this example but this is the C# version
            ' Useful if this method accepted an Expression from VB.NET or C# callers
            creationExpression = CType(expr.Body, Expression)
        End If

        'Test to see if this expression can be converted to new expression as is done in EL 5.0 function 'protected TypeRegistration GetTraceListenerTypeRegistration()'
        'Throws error, "Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.NewExpression'.� when casting my expr.Body to NewExpression"
        Dim expression As System.Linq.Expressions.NewExpression = CType(creationExpression, NewExpression)

        Return expr
    End Function

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jul 27, 2012 at 5:53 PM

I had to add one more step, that was to take the new expression created (creationExpression) and put it back into a lambda tree expression. Thanks for the quick solution!  Here is the complete working function below.

    Protected Overrides Function GetCreationExpression() As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener))

        Dim expr As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener)) = Function() New ExtendedFormattedDatabaseTraceListener(Container.Resolved(Of Microsoft.Practices.EnterpriseLibrary.Data.Database)("PHRSDataAccess"), "writestoredproc", "addCategoryStoredProc", Container.ResolvedIfNotNull(Of Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.ILogFormatter)("Default Formatter"))

        Dim creationExpression As System.Linq.Expressions.Expression
   
        'Convert the UnaryExpression type to Expression type
        If (expr.Body.GetType() = GetType(UnaryExpression)) Then
            creationExpression = CType(CType(expr.Body, UnaryExpression).Operand, Expression)
        Else
            ' Not actually called in this example but this is the C# version
            ' Useful if this method accepted an Expression from VB.NET or C# callers
            creationExpression = CType(expr.Body, Expression)
        End If

        Dim lamdaExpressionTree As System.Linq.Expressions.Expression(Of System.Func(Of System.Diagnostics.TraceListener)) = System.Linq.Expressions.Expression.Lambda(Of System.Func(Of System.Diagnostics.TraceListener))(creationExpression)

        Return lamdaExpressionTree
    End Function