Back in the mists of .net 1.x, if you wanted to put any kind of logging (or - if you're posh - telemetry) in your application, you generally avoided the Base Class Libraries, and for good reason. What was there was basic, and not massively flexible.
You had System.Diagnostics.Trace and System.Diagnostics.Debug. They would write out to any and all TraceListeners defined in config (of which there could be Default, EventLog and TextWriter). There was no great difference between the two - the output wasn't categorised as trace or debug, and they both went to the same set of listeners.
Categories were a bit rubbish - they simply got prepended to the text that was output. No filtering and no routing of categorised output to a specific listener. The way to do conditional output was to check with a named Switch before calling Write. The output level of the Switch (or rather its derived classes TraceSwitch and BooleanSwitch) would get set in config. This lead to code like:
which leads to the WriteIf family of methods:
(One kinda nifty feature was that each of the Write methods on these classes are marked with the ConditionalAttribute, meaning Trace.Write would only work if you'd compiled with the TRACE constant defined, and Debug.Write requires the DEBUG constant. The nifty bit is that if you don't have the constants defined, the call to the method is optimised away. It's like surrounding the call to Debug.Write in #ifdef's. But it wouldn't optimise away the "if" when using switches. Hence WriteIf.)
All in, not too terrible, but not very practical, either. The switch idea is ok, but you decide in code (not config) if it's going to be all or nothing (BooleanSwitch) or based on trace level (TraceSwitch). It's a bit restricting that you can only output text, and any extra information you want, you have to get yourself. You're mainly limited by outputting everything to all listeners.
The normal reaction to this was to reach for a 3rd party solution, such as log4net, or Enterprise Library. Both of these provide categorisation, routing to different listeners and finer control of what gets output.
It's what we did at work. We used Enterprise Library (and Enterprise Instrumentation Framework before that, I believe) and it hurt, so we wrote a wrapper API for it, and that also hurt, so we wrote a new wrapper for it, and then migrated to EntLib 2, which changed their API, and we wrote a new wrapper for that.
And now I'm looking at WCF, and especially the Service Trace Viewer, and I need to understand how that works, which means a dive back into .net 2's tracing features.
Guess what? They've pretty much fixed it.
You've still got Trace and Debug, and they still function basically the same. But you don't use those in new code. You use TraceSource instead. You create a named instance of one of these, and call TraceEvent, TraceInformation or TraceData on it. Each TraceSource can have its own set of TraceListeners or use any from a pool of shared listeners. Giving it a name effectively gives you categories (a common naming convention is to use your namespace), and lets you define routing in config. A TraceSource also has a SourceSwitch to provide blanket filtering (by trace level) for all events from a TraceSource. Each listener can have a TraceFilter, which also filters events - useful for shared listeners where the TraceSource isn't configured the same. And a TraceListener can now automatically output a bunch of useful information along with the text, including callstack, timestamp and process and thread id. Arbitrary data can be logged with TraceData.
There are even a few more listeners: Default, EventLog, TextWriter, Console, DelimitedList, WebPage (nice!) and the most interesting one, XmlWriterTraceListener.
(And there's a StopWatch, too.)
Indeed it looks like the only reason to go with a 3rd party solution now is for more out of the box listeners. Log4net ships with a ton of them. Enterprise Library (which was rearchitected to use .net 2's logging) adds a few more (MSMQ, Wmi, email and so on). But if you don't want to take the dependency, it's not hard to write a custom TraceListener (or just use standard .net 2 APIs and the EntLib listeners).
But the killer app for .net 2 logging? That's where XmlWriterTraceListener, WCF's Service Trace Viewer, the CorrelationManager's ActivityId's and End-to-End Tracing come in. The next post will have pretty pictures...
PS. A few more useful links:
- MSDN's tracing docs (part 1)
- More info on .net 1.x's poor logging
- Tracing in asp.net + playing nice with System.Diagnostics.Trace
- BCL Team Blog - A Tracing Primer - Part I
- BCL Team Blog - A Tracing Primer - Part II (A)
- BCL Team Blog - A Tracing Primer - Part II (B)
- BCL Team Blog - A Tracing Primer - Part II (C)