Introduction
When I first started using Visual Studio, I was disappointed that Trace output goes into a standard text window within the IDE. What I needed was a record oriented logging system with output to a graphical console. My solution was a fairly simple solution based on .NET remoting and Windows .NET Forms.
The Console
The console simply presents a list of logging messages received by a client. You can apply sorts on the columns. New messages will be placed at the top of the list.
There is no absolute guarantee that the messages will show up in the exact same order that they were sent. This is because I am using the default thread pool with .NET remoting. So, even though the messages will be sent in order, the console may not execute the messages in the order received. It appears, though, that this is a very rare situation. Most of the time, the console will show the message in the correct order. Regardless of message execution ordering, the timestamps on entries will always reflect the time the message was sent.
You can drill down on individual messages on the console by double clicking a row. You will get something like this:
The Console Configuration
The application configuration file should read as follows:
="1.0"="utf-8"
<configuration>
<appSettings>
<add key="port" value="1439"/>
</appSettings>
</configuration>
You will also want to make sure that the firewall allows this port to be opened. You can use any port you wish. You will just have to make sure that the port specified in the client config file is the same.
The Client API
The API is fairly simple. First, add a references to the logger_client.dll from the logger project.
Second, add a reference within your code:
using LiveSwitch.LoggerClient;
Next, instantiate a Logger proxy. These are lightweight objects, so they don't have to be disposed. You can create as many as necessary without wasting heavyweight resources.
Logger logger = new Logger("component name");
The logger class has six relevant methods:
logger.Debug("text");
logger.Info("text");
logger.Warning("text");
logger.Warning("text", exception);
logger.Error("text");
logger.Error("text", exception);
Client Configuration
The client application requires an application setting in its configuration file. Note that you cannot use application properties, since the dependent client DLL needs to read the executable's config file, and it does not have access to the code generated by the IDE.
An application's properties are provided in a file named <exe file name>.config.
Here is an example:
="1.0"="utf-8"
<configuration>
<appSettings>
<add key="loggerUrl" value="tcp://localhost:1439/logger"/>
</appSettings>
</configuration>
Internals
Messages are batched on the client library side and sent by a single worker thread. The following is the main loop, shared by all client logger instances, used to ship batches of requests to the logger application.
private void Start()
{
while (true)
{
ILogger.LogMessage[] msgs;
lock (this)
{
while (_list.Count == 0)
{
Monitor.Wait(this);
}
msgs = _list.ToArray();
_list.Clear();
}
_client.Log(msgs);
}
}
As you can see, the logger client library background thread uses monitor locks to synchronize with the application.
Here, you can see the application's info logger notifying the background thread that data is ready.
internal void LogInfo(string comp, string data)
{
ILogger.LogMessage msg =
new ILogger.LogMessage(ILogger.LogMessageType.INFO,
data, comp, DateTime.Now, _host, null);
lock (this)
{
_list.Add(msg);
Monitor.Pulse(this);
}
}
As you can see, the interaction is very textbook.
Conclusion
This logging system has been extremely helpful while developing applications. I hope some of you will find it useful.