/*=====================================================================

  File:  LoggingSinks.cs
  Desc:  Pluggable channel sinks for logging. 
  
---------------------------------------------------------------------
This file is part of the Microsoft .NET Framework SDK Code Samples.

  Copyright (C) Microsoft Corporation 2001.  All rights reserved.

This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
=====================================================================*/

using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Metadata;
using System.Text;
using System.Threading;
using System.Web;
using System.Xml;

namespace Logging
{ 
	#region BaseLoggingChannelSinkProvider
    // Base class which handles parsing common configuration entries.
    public class BaseLoggingChannelSinkProvider
    {
        // configurable settings
        private TextWriter _output = null;         // output writer
        private bool _bExceptionsOnly = false;     // should we only log exceptions?
        private bool _bIncludeTimestamp = true;    // should timestamp be included?
        private bool _bHeadersOnly = false;        // should only headers be generated?
        private bool _bHttpLiteralRequest = false; // print http requests in literal form
        private bool _bSynchronized = true;        // should the output be synchronized?
        private bool _bXmlPrettyPrint = false;     // should xml output be pretty printed?

        private bool _bTextLogging = true;         // is text logging enabled?
        private bool _bBinaryLogging = true;       // is binary logging is enabled?
        private int  _bytesPerLine = 16;           // number of bytes per line for binary logging
        
        public BaseLoggingChannelSinkProvider()
        {
        }

        protected void ProcessConfigEntry(DictionaryEntry entry)
        {
            String keyStr = entry.Key.ToString().ToLower(CultureInfo.InvariantCulture);
            switch (keyStr)
            {

            case "binarylogging":
            {
                _bBinaryLogging = Convert.ToBoolean(entry.Value);              
                break;
            }

            case "bytesperline":
            {
                _bytesPerLine = Convert.ToInt32(entry.Value);
                break;
            }

            case "exceptionsonly":
            {
                _bExceptionsOnly = Convert.ToBoolean(entry.Value);
                break;
            }

            case "headersonly":
            {
                _bHeadersOnly = Convert.ToBoolean(entry.Value);
                break;
            }

            case "httpliteralrequest":
            {
                _bHttpLiteralRequest = Convert.ToBoolean(entry.Value);
                break;
            }
            
            case "logfile":
            {
                // redirect output to a file
                string filename = (string)entry.Value;
                if (filename != null)
                {
                    filename = LoggingUtil.MapPath(filename);
                    _output = new StreamWriter(
                        File.Open(
                            filename, FileMode.OpenOrCreate | FileMode.Append,
                            FileAccess.Write, FileShare.Read));
                }

                break;
            }

            case "synchronized":
            {
                _bSynchronized = Convert.ToBoolean(entry.Value);
                break; 
            }

            case "textlogging":
            {
                _bTextLogging = Convert.ToBoolean(entry.Value);
                break; 
            }

            case "timestamp":
            {
                _bIncludeTimestamp = Convert.ToBoolean(entry.Value);
                break;
            }

            case "xmlprettyprint":
            {
                _bXmlPrettyPrint = Convert.ToBoolean(entry.Value);
                break;
            }

            default:
            {
                throw new Exception("Unrecognized property in " + this.GetType().FullName + ": " + keyStr);
            }

            } // switch (keyStr)
            
        } // ProcessConfigEntry

        internal void ApplySettingsToLogger(LoggingHelper logger)
        {
            if (_output != null)
                logger.Out = _output;

            logger.Synchronized = _bSynchronized;
            logger.IncludeTimestamp = _bIncludeTimestamp;
            logger.ExceptionsOnly = _bExceptionsOnly;
            logger.HeadersOnly = _bHeadersOnly;

            if (_bHttpLiteralRequest)
                logger.RequestHeadersLogger = new HttpHeadersLogger();

            if (_bBinaryLogging)
            {
                IStreamLogger binLogger = new BinaryStreamLogger(_bytesPerLine);
                logger.AddRequestStreamLogger(binLogger);
                logger.AddResponseStreamLogger(binLogger);
            }

            if (_bTextLogging)
            {
                IStreamLogger textLogger = new TextStreamLogger();
                logger.AddRequestStreamLogger(textLogger);
                logger.AddResponseStreamLogger(textLogger);
            }

            if (_bXmlPrettyPrint)
            {
                IStreamLogger xmlLogger = new XmlTextStreamLogger();
                logger.AddRequestStreamLogger(xmlLogger);
                logger.AddResponseStreamLogger(xmlLogger);
            }
        } // ApplySettingsToLogger
    } // BaseLoggingChannelSinkProvider
	#endregion

