Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Logging Indigo Messages on Console host process

0.00/5 (No votes)
5 Apr 2004 1  
The Tiny Logger is a lightweight logging solution for an Indigo Message Exchange pattern on the MessageBus. This article describes how to build an Indigo Port Extension to log and publish messages on the Console host process . It is useful for evaluating the MSDN Indigo samples or for test purposes.

This article has been written based on a pre-PDC 2003 build of Microsoft Windows Code Name "Longhorn". This preview is far from the beta version and I am sure it will not work for some details. I will post its update ASAP. I included in this document, implementation of the logging classes, so if you don't have the PDC bits, just look at them here.

Contents

Introduction

The Indigo (part of the next generation of MS Windows code - Longhorn) is a new message driven communication model for the Service Oriented Architecture that enables secure, reliable, transactional messaging across heterogeneous communication systems. The following figure shows its logical model:

The above high abstracted communication model is based on the Software Bus that allows to connect an application code via the Service connector. The Service layer encapsulates a connectivity in the business logical model, where the consumer of the service doesn't need to know where a physical business logic is located and how it is implemented. The Indigo represents a loosely coupled message pushing communication model based on the Message Exchange Patterns. The Indigo Message flows through the Software Bus to the destination Service in direct or forwarding manner.

The Software Bus standardizes a connectivity between the Services using the SoapMessage envelope abstracted into the Message, where:

  • Headers represent the communication pattern (connectivity, message exchange, etc.) to process the message on the Software Bus.
  • Content represents the business data passing to the CodeBehind via a Service connector. Note that in the special cases such as WS-Eventing, the message content can be also part of the communication pattern.

The Message on the Software Bus is a stateful object with predefined lifetime and behavior at the specified Service node. Based on the type of Headers, the Message travels with or without the Content. This behavior is controlled by the Service Managers as a logical conversation process between the Service and its consumer.

The Software Bus is implemented in the Indigo model by the System.MessageBus namespace.

The CodeBehind (business logic) is connected to the Software Bus via the Service Connector. The connector encapsulates the three major parts of the Indigo infrastructure. The following picture shows their position in the message workflow:

Port represents the in/out gateway to the service. The Message flows through the send or/and receive pipeline stages in the logical manner to process a specific task based on the message headers. Using the base PortExtension class, the message workflow in the port pipeline can be customized for additional purposes such as logging, custom conversation, etc. Note that the Port is connected to the wire (Software Bus) via a Transport adapter to handle an underlying wire protocol for a specific format such as HTTP or TCP (or custom, of course).

Channel represents a layer to perform a communication behavior and transferring a message pattern to/from the port. There are two kinds of channels in the service connector:

  • Untyped channel represents a lower level untyped message pattern to receive, send, reverse and forward messages. This type of channel is implemented as a PortExtension.
  • Typed channel is the highest layer of the strong message exchange pattern between the service and port. The Indigo infrastructure has built-in two typed Channels such as Datagram (no guarantees of message delivering) and Dialog channels for reliable and transacted exchange messages between the services.

Service represents a top-most layer in the Indigo infrastructure encapsulating a communication model from the business logic, using the strongly typed method invoking design pattern.

The communication complexity based on the application requirements is provided in the Indigo by its Service Managers. The Managers populate the Port pipeline using built-in PortExtensions such as DialogPortExtension, ReplyPortExtension and etc. This "load" process can be done administratively or programmatically based on the application needs. Note that before the port has been opened, the configuration changes are acceptable. The Indigo has predefined a ServiceEnvironment (known as "main" - see the machine.config file) template configuration, which can be adjusted in the app.config file or programmatically in the host process file.

The Indigo is a SoapMessage driven communication model using the pushing mechanism between the nodes (Ports). From the Service point of view, the message conversation between the Services are fully transparent, and without a logging mechanism it is very difficult for troubleshooting. This article describes a Tiny Logger of the SoapMessages published on the console host process. In many cases, like the MSDN Indigo Samples, the console program is a simple host process without any additional functionality specially on the server side, so why can it not be useful for logging messages from/to the Software Bus? Let's look at how it can be implemented in the Indigo.

