Xml日志記錄文件最優方案(附源代碼)

作者: JasenKin  來源: 博客園  發布時間: 2011-03-06 21:40  閱讀: 2446 次  推薦: 1   原文鏈接   [收藏]  
摘要:該文章中我們將要講的是如何高效的增加內容(對象實體內容)到xml日志文件中。

  Xml作為數據存儲的一種方式,當數據非常大的時候,我們將碰到很多Xml處理的問題。通常,我們對Xml文件進行編輯的最直接的方式是將xml文件加載到XmlDocument,在內存中來對XmlDocument進行修改,然后再保存到磁盤中。這樣的話我們將不得不將整個XML document 加載到內存中,這明顯是不明智的(對于大數據XML文件來說,內存將消耗很大,哥表示鴨梨很大)。下面我們將要講的是如何高效的增加內容(對象實體內容)到xml日志文件中。

  (一)設計概要

  總體來說,我們將(通過代碼)創建兩種不同的文件,第一種為Xml文件,第二種為xml片段(txt文件),如下圖所示:

  我們通過如下的定義來使2個不同的文件相關聯。

 
<!ENTITY yourEntityRefName SYSTEM
"your xml fragement address(relative or obsolute address) ">

  (二)xml文件的生成

  先來看下如何創建相關的xml文件,代碼如下:

private static voidInitXmlFile(stringxmlLogFilePath,stringxmlLogContentFileName,stringentityRef)
{

stringdocType= string.Format("\n<!DOCTYPEXmlLogFile\n[\n<!ENTITY{0}SYSTEM\"{1}\">\n]>\n",entityRef,xmlLogContentFileName);
XmlWriterSettingswrapperSettings
=newXmlWriterSettings()
{
Indent
=true
};
using(XmlWriterwriter=XmlWriter.Create(xmlLogFilePath,wrapperSettings))
{
writer.WriteStartDocument();
writer.WriteRaw(docType);
writer.WriteStartElement(ConfigResource.XmlLogFile);

writer.WriteStartElement(ConfigResource.XmlLogContent);
writer.WriteEntityRef(entityRef);
writer.WriteEndElement();

writer.WriteEndElement();
writer.Close();
}
}

  對xml文件內容的寫入主要通過XmlWriter來進行操作的。這個方法比較簡單,不再講解,看下我們通過這個方法生成的文件內容:

<?xmlversion="1.0"encoding="utf-8"?>
<!DOCTYPEXmlLogFile
[

<!ENTITYLocationsSYSTEM"XmlLogContentFile-20110220000120.txt">
]>
<XmlLogFile>
<XmlLogContent>&Locations;</XmlLogContent>
</XmlLogFile>

  Locations為實體引用名稱,與之相對應的為&Locations; 。

  XmlLogContentFile-20110220000120.txt為Xml片段的文件名稱,路徑是相對于XmlLogFile-20110220000120.xml的。

  &Locations;相當于占位符的作用,將用XmlLogContentFile-20110220000120.txt文件的內容來替換XmlLogFile-20110220000120.xml的&Locations;

  (三)Xml片段文件的生成

  Xml片段文件的生成過程思路為:通過System.IO.FileStream和System.Xml.XmlWriter在文件的末尾處增加文件的內容(效率較高,因為是直接在文件的末尾添加的內容),內容格式為Xml,其中涉及到反射的部分內容。

privatestaticvoidInitEntityRefFile(stringxmlLogContentFilePath,objectlogObject,stringentityRef)
{

using(FileStreamfileStream=newFileStream(xmlLogContentFilePath,FileMode.Append,
FileAccess.Write,FileShare.Read))
{
XmlWriterSettingssettings
=newXmlWriterSettings()
{
ConformanceLevel
=ConformanceLevel.Fragment,
Indent
=true,
OmitXmlDeclaration
=false
};

WriteContent(logObject,fileStream,settings);
}
}


