9

I have a TraceSource object that I use to log the initialization of a VB.Net application. It has several TraceListeners attached:

  • ConsoleTraceListener
  • TextWriterTraceListener
  • EventLogTraceListener

For the first two I want the entry output to be "raw" - that is, without the standard header:

SourceName TraceEventType: Id :

I have implemented a wrapper that does this when the TraceEventType is set to Verbose:

If _buffer.EventType = TraceEventType.Verbose Then
    For Each listener As TraceListener In _traceSource.Listeners
        listener.Write(_buffer.Text)
    Next
Else
    _traceSource.TraceEvent(_buffer.EventType, id, _buffer.Text)
End If

I could do this for all the tracing, but then all entries in the EventLog would be listed with Level = Information. So I want to be able to specify the severity of the trace message, but I can't find any method on the TraceSource or the TraceListeners that allows me to do this. As far as I can tell, TraceListener has these options for writing to it:

  • Write()
  • WriteLine()
  • TraceData()
  • TraceEvent()
  • TraceTransfer()

The last 3 allows for providing a TraceEventType (which correctly labels the EventLog entries, but the resulting output to the console and the log file then includes the prefixes and ends up like this (for example):

Bootstrapper Warning: 0 : Failed to validate assembly

Is there a way to override how the ConsoleTraceListener and TextWriterTraceListener format their output to not include this header, while at the same time being able to tag the entries with a TraceEventType (for the EventLog)?

This is the best I have come up with so far:

For Each listener As TraceListener In _traceSource.Listeners
    If listener.GetType Is GetType(ConsoleTraceListener) OrElse listener.GetType Is GetType(TextWriterTraceListener) Then
        listener.Write(_buffer.Text)
    Else
        listener.TraceEvent(Nothing, _traceSource.Name, _buffer.EventType, id, _buffer.Text)
    End If
Next

This seems to work, but in the documentation on the TraceListener.TraceEvent Method from Microsoft, it says:

Important: This method is not intended to be called directly by application code but by members of the Debug, Trace, and TraceSource classes to write trace data to output.

..so I'm not sure if it's a good thing to do?

Edit:

I just realized that if I do something like my last example here, I don't need the TraceSource at all, since it's being bypassed anyway. But it also means I have to implement my own filtering and switching mechanisms (but that is maybe an OK price to pay to get it to work the way I want).

d7samurai
  • 3,086
  • 2
  • 30
  • 43

3 Answers3

2

Another similar project that has formattable listeners that you could use is Essential Diagnostics, which was actually originally inspired by Ukadc.Diagnostics.

You have indicated, however, that you don't want external dependencies, but you still have several options without re-writing parts of the Framework:

(A) Rather than rewrite TraceSource, the designed extension point in the .NET Framework is to write your own TraceListener.

If you write your own trace listeners "ConsoleWithoutPrefixListener" and "FileWithoutPrefixListener", then you can override the TraceEvent() methods to just forward the message to TraceWrite() (and drop the prefixes).

In fact neither ConsoleTraceListener or TextWriterTraceListener are sealed, so I think you could inherit from them and get this working with a one line override of the TraceEvent() method (plus the constructor).

(B) Another alternative would be to leave EventLogTraceListener configurated against the source but configure the other two listeners under (rather than trace source).

The drawback of this is that in your code you need to log twice every time, e.g.:

_traceSource.TraceEvent(_buffer.EventType, id, _buffer.Text) Trace.TraceWrite(_buffer.Text)

If you want to write some messages with prefixes and some without then you will need two trace sources: one that is configured with all three listeners, and one with only the event log listener.

Then, in your wrapper either write to source A (all three) or source B + Trace static methods.

(C) Personally, my guidance would be not to use tracing for writing to the event log -- if issues are important enough to write to the event log you generally don't want the user to be able to turn them off via configuration.

In this case your wrapper writes to the event log directly (EventLog.WriteEntry or whatever) and then your code writes to the trace source and/or Trace static methods for the file and console.

Note that to get writing to Event Log working correctly you need to take into account permissions. To create an event log source you need to be running as administrator. As a developer you probably normally have admin permissions, so you need to properly test this in the context of someone who doesn't.

Also note that it is only the initial creation that needs admin permissions, and this is automatically done when you write the first message, so if you have already done it as a developer admin you need to find a clean machine to test on.

Because of this, normally you need to have an EventLogInstaller as part of your code, which is run by InstallUtil (or equivalent MSI or whatever) that creates the event log source during install (because install is done by an administrator). Then, when the program runs the source exists.

So, what does this have to do with writing to traces -- well, if the only thing you do is configure the EventLogTraceListener in your config then for normal users it won't work; it will try and write events to the source (in the initializeData attribute), which will then try to create source and if not running as admin will fail.

If you do add an installer for the event source, then you still have a problem if someone changes the config file.

Because of this, I would recommend that both the EventLogInstaller and EventLog are created directly in code, to ensure the names match, and not go through the tracing infrastructure.

Sly Gryphon
  • 3,751
  • 1
  • 25
  • 17
2

Here is my full solution to this, inspired by @Sly's answer.

To suppress the header information when using the TraceEvent() method you can inherit from ConsoleTraceListener or TextWriterTraceListener (or whatever flavour of listener is required) like so;

namespace Custom.Lib {
    public class ConsoleTraceListener : System.Diagnostics.ConsoleTraceListener {

        // overridding this method here will suppress header information
        // your TraceEvent() messages will still reach the listener
        public override void Write(string message) {
            //base.Write(message);
        }

    }
}

N.B. When attempting to override the TraceEvent method I noticed that the header information had not been added to the message string at this point. Instead I opted to silence the call to Write(string) which doesn't seem to have any other knock-on effects but it does feel a little 'hackish', if anyone has a 'cleaner approach' I'm open to it.

The configuration to use this customer listener should then look something like this;

  <system.diagnostics>
    <sources>
      <source name="AppTrace" switchName="sourceSwitch" switchType="System.Diagnostics.SourceSwitch">
        <listeners>
          <add name="consoleListener"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="sourceSwitch" value="Information"/>
    </switches>
    <sharedListeners>
      <add name="consoleListener" type="Custom.Lib.ConsoleTraceListener, Custom.Lib" initializeData=""/>
    </sharedListeners>
  </system.diagnostics>
Community
  • 1
  • 1
Red Taz
  • 4,159
  • 4
  • 38
  • 60
0

Look at the Ukadc.Diagnostics project on codeplex. It is an addon to System.Diagnostics that gives you the ability to format your logging/tracing output however you would like (similar to what you can do with log4net and NLog). You use it through configuration, so your code will not take a direct dependency on the library. The library comes with configurable objects for formatting and the custom TraceListeners required to take advantage of the formatting. The library also makes it easy for you to write your own formatting "tokens" and your own TraceListener.

For example, you could configure the Ukadc.Diagnostics ConsoleTraceListener to use a formatting statement something like this:

{DateTime} {Source} {EventType} {Message}

Every message logged would cause the date/time, the source name, the event type, and the message.

Try it out, I think that you will like it. I have used it some myself (mostly for prototyping, not for a "real" product yet) and have had good success.

Note that for some tokens (like DateTime), you can also apply standard formats that are appropriate for the type (e.g. for DateTime you can specify the format in which the date/time should be written).

The file trace listener that comes with Ukadc.Diagnostics also allows its filename to be specified using the token system.

Community
  • 1
  • 1
wageoghe
  • 27,390
  • 13
  • 88
  • 116
  • Thanks, I'll have a look (though I prefer not to use external addons, for some reason). And now I have also made my own tracing system where I can do similar things. My class functions similarly to TraceSource, but it has two listener collections. One that functions in the standard way, using TraceEvent(), and the other uses the "raw" Write() function. Every logged message is pushed to all listeners in both categories (depending on certain flags etc), so you can add the listeners that require EventType tagging in one, and the ones with plain/custom formatting in the other. Works well. – d7samurai Oct 31 '10 at 20:16
  • I think the library is really cool, so go ahead and try it out, you might like it. One way it could help your workflow is that you could use the same set of TraceListener classes, but change what actually gets written via configuration. So, you could have a "raw" format associated with some listeners (Console and TextWriter) and an "event" format associated with others (EventLog). If you decide next week or next month that you want to add or remove some information from either format, then you just have to change the format in the app.config. No code changes. Good luck! – wageoghe Oct 31 '10 at 20:23
  • d7samurai, are you familiar with the NotInventedHere-Antipattern? – M.Stramm Mar 13 '12 at 14:16
  • @M.Stramm - no, i wasn't. but there's a difference between that and having requirements not met by existing functionality, not wanting external code dependencies, messing up internal architecture esthetics etc. i do think i have a slight OCD tendency when it comes to coding, though, and i usually rewrite my own code - from scratch - many times over during development, to keep everything "neat", tight and consistent. i can spend hours just pondering variable names / naming structures for an application. – d7samurai Oct 19 '12 at 11:45
  • @M.Stramm - also note that the purpose of this particular project was to function as an extremely bare-boned BOOTSTRAPPER (running as a windows service, dynamically maintaining / downloading the components of the main application at startup). – d7samurai Oct 19 '12 at 11:52
  • @d7samurai i get it, just remember not to reinvent everything. fear of using external libraries really hinders development and diverts resources from where they are needed. if existing libraries can't provide what you need or are too unstable that's a whole other story, but logging is really not something you have to reinvent. it has been done many many times over and over again already – M.Stramm Oct 19 '12 at 16:44
  • @M.Stramm i wasn't trying to reinvent logging. i wanted MY logging to log my data in the way i wanted. the question i asked was whether there was a way to customize the built-in logging functionality to log according to my formatting preferences - i.e simply outputting the log data in a clean way without redundant data. this was about esthetics. – d7samurai Oct 19 '12 at 17:02
  • @M.Stramm there wasn't, apparently, but although there are external libraries that provide such [customization] functionality, adding that would *bloat* and *complicate* the application - especially considering there were/are ways to get around it (as i explained). is it somehow a virtue to use external libraries? for pure resource efficiency, the obvious path would be to simply accept the extra header data in the log entry and move on. but as i said, i like things to be clean, and in this case, it was as much about finding out whether there was a way to customize the existing or not. – d7samurai Oct 19 '12 at 17:06
  • @d7samurai My point is that this answer provides you with exactly that: a library which allows you (like a hundred others) to have YOUR logging to log your data the way you want it. Yes, there is virtue in trusting tested robust libraries over your own ad-hoc solutions (but is not always the right choice). Anyways, why are you so keen on defending your decision? It's fine, I didn't try to attack you or turn this into a lengthy discussion. – M.Stramm Oct 21 '12 at 17:22
  • @M.Stramm. I got your point. For some reason you implied that I have an irrational fear of things not made by myself - because I didn't want to use an external library (to do something I can accomplish without it, in an application which design philosophy is to be as self contained and minimal as possible, even). MY point, as formulated in *the question*, was whether the *built-in* logging functionality could be customized to my preferences. Sometimes the reason people don't want to go out in the rain is not that they are afraid of getting wet. You posed a question. I am explaining how it is. – d7samurai Oct 21 '12 at 19:11