	#region LoggingClientChannelSinkProvider
    public class LoggingClientChannelSinkProvider : BaseLoggingChannelSinkProvider, IClientChannelSinkProvider
    {
        private IClientChannelSinkProvider _next = null;
        
        public LoggingClientChannelSinkProvider() : base()
        {
        }

        public LoggingClientChannelSinkProvider(IDictionary properties, ICollection providerData) : base()
        {
            if (properties != null)
            {                
                foreach (DictionaryEntry entry in properties)
                {
                    String keyStr = entry.Key.ToString().ToLower(CultureInfo.InvariantCulture);
                    switch (keyStr)
                    {                    

                    default:
                    {
                        // delegate to base class
                        ProcessConfigEntry(entry);
                        break;
                    }

                    } // switch (keyStr)
                }
            }
        } // LoggingClientChannelSinkProvider

        public IClientChannelSink CreateSink(IChannelSender channel, String url, 
                                             Object remoteChannelData)
        {
            IClientChannelSink nextSink = null;
            if (_next != null)
            {
                nextSink = _next.CreateSink(channel, url, remoteChannelData);
                if (nextSink == null)
                    return null;
            }
            
            LoggingHelper logger = new LoggingHelper();
            ApplySettingsToLogger(logger);
            LoggingClientChannelSink sink = new LoggingClientChannelSink(logger, nextSink);
            return sink;
        }

        public IClientChannelSinkProvider Next
        {
            get { return _next; }
            set { _next = value; }
        }
    } // class LoggingClientChannelSinkProvider
	#endregion

	#region LoggingClientChannelSink
    internal class LoggingClientChannelSink : BaseChannelObjectWithProperties, 
                                              IClientChannelSink, 
                                              IMessageSink,
                                              ILoggingSink
    {
        private Int64 _requestId = 0;

        private IClientChannelSink _nextSink = null;

        private bool          _bEnabled = true;
        private LoggingHelper _logger = null;
    
        // hide default constructor        
        private LoggingClientChannelSink()
        {
        }       

        internal LoggingClientChannelSink(LoggingHelper logger, IClientChannelSink nextSink) : base()
        {
            _nextSink = nextSink;

            _logger = logger;
        } // LoggingClientChannelSink
        
        //
        // IMessageSink implementation
        //       
        
        IMessageSink IMessageSink.NextSink { get { return null; } }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            // verify that next sink is also a message sink
            IMessageSink nextSink = _nextSink as IMessageSink;
            if (nextSink == null)
            {                
                throw new Exception("The sink after the logging sink must implement IMessageSink if the logging sink is placed before the formatter.");
            }

            Int64 requestId = Interlocked.Increment(ref _requestId);
                
            try
            {
                if (_bEnabled)
                    _logger.PrintRequestMessage(requestId, msg);
            
                IMessage replyMsg = nextSink.SyncProcessMessage(msg);

                if (_bEnabled)
                    _logger.PrintResponseMessage(requestId, replyMsg);

                return replyMsg;
            }
            catch (Exception e)
            {
                _logger.PrintException(requestId, e);
                throw;
            }
        } // SyncProcessMessage

        private class AsyncInterceptorSink : IMessageSink
        {
            private IMessageSink _nextSink;
            private Int64 _requestId;
            private LoggingClientChannelSink _channelSink;
            
            internal AsyncInterceptorSink(IMessageSink nextSink, Int64 requestId, LoggingClientChannelSink channelSink)
            {
                _nextSink = nextSink;
                _requestId = requestId;
                _channelSink = channelSink;
            }

            public IMessageSink NextSink { get { return _nextSink; } }

            public IMessage SyncProcessMessage(IMessage msg)
            {
                _channelSink.AsyncPrintResponseMessageHelper(_requestId, msg);

                return _nextSink.SyncProcessMessage(msg);
            }

            public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
            {
                throw new NotSupportedException();
            }

        } // class AsyncInterceptorSink

        internal void AsyncPrintResponseMessageHelper(Int64 requestId, IMessage replyMsg)
        {
            if (_bEnabled)
                _logger.PrintResponseMessage(requestId, replyMsg);
        } // AsyncPrintResponseMessageHelper

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {   
            // verify that next sink is also a message sink
            IMessageSink nextSink = _nextSink as IMessageSink;
            if (nextSink == null)
            {                
                throw new Exception("The sink after the logging sink must implement IMessageSink if the logging sink is placed before the formatter.");
            }
           
            Int64 requestId = Interlocked.Increment(ref _requestId);
                
            try
            {
                if (_bEnabled)
                    _logger.PrintRequestMessage(requestId, msg);
            
                IMessage replyMsg = nextSink.SyncProcessMessage(msg);

                return nextSink.AsyncProcessMessage(msg, new AsyncInterceptorSink(replySink, requestId, this));
            }
            catch (Exception e)
            {
                _logger.PrintException(requestId, e);
                throw;
            }
        } // AsyncProcessMessage