privatestaticvoidWriteContent(objectlogObject,FileStreamfileStream,XmlWriterSettingssettings)
{

using(XmlWriterwriter=XmlWriter.Create(fileStream,settings))
{
Typetype
=logObject.GetType();
writer.WriteStartElement(type.Name);
writer.WriteAttributeString(ConfigResource.Id,logObject.GetHashCode().ToString());


if(logObject.GetType().IsPrimitive||
(logObject.GetType()==typeof(string)))
{
writer.WriteElementString(logObject.GetType().Name,logObject.ToString());
}

else
{
PropertyInfo[]infos
=type.GetProperties();
foreach(PropertyInfoinfoininfos)
{

if(ValidateProperty(info))
{
writer.WriteElementString(info.Name,
(info.GetValue(logObject,
null)??string.Empty).ToString());
}
}
}

writer.WriteEndElement();
writer.WriteWhitespace(
"\n");
writer.Close();
}
}
 
private static bool ValidateProperty(PropertyInfo info)
{
return info.CanRead && (info.PropertyType.IsPrimitive
|| (info.PropertyType == typeof(string))
|| (info.PropertyType == typeof(DateTime)
|| (info.PropertyType == typeof(DateTime?))));
}

  代碼

 
writer.WriteAttributeString(ConfigResource.Id,logObject.GetHashCode().ToString());

if (logObject.GetType().IsPrimitive ||
(logObject.GetType() == typeof(string)))
{
writer.WriteElementString(logObject.GetType().Name, logObject.ToString());
}

  第一行為該實體增加一個Id特性,采用對象的哈希值來進行賦值,方便以后的單元測試(通過對象的哈希值來查找相應的Xml內容)。

  余下的幾行為:當實體的類型是基元類型或者字符串類型的時候,直接通過writer.WriteElementString()方法將類型名稱,實體對象值作為參數直接寫入xml片段文件中。

  否則

 
else
{
PropertyInfo[] infos
= type.GetProperties();
foreach (PropertyInfo info in infos)
{

if (ValidateProperty(info))
{
writer.WriteElementString(info.Name,
(info.GetValue(logObject,
null) ?? string.Empty).ToString());
}
}
}

通過反射來獲取所有屬性相對應的值,其中屬性必須是可讀的,并且為(基元類型,string,DateTiem?,DateTime)其中一種(這個大家可以擴展一下相關功能)

  如下所示,我們通過基元類型float,字符串類型string,對象類型Error【Point為Error的屬性,不是(基元類型,string,DateTiem?,DateTime)其中一種】來進行測試。

XmlLogHelper.Write("stringtypesample");
XmlLogHelper.Write(
3.3);
XmlLogHelper.Write(DateTime.Now);
Errorerror
=newError()
{
Time
=DateTime.Now,
Point
=newSystem.Drawing.Point(0,0),
Description
="C#Error",
Level
=2,
Name
="Error"
};
XmlLogHelper.Write(error);

  輸出內容如下:

  (四)采用lock來避免異常的發生,其次特別要注意對資源的及時釋放。

privatestaticreadonlyobjectlockObject=newobject();

publicstaticvoidWrite(objectlogObject)
{

if(logObject==null)
{

return;
}


lock(lockObject)
{
Writing(logObject);
}
}


privatestaticvoidWriting(objectlogObject)
{

stringentityRef=ConfigResource.EntityRef;
stringbaseDirectory=InitDirectory();
stringbaseName=DateTime.Now.ToString("yyyyMMddHHmmss");
stringxmlLogFilePath=Path.Combine(baseDirectory,string.Format(ConfigResource.XmlLogFileName,baseName));
XmlLogHelper.XmlFilePath
=xmlLogFilePath;
stringxmlLogContentFileName=string.Format(ConfigResource.XmlLogContentFileName,baseName);
stringxmlLogContentFilePath=Path.Combine(baseDirectory,xmlLogContentFileName);

if(!File.Exists(xmlLogFilePath))
{
InitXmlFile(xmlLogFilePath,xmlLogContentFileName,entityRef);
}

InitEntityRefFile(xmlLogContentFilePath,logObject,entityRef);
}

