Using EventLogTraceListener inside a library DLL - c#

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
}
}

Related

How to use trace sources in application code?

I have successfully configured logging in the App.config file of an application. The relevant parts are as follows.
<system.diagnostics>
<sources>
<source name="System.Net.Http">
<listeners>
<add name="PushTraceListener" />
</listeners>
</source>
</sources>
<switches>
<add name="System.Net.Http" value="Verbose"/>
</switches>
<sharedListeners>
<add name="PushTraceListener"
type="PushCore.Logging.LoggingTraceListener, PushCore" />
</sharedListeners>
</system.diagnostics>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="true"
logMalformedMessages="false"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxMessagesToLog="3000"
maxSizeOfMessageToLog="2000"/>
</diagnostics>
</system.serviceModel>
While this works fine, I seem unable to do the same in the application code, which I tried as follows.
var LoggingTraceListener = new LoggingTraceListener
{ Name = "PushTraceListener", Filter = new LoggingTraceFilter() };
Trace.AutoFlush = true;
var TraceSourceNames = new string[] { "System.Net.Http" };
foreach (var TraceSourceName in TraceSourceNames)
{
var TraceSource = new TraceSource(TraceSourceName, SourceLevels.Verbose);
TraceSource.Switch = new SourceSwitch(TraceSourceName, "Verbose");
TraceSource.Listeners.Add(LoggingTraceListener);
}
Trace.Listeners.Add(LoggingTraceListener);
The TraceListener itself seems to behave as expected, as it recieves messages written via Trace, but apparently not from the instantiated TraceSource instance. I somehow believe that instantiation of the TraceSource is not desired here, but instead an existing trace source would have to be used. However, I don't know how to do that.
Is it possible to achieve loggig as desired in the first place? If so, how?
A justified question would be why I would like to do access in code instead of using App.config if the configuration works fine. The reason is that App.config can only be used for an actual application but not for a Windows service.
Any suggestions?

Cannot get WPF binding error trace information to write to log file configured in code

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. :)

C# enable/disable network tracing at runtime?

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.

Using TraceSource from a class library and using it in other components of the same executable

I have created a library for event logging utilities using system.Diagnostics like the one below:
public class Logger
{
static TraceSource ts = new TraceSource("TestApp");
public void Log(string message)
{
ts.TraceEvent(TraceEventType.Verbose, 0, message);
}
}
I want to use this Log function in my app and other components (dll) of the same application. I tried declaring listeners in app.config of my application, but it didnt work :(. My app.config looks like below:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="TestApp"
switchName="mySwitch"
switchType="System.Diagnostics.SourceSwitch" >
<listeners>
<clear/>
<add name="EventLogListener"
type="System.Diagnostics.EventLogTraceListener"
initializeData="Title for events" />
</listeners>
</source>
</sources>
<switches>
<add name="mySwitch" value="Verbose" />
</switches>
</system.diagnostics>
</configuration>
If i move the class Logger in the application itself (the executable), with the given manifest file, i could see the logs in the application channed in the eventviewer. But i dont want to use it this way.
Can someone please help me find out what is the underlying problem here?
I realized that "Trace" was not enabled for the class library in the csproj. After enabling that i am at least seeing those events in textwriterListener/

How to filter trace listened by event id?

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.

Categories