The old and tried way of writing trace logs while developing seems to never go away.
The Trace
class is handy for that. The one annoying thing about trace files is that you have to keep looking at those files, and close and reopen every time you change something..
Fortunately, there is a predefined TraceListener
that can echo everything directly to a console, the ConsoleTraceListener
, but this really has annoying
side-effects if you have a lot of stuff happening, or if you need console input as well.
Enter the TracePipe
class!
This little thingy implements a new TraceListener
class, and allows you to write trace information to any connected pipe reader.
Here's how it works:
There's a static class that handles some cleanups and controls the connected threads. Inside, we have a private class that is the actual TraceListener
.
You basically paste the code below in some toolbox of yours, call the Start
method - supplying a pipe name and the maximum clients allowed.
That's it - well almost - you also need to have a tiny application that can connect to the pipe, an example given below..
What we do is start a pipe server.. Every time a client connects to it, we add it to the Trace.Listener
, and it starts to receive trace information.
If a client disconnects, the corresponding TraceListener
disappears.
public class TracePipe
{
protected static int PipeCounter = 0;
public static string PipeName = "";
public static int MaxPipes = 0;
public static Boolean AutoFlush = true;
public static Boolean DiagnosticToConsole = true;
public static void Start(string Pipe, int MaxAllowed)
{
PipeName = Pipe;
MaxPipes = MaxAllowed;
NewThread();
}
public static void Start()
{
string s = Process.GetCurrentProcess().MainModule.ModuleName;
string[] ar = s.Split('.');
PipeName=ar[0];
MaxPipes = 3;
NewThread();
}
protected static void info(string s)
{
if(DiagnosticToConsole)
Console.WriteLine(s);
}
public class PipeWriterTraceListener : TextWriterTraceListener
{
public delegate void pipebroken(object data);
public PipeWriterTraceListener(Stream stream) : base(stream) { }
private void errorhandler(IOException e)
{
info(string.Format("Error: {0} : {1}", this.Name, e.Message));
pipebroken br = new pipebroken(PipeBreakBegin);
br.BeginInvoke(this, PipeBreakEnd, this);
this.Dispose();
}
private void PipeBreakBegin(object data)
{
Thread.Sleep(1);
}
private void PipeBreakEnd(IAsyncResult ia)
{
Trace.Listeners.Remove((PipeWriterTraceListener)ia.AsyncState);
}
public override void Write(string message)
{
try
{
base.Write(message);
if (TracePipe.AutoFlush)
base.Flush();
}
catch (IOException e)
{
errorhandler(e);
}
}
public override void WriteLine(string message, string category)
{
try
{
base.WriteLine(message, category);
if (TracePipe.AutoFlush)
base.Flush();
}
catch (IOException e)
{
errorhandler(e);
}
}
public override void Write(string message, string category)
{
try
{
base.Write(message, category);
if(TracePipe.AutoFlush)
base.Flush();
}
catch (IOException e)
{
errorhandler(e);
}
}
public override void WriteLine(string message)
{
try
{
base.WriteLine(message);
if (TracePipe.AutoFlush)
base.Flush();
}
catch (IOException e)
{
errorhandler(e);
}
}
public override void Flush()
{
try
{
base.Flush();
}
catch (IOException e)
{
errorhandler(e);
}
}
public override void Close()
{
try
{
base.Close();
}
catch (Exception e)
{ ; }
}
}
private static void NewThread()
{
Thread newThread = new Thread(ThreadServer);
newThread.Name = string.Format("pipe {0} [{1}]", PipeName, PipeCounter++);
newThread.IsBackground = true;
newThread.Start();
}
private static void ThreadServer(object data)
{
try
{
NamedPipeServerStream pServer =
new NamedPipeServerStream(PipeName, PipeDirection.Out, MaxPipes);
pServer.WaitForConnection();
PipeWriterTraceListener lPipe = new PipeWriterTraceListener(pServer);
lPipe.Name = Thread.CurrentThread.Name;
Trace.Listeners.Add(lPipe);
NewThread();
}
catch (Exception e)
{
info("Pipe connection attempt failed: " + e.Message);
}
}
The basic pipe reader can look like this:
static void cout(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
if ((args.Length == 0) || (args[0]=="-?"))
{
cout("Usage:\r\npipereader <pipe> [server] [-n] [-w]\r\n" +
"pipe A named pipe to monitor.\r\n" +
"server '.' or nothing is local machine.\r\n" +
"-nowait Check pipe on startup, exit if not found.\r\n" +
"-noloop Do not Loop wait. If the pipe closes we exit.\r\n");
return;
}
string pip=args[0];
string serv = ".";
Boolean nowait=false;
Boolean noloop=false;
for(var i=1;i<args.Length;i++)
{
if(args[i].Substring(0,1)!="-"){
serv=args[i];
}else{
if(args[i]=="-nowait") nowait=true;
if(args[i]=="-noloop") noloop=true;
}
}
NamedPipeClientStream pipeClient =
new NamedPipeClientStream(serv, pip, PipeDirection.In);
if (nowait)
{
try
{
pipeClient.Connect(2000);
}
catch (TimeoutException e)
{
cout("No pipe found..");
return;
}
}
else
{
cout(string.Format("Waiting for pipe {0} on {1}", pip, serv));
pipeClient.Connect();
}
do
{
cout("Connected to pipe.");
cout(string.Format("There are currently {0} pipe server instances open.",
pipeClient.NumberOfServerInstances));
using (StreamReader sr = new StreamReader(pipeClient))
{
string temp;
while ((temp = sr.ReadLine()) != null)
{
Console.WriteLine(temp);
}
}
cout(string.Format("Pipe {0} on {1} closed at {2}",
pip, serv, DateTime.Now));
if (!noloop)
{
pipeClient = new NamedPipeClientStream(serv, pip, PipeDirection.In);
cout("Waiting for pipe..");
pipeClient.Connect();
}
} while (!noloop);
}
.. And remember: it runs just as well over a network...
I have used this routinely now for quite a while, and it seems stable enough.. Sometimes though, something goes wrong if you keep disconnecting and connecting pipe clients.
Please try and comment back.