zoukankan      html  css  js  c++  java
  • 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 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.WriteEndElement();
                    writer.Close();
                }
            }

    对xml文件内容的写入主要通过XmlWriter来进行操作的。这个方法比较简单,不再讲解,看下我们通过这个方法生成的文件内容:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE XmlLogFile 
     [ 
     
    <!ENTITY Locations SYSTEM "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,其中涉及到反射的部分内容。

            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");
                    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("string type sample");
                XmlLogHelper.Write(
    3.3);
                XmlLogHelper.Write(DateTime.Now);
                Error error 
    = new Error()
                {
                    Time 
    = DateTime.Now,
                    Point 
    = new System.Drawing.Point(00),
                    Description 
    = "C# Error",
                    Level 
    = 2,
                    Name 
    = "Error"
                };
                XmlLogHelper.Write(error);

    输出内容如下:

     

    (四)采用lock来避免异常的发生,其次特别要注意对资源的及时释放。

            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);
            }

               采用lock来避免同时对文件进行操作,避免异常的发生,保证每次操作都是仅有一个在进行。

               lock (lockObject)
                {
                    Writing(logObject);
                }

               采用using来及时释放掉资源。
                 using (FileStream fileStream = new FileStream(xmlLogContentFilePath, FileMode.Append,
                    FileAccess.Write, FileShare.Read))
                {

                 }

     

    (五)单元测试

    单元测试的主要代码如下,主要是对Write()方法进行测试,如下:

            [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));
                }
            }

    上面仅仅是针对一个自定义的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日志文件来对日志进行记录。

     

    源代码下载:Jasen.XmlLogFileSample.rar XML日志文件源代码下载

     

  • 相关阅读:
    Linux命令 tr
    Linux命令 xargs
    Python 文件读写 文件和路径
    Python 正则表达式 匹配任意字符
    Python 正则表达式 贪心匹配和非贪心匹配
    Python 正则表达式 匹配次数
    Python 正则表达式 利用括号分组
    Python 正则表达式 search vs match
    【转-整理】 SQL2008 提示评估期已过的解决方法
    Java http接口进阶——自我理解为接口框架形式
  • 原文地址:https://www.cnblogs.com/jasenkin/p/xml_log_file_helper_and_unit.html
Copyright © 2011-2022 走看看