The whole solution is attached to the post. Written [hurriedly] in VS2008 Beta2, but it will probably work just fine in VS2005. It's no where near pristine code; more a proof-of-concept, so take what you will from it.
One of the apps we developed internally consists of many instances of a service running on multiple machines, talking to many external services and then to a central DB, and doing so at a very high frequency with many threads.
We find that it's difficult to profile and debug such services due to both the complexity introduced by the multiple threads, and due to the fact that it's difficult to replicate the behavior in a test environment since the live one offered so much more data and opportunity for failure.
What we ended up doing in the latest incarnation was throwing in Trace.Write() statements everywhere, to allow the service to communicate not only what it was currently doing (as in the traditional "printf debugging"), but to also throw in occasional useful measures (as in a standard windows performance counters).
The problem is, how do we view these traces? The built-in eventlog and logfile trace listeners weren't suitable for us because they inundate their respective targets with an ever-increasing amount of data; which requires maintenance.
What we wanted was something exactly like the SQL Profiler, or ProcMon (previously "FileMon" + "RegMon"), something that would let us view what's happening as it happens, without being too intrusive.
The solution consists of two parts:
The Listener
The listener is fairly simple. All you need to do in order to implement a custom listener is inherit from the System.Diagnostics.TraceListener class, then implement the Write() and WriteLine() methods. Here is a good article explaining it.
In this particular case, the complication comes in the constructor, where we perform some intialization to bind to the TCP port and get a stream to write to.
In the Write() methods, we check if there's someone connected to us. If there is, we write the string to them. If not, we discard it.
This way, we have a lightweight tracing solution that only really does anything when you're watching. Dare I say, a Schrödinger Listener? :)
The Viewer
The core of the viewer is just opening a TCP connection and reading strings from it. Everything else is window dressing. It could be a console app, an MMC plugin, a WPF app - it really doesn't matter.
What could be done better?
I'm sure there's plenty more that could be done to this to make it more useful. If you have suggestions, feel free to post them here as comments.
Avi