In the examples I can find the tracing is enabled via config file, for example
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.diagnostics>
<sources>
<source name="System.Net" tracemode="includehex" maxdatasize="1024">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
</switches>
<sharedListeners>
<add name="System.Net"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="network.log"
/>
</sharedListeners>
<trace autoflush="true" indentsize="4" />
</system.diagnostics>
</configuration>
But I don't want config file to be shipped with my dll. Moreover I need to be able to enable/disable tracing and change log name "on the fly" in my code.
What I came up with:
FileStream stream = new FileStream("D:\\network1.log", FileMode.OpenOrCreate);
TextWriterTraceListener listener = new TextWriterTraceListener(stream);
Trace.Listeners.Add(listener);
Trace.AutoFlush = true;
TraceSwitch ts = new TraceSwitch("System.Net", ".Net");
ts.Level = TraceLevel.Verbose;
and it is not logging anything, and I don't know where to add the switch, and if that is correct at all.
The listener works but it don't fetch any data.
I've been reading msdn and this blog post http://www.codeguru.com/csharp/.net/article.php/c19405/Tracing-in-NET-and-Implementing-Your-Own-Trace-Listeners.htm and from the way I understand it, once added to the Trace.Listeners a listener should log all thace info in the current exe, but that's obvioustly not the case.
So how to achieve that?
Edit: I've managed to hack around this limitation by dissasembling the TextWriterTraceListener and implementing my own TraceListener with the dissasembled code, adding if staatements in Write and WriteLine methods that check some static fields of another class, and adding my type in the config.
You can't enable the tracing of System.Net from code, at least not in a supported way.
First let me point out that you mixed up a TraceSwitch and a TraceSource. The TraceSource is the one that gets used to trace messages to. The TraceSwitch only controls what gets logged.
When you instantiate a TraceSource it initializes itself by checking the <sources> collection of the system.diagnostics setting for a <source> with the same name. When there is one, the instance is build-up with the settings provided. Your program will then use that specific TraceSource instance to log its data by calling TraceEvent or any of the other methods.
If there is no config found, the TraceSource is configured with the DefaultTraceListener with the SwitchLevel set to Off.
What I described above is also done by the internal System.Net.Logging class in its InitializeLogging method. It creates its own instance of a TraceSource for System.Net that once created, either is configured as defined in the config file or with the Defaults. All the logging that is done by the classes in the System.Net namespace use that private/internal TraceSource.
There is no public interface that allows you to insert or intercept TraceSource construction so you can't control which listener to connect or set Switch levels on a shared/singleton like TraceSource. Nor is there an option to get to the internal System.Diagnostics.DiagnosticsConfiguration class instance.
I did venture a bit in System.Configuration.Internal but I gave-up on that.
If you do fancy reflection you can grab the internal static type System.Net.Logging and get a reference to the TraceSource from the static property Web. Once you have the reference, I imagine you can set the listeners and switchlevel. That is an internal implementation detail and might change without notice so I don't recommend this.
To get control over logging, create overloads for the classes in System.Net and add your own TraceSource instance and log info to that instance.
Related
I've written a library that gets called by several applications all residing in the same folder. Currently it's using log4net with the log4net.config file for event logging which works well. But log4net is now a dormant project with no foreseeable updates, and it appears that EventLogTraceListener can accomplish much the same thing without relying on a 3rd party library. However, I'm not sure there's a way to use an app.config file (out of the box) with a DLL. For example, the app.config file below works in a console app (MyApp.exe.config) but won't work when only using my DLL (MyLibrary.dll.config):
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<system.diagnostics>
<trace autoflush="true" indentsize="0">
<listeners>
<add name="MyListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="MyListener.log"/>
<add name="MyEventListener" type="System.Diagnostics.EventLogTraceListener" initializeData="MyLog"/>
</listeners>
</trace>
<switches>
<add name="MySwitch" value="Verbose"/>
</switches>
</system.diagnostics>
</configuration>
From doing some research it appears that config files for DLLs aren't really viable since the calling app's config file is normally used. But I don't want to have to edit the config file of multiple applications to change logging level, etc. I realize I could roll my own but it's nice that .NET has all this functionality built-in. Just need to know if there's a way to make it work using a DLL.config file?
var log = Trace.Listeners.OfType<EventLogTraceListener>().First(); // Works for an app but not a DLL (no listener)
log.TraceEvent(new TraceEventCache(), "Delme1", TraceEventType.Verbose, 123, "Testing");
UPDATE #1
Based on the answers here it doesn't look like this is supported by design. There also doesn't appear to be any user available objects for manipulating the systems.diagnostic entries. I think I'm going to just create my own XML file for storing the info I need.
UPDATE #2
Per Microsoft's recommendation, I switched to using the TraceSource class and updated my config file to match. I found a way to dynamically load the file from a DLL but that doesn't appear to update the TraceSource class settings, even after issuing a Trace.Refresh(). Everything works when I create a config file with the same settings at the application level, but that's not what I want. Is there any way to load a config file as shown below and also have its settings update the TraceSource class with Listeners, etc.? I find it curious that SystemDiagnosticsSection is marked internal and not accessible to user apps. Seems Microsoft has made this nearly(?) impossible (but I'm so close!)
Config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<sources>
<source name="MyExceptionLibrary" switchName="MySwitch" switchType="System.Diagnostics.SourceSwitch">
<listeners>
<remove name="Default"/>
<add name="MyListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Logger.log">
<filter type="System.Diagnostics.EventTypeFilter" initializeData="Off"/>
</add>
<add name="MyEventListener" type="System.Diagnostics.EventLogTraceListener" initializeData="Blah">
<filter type="System.Diagnostics.EventTypeFilter" initializeData="Verbose"/>
</add>
</listeners>
</source>
</sources>
<switches>
<add name="MySwitch" value="Verbose"/>
</switches>
</system.diagnostics>
</configuration>
Code:
public static class Logger
{
private static readonly TraceSource s_traceSource = new TraceSource("MyExceptionLibrary");
static Logger()
{
var config = ConfigurationManager.OpenExeConfiguration("MyExceptionLibrary.dll"); // Confirmed config loaded
ConfigurationManager.RefreshSection("system.diagnostics"); // Doesn't seem to make any difference
Debug.WriteLine($"Config file: {config.FilePath}"); // Points to new config file
var section = config.GetSection("system.diagnostics"); // Contains data from file
Debug.Write(section.SectionInformation.Type); // SystemDiagnosticSection
Trace.Refresh(); // Doesn't seem to make any difference
Debug.WriteLine(s_traceSource.Listeners[0].Name); // Still shows Default
s_traceSource.TraceEvent(TraceEventType.Verbose, 123, "Testing"); // No event log entry is created
}
}
I've not worked with the System.Diagnostics.TraceSource package before so I want to make sure I implement it correctly. In the past I've used NLog (before that log4net) where you configure the settings in a config file (or programmatically) and in each class you declare this:
private static Logger Logger = LogManager.GetCurrentClassLogger();
then statements like this as required:
Logger.Error("some message", exc);
But with TraceSource you create a new TraceSource, set its properties and assign your listener(s) and then write your messages to it.
Do I create a TraceSource instance for every class like NLog? Or do I have one per application that all classes use? And if the former, it seems like at least I should centralize the creation of the TraceSource into a factory or something so the code is simplified and I'm not having to setup the settings, listeners etc every time I need use it. What's the proper usage here?
Note: due to the nature of how this code will be deployed, I cannot have a config file, I have to do it all programmatically.
just use
class DracBlahBlah
{
private static readonly TraceSource = new TraceSource("deathtoexecutives", SourceLevel.Error);
}
and in the other classes
class werewolf
{
private static readonly TraceSource = new TraceSource("deathtoexecutives", SourceLevel.Error);
}
then assign the listeners using config
<system.diagnostics>
<sharedListeners>
<add name="p0rn" type="p0rn.Tracelistener, super.bad.crazytown" />
</sharedListeners>
<sources>
<source name="deathtoexecutives" switchValue="Verbose">
<listeners>
<add name="p0rn" />
</listeners>
</source>
I'm trying to debug what I believe is a WPF binding issue that is only happening on one machine in production -- I cannot repro on a developer machine. In order to do this, I've been trying to get the binding trace information to output to a log file. Following answers like this one, I've been able to get it to output to a hard-coded location by configuring it in App.config:
<system.diagnostics>
<sources>
<source name="System.Windows.Data" switchName="SourceSwitch" >
<listeners>
<add name="textListener" />
</listeners>
</source>
</sources>
<switches>
<add name="SourceSwitch" value="All" />
</switches>
<sharedListeners>
<add name="textListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="c:\BindingErrors.log" />
</sharedListeners>
<trace autoflush="true" indentsize="4"/>
</system.diagnostics>
This works fine on my machine where I have administrative rights to the c:\ drive. The problem is I want to write the log somewhere the user has rights to, e.g. their TEMP folder. So I want to do something like this, using the %TEMP% environmental variable:
initializeData="%TEMP%\BindingErrors.log"
This isn't working, though, and I guess it won't work -- see this answer; so, following the advice in that answer, I've attempted to configure the output via code instead of App.config. Here's what I've tried so far:
var listener = new
TextWriterTraceListener(Environment.ExpandEnvironmentVariables(
#"%TEMP%\BindingErrors.log"), "myListener");
Trace.Listeners.Add(listener);
Trace.WriteLine("foo"); // just to see if it works at all.
Trace.Flush();
But this only writes foo to the log file in the %TEMP% folder. It doesn't write the binding errors. I've tried to replicate what the App.config had, but there's no Sources collection, so when I instantiate a TraceSource, like this:
var source = new TraceSource("mySource", SourceLevels.Information);
I don't know what to do with it, and there's no Listeners collection to which I can add my listener instance.
MSDN doesn't seem to bring it all together for me, or I'm missing some critical details. Can someone please help me figure out what I'm doing wrong?
…there's no Listeners collection to which I can add my listener instance.
Actually, there is: PresentationTraceSources.DataBindingSource.Listeners
The property returns the TraceSource object that is used when data binding messages are output. You can add any listener to that source, such as a TextWriterTraceListener you've created in code-behind by expanding the %TEMP% environment variable and using that as the directory for your output file.
Note that the programmatic approach requires recompiling to change the output location, or the addition of some other configuration value that can be read at run-time. A different technique allows you to specify the entire configuration in the app.config file, by implementing a custom TraceListener that knows how to expand environment variables.
For example:
namespace TestSO39836570TraceListenerBindingErrors
{
class EnvironmentAwareTextWriterTraceListener : TextWriterTraceListener
{
public EnvironmentAwareTextWriterTraceListener(string path)
: base(Environment.ExpandEnvironmentVariables(path))
{ }
public EnvironmentAwareTextWriterTraceListener(string path, string name)
: base(Environment.ExpandEnvironmentVariables(path), name)
{ }
}
}
Then in the app.config file, you can specify the listener:
<system.diagnostics>
<sources>
<source name="System.Windows.Data" switchName="SourceSwitch">
<listeners>
<add name="textListener"/>
</listeners>
</source>
</sources>
<switches>
<add name="SourceSwitch" value="All"/>
</switches>
<sharedListeners>
<add name="textListener"
type="TestSO39836570TraceListenerBindingErrors.EnvironmentAwareTextWriterTraceListener, TestSO39836570TraceListenerBindingErrors"
initializeData="%temp%\BindingErrors.log"/>
</sharedListeners>
<trace autoflush="true" indentsize="4"/>
</system.diagnostics>
Note that when specifying a custom TraceListener type that is found in your own program assembly, you need to specify the assembly name in the type attribute, by following the fully-qualified type name with a comma and then the assembly name (in the example above, the type's namespace is identical to the assembly name, per the defaults for a project created in Visual Studio).
You may of course opt for shorter namespace and type names than the ones I've used here. :)
I have my Logging set up to use a different TraceSource for each Class.
Is it possible to Configure a wildcard that writes events for all Sources?
<system.diagnostics>
<sources>
<source name ="wildcard" switchValue="Warning">
<listeners>
<add name="textlog" />
</listeners>
</source>
<source name="MySpecificClass" switchValue="All">
<listeners>
<add name="textlog" />
</listeners>
</source>
</sources>
<sharedListeners>
<add name="textlog"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="Log.log">
</add>
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
I'm not aware of a built in way to do that automatically. However, if you have a look at the TraceLogger in Castle's git repository, you can see that they have essentially wrapped and extended TraceSource to support "hierarichal" naming.
https://github.com/castleproject/Core/blob/master/src/Castle.Core/Core/Logging/TraceLogger.cs
I would copy the code here, but it might not be proper to just cut and paste there code into SO.
I can explain how the ideas presented in the class could work for your (without you having to use Castle)
In essence, in your client code (that wants to log stuff), you would create an instance of your "logger" (rather than TraceSource). As input to the logger, you would give, for example, the fully qualified class name. Inside the constructor, use the input name to try to resolve a TraceSource. If there is a TraceSource configured with that name, use that TraceSource to do the work. If not, trim off the rightmost part of the fully qualified name. Try to resolve a TraceSource with that name. If there is a TraceSource configured with that name, use it. And so on. If you don't find any TraceSources, then don't log anything from your "logger". You could add on the ability to recognize a TraceSource that has been configured with a wildcard name (""). If you never find a TraceSource using the name trimming technique and if there is a "" TraceSource, use the "*" TraceSource as the fallback.
So, you might have something like this:
class MyTraceSource
{
private TraceSource ts;
public MyTraceSource(string name)
{
ResolveTraceSource(name);
}
private void ResolveTraceSource(string name)
{
//Check for a configured TraceSource from most qualified name (as input) to least qualified ("").
//Assume name like this: Namespace1:Namespace2:Class
//Try to resolve:
// TraceSource("Namespace1.Namespace2.Class");
// TraceSource("Namespace1.Namespace2");
// TraceSource("Namespace1");
//If you still haven't found one, try to resolve
// TraceSource("*");
}
//Implement either TraceSource API, or whatever API you prefer for logging.
}
I have actually done something like this myself as part of a prototype (that we ended up not using) and it worked pretty well for mimicking the way that you can specify loggers in log4net and NLog.
Good luck!
I'm using next method to add a trace record:
TraceSource.TraceEvent(TraceEventType, Int32, String)
where Int32 represents event id.
So how to filter in TraceSwitch to listen only by specified event id? Ir this is impossible?
<system.diagnostics>
<sources>
<source name="MyTraceSource" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch>"
<listeners>
<add name="console" type="System.Diagnostics.ConsoleTraceListener" />
</listeners>
</source>
</sources>
<switches>
<add name="sourceSwitch" value="?" />
</switches>
</system.diagnostics>
It's possible but you need to write a custom TraceFilter and override the ShouldTrace method. The id is passed to it, but no out-of-the-box filter supports it.
Then, you can declare it like this in a .config file:
<source name="MyTraceSource" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch">
<listeners>
<add name="console" type="System.Diagnostics.ConsoleTraceListener">
<filter type="YourNamespace.YourFilter, YourAssembly, ..." />
</add>
</listeners>
</source>
You can try Ukadc.Diagnostics from codeplex. This project provides some useful extensions for System.Diagnostics. The coolest thing, in my opinion, that they provide is a token based system that can be used to define log/trace output format similar to what you can achieve with log4net and NLog. It is a configuration-only dependency. That is, if you code is already using TraceSources, you only have to put Ukadc.Diagnostics on your machine and have your app.config point to their TraceListeners, PropertyTokens, etc.
You still instrument your code using System.Diagnostics.TraceSource objects.
To your point, using Ukadc.Diagnostics you can filter based on most property tokens (including EventId).
Note that the token system can only be used (as far as I know) with the corresponding TraceListeners provided in Ukadc.Diagnostics (or any TraceListener that you write based on their base TraceListener class).
I have not used this project in production, but I have fooled around with it quite a bit and have been quite impressed. It works well and is easy to extend.