Search code examples
c#.netwpftracesystem.diagnostics

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?


Solution

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