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
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);
}
}
}
}
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();
}
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With