采用lock來避免同時對文件進行操作,避免異常的發生,保證每次操作都是僅有一個在進行。

 
lock (lockObject)
{
Writing(logObject);
}

采用using來及時釋放掉資源。

 
using (FileStream fileStream = new FileStream(xmlLogContentFilePath, FileMode.Append,
FileAccess.Write, FileShare.Read))
{

}

  (五)單元測試

  單元測試的主要代碼如下,主要是對Write()方法進行測試,如下:

[TestMethod()]
publicvoidWriteTest()
{
DeleteFiles();//刪除目錄下所有文件,避免產生不必要的影響。
List
<Error>errors=InitErrorData(9);
AssertXmlContent(errors);
}


privatestaticvoidAssertXmlContent(List<Error>errors)
{

foreach(Errorerrorinerrors)
{
XmlLogHelper.Write(error);

XmlDocumentdoc
=GetXmlDocument();
XmlNodenode
=doc.SelectSingleNode("//Error[@Id='"+error.GetHashCode().ToString()+"']");
Assert.IsTrue(node.Name
==typeof(Error).Name);

stringpath=string.Format("//Error[@Id='{0}']//",error.GetHashCode().ToString());
XmlNodelevelNode
=doc.SelectSingleNode(path+"Level");
XmlNodenameNode
=doc.SelectSingleNode(path+"Name");
XmlNodedescriptionNode
=doc.SelectSingleNode(path+"Description");
XmlNodetimeNode
=doc.SelectSingleNode(path+"Time");
XmlNodepointNode
=doc.SelectSingleNode(path+"Point");

Assert.IsTrue(nameNode.Name
=="Name");
Assert.IsTrue(levelNode.Name
=="Level");
Assert.IsTrue(descriptionNode.Name
=="Description");
Assert.IsTrue(timeNode.Name
=="Time");

Assert.IsNotNull(levelNode);
Assert.IsNotNull(nameNode);
Assert.IsNotNull(descriptionNode);
Assert.IsNotNull(timeNode);
Assert.IsNull(pointNode);

Assert.IsTrue(nameNode.InnerText
==(error.Name??string.Empty));
Assert.IsTrue(levelNode.InnerText
==error.Level.ToString());
Assert.IsTrue(timeNode.InnerText
==DateTime.MinValue.ToString());
Assert.IsTrue(descriptionNode.InnerText
==(error.Description??string.Empty));
}
}

  上面僅僅是針對一個自定義的Error類進行了驗證................

  (六)其他應用

  當我們的Xml日志文件可以記錄的時候,我們可能想通過界面來看下效果,比如如下所示意的圖中,點擊【生成XML日志文件】,再點擊【獲取XML日志文件】的時候,我們能夠看到生成的XML日志文件。

  其中生成的文件名稱顯示如下:

  多次點擊【生成XML日志文件】,再點擊【獲取XML日志文件】的時候,我們能夠看到生成的XML日志文件數量也遞增(因為我將文件的名稱設置為string baseName = DateTime.Now.ToString("yyyyMMddHHmmss");,按照秒數來計算的)。點擊任何一個文件,將顯示該文件的相關全部xml內容(包括xml文件和xml片段)。

  點擊【刪除XML日志文件】將刪除所有的xml文件,如下:

  (七)總結

  對于流的操作來說,應盡快釋放掉系統資源,促使GC的Finalize()方法的執行,同時可以避免異常的發生。對于Xml日志來說,當數據量越來越大的時候,我們可以將內容分為兩部分,一部分為標準的哦xml文件,另一部分為xml片段文件。

  這樣,我們能夠在xml片段文件中方便地在文件末尾處增加相關的內容,這種效率是非常快的,而通常我們通過XMLDocument來加載數據非常消耗內存,效率較低(數據量越大越明顯)。同時在讀取xml文件的時候也會通過實體引用將相關的xml片段引用進來,從而使二個文件成為一個整體。再次,在將對象轉換成xml的時候,通過反射來獲取相關的數據,并將數據寫入xml格式中,這個地方還有提高。希望各位在看完此文后也能熟練的運用XML日志文件來對日志進行記錄。

1
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()