Logging Indigo Messages on Console host process






3.40/5 (22 votes)
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 PortExtension
s 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)
orConsoleLoggingPort(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 theWireUp
method, the Service Manager calls theCreate
method to retrieve the configurable properties packaged in theConsoleLoggingSettings
object (its type is obtained by methodServiceComponentType
). 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. TheConsoleLoggingPort
class is derived from thePortExtension
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
- [1] Longhorn SDK (Indigo Samples)
- [2] Indigo newsgroup
- [3] A Guide to Developing and Running Connected Systems with Indigo.
- [4] On the road to Indigo
- [5] Indigo Hub
- [6] Writing Asynchronous, Bidirectional, Stateful, Reliable Web Services with Indigo.
- [7] Digging into Channel Types
- [8] Inside "Indigo," Chapter 2: The Journey of a Message
- [9] Longhorn Indigo - MSDN.
- [10] Creating Indigo Applications with the PDC Release of Visual Studio .NET Whidbey
- [11] Introducing Microsoft WinFX written by Brent Rector, published by Microsoft Press, chapter 6 (Communication).