天天看點

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

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

(一)設計概要

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

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

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

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

(二)xml檔案的生成

先來看下如何建立相關的xml檔案,代碼如下:

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

        private static void InitXmlFile(string xmlLogFilePath, string xmlLogContentFileName, string entityRef)

        {

            string docType = string.Format("\n<!DOCTYPE XmlLogFile \n [ \n <!ENTITY {0} SYSTEM \"{1}\">\n ]>\n", entityRef, xmlLogContentFileName);

            XmlWriterSettings wrapperSettings = new XmlWriterSettings()

            {

                Indent = true

            };

            using (XmlWriter writer = XmlWriter.Create(xmlLogFilePath, wrapperSettings))

                writer.WriteStartDocument();

                writer.WriteRaw(docType);

                writer.WriteStartElement(ConfigResource.XmlLogFile);

                writer.WriteStartElement(ConfigResource.XmlLogContent);

                writer.WriteEntityRef(entityRef);

                writer.WriteEndElement();

                writer.Close();

            }

        }

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

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

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

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE XmlLogFile 

 [ 

 <!ENTITY Locations SYSTEM "XmlLogContentFile-20110220000120.txt">

 ]>

<XmlLogFile>

  <XmlLogContent>&Locations;</XmlLogContent>

</XmlLogFile>

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

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,其中涉及到反射的部分内容。

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

        private static void InitEntityRefFile(string xmlLogContentFilePath, object logObject, string entityRef)

            using (FileStream fileStream = new FileStream(xmlLogContentFilePath, FileMode.Append,

                FileAccess.Write, FileShare.Read))

                XmlWriterSettings settings = new XmlWriterSettings()

                {

                    ConformanceLevel = ConformanceLevel.Fragment,

                    Indent = true,

                    OmitXmlDeclaration = false

                };

                WriteContent(logObject, fileStream, settings);

        private static void WriteContent(object logObject, FileStream fileStream, XmlWriterSettings settings)

            using (XmlWriter writer = XmlWriter.Create(fileStream, settings))

                Type type = 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 (PropertyInfo info in infos)

                    {

                        if (ValidateProperty(info))

                        {

                            writer.WriteElementString(info.Name, 

                                (info.GetValue(logObject, null) ?? string.Empty).ToString());

                        }

                    }

                writer.WriteEndElement();               

                writer.WriteWhitespace("\n");

            }                      

        private static bool ValidateProperty(PropertyInfo info)

        {

            return info.CanRead && (info.PropertyType.IsPrimitive

                || (info.PropertyType == typeof(string))

                || (info.PropertyType == typeof(DateTime)

                || (info.PropertyType == typeof(DateTime?))));

        }

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

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

第一行為該實體增加一個Id特性,采用對象的哈希值來進行指派,友善以後的單元測試(通過對象的哈希值來查找相應的Xml内容)。

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

否則          else

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

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

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

            XmlLogHelper.Write("string type sample");

            XmlLogHelper.Write(3.3);

            XmlLogHelper.Write(DateTime.Now);

            Error error = new Error()

                Time = DateTime.Now,

                Point = new System.Drawing.Point(0, 0),

                Description = "C# Error",

                Level = 2,

                Name = "Error"

            XmlLogHelper.Write(error);

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

輸出内容如下:

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

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

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

        private static readonly object lockObject = new object();

        public static void Write(object logObject)

            if (logObject == null)

                return;

            lock (lockObject)

                Writing(logObject);

        private static void Writing(object logObject)

            string entityRef = ConfigResource.EntityRef;

            string baseDirectory = InitDirectory();

            string baseName = DateTime.Now.ToString("yyyyMMddHHmmss");

            string xmlLogFilePath =Path.Combine(baseDirectory ,string.Format(ConfigResource.XmlLogFileName,baseName));

            XmlLogHelper.XmlFilePath = xmlLogFilePath;

            string xmlLogContentFileName = string.Format(ConfigResource.XmlLogContentFileName,baseName);

            string xmlLogContentFilePath = Path.Combine(baseDirectory, xmlLogContentFileName);

            if (!File.Exists(xmlLogFilePath))

                InitXmlFile(xmlLogFilePath, xmlLogContentFileName, entityRef);

            InitEntityRefFile(xmlLogContentFilePath, logObject, entityRef);

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

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

           lock (lockObject)

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

             using (FileStream fileStream = new FileStream(xmlLogContentFilePath, FileMode.Append,

                FileAccess.Write, FileShare.Read))

            {

             }

(五)單元測試

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

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

        [TestMethod()]

        public void WriteTest()

            DeleteFiles();//删除目錄下所有檔案,避免産生不必要的影響。

            List<Error> errors = InitErrorData(9);

            AssertXmlContent(errors);

        private static void AssertXmlContent(List<Error> errors)

            foreach (Error error in errors)

                XmlLogHelper.Write(error);

                XmlDocument doc = GetXmlDocument();

                XmlNode node = doc.SelectSingleNode("//Error[@Id='" + error.GetHashCode().ToString() + "']");

                Assert.IsTrue(node.Name == typeof(Error).Name);

                string path = string.Format("//Error[@Id='{0}']//", error.GetHashCode().ToString());

                XmlNode levelNode = doc.SelectSingleNode(path + "Level");

                XmlNode nameNode = doc.SelectSingleNode(path + "Name");

                XmlNode descriptionNode = doc.SelectSingleNode(path + "Description");

                XmlNode timeNode = doc.SelectSingleNode(path + "Time");

                XmlNode pointNode = 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));

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

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

(六)其他應用

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

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

其中生成的檔案名稱顯示如下:

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

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

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

點選【删除XML日志檔案】将删除所有的xml檔案,如下:

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

(七)總結

對于流的操作來說,應盡快釋放掉系統資源,促使GC的Finalize()方法的執行,同時可以避免異常的發生。對于Xml日志來說,當資料量越來越大的時候,我們可以将内容分為兩部分,一部分為标準的哦xml檔案,另一部分為xml片段檔案。這樣,我們能夠在xml片段檔案中友善地在檔案末尾處增加相關的内容,這種效率是非常快的,而通常我們通過XMLDocument來加載資料非常消耗記憶體,效率較低(資料量越大越明顯)。同時在讀取xml檔案的時候也會通過實體引用将相關的xml片段引用進來,進而使二個檔案成為一個整體。再次,在将對象轉換成xml的時候,通過反射來擷取相關的資料,并将資料寫入xml格式中,這個地方還有提高。希望各位在看完此文後也能熟練的運用XML日志檔案來對日志進行記錄。

源代碼下載下傳:

Jasen.XmlLogFileSample.rar XML日志檔案源代碼下載下傳