Features

The Tiny Indigo Logger has the following features:

  • Logging incoming messages to the Port
  • Logging outgoing messages from the Port
  • Publishing Messages on the Console host process
  • XML text formatted output layout
  • Loosely coupled plug-in using the host process (or machine) config file
  • Programmatically plug-into the Port

The following screen snippet shows the objects on the opened Port captured in the Indigo BothWayServiceMessageSession sample at the Server side:

Concept

The Message Logging concept is based on injecting the publisher handler in the port pipeline. The following picture shows capturing the Messages in the receive Port before the Spy stage (first PortExtension handler).

The incoming SoapMessage from the Transporter flows to the ConsolePublisher (alias stage of the Spy) where it is captured and dumped on the console screen and then forwarded to the original Spy's stage handler and so on. The captured message is colored making them different from the messages driven by the application. Every message has its own unique ID based on the communication service pattern, for instance, MessageId, RelatesTo, SequenceId, SequenceAckowledgementId, etc. This unique ID is bolded on the console screen - see the above picture.

The included Tiny Logger solution has a hard coded position of the capturing messages in the port workflow, but it is very easy to change for another one or add more stages based on the configuration properties.

Usage

The Tiny Logger requires to have a Console host process, and PortExtensionLib assembly installed into the GAC or in your application bin folder.

The ConsoleLoggingPort extension can be injected into the Port using one of the following ways:

  • Tightly coupled - creating the instance of the ConsoleLoggingPort(Port port) or ConsoleLoggingPort(Port port ConsoleLoggingSettings settings) before the Port is opened in your host program.
  • Loosely coupled - inserting into the <serviceEnvironment> section of the host process' app.config file - see the following config snippet from the Indigo Sample, element <rk.logging mode="on" color="on" details="true" setconsole="true"/>.
    <configuration>
      <system.messagebus>
        <!--
        <configurationHandlers> 
          <add name="serviceEnvironmentItems">
            <add name="rk.logging" 
              type="RKiss.PortExtensionLib.ConsoleLoggingPortConfigHandler, 
              RKiss.PortExtensionLib" />
          </add>
        </configurationHandlers> 
        -->
        <serviceEnvironments>
        <serviceEnvironment name="main">
          <port> 
            <identityRole>
              soap.tcp://localhost:46001/HelloClient/</identityRole>
          </port>
          <!-- CAUTION: Security disabled for 
                  demonstration purposes only. -->
          <remove name="securityManager" />
          <policyManager>
            <!-- CAUTION: Security disabled for 
                 demonstration purposes only. -->
            <!-- Permits unsigned policy statements. 
                 Default requires signed policy statements -->
            <areUntrustedPolicyAttachmentsAccepted>true
                 </areUntrustedPolicyAttachmentsAccepted>
            <isPolicyReturned>true</isPolicyReturned>
          </policyManager>
          <rk.logging mode="on" color="on" 
                  details="true" setconsole="true" /> 
        </serviceEnvironment>
       </serviceEnvironments>
     </system.messagebus>
    </configuration>

    Note that the serviceEnvironmentItem <rk.logging /> can be referenced locally like it's shown in the above commented section, or machine wide in the machine.config:

    <add name="serviceEnvironmentItems">
     <add name="dialogManager" 
     type="System.MessageBus.Configuration.DialogManagerConfigurationHandler, 
       System.MessageBus, 
     Version=1.2.3400.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      ...
     <add name="rk.logging" 
       type="RKiss.PortExtensionLib.ConsoleLoggingPortConfigHandler, 
       RKiss.PortExtensionLib, 
       Version=1.0.0.0, Culture=neutral, PublicKeyToken=5e79b308cfc5975f" />
    </add>

