add/remove TraceListener to all TraceSources
Asked Answered
F

4

17

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

Fina answered 14/5, 2012 at 10:20 Comment(1)
can you award an answer?Stound
A
4

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);
                }
            }
        }
    }
Affaire answered 16/7, 2015 at 19:21 Comment(1)
This only gets TraceSources that have been instantiated. If there was a way to make this an observable collection, then we would be cooking with gas.Stound
V
1

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();
        }
    }
}
Vere answered 22/3, 2013 at 17:41 Comment(1)
Or without 4.0 or just if you feel like it, you can just use a generic lazy-create pattern as in "if(null == _source) _source = new TraceSource...return _source)"... The Listeners.AddRange trick works great and I will be copying this everywhere. :)Villosity
S
1

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.

Socratic answered 29/5, 2021 at 5:39 Comment(0)
R
0

I've just run into this issue myself. In my case I also have trace sources created in each class. The listeners that are in app.config get added to all sources no problem, but there is a particular listener I need to add at runtime. This of course only works with the trace source the listener is added to. I see two options - create a single trace source and pass it around to the rest of the app (yuck), or have a single static trace source in a helper class that everything else references. So I have:

public class LogHelper { /// /// The trace source logger for the application. /// public static readonly TraceSource Logger = new TraceSource("FruityBlergs", SourceLevels.All); }

The LogHelper class also sets up the listener and various other filters that get configured at runtime. This has worked well for me so far. If for some reason a particular class needs different behavior you can still create a trace source in that class.

Retiarius answered 3/9, 2014 at 1:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.