一起谈.NET技术,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<!DOCTYPEXmlLogFile\n[\n<!ENTITY{0}SYSTEM\ " { 1 }\ " >\n]>\n " ,entityRef,xmlLogContentFileName);
XmlWriterSettingswrapperSettings
= new XmlWriterSettings()
{
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,其中涉及到反射的部分内容。

private static void InitEntityRefFile( string xmlLogContentFilePath, object logObject, string entityRef)
{
using (FileStreamfileStream = new FileStream(xmlLogContentFilePath,FileMode.Append,
FileAccess.Write,FileShare.Read))
{
XmlWriterSettingssettings
= new XmlWriterSettings()
{
ConformanceLevel
= ConformanceLevel.Fragment,
Indent
= true ,
OmitXmlDeclaration
= false
};

WriteContent(logObject,fileStream,settings);
}
}

private static void WriteContent( object logObject,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 (PropertyInfoinfo 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( " stringtypesample " );
XmlLogHelper.Write(
3.3 );
XmlLogHelper.Write(DateTime.Now);
Errorerror
= 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 (Errorerror in errors)
{
XmlLogHelper.Write(error);

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

string path = 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日志文件来对日志进行记录。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
27天前
|
XML JavaScript 数据格式
XML 相关技术
XML 相关技术
|
27天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
131 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
27天前
|
SQL Oracle 关系型数据库
【赵渝强老师】Oracle的控制文件与归档日志文件
本文介绍了Oracle数据库中的控制文件和归档日志文件。控制文件记录了数据库的物理结构信息,如数据库名、数据文件和联机日志文件的位置等。为了保护数据库,通常会进行控制文件的多路复用。归档日志文件是联机重做日志文件的副本,用于记录数据库的变更历史。文章还提供了相关SQL语句,帮助查看和设置数据库的日志模式。
【赵渝强老师】Oracle的控制文件与归档日志文件
|
27天前
|
Oracle 关系型数据库 数据库
【赵渝强老师】Oracle的参数文件与告警日志文件
本文介绍了Oracle数据库的参数文件和告警日志文件。参数文件分为初始化参数文件(PFile)和服务器端参数文件(SPFile),在数据库启动时读取并分配资源。告警日志文件记录了数据库的重要活动、错误和警告信息,帮助诊断问题。文中还提供了相关视频讲解和示例代码。
|
2月前
|
监控 Linux 应用服务中间件
系统监控:使用日志文件 journalctl的使用
本文介绍了如何使用`journalctl`命令来监控和查看Linux系统的日志文件,包括查看特定行数、过滤日志级别、实时跟踪日志、按时间段查询日志以及日志轮换和压缩的配置。
76 2
系统监控:使用日志文件 journalctl的使用
|
2月前
|
SQL 数据库
为什么 SQL 日志文件很大,我应该如何处理?
为什么 SQL 日志文件很大,我应该如何处理?
|
2月前
|
SQL 数据库
为什么SQL日志文件很大,该如何处理?
为什么SQL日志文件很大,该如何处理?
|
1月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
219 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
2月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
278 3
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1674 14