views:

2202

answers:

5

The project I'm currently working on uses Enterprise Libraries V3.1 framework for logging.

I need to take the log file that's generated and archive it off at specific points. The built in Trace Listeners seem to keep the file open in-between logging events. I've set up a custom Trace Listener which will append to a file and close it, so that the file is always shiftable.

It looks like this (minus error handling for clarity):

[ConfigurationElementType(typeof(CustomTraceListenerData))]
public class AlwaysClosedTextFileTraceListener : CustomTraceListener
{
    private string logFilePath;

    public AlwaysClosedTextFileTraceListener ()
    {
        logFilePath = @"hardcodedpath\log.txt";
    }

    public override void Write(string message)
    {
        using (StreamWriter logFile = File.AppendText(logFilePath))
        {
            logFile.Write(message);
            logFile.Flush();
            logFile.Close();
        }
    }

    public override void WriteLine(string message)
    {
        using (StreamWriter logFile = File.AppendText(logFilePath))
        {
            logFile.WriteLine(message);
            logFile.Flush();
        }
    }

    public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
    {
        if (data is LogEntry && this.Formatter != null)
        {
            WriteLine(this.Formatter.Format(data as LogEntry));
        }
        else
        {
            WriteLine(data.ToString());
        }
    }
}

This works fine, but I'd much rather be passing in the path as a parameter somehow, rather than hardcoding it.

For fun, I tried adding it to the constructor, to see what happens:

    public LogFolderTraceListener(string logFilePath)
    {
        this.logFilePath = logFilePath;
    }

When I do this, I get returned an error message hinting towards what I'm doing wrong:

System.InvalidOperationException : The type 'AlwaysClosedTextFileTraceListener' specified for custom trace listener named 'MyLogFile' does not a default constructor, which is required when no InitData is specified in the configuration.

From here on in, my investigations have very much come to, the opposite of dead ends, infinite probability problems.

I have found this thumbing through the source code for the inbuilt RollingTraceListener

  • There is a class RollingFlatFileTraceListenerData : TraceListenerData which seems to contain all the settings passed into the constructor
  • Camped out at the bottom of the file for RollingFlatFileTraceListenerData is the class RollingTraceListenerAssembler : TraceListenerAsssembler which seems to be a factory
  • There is another class SystemDiagnosticsTraceListenerNode : TraceListenerNode which seems to make the Data class presentable to the configuration application

My question is this: how do I create a CustomTraceListener with a configurable parameter of path?

A: 

I suspect that perhaps the Enterprise Application Blocks although (probably) wonderful, seem unnecessarily complicated and ultimately more trouble than their worth for this kind of customisation.

Don Vince
That's not a helpful answer.
John Saunders
@John - you're absolutely right, I just added it because I suspected it to be the case and why there wasn't much coming in by the way of answers. It's starting to get a few more answers now so hopefully not :)
Don Vince
...but unfortunately it's a sentiment that rings true to me as well. I still use it cause I like the challenge.
Merritt
A: 

For what it is worth this is how I implemented it. In my this.buildCurrPath() I can read from a config file or in this case I just get the "launch pad" for the web app. But it works fine for me. I have not put it into any production code yet, but it should go out soon.

[ConfigurationElementType(typeof(CustomTraceListenerData))]
public class CustomListener: CustomTraceListener
{

 #region Fields (3) 
    private int logSize;
    StreamWriter sw;
 #endregion Fields 

 #region Constructors (1) 

    public CustomListener ():base()
    {
        string startPath = this.buildCurrPath();
        sw = new StreamWriter(startPath + "\\Logs\\test.log");
        sw.AutoFlush = true;

    }
Bob Cummings
A: 

the problem is typical microsoft .. (add your own adjectives here) ..

1) when you add a custom trace listener, the 'raw' app.config statement added is:

   name="Custom Trace Listener" initializeData="" formatter="Text Formatter" />

2) notice the 'initializeData' - this is what the cryptic error message is calling'InitData'.

3) So what its all saying is that you need to have a constructor that accepts initialization data - in vb parlance:

  sub new (byval initstuff as string)

4) OR remove the 'initializeData=""' and have a default constructor:

  sub new()

I suspect the P&P folks live in a bubble. riix.

riix
+1  A: 

The CustomTraceListener derives from TraceListener, this has a StringDictionary called Attributes.

This will contain all the attributes in the configuration line for your TraceListener and can be gotten out by name, eg.

string logFileName= Attributes["fileName"]
squig
A: 

I have just had the same issue (except with Enterprise Library v4.1).

The solution I've found is to remove the default constructor and the only have a constructor with a string parameter for the filename i.e.

public AlwaysClosedTextFileTraceListener (string pathParameter)   
{   
    logFilePath = pathParameter;   
} 

Then in the app.config put your path in the initializeData parameter

<add ... initializeData="C:\Logs\myLog.log" />

Whilst this isn't recognised by the Entriprise Library configuration editor and isn't as neat as it could be, it works as long as there is only one parameter.

If someone works out how to do it properly, please post and let us know - it's not supposed to be this difficult, surely.

Grhm