Xml日志记录文件最优方案(附源代码)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Xml作为数据存储的一种方式,当数据非常大的时候,我们将碰到很多Xml处理的问题。通常,我们对Xml文件进行编辑的最直接的方式是将xml文件加载到XmlDocument,在内存中来对XmlDocument进行修改,然后再保存到磁盘中。

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(0, 0),
                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日志文件源代码下载

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
8天前
|
数据采集 大数据 BI
终于有人把指标管理平台讲明白了!
企业常因数据口径不一、重复开发、效率低下等问题陷入“数据扯皮”。搭建指标管理平台可统一标准,提升数据质量与协作效率。通过FineBI等工具,实现数据连接、指标管理、分析应用三层架构,推动数据驱动决策,助力企业降本增效,真正实现数据资产化。
终于有人把指标管理平台讲明白了!
|
Kubernetes Cloud Native 持续交付
云原生之旅:Docker容器化与Kubernetes集群管理
【9月更文挑战第33天】在数字化转型的浪潮中,云原生技术如同一艘航船,带领企业乘风破浪。本篇文章将作为你的航海指南,从Docker容器化的基础讲起,直至Kubernetes集群的高级管理,我们将一起探索云原生的奥秘。你将学习到如何封装应用、实现环境隔离,以及如何在Kubernetes集群中部署、监控和扩展你的服务。让我们启航,驶向灵活、可伸缩的云原生未来。
159 7
|
7月前
有偿创建 CosyVoice2-0.5B 大模型
有偿创建 CosyVoice2-0.5B 大模型,希望有人能帮忙在创空间 创建一个这样的模型,官方的老出502
450 22
|
Linux 数据安全/隐私保护 Docker
优化Docker权限管理:配置Docker用户组
Docker 利用 Linux 的用户和组权限来管理对 Docker 守护进程的访问权限。一般情况下,只有 root 用户和属于 docker 用户组的用户才被允许访问 Docker 守护进程。在 Linux 系统上使用 Docker 时,如果您尚未配置 docker 用户组,那么作为非 root 用户执行 Docker 相关命令将要求使用 sudo 来提升权限。
1484 2
优化Docker权限管理:配置Docker用户组
|
12月前
|
机器学习/深度学习 人工智能 自然语言处理
企业内训|LLM大模型技术在金融领域的应用及实践-某商业银行分行IT团队
本企业培训是TsingtaoAI技术团队专们为某商业银行分行IT团队开发的LLM大模型技术课程。课程深入分析大模型在金融行业中的发展趋势、底层技术及应用场景,重点提升学员在大模型应用中的实际操作能力与业务场景适应力。通过对全球商用 LLM 产品及国内外技术生态的深度对比,学员将了解大模型在不同企业中的发展路径,掌握如 GPT 系列、Claude 系列、文心一言等大模型的前沿技术。针对金融行业的业务需求,学员将学会如何结合多模态技术改进用户体验、数据分析等服务流程,并掌握大模型训练与工具链的实操技术,尤其是模型的微调、迁移学习与压缩技术。
404 2
|
SQL 前端开发 JavaScript
前端vite+vue3结合后端node+koa——实现代码模板展示平台(支持模糊搜索+分页查询)
前端vite+vue3结合后端node+koa——实现代码模板展示平台(支持模糊搜索+分页查询)
343 4
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的校园跑腿的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的校园跑腿的详细设计和实现(源码+lw+部署文档+讲解等)
133 0
|
Linux API 虚拟化
漫谈容器化技术(docker原理篇)
漫谈容器化技术(docker原理篇)
841 0
|
SQL 存储 数据采集
ARMS在APM工具选型中的实践
当前的系统在数字化转型需求以及互联网架构实施的影响下,越来越普遍地使用了微服务架构,我们在享受微服务带来的好处(开发效率高, 独立部署, 水平扩展, 故障与资源隔离等等)外,也带来测试,事务,应用监控等各方面的困难。
4004 105
ARMS在APM工具选型中的实践
|
Linux Apache 弹性计算