The Console Publisher can be configured by setting its properties:

NAME DEFAULT VALUE NOTE
mode "on" Enable/Disable Logging
send "true" Log outgoing messages
receive "true" Log incoming messages
details "false" Log headers
color "on" Color on/off
setconsole "true" Set-up the console buffer and windows

Here is an example:

<rk.logging mode="on" color="on" details="true" setconsole="true" />

and its result shows the following screen snippet:

I am recommending the following configuration set-up of the Tiny Logger:

  • installing the PortExtensionLib assembly into the GAC.
  • adding the ConsoleLoggingPortConfigHandler into the machine.config file.
  • adding the <rk.logging /> section into the host process config file.

Note that the Tiny Logger doesn't have any run time user interface. The messages are written to the console circulated buffer and they are managed based on the standard features of the Console such as Find, Select, Copy, etc.

Implementation

The Tiny Logger implementation is divided into three parts - files based on their functionalities:

  • ConsoleLoggingSettings.cs

    Basically, this class has only one responsibility - wire up the ConsoleLoggingPort class to the Port. Before calling the WireUp method, the Service Manager calls the Create method to retrieve the configurable properties packaged in the ConsoleLoggingSettings object (its type is obtained by method ServiceComponentType). The following code snippet shows the implementation in detail:

    public class ConsoleLoggingPortConfigHandler: 
                         IServiceEnvironmentConfigurationHandler
    {
       public ConsoleLoggingPortConfigHandler()
       {
          Trace.WriteLine("ConsoleLoggingPortConfigHandler ctor");
       }
       public object Create(object parent, object context, XmlNode section)
       {
          ConsoleLoggingSettings settings = new ConsoleLoggingSettings();
    
          // Exit if there are no configuration settings.
    
          if(section == null)
             return settings;
    
          XmlNode currentAttribute;
          XmlAttributeCollection nodeAttributes = section.Attributes;
    
          // Get the mode attribute.
    
          currentAttribute = nodeAttributes.RemoveNamedItem("mode");
          if(currentAttribute != null && 
            currentAttribute.Value.ToUpper(CultureInfo.InvariantCulture)
                                            == "OFF")
          {
             settings.Mode = LoggingMode.Off;
          }
    
          // Get the send attribute.
    
          currentAttribute = nodeAttributes.RemoveNamedItem("send");
          if(currentAttribute != null)
          {
             settings.Send = Convert.ToBoolean(currentAttribute.Value);
          }
    
          // other attributes ...
    
    
          return settings;
       }
       public void WireUp(object items, ServiceEnvironment se)
       {
          Port port = se[typeof(Port)] as Port;
          ConsoleLoggingPort clp = new 
            ConsoleLoggingPort(port, items as ConsoleLoggingSettings);
    
          Trace.WriteLine("ConsoleLoggingPortConfigHandler WireUp is done.");
       }
       public Type ServiceComponentType
       {
          get
          {
             //register our settings object
    
             return typeof(ConsoleLoggingSettings);
          }
       }
    }
  • ConsoleLoggingPortEx.cs

    This class has the responsibility for injecting the ConsolePublisher into the proper stage of the Port pipeline using the Indigo pipeline stage strategy and the PortExtension base class support. The ConsoleLoggingPort class is derived from the PortExtension class necessary for the Indigo plumbing pattern. We need to create two stages such as stage for our Console Publisher (logging) and the next one called as alias which represents the original stage (in our logger solution - the Transmit stage respectively the Spy stage). The Indigo Port pipeline model has an open stage infrastructure so we can create more stages, for instance, the stage after the Transmit stage and etc. Notice that the pipeline stage strategy needs to have a valid handler for each known stage, otherwise the message workflow will have an undefined behavior.

    public class ConsoleLoggingPort : PortExtension
    {
       ConsoleLoggingSettings settings;
       // the logging stages 
    
       static Stage aliasedStage;
       static Stage loggingStage;
    
       #region Stages used for send pipeline.
       StageAlias[] sendAliases;
       Stage[] sendStages;
       IMessageHandler[] sendHandlers;
       #endregion
    
       #region Stages used for receive pipeline.
       StageAlias[] receiveAliases;
       Stage[] receiveStages;
       IMessageHandler[] receiveHandlers;
       #endregion
    
       #region Constructor
       // Constructor used by the configuration system, internal only
    
       internal ConsoleLoggingPort() : this(null) {}
       
       // Most commonly used ctor - defaults values
    
       public ConsoleLoggingPort(Port port) : this(port, null) {}
    
       // Constructor for config file
    
       public ConsoleLoggingPort(Port port, ConsoleLoggingSettings settings)
       {
         // config properties
    
         this.settings = settings == null ? 
              new ConsoleLoggingSettings() : settings;
    
         // We have to create two stages with the following unique names 
    
         string strGuid = Guid.NewGuid().ToString();
         loggingStage = new 
            Stage(new Uri(string.Format("uuid:{0};id=0", strGuid)));
         aliasedStage = new 
            Stage(new Uri(string.Format("uuid:{0};id=1", strGuid)));
    
         if(this.settings.Mode == LoggingMode.On)
         {
            if(this.settings.Send)
            {
              #region Message workflow stages for Send handlers 
              // The first one is our logging and next one is Transmit.
    
              Stage[] aliasedSendStages = new 
                                 Stage[] { loggingStage, aliasedStage };
    
              // The stage for our logging handler.
    
              sendStages = new Stage[] { loggingStage };
    
              // this StageAlias will replace an actualy
    
              // Transmit stage in the send pipeline
    
              sendAliases = new StageAlias[] 
                            { new StageAlias(PortSendStages.Transmit, 
                            aliasedStage, aliasedSendStages) };
              #endregion
    
              #region attach the ConsolePublisher
              sendHandlers = new IMessageHandler[] 
                       {new ConsolePublisher("SEND", this.settings)};
              #endregion
           }
    
           if(this.settings.Receive)
           {
              #region Message workflow stages for Receive handlers
              // The first one is our logging and next one is Spy.
    
              Stage[] aliasedReceiveStages = 
                  new Stage[] { loggingStage, aliasedStage };
    
              // The stage for our logging handler.
    
              receiveStages = new Stage[] { loggingStage };
    
              // this StageAlias will replace an actualy
    
              // Spy stage in the receive pipeline
    
              this.receiveAliases = 
                 new StageAlias[] {new StageAlias(PortReceiveStages.Spy, 
                 aliasedStage, aliasedReceiveStages) };
              #endregion
    
              #region attach the ConsolePublisher
              receiveHandlers = new IMessageHandler[] 
                     { new ConsolePublisher("RECEIVE", 
                     this.settings) };
              #endregion
           }
    
           #region remove and add our extension from the port pipeline
           if(port.Extensions.Contains(this))
              port.Extensions.Remove(this);
           else
              port.Extensions.Add(this);
           #endregion
         }
       }
       #endregion
    
       #region PortExtension - overrides
       public override IMessageHandler[] 
         CreateReceiveHandlers() { return receiveHandlers; }
       public override IMessageHandler[] 
         CreateSendHandlers()    { return sendHandlers;    }
       public override StageAlias[] 
         GetSendAliases() { return sendAliases; }
       public override Stage[] 
         GetSendStages() { return sendStages; }
       public override StageAlias[] 
         GetReceiveAliases() { return receiveAliases; }
       public override Stage[] 
         GetReceiveStages() { return receiveStages; }
       public override void OnOpening()
       { 
          #region Logo
          Console.ForegroundColor = ConsoleColor.Green;
          Console.WriteLine("********************" + 
              "****************************************");
          Console.WriteLine(" Tiny Console Logger Version" + 
              " 0.9, created by Roman Kiss     ");
          Console.WriteLine("********************" + 
              "****************************************");
          Console.ResetColor();
          #endregion
    
          #region Setup Screen
          if(settings.SetConsole)
          {
             Console.SetBufferSize(300, 5000);
             Console.SetWindowSize(75, 50);
          }
          #endregion
    
          #region dump all port collections
          Console.ForegroundColor = settings.Color == ColorMode.On ? 
             ConsoleColor.Cyan : Console.ForegroundColor;
             
          Console.WriteLine(">>> The Port has been opened");
          
          Console.ForegroundColor = settings.Color == ColorMode.On ? 
             ConsoleColor.DarkCyan : Console.ForegroundColor;
          
          Console.WriteLine("PortExtensions:");
          foreach(object obj in this.Port.Extensions)
             Console.WriteLine(" " + obj.ToString());
    
          Console.WriteLine("PortFormatters:");
          foreach(object obj in this.Port.Formatters)
             Console.WriteLine(" " + obj.ToString());
    
          Console.WriteLine("PortHeaderTypes:");
          foreach(object obj in this.Port.HeaderTypes)
             Console.WriteLine(" " + obj.ToString());
    
          Console.WriteLine("PortTransportAddresses:");
          foreach(object obj in this.Port.TransportAddresses)
             Console.WriteLine(" " + obj.ToString());
    
          Console.WriteLine("PortTransports:");
          foreach(object obj in this.Port.Transports)
             Console.WriteLine(" " + obj.ToString());
    
          Console.WriteLine("==========================" + 
                             "================================");
          Console.WriteLine("");
          Console.ResetColor();
          #endregion
       }
       public override void OnClosed()
       {
          Console.WriteLine("The Port has been closed");
       }
       #endregion
    }
  • ConsolePublisher.cs

    This endpoint class is our Message processing handler. It must be derived from the SyncMessageHandler base class to be part of the pipeline message workflow. Its implementation is straightforward to write the Message envelope (such as Headers and Body) on the Console screen using the XML text formatted layout.

    #region Console Publisher
    // Simple handler that delegates message
    
    // to the Console in the xml formatted text
    
    class ConsolePublisher : SyncMessageHandler
    {
       #region Private Members
       private string prompt;
       #endregion
    
       #region Constructor
       ConsoleLoggingSettings settings;
       public ConsolePublisher(string prompt, 
           ConsoleLoggingSettings settings) : base()
       {
          this.prompt = prompt;
          this.settings = settings == null ? 
             new ConsoleLoggingSettings() : settings;
       }
       #endregion
    
       #region ProcessMessage
       public override bool ProcessMessage(Message msg)
       {
          #region validation
          if(msg == null)
             throw new ArgumentNullException("message");
    
          if(msg.Encoding == null)
             throw new ArgumentException("message");
          #endregion
    
          lock(this)
          {
             #region Capture message
             Message message = msg.Clone();
    
             try
             {
                #region Prompt Line
                MessageIdHeader mih = 
                  message.Headers[typeof(MessageIdHeader)] 
                  as MessageIdHeader;
                RelatesToHeader rth = 
                  message.Headers[typeof(RelatesToHeader)] 
                  as RelatesToHeader;
                MessageHeader WsrmSeq = message.Headers["Sequence", 
                      @"http://schemas.xmlsoap.org/ws/2003/02/RM"] 
                      as MessageHeader;
                MessageHeader WsrmSeqAck = 
                      message.Headers["SequenceAcknowledgement", 
                      @"http://schemas.xmlsoap.org/ws/2003/02/RM"] 
                      as MessageHeader;
    
                // select properly Id to show up 
    
                string strId = "Unknown Message Id";
    
                if(mih != null)
                {
                   strId = mih.MessageId.ToString();
                }
                else if(rth != null)
                {
                   strId = string.Concat("RelatesTo: " 
                             + rth.RelatesTo.ToString());
                }
                else if(WsrmSeq != null) 
                {
                   strId = string.Concat("Sequence: ", 
                           WsrmSeq.Element.InnerText.Substring(
                           WsrmSeq.Element.InnerText.LastIndexOf('/') + 1));
                }
                else if(WsrmSeqAck != null)
                {
                   strId = string.Concat("SequenceACK: ", 
                           WsrmSeqAck.Element.InnerText.Substring(
                         WsrmSeqAck.Element.InnerText.LastIndexOf('/') + 1));
                }
    
                Console.WriteLine("");
                Console.ForegroundColor = settings.Color == ColorMode.On ? 
                   ConsoleColor.Yellow : Console.ForegroundColor;
                
                Console.WriteLine(string.Format(">>> {0} {1}", 
                                                          prompt, strId));
                
                Console.ForegroundColor = settings.Color == ColorMode.On ? 
                   ConsoleColor.DarkYellow : Console.ForegroundColor;
                #endregion
    
                #region Message headers
                foreach(MessageHeader mh in message.Headers)
                {
                   try
                   {
                      if(settings.Details)
                      {
                         XmlNode node = mh.Element;
    
                         if(node != null)
                            Console.WriteLine(OutputXmlLayout(node.OuterXml));
                      }
                      else
                         Console.WriteLine(mh.Name + ": " + mh.StringValue);
                    }
                    catch(Exception ex)
                    {
                       Console.WriteLine(string.Format("[{0}={1}], 
                               {2}", mh.Name, mh.StringValue, ex.Message));
                    }
                }
                #endregion
    
                #region Message Content
                Console.WriteLine("-----------" + 
                  "--<Message.Content>---------------------------");
    
                XmlReader reader = message.Content.Reader;
                XmlDocument document = new XmlDocument(reader.NameTable);
    
                while(reader.MoveToContent() == XmlNodeType.Element)
                {
                   try
                   {
                      XmlNode node = document.ReadNode(reader);
    
                      if(node != null)
                         Console.WriteLine(OutputXmlLayout(node.OuterXml));
                   }
                   catch(Exception ex)
                   {
                      Console.WriteLine(ex.Message);
                   }
                }
    
                Console.WriteLine("===========" + 
                         "==============================================");
                Console.WriteLine("");
                #endregion
    
             }
             catch(Exception ex)
             {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Logging Internal Error: {0}", 
                                                    ex.Message);
             }
             finally
             {
                //clean-up
    
                Console.ResetColor();
                message.Close();
             } 
             #endregion
          }
    
          // follow next handler in the pipeline
    
          return true;
       }
       #endregion
    
       #region Helpers
       /// <summary>Helper class to layout
    
       /// the xml formatted string</summary>
    
       /// <param name="strSource"></param>
    
       /// <returns></returns>
    
       private string OutputXmlLayout(string strSource)
       {
          StringBuilder sb = new StringBuilder();
    
          using(StringWriter sw = new StringWriter(sb))
          {
             XmlTextWriter tw = new XmlTextWriter(sw);
    
             tw.Indentation = 3;
             tw.Formatting = Formatting.Indented;
    
             // load xml formatted text from source string
    
             XmlDocument doc = new XmlDocument();
    
             doc.LoadXml(strSource);
    
             // save to the writer
    
             doc.Save(tw);
             tw.Flush();
             tw.Close();
         }
    
         return sb.ToString();
       }
       #endregion
    }
    #endregion

Conclusion

In this article, I showed you how can Indigo Port pipeline be extended for the custom stage. It is based on the PortExtension sample from the MSDN Indigo Samples. Usually, for test purpose, often used is the Console process (see MSDN Indigo Samples) hosting your services and/or remotable objects. Simply plug-in the Tiny Logger to the host process configuration, you can see the Indigo conversation pattern between the service and its consumer on the Console screen. I hope you will enjoy it.

Appendix

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here