Introduction
The Microsfot Enterprise Library provide us a collection of reusable software components, so software developers can take advantage of them to put them into enterprise use. Now I will share my personal experiences with my friends of how to extend current enterprise library functionality and improve the performance of enterprise applications.
Context
We currently have a project which is intended to create a data repository cache for reference data and the requirement from the customer is that we need to log everything the user does. Currently, our company still uses enterprise lib 2.0 due to some security reasons. It is enough to fulfill the log function using a text formatter but that causes poor readability.
Let's start
Okay, first, we need to code our own xmlformatter which must extend from LogFormatter.
[ConfigurationElementType(typeof(CustomFormatterData))]
public class LongXmlFormatter : LogFormatter
{
private NameValueCollection Attributes = null;
public LongXmlFormatter(NameValueCollection attributes)
{
this.Attributes = attributes;
}
public override string Format(LogEntry log)
{
using (StringWriter sw = new StringWriterWithEncoding(new StringBuilder(), Encoding.UTF8))
{
XmlTextWriter w = new XmlTextWriter(sw);
w.Formatting = Formatting.Indented;
w.Indentation = 2;
w.WriteStartDocument(true);
w.WriteStartElement("LogEntry");
w.WriteElementString("Timestamp", log.TimeStampString);
w.WriteElementString("Message", log.Message);
w.WriteElementString("Category", log.CategoriesStrings[0].ToString());
w.WriteElementString("Priority", log.Priority.ToString());
w.WriteElementString("EventId", log.EventId.ToString(CultureInfo.InvariantCulture));
w.WriteElementString("Severity", log.Severity.ToString());
w.WriteElementString("Title", log.Title);
w.WriteElementString("Machine", log.MachineName);
w.WriteElementString("AppDomain", log.AppDomainName);
w.WriteElementString("ProcessId", log.ProcessId);
w.WriteElementString("ProcessName", log.ProcessName);
w.WriteElementString("Win32ThreadId", log.Win32ThreadId);
w.WriteElementString("ThreadName", log.ManagedThreadName);
w.WriteEndElement();
w.WriteEndDocument();
return sw.ToString().Substring(55);
}
}
}
And in log config file, we defined providers that provide users 3 different formatters, so that they can flexibly pick up the formatter they want through the entprise config GUI.
And formatters section in config file would be like below.
<formatters>
<add type="MyAssembly.Logging.ShortXmlFormatter, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1c35d9e2a9c27e83"
name="Xml Short Formatter" />
<add template="Timestamp: {timestamp}
Message: {message}
Category: {category}
Priority: {priority}
EventId: {eventid}
Severity: {severity}
Title:{title}
Machine: {machine}
Application Domain: {appDomain}
Process Id: {processId}
Process Name: {processName}
Win32 Thread Id: {win32ThreadId}
Thread Name: {threadName}
Extended Properties: {dictionary({key} - {value}
)}"
type="MyAssembly.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=7fa4cc4c62ff1809"
name="Text Formatter" />
<add prefix="x" namespace="MyAssembly" type="MyAssembly.LongXmlFormatter, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1c35d9e2a9c27e83"
name="Xml Formatter" />
</formatters>
Through above code, we can create correct XML layout but presenting as text formatter like below.
<LogEntry>
<Timestamp>23/02/2009 2:03:35 PM</Timestamp>
<Message>project initialization starting...</Message>
<Category>Information</Category>
<Priority>-1</Priority>
<EventId>1</EventId>
<Severity>Information</Severity>
<Title />
<Machine>Asset</Machine>
<AppDomain>UTA_6657793a-1f3a-40e8-86f3-5996dcd2cea2</AppDomain>
<ProcessId>14796</ProcessId>
<ProcessName>C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\vstesthost.exe</ProcessName>
<Win32ThreadId>4360</Win32ThreadId>
<ThreadName>Agent: adapter run thread for test 'ApplyMapFilterTest' with id 'df8ee337-9d6d-472a-93f5-949a5409ffa9'</ThreadName>
</LogEntry>
Now the question is how we can present it as XML format and show it in browser like IE. Technically, browser can only open xml file that follow the scheme strictly. We can see the drawback of above format that does not have root node element, so IE can not analyse and present them correctly. However, we can not add the root node element in our xmlFormatter class that would cause the duplicate root element each time when you append new log event. Therefore, this is not the right solution.
Then, xslt would come to the rescue. We define below xml tranforming file as xmlLogTransforms.xslt.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>My Log File</title>
<style>
body
{
font-family: Arial, sans-serif;
font-size: 10pt;
color: #001;
margin: 0px 6px;
padding: 0px;
cursor: default;
}
.LineItem
{
cursor: pointer;
}
.LineItem:hover
{
color: #418;
}
.EntryDetails
{
margin: 4px 18px 12px 218px;
}
.ErrorStackHeader
{
cursor: pointer;
}
.ErrorStack
{
font-family: Lucida Console;
font-size: 8pt;
color: #f00;
margin: 8px -8px 8px 18px;
}
</style>
<script language="JavaScript" type="text/javascript">
function ShowHide(panelID)
{
var panel = document.getElementById(panelID);
if (panel != null)
{
if (panel.style.visibility == "hidden")
{
panel.style.display = "block";
panel.style.visibility = "visible";
}
else
{
panel.style.display = "none";
panel.style.visibility = "hidden";
}
}
}
</script>
</head>
<body>
<xsl:for-each select="MyLog/LogEntry">
<xsl:sort select="Timestamp" order="ascending" />
<div class="LineItem" onclick="ShowHide('{generate-id(Message)}')">
<span class="EntryDetailsLabel">
<xsl:value-of select="Timestamp"/>:
</span>
<xsl:value-of select="Message" />
</div>
<div class="EntryDetails" style="visibility: hidden; display: none;" id="{generate-id(Message)}">
<div>
<span class="EntryLogDetailsLabel">Category: </span>
<xsl:value-of select="Category" />
</div>
<div>
<span class="EntryLogDetailsLabel">Priority: </span>
<xsl:choose>
<xsl:when test="Priority = -1">Lowest</xsl:when>
<xsl:when test="Priority = 1">Low</xsl:when>
<xsl:when test="Priority = 2">Medium</xsl:when>
<xsl:when test="Priority = 3">High</xsl:when>
<xsl:when test="Priority = 4">Urgent</xsl:when>
</xsl:choose>
</div>
<div>
Then we can put the user favorite xml file name on the same folder as the original txt formatter log file. Then when user click their favorite xml file name, it will automatically locate the txt formatter log file and transform them and present them as neat xml file in the browser. The xml file looks like below.
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="xmlLogTransforms.xslt" ?>
<!DOCTYPE MyLog [
<!ENTITY LogEntries SYSTEM "favorite.log">
]>
<MyLog>
&LogEntries;
</MyLog>
Conclusion
Now, user should be able to see the concise and lovely xml formatter log file, which outstandingly offer us below improvements.
- optimize the performance by speeding up the opening process outstandingly.
- user can customize the layout style by themselves.
- improve reading capability by clicking show and hiding.