        //
        // end of IMessageSink implementation
        //  

        public void ProcessMessage(IMessage msg,
                                   ITransportHeaders requestHeaders, Stream requestStream,
                                   out ITransportHeaders responseHeaders, out Stream responseStream)
        {
            Int64 requestId = Interlocked.Increment(ref _requestId);

            try
            {
                if (_bEnabled)
                    _logger.PrintRequest(requestId, requestHeaders, ref requestStream);

                _nextSink.ProcessMessage(msg, requestHeaders, requestStream,
                                         out responseHeaders, out responseStream);

                if (_bEnabled)
                    _logger.PrintResponse(requestId, responseHeaders, ref responseStream);
            }
            catch (Exception e)
            {
                _logger.PrintException(requestId, e);
                throw;
            }
        } // ProcessMessage
       
        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
                                        ITransportHeaders headers, Stream stream)
        {
            Int64 requestId = Interlocked.Increment(ref _requestId);

            try
            {
                if (_bEnabled)
                    _logger.PrintRequest(requestId, headers, ref stream);

                // never gets called, this sink is always first
                sinkStack.Push(this, requestId);
                _nextSink.AsyncProcessRequest(sinkStack, msg, headers, stream);
            }
            catch (Exception e)
            {
                _logger.PrintException(requestId, e);
                throw;
            }
        } // AsyncProcessRequest

        public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
                                         ITransportHeaders headers, Stream stream)
        {
            Int64 requestId = (Int64)state;

            if (_bEnabled)
                _logger.PrintResponse(requestId, headers, ref stream);
            sinkStack.AsyncProcessResponse(headers, stream);
        } // AsyncProcessResponse

        public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
        {
            // we always want a stream to read from
            return null;
        }

        public IClientChannelSink NextChannelSink
        {
            get { return _nextSink; }
            set { _nextSink = value; }
        }

        public bool Enabled
        {
            get { return _bEnabled; }
            set { _bEnabled = value; }
        } // Enabled

        public TextWriter Out
        {
            set { _logger.Out = value; }
        } // Out

        //
        // Properties support
        //
 
        public override object this[object key]
        {
            get
            {
                if (key == typeof(LoggingSinkKey))
                    return this;
                
                return null;
            }

            set
            {
                throw new NotSupportedException();
            }
        }

        public override ICollection Keys
        {
            get
            {
                ArrayList keys = new ArrayList(1);
                keys.Add(typeof(LoggingSinkKey));
                return keys;
            }
        }

        //
        // end of Properties support
        // 
        
    } // class LoggingClientChannelSink
	#endregion

	#region LogginServerChannelSinkProvider
    public class LoggingServerChannelSinkProvider : BaseLoggingChannelSinkProvider, IServerChannelSinkProvider
    {
        private IServerChannelSinkProvider _next = null;      

        public LoggingServerChannelSinkProvider() : base()
        {
        }

        public LoggingServerChannelSinkProvider(IDictionary properties, ICollection providerData) : base()
        {
            if (properties != null)
            {                
                foreach (DictionaryEntry entry in properties)
                {
                    String keyStr = entry.Key.ToString().ToLower(CultureInfo.InvariantCulture);
                    switch (keyStr)
                    {
                    
                    default:
                    {
                        ProcessConfigEntry(entry);
                        break;
                    }

                    } // switch (keyStr)
                }
            }
        } // LoggingServerChannelSinkProvider

        public void GetChannelData(IChannelDataStore channelData)
        {
        }

        public IServerChannelSink CreateSink(IChannelReceiver channel)
        {
            IServerChannelSink nextSink = null;
            if (_next != null)
                nextSink = _next.CreateSink(channel);

            LoggingHelper logger = new LoggingHelper();
            ApplySettingsToLogger(logger);
            LoggingServerChannelSink sink = new LoggingServerChannelSink(logger, nextSink);
            return sink;
        }

        public IServerChannelSinkProvider Next
        {
            get { return _next; }
            set { _next = value; }
        }
    } // class LoggingServerChannelSinkProvider
	#endregion

	#region LoggingServerChannelSink
    internal class LoggingServerChannelSink : BaseChannelObjectWithProperties, IServerChannelSink,
                                              ILoggingSink
    {
        private Int64 _requestId = 0;

        private IServerChannelSink _nextSink = null;
        
        private bool          _bEnabled = true;
        private LoggingHelper _logger = null;
        
        internal class AsyncLoggingData
        {
            private Int64 _requestId;
            private bool _bMessage;

            internal AsyncLoggingData(Int64 requestId, bool bMessage)
            {
                _requestId = requestId;
                _bMessage = bMessage;
            } // AsyncLoggingData  
            
            internal Int64 RequestId { get { return _requestId; } }
            internal bool ShouldLogMessage { get { return _bMessage; } }
        } // class AsyncLoggingData
    
        // hide default constructor
        private LoggingServerChannelSink()
        {
        }

        internal LoggingServerChannelSink(LoggingHelper logger, IServerChannelSink nextSink) : base()
        {            
            _nextSink = nextSink;
            _logger = logger;
        } // LoggingServerChannelSink

        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
                                               IMessage requestMsg,
                                               ITransportHeaders requestHeaders, Stream requestStream,
                                               out IMessage responseMsg, out ITransportHeaders responseHeaders,
                                               out Stream responseStream)
        {
            Int64 requestId = Interlocked.Increment(ref _requestId);

            try
            {
                ServerProcessing processing;

                if (requestMsg != null)
                {
                    // The logging sink is after a formatter, so print some information about
                    //   the message.
                    if (_bEnabled)
                        _logger.PrintRequestMessage(requestId, requestMsg);

                    sinkStack.Push(this, requestId);
                    processing =
                        _nextSink.ProcessMessage(
                            sinkStack, requestMsg, requestHeaders, requestStream,
                            out responseMsg, out responseHeaders, out responseStream);

                    switch (processing)
                    {

                    case ServerProcessing.Complete:
                    {
                        sinkStack.Pop(this);
                        if (_bEnabled)
                            _logger.PrintResponseMessage(requestId, responseMsg);
                        break;
                    }

                    case ServerProcessing.OneWay:
                    {
                        sinkStack.Pop(this);
                        break;
                    }

                    case ServerProcessing.Async:
                    {
                        sinkStack.Store(this, new AsyncLoggingData(requestId, true));
                        break;
                    }

                    } // switch (processing)

                    return processing;
                } // if (requestMsg != null)

                // The logging sink is before the formatter, so log the streams.

                if (_bEnabled)
                    _logger.PrintRequest(requestId, requestHeaders, ref requestStream);

                sinkStack.Push(this, requestId);
                processing =
                    _nextSink.ProcessMessage(sinkStack, null, requestHeaders, requestStream, out responseMsg,
                                             out responseHeaders, out responseStream);

                switch (processing)
                {

                case ServerProcessing.Complete:
                {
                    sinkStack.Pop(this);
                    if (_bEnabled)
                        _logger.PrintResponse(requestId, responseHeaders, ref responseStream);
                    break;
                }

                case ServerProcessing.OneWay:
                {
                    sinkStack.Pop(this);
                    break;
                }

                case ServerProcessing.Async:
                {
                    sinkStack.Store(this, new AsyncLoggingData(requestId, false));
                    break;
                }

                } // switch (processing)

                return processing;
            }
            catch (Exception e)
            {
                _logger.PrintException(requestId, e);
                throw;
            }
        } // ProcessMessage

        public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state,
                                         IMessage msg, ITransportHeaders headers, Stream stream)
        {
            AsyncLoggingData data = (AsyncLoggingData)state;
            Int64 requestId = data.RequestId;
            
            if (_bEnabled)
            {
                if (data.ShouldLogMessage)
                    _logger.PrintResponseMessage(requestId, msg);
                else
                    _logger.PrintResponse(requestId, headers, ref stream);
            }
            sinkStack.AsyncProcessResponse(msg, headers, stream);
        } // AsyncProcessResponse

        public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state,
                                        IMessage msg, ITransportHeaders headers)
        {
            // we always want a stream to read from 
            return null;
        } // GetResponseStream

        public IServerChannelSink NextChannelSink
        {
            get { return _nextSink; }
            set { _nextSink = value; } 
        }

        public bool Enabled
        {
            get { return _bEnabled; }
            set { _bEnabled = value; }
        } // Enabled

        public TextWriter Out
        {
            set { _logger.Out = value; }
        } // Out
        
        //
        // Properties support
        //
 
        public override object this[object key]
        {
            get
            {
                if (key == typeof(LoggingSinkKey))
                    return this;
                
                return null;
            }

            set
            {
                throw new NotSupportedException();
            }
        }

        public override ICollection Keys
        {
            get
            {
                ArrayList keys = new ArrayList(1);
                keys.Add(typeof(LoggingSinkKey));
                return keys;
            }
        }
        //
        // end of Properties support
        // 
    } // class LoggingServerChannelSink
	#endregion

	#region LoggingHelper
    internal class LoggingHelper
    {
        private IHeadersLogger _requestHeadersLogger;
        private ArrayList      _requestStreamLoggers;

        private IHeadersLogger _responseHeadersLogger;
        private ArrayList      _responseStreamLoggers;

        private TextWriter _output;        // output writer
        private bool _bSynchronized;       // should we synchronize output?
        private bool _bIncludeTimestamp;   // should we include a timestamp?
        private bool _bExceptionsOnly;     // should we only log exceptions?
        private bool _bHeadersOnly;        // should we only print headers?

        internal LoggingHelper()
        {
            _requestHeadersLogger = new StandardHeadersLogger();
            _responseHeadersLogger = _requestHeadersLogger;
           
            _requestStreamLoggers = new ArrayList(1);            
            _responseStreamLoggers = new ArrayList(1);            
            
            _output = Console.Out;
            _bSynchronized = true;
            _bIncludeTimestamp = false;
            _bHeadersOnly = false;
        }

        internal IHeadersLogger RequestHeadersLogger
        {
            set { _requestHeadersLogger = value; }
        }

        internal void AddRequestStreamLogger(IStreamLogger logger)
        {
            _requestStreamLoggers.Insert(0, logger);
        }

        internal void AddResponseStreamLogger(IStreamLogger logger)
        {
            _responseStreamLoggers.Insert(0, logger);
        }

        internal TextWriter Out
        {
            get { return _output; }
            set { _output = value; }
        }
        
        internal bool Synchronized
        {
            get { return _bSynchronized; }
            set { _bSynchronized = value; }
        }

        internal bool IncludeTimestamp
        {
            get { return _bIncludeTimestamp; }
            set { _bIncludeTimestamp = value; }
        }

        internal bool HeadersOnly
        {
            get { return _bHeadersOnly; }
            set { _bHeadersOnly = value; }
        }

        internal bool ExceptionsOnly
        {
            get { return _bExceptionsOnly; }
            set { _bExceptionsOnly = value; }
        }
        
        private static void PrintRequestNumberStart(Int64 requestId, TextWriter output)
        {
            output.Write("------------Request #");
            output.Write(requestId);
            output.WriteLine("-------------");
        } // PrintRequestNumberStart

        private static void PrintRequestNumberEnd(Int64 requestId, TextWriter output)
        {
            output.Write("---------End of Request #");
            output.Write(requestId);
            output.WriteLine("---------");
        } // PrintRequestNumberEnd

        private static void PrintResponseNumberStart(Int64 requestId, TextWriter output)
        {
            output.Write("------------Response #");
            output.Write(requestId);
            output.WriteLine("------------");
        } // PrintResponseNumberStart

        private static void PrintResponseNumberEnd(Int64 requestId, TextWriter output)
        {
            output.Write("---------End of Response #");
            output.Write(requestId);
            output.WriteLine("--------");
        } // PrintResponseNumberEnd


        internal void PrintRequest(
            Int64 requestId,
            ITransportHeaders requestHeaders, ref Stream requestStream)
        {
            if (!ExceptionsOnly)
            {
                if (_bSynchronized)
                {
                    lock (_output)
                    {
                        InternalPrintRequest(requestId, requestHeaders, ref requestStream);
                    }
                }
                else
                {
                    InternalPrintRequest(requestId, requestHeaders, ref requestStream);
                }
            }
        } // PrintRequest
    
        private void InternalPrintRequest(
            Int64 requestId,
            ITransportHeaders requestHeaders, ref Stream requestStream)
        {
            PrintRequestNumberStart(requestId, _output);
            PrintTimestampIfNecessary();
            PrintHeadersAndStream(
                _requestHeadersLogger, _requestStreamLoggers,
                requestHeaders, ref requestStream);
            PrintRequestNumberEnd(requestId, _output);
            _output.Flush();
        } // InternalPrintRequest      

        internal void PrintRequestMessage(
            Int64 requestId, IMessage msg)
        {
            if (!ExceptionsOnly)
            {
                if (_bSynchronized)
                {
                    lock (_output)
                    {
                        InternalPrintRequestMessage(requestId, msg);
                    }
                }
                else
                {
                    InternalPrintRequestMessage(requestId, msg);
                }
            }
        } // PrintRequestMessage

        private void InternalPrintRequestMessage(
            Int64 requestId, IMessage msg
            )
        {
            PrintRequestNumberStart(requestId, _output);
            PrintTimestampIfNecessary();
            LogMessage(msg);
            PrintRequestNumberEnd(requestId, _output);
            _output.Flush();
        } // InternalPrintRequestMessage

        
        internal void PrintResponse(
            Int64 requestId,
            ITransportHeaders responseHeaders, ref Stream responseStream)
        {
            if (!ExceptionsOnly)
            {
                if (_bSynchronized)
                {
                    lock (_output)
                    {
                        InternalPrintResponse(requestId, responseHeaders, ref responseStream);
                    }
                }
                else
                {
                    InternalPrintResponse(requestId, responseHeaders, ref responseStream);
                }
            }
        } // PrintResponse

        private void InternalPrintResponse(
            Int64 requestId,
            ITransportHeaders responseHeaders, ref Stream responseStream)
        {           
            PrintResponseNumberStart(requestId, _output);
            PrintTimestampIfNecessary();
            PrintHeadersAndStream(
                _responseHeadersLogger, _responseStreamLoggers,
                responseHeaders, ref responseStream);
            PrintResponseNumberEnd(requestId, _output);
            _output.Flush();      
        } // InternalPrintResponse

        internal void PrintResponseMessage(
            Int64 requestId, IMessage msg)
        {
            if (!ExceptionsOnly)
            {
                if (_bSynchronized)
                {
                    lock (_output)
                    {
                        InternalPrintResponseMessage(requestId, msg);
                    }
                }
                else
                {
                    InternalPrintResponseMessage(requestId, msg);
                }
            }
        } // PrintResponseMessage

        private void InternalPrintResponseMessage(
            Int64 requestId, IMessage msg
            )
        {
            PrintResponseNumberStart(requestId, _output);
            PrintTimestampIfNecessary();
            LogMessage(msg);
            PrintResponseNumberEnd(requestId, _output);
            _output.Flush();
        } // InternalPrintResponseMessage


        internal void PrintException(Int64 requestId, Exception e)
        {
            _output.Write("-----------Exception #");
            _output.Write(requestId);
            _output.WriteLine("------------");
            PrintTimestampIfNecessary();
            _output.WriteLine(e.ToString());
            _output.Write("--------End of Exception #");
            _output.Write(requestId);
            _output.WriteLine("--------");
            _output.Flush();      
        } // PrintException

        private void PrintTimestampIfNecessary()
        {
            if (_bIncludeTimestamp)
            {
                _output.Write("Time: ");
                _output.WriteLine(DateTime.Now.ToString());
            }
        } // PrintTimestampIfNecessary
        
        private void PrintHeadersAndStream(
            IHeadersLogger headersLogger, ICollection streamLoggers,
            ITransportHeaders headers, ref Stream stream)
        {           
            PrintHeaders(headersLogger, headers);
            if (!_bHeadersOnly)
                PrintStream(streamLoggers, headers, ref stream);
        } // PrintHeadersAndStream

        private void PrintHeaders(IHeadersLogger headersLogger, ITransportHeaders headers)
        {
            if (headersLogger != null)
                headersLogger.PrintHeaders(_output, headers);
        } // PrintHeaders

        private void PrintStream(ICollection streamLoggers,
                                 ITransportHeaders headers, ref Stream stream)
        {
            if ((streamLoggers == null) || (stream == null))
                return;

            // If we can't reset the stream's position after printing its content,
            //   we must make a copy.
            if (!stream.CanSeek)
                stream = CopyStream(stream);

            // save the original position of the stream
            long startPosition = stream.Position;

            foreach (IStreamLogger logger in streamLoggers)
            {
                if (logger.CanLog(headers))
                {
                    logger.PrintStream(_output, headers, stream);
                    break;
                }
            }

            // restore the original position of the stream           
            stream.Position = startPosition;
        } // PrintStream
        
        private void LogMessage(IMessage msg)
        {
            if (msg == null)
                return;

            IDictionary properties = msg.Properties;
            IDictionaryEnumerator e = properties.GetEnumerator();
            while (e.MoveNext())
            {
                DictionaryEntry entry = e.Entry;                
                _output.Write(entry.Key);
                _output.Write(": ");
                _output.WriteLine(entry.Value);
            }
        } // LogMessage

        private static Stream CopyStream(Stream stream)
        {
            Stream streamCopy = new MemoryStream();

            const int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];

            int readCount;
            do
            {
                readCount = stream.Read(buffer, 0, bufferSize);
                if (readCount > 0)
                    streamCopy.Write(buffer, 0, readCount);
            } while (readCount > 0);
            
            // close original stream
            stream.Close();

            streamCopy.Position = 0;
            return streamCopy;
        } // CopyStream  

    } // class LoggingHelper
	#endregion

	#region LoggingUtil
    internal class LoggingUtil
    {        
        internal static String MapPath(String filename)
        {
            // check to see if we are hosted in IIS, and if so, map the path to the
            //   current vdir.

            HttpContext context = HttpContext.Current;
            if (context != null)
            {
                try
                {
                    filename = context.Request.MapPath(filename);
                } 
                catch
                {
                    // ignore any exceptions, so we'll just use the original path.
                }                
            }

            return filename;
        } // MapPath

        internal static bool StringStartsWithDoubleUnderscore(String str)
        {
            if ((str == null) || (str.Length < 2))
                return false;

            return (str[0] == '_') && (str[1] == '_');
        } // StringStartsWithDoubleUnderscore

        internal static void WriteHeader(TextWriter output, DictionaryEntry header)
        {
            output.Write(header.Key);
            output.Write(": ");
            output.WriteLine(header.Value);    
        } // WriteHeader

    } // class LoggingUtil
	#endregion

	#region IHeadersLogger
    internal interface IHeadersLogger
    {
        void PrintHeaders(TextWriter output, ITransportHeaders headers);
    } //  interface IHeadersLogger
	#endregion

	#region IStreamLogger
    internal interface IStreamLogger
    {
        bool CanLog(ITransportHeaders headers);

        void PrintStream(TextWriter output, ITransportHeaders headers, Stream stream);
    } //  interface IHeadersLogger
	#endregion

	#region StandardHeadersLogger
    internal class StandardHeadersLogger : IHeadersLogger
    {
        public void PrintHeaders(TextWriter output, ITransportHeaders headers)
        {
            InternalPrintHeaders(output, headers);
        } // PrintHeaders

        internal static void InternalPrintHeaders(TextWriter output, ITransportHeaders headers)
        {
            output.WriteLine("------Headers-------");

            foreach (DictionaryEntry header in headers)
            {
                LoggingUtil.WriteHeader(output, header);               
            }
            
            output.WriteLine("---End of Headers---");
        } // InternalPrintHeaders

    } // class StandardHeadersLogger
	#endregion

	#region TextStreamLogger
    internal class TextStreamLogger : IStreamLogger
    {
        public bool CanLog(ITransportHeaders headers)
        {
            String contentType = headers["Content-Type"] as String;
            if ((contentType != null) && contentType.StartsWith("text"))                         
                return true;            

            return false;
        } // CanLog

        public void PrintStream(TextWriter output, ITransportHeaders headers, Stream stream)
        {
            InternalPrintStream(output, headers, stream);
        }

        internal static void InternalPrintStream(TextWriter output, ITransportHeaders headers, Stream stream)
        {
            // If there is a Content-Type header and it starts with "text", print the
            //   buffer contents.
            String contentType = headers["Content-Type"] as String;
            if ((contentType != null) && contentType.StartsWith("text"))
            {
                StreamReader sr = new StreamReader(stream);
                String line;
                while ((line = sr.ReadLine()) != null)
                {
                    output.WriteLine(line);
                }
            }
        }
    } // class TextStreamLogger
	#endregion

	#region ILoggingSink
    public interface ILoggingSink
    {
        bool Enabled { get; set; }

        TextWriter Out { set; }
    } // interface ILoggingSink
	#endregion

	#region LogginSinkKey
    // The type of this class is used as the key to get the ILoggingSink interface
    // to one of the logging sinks.
    public class LoggingSinkKey
    {
    }
	#endregion

    //
    // SPECIALIZED LOGGERS
    //
    
	#region HttpHeadersLogger
    // Prints out http headers in literal form
    internal class HttpHeadersLogger : IHeadersLogger
    {
        public void PrintHeaders(TextWriter output, ITransportHeaders headers)
        {
            String verb = (String)headers["__RequestVerb"];
            String requestUri = (String)headers[CommonTransportKeys.RequestUri];
            String version = (String)headers["__HttpVersion"];

            // Make sure that all three components of the first line of an http request are
            //   present; otherwise, just print out the headers in the usual fashion. 
            if ((verb == null) || (requestUri == null) || (version == null))
            {
                StandardHeadersLogger.InternalPrintHeaders(output, headers);
                return;
            }
            
            // Print "special headers" first.
            output.WriteLine("--Special Headers---");
            foreach (DictionaryEntry header in headers)
            {
                String keyStr = header.Key.ToString();
                if (LoggingUtil.StringStartsWithDoubleUnderscore(keyStr))
                {                    
                    // display the header if it isn't one of the http special headers
                    if ((String.Compare(keyStr, "__RequestVerb", true, CultureInfo.InvariantCulture) != 0) &&
                        (String.Compare(keyStr, CommonTransportKeys.RequestUri, true, CultureInfo.InvariantCulture) != 0) &&
                        (String.Compare(keyStr, "__HttpVersion", true, CultureInfo.InvariantCulture) != 0))
                    {
                        LoggingUtil.WriteHeader(output, header);                    
                    }
                }
            }

            // Now print out rest, so that the log looks like a literal HTTP request
            output.WriteLine("--------HTTP--------");

            // first line
            output.Write(verb);
            output.Write(" ");
            output.Write(requestUri);
            output.Write(" ");
            output.WriteLine(version);

            foreach (DictionaryEntry header in headers)
            {
                String keyStr = header.Key.ToString();
                if (!LoggingUtil.StringStartsWithDoubleUnderscore(keyStr))
                    LoggingUtil.WriteHeader(output, header);                  
            }
            
            // The end of headers marker.
            output.WriteLine(); 

            // The content stream will immediately follow, so don't write anything else here.

        } // PrintHeaders
    } // class HttpHeadersLogger
	#endregion

	#region XmlTextStreamLogger
    internal class XmlTextStreamLogger : IStreamLogger
    {
        public bool CanLog(ITransportHeaders headers)
        {
            String contentType = headers["Content-Type"] as String;
            if ((contentType != null) && contentType.StartsWith("text"))
            {                
                if (contentType.StartsWith("text/xml"))
                    return true;
            }

            return false;
        } // CanLog

        public void PrintStream(TextWriter output, ITransportHeaders headers, Stream stream)
        {                
            try
            {                        
                XmlTextReader reader = new XmlTextReader(new StreamReader(stream));
                reader.WhitespaceHandling =  WhitespaceHandling.Significant;
                XmlTextWriter xtw = new XmlTextWriter(output);                        
                xtw.Formatting = Formatting.Indented;
                xtw.Indentation = 2;
                
                xtw.WriteNode(reader, true);
                output.WriteLine();
            }
            catch (Exception e)
            {
                output.WriteLine("*****EXCEPTION*****");
                output.WriteLine(e);
            }
        } // PrintStream

    } // class XmlTextStreamLogger
	#endregion

	#region BinaryStreamLogger
    internal class BinaryStreamLogger : IStreamLogger
    {
        private int _bytesPerLine;
        
        internal BinaryStreamLogger(int bytesPerLine)
        {
            _bytesPerLine = bytesPerLine;
        }

        public bool CanLog(ITransportHeaders headers)
        {
            return true;
        } // CanLog

        public void PrintStream(TextWriter output, ITransportHeaders headers, Stream stream)
        {
            // 3 spaces per byte for numeric representation
            StringBuilder byteSb = new StringBuilder(_bytesPerLine * 3);
            // 1 space for each character representation
            StringBuilder charSb = new StringBuilder(_bytesPerLine);
            
            bool bFoundEnd = false;
            while (!bFoundEnd)
            {
                byteSb.Length = 0;
                charSb.Length = 0;
                
                int co = 0;
                while (co < _bytesPerLine)
                {
                    // try to read in the next element
                    int b = stream.ReadByte();
                    if (b == -1)
                    {
                        bFoundEnd = true;
                        break;
                    }

                    ByteToHexString(byteSb, (byte)b);
                    byteSb.Append(' ');       
                
                    DumpChar(charSb, (char)b);               

                    co++;
                }

                output.Write(byteSb.ToString());
                if (co < _bytesPerLine)
                {
                    // fill in the space so that the last line of character
                    //   output will be properly aligned.
                    while (co++ < _bytesPerLine)
                    {
                        output.Write("   "); // 3 spaces
                    }
                }
                output.Write("   ");
                output.WriteLine(charSb.ToString());
            }
            
        } // PrintStream

        private static void DumpChar(StringBuilder sb, char ch)
        {
            if (!Char.IsLetterOrDigit(ch) &&
                !Char.IsSymbol(ch) &&
                !Char.IsPunctuation(ch) &&
                (ch != ' '))
            {
                ch = '~';
            }

            sb.Append(ch);
        } // DumpChar

        private static void ByteToHexString(StringBuilder sb, byte b)
        {
           sb.Append(DecimalToCharacterHexDigit(b >> 4));
           sb.Append(DecimalToCharacterHexDigit(b & 0xF));  
        } // ByteToHexString

        private static char DecimalToCharacterHexDigit(int i)
        {
            switch (i)
            {
            case 15: return 'F';
            case 14: return 'E';
            case 13: return 'D';
            case 12: return 'C';
            case 11: return 'B';
            case 10: return 'A';
            default: return (char)(i + (byte)'0');
            } 
        } // DecimalToCharacterHexDigit

    } // class BinaryStreamLogger
	#endregion

} // namespace Logging
