Click here to Skip to main content
Click here to Skip to main content

TraceListener for Named Pipes

, 11 Jul 2011
Rate this:
Please Sign up or sign in to vote.
Allows you to very easily see live trace output on any machine.

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);
            // A bit of gymnastics required here..
            // we cannot remove the listener directly
            // because that will trigger an exception on the Trace object.
            // We must allow this routine to finish,
            // and call Trace.Listeners.Remove from the outside.
            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Kári Poulsen

Faroe Islands Faroe Islands
No Biography provided

Comments and Discussions

 
QuestionThis is interesting and might be what I need [modified] PinmemberMember 798058310-Jul-13 5:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 11 Jul 2011
Article Copyright 2011 by Kári Poulsen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid