Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

add/remove TraceListener to all TraceSources

I am looking for a way to add and remove a TraceListener for all existing TraceSources.

(I am not sure my approach is correct here, what other ways could I use? Basically I want to log all trace output into a file that uses the current project name as filename. Whenever a user creates or reopens a project, I want to append logs to the correct file. There can only be one project open at a time.)

Code example:

I create several TraceSources in my application, one for each class

public class Class1
{
    private static readonly System.Diagnostics.TraceSource trace = 
            new System.Diagnostics.TraceSource("Class1");
}

public class Class2
{
    private static readonly System.Diagnostics.TraceSource trace = 
            new System.Diagnostics.TraceSource("Class2");
}

I now want to add or remove a traceListener to all my traceSources at Runtime, like this:

private System.Diagnostics.TextWriterTraceListener myListener;

private onProjectOpen()
{
    // user created a new project or opened an existing one
    myListener = new System.Diagnostics.TextWriterTraceListener("log-"+projectname+".log");
    ALL_TRACESOURCES.Add ( myListener) ; // <-- how to do this?
}

private onProjectClose()
{
    // user closed a project
    ALL_TRACESOURCES.Remove( myListener) ; // <-- how to do this?
    myListener.Flush();
    myListener.Close();
    myListener.Dispose(); // <-- not sure if all this is neccessary
}

So far I found no way to do this without making all my traceSources public (seems like a bad idea) and then listing all my classes like this:

Class1.Trace.Add( myListener );
Class2.Trace.Add( myListener );
...

which seems like a bad design choice on several levels.

Or

Add all my TraceSources to a custom global collection in the constructor of each class (Easy to forget / mess up; and global variables are bad )

Is there a better way? Basically I am looking for a way to set another default listener

like image 561
HugoRune Avatar asked May 14 '12 10:05

HugoRune


3 Answers

Based on this StackOverflow answer and this answer

Here is one way to do it:

    private static void AttachToAllTraceSources(TraceListener yourListener)
    {
        TraceSource ts = new TraceSource("foo");
        List<WeakReference> list = (List<WeakReference>)GetInstanceField(typeof(TraceSource), ts, "tracesources");
        foreach(var weakReference in list)
        {
            if(weakReference.IsAlive)
            {
                TraceSource source = (weakReference.Target as TraceSource);
                if(source != null && source.Name != "foo")
                {
                    source.Listeners.Add(yourListener);
                }
            }
        }
    }
like image 116
jeoffman Avatar answered Nov 05 '22 01:11

jeoffman


In .NET 4 and above you could use Lazy<> to lazy-load your TraceSources after listeners have been configured. See the following working example program:

public static class TraceSources
{
    public static TraceSource Create(string sourceName)
    {
        var source = new TraceSource(sourceName);
        source.Listeners.AddRange(Trace.Listeners);
        source.Switch.Level = SourceLevels.All;
        return source;
    }
}

public class Class1
{
    private static readonly Lazy<TraceSource> trace = new 
            Lazy<TraceSource>(() => TraceSources.Create("Class1"));

    public void DoSomething()
    {
        trace.Value.TraceEvent(TraceEventType.Information, 1, "Class1 speaking up");
    }
}

public class Class2
{
    private static readonly Lazy<TraceSource> trace = new
            Lazy<TraceSource>(() => TraceSources.Create("Class2"));

    public void DoSomethingElse()
    {
        trace.Value.TraceEvent(TraceEventType.Information, 2, "Class2 speaking out");
    }
}

public class Program
{
    static void Main(string[] args)
    {
        try
        {
            var listener = new TextWriterTraceListener(@"C:\trace.txt");
            Trace.Listeners.Add(listener);

            var classOne = new Class1();
            var classTwo = new Class2();
            classOne.DoSomething();
            classTwo.DoSomethingElse();
        }
        finally
        {
            Trace.Close();
        }
    }
}
like image 45
decates Avatar answered Nov 05 '22 01:11

decates


OK, I'm incredibly late to this party -- hard to believe that seven years later, someone is still wrestling with the task of getting all trace sources in the system (and not finding a good answer anywhere).

Here is a reflective method to find all trace sources:

    /// <summary>
    /// Get all trace sources instantiated by the current process.
    /// </summary>
    /// <remarks>
    /// This is  reaching into a part of the .Net code that Microsoft haven't given public access to, that's not part of their API.
    /// Found from inspection of the .Net (4.8) reference source that the TraceSource class holds a static cache of all trace sources, and the names of the corresponding members.
    /// </remarks>
    /// <returns>List of all current trace sources</returns>
    public static List<TraceSource> GetAll()
    {
        var result = new List<TraceSource>(); 
        var privateStaticMethods = typeof(TraceSource).GetMethods(BindingFlags.Static | BindingFlags.NonPublic);
        var pruneMethod = privateStaticMethods.FirstOrDefault(m => m.Name == "_pruneCachedTraceSources");
        var privateStaticFields = typeof(TraceSource).GetFields(BindingFlags.Static | BindingFlags.NonPublic);
        var tracesourcesField = privateStaticFields.FirstOrDefault(f => f.Name == "tracesources");
        if (tracesourcesField != null)
        {
            var tracesourceValue = tracesourcesField.GetValue(null);
            var tracesources = tracesourceValue as List<WeakReference>;
            if (tracesources != null)
            {
                lock (tracesources)
                {
                    if (pruneMethod != null)
                    {
                        pruneMethod.Invoke(null, new object[] { });
                    }
                    for (int i = 0; i < tracesources.Count; i++)
                    {
                        var target = tracesources[i].Target;
                        TraceSource tracesource = target as TraceSource;
                        if (tracesource != null)
                        {
                            result.Add(tracesource);
                        }
                    }
                }
            }
        }
        return result;
    }

Currently using .Net Framework 4.8, where it works; I haven't tried this with 5. Maybe it is of note that if you manage an own list of all trace sources, you are doubling up.

Use this to get all trace sources. Maybe filter for the ones you want. Then add your trace listener to all of them.

like image 1
J S Avatar answered Nov 04 '22 23:11

J S