EntityFramework之Log(五)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

关于日志

属性日志

  DbContext.Database.Log 属性被设置为一个委托,该委托能接受带有一个字符串参数的任何方法,最主要的是,通过设置它到 TextWriter 的 Write 方法将能应用于任何的TextWriter,通过上下文自动生成的所有SQL语句将被记录到Writer中。

例如,如下代码将记录SQL在控制台上:

              using (var ctx = new EntityDbContext())
              {
                 ctx.Database.Log = Console.WriteLine;
              }

【注意】上下文中的日志被设置到 Console.WriteLine ;则其所有SQL代码都将会输出在控制台上。

下面我们进行一些简单的查询、修改利用日志属性来演示在控制台上进行输出(依然利用前一篇文章所给出个三给类,如若不知其关系,请参考前一篇文章):

复制代码
            using (var ctx = new EntityDbContext())
            {
                ctx.Database.Log = Console.WriteLine;
或者
ctx.Database.log = s => Console.WriteLine(s);
var stu = ctx.Set<Student>().First(p => p.Name == "xpy0928"); stu.Grades.First().Fraction = 90; stu.Grades.Add(new Grade() { Fraction = 40 }); ctx.SaveChangesAsync().Wait(); }
复制代码

则在控制台打印如下SQL代码:

 日志记录内容

(1)各种SQL命令

    查询语句。例如:Linq查询、原始SQL查询

    作为SaveChanges的一部分的插入(inserted)、修改(update)以及删除(delete)

    由延迟加载自动生成的关联查询

(2)参数

(3)是否正在被异步执行的命令

(4)当命令开始执行时,时间戳的显示

(5)无论命令是否被成功完成,还是通过抛出异常而失败或者是通过异步被取消

(6)一些显示结果的值

(7)执行命令所需要的时间

输出日志

依上述,将日志输出在控制台是so easy,像这种输出在控制台中只能作为一些小demo,所以应用性不广,如果你要是在window form中或者是Web应用程序中的话,完全可以将日志输出在内存、文件等中。下面演示输出到文件中,其余不予演示。

复制代码
            using (var ctx = new EntityDbContext())
            {
var sw = new StreamWriter(@"d:\Data.log") { AutoFlush = true };
ctx.Database.Log
= s => { sw.Write(s); }; }
复制代码

结果文件中输出如下:

那么问题来了,如果我们需要将输出的内容进行格式化,那么我们应该怎样通过一种简单的方式怎来做呢?

稍等,容我一一讲来。

日志结果

默认的日志记录器记录sql语句文本,参数以及在命令发到数据库之前带着时间戳的“Executeing”(通过上述文本可知)。一个“Completed”(完成的)包含总的时间被记录在执行命令的下面。

【注意】异步命令中的“Completed”是直到异步任务执行完成、失败或者被取消才被记录下来,当然,它因不同的命令和是否成功执行而产生不同的信息。

执行成功

成功完成输出的是如上述文件中的 执行-- 已在 0 毫秒内完成,结果为: SqlDataReader 

执行失败

通过异常来告知失败,输出中包含了失败的信息。

执行取消

异步命令中的任务被取消会通过异常来告知结果是失败的。因为当试图去取消时,底层的ADO.NET会这样操作,而EF是基于ADO.NET的。

日志格式化

在Database.log属性下我们要充分使用 DatabaseLogFormatter 对象,这个对象有效结合了 IDbCommandInterceptor 来实现,通过接受一个字符串的委托和上下文。这意味着在DatabaseLogFormatter上截取方法之前和通过EF执行命令之后被调用,这些DatabaseLogFormatter方法获取有格式的日志并将其传递给一个委托代理。

 

通过从DatabaseLogFormatter上派生出一个类并且适当的重写其方法就能实现有格式的日志输出。重写最常用的方法有以下三者:

 LogCommand 

在命令被执行之前重写此方法来进行更改。通过默认的LogCommand来调用LogParameter的每个参数。当然你也可以进行重写或者处理参数不同。

 LogResult 

重写此方法来更改原有执行命令后记录下来的结果。

 LogParameter

重写此方法来更改其格式和参数所记录的内容。

 例如,在每条命令被发送到数据库之前,我们想仅仅通过单行来进行记录。这就需要重写两个方法:

(1)重写 LogCommand 来得到SQL单行的格式

(2)重写 LogResult 不需要做任何动作。(当执行时让其执行可重写的,因为这样在性能上可能会稍微高效一点,但是在功能还是等同的)

依据上述,下面我们来写出代码

复制代码
    public class SingleLineFormatter : DatabaseLogFormatter
    {
        public SingleLineFormatter(DbContext ctx, Action<string> action)
            : base(ctx, action)
        {

        }

        public override void LogCommand<TResult>(System.Data.Common.DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
        {
            Write(
                string.Format("DbContext '{0}' is Executing Command '{1}' '{2}'",
                Context.GetType().Name,
                command.CommandText.Replace(Environment.NewLine,""),
                Environment.NewLine));
base.LogCommand<TResult>(command, interceptionContext); } public override void LogResult<TResult>(System.Data.Common.DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext) { base.LogResult<TResult>(command, interceptionContext); } }
复制代码

上述日志输出会调用 Write 方法,此方法将输出发送到配置的写入委托。

【注意】上述代码只是通过一种简单的方式除去换行符,如果是在复杂的SQL语句中,上述方式可能会没有效果。

设置DatabaseLogFormatter

 一旦DatabaseLogFormatter的派生类被创建,那么你需要将其注册到EF中,否则也无法进行格式化输出。通过使用Code-Based Configuration,这也就是说,创建一个新的继承自DbConfiguration的类与DbContext上下文类所在同一个程序集,然后会在创建的新类的构造函数中调用 SetDatabaseLogFormatter 方法,该方法接受的参数就是格式化类中构造函数中的两个参数。

复制代码
    public class DbContextConfiguration : DbConfiguration
    {
        public DbContextConfiguration()
        {
            SetDatabaseLogFormatter(
                (context, action) => new SingleLineFormatter(context, action));
        }
    }
复制代码

基于上我们新创建的 SingleLineFormatter 会应用在设置使用Database.log的任何时刻,所以你会看到如下结果:

 

 接下来我们将看 IDbCommandInterceptor 实现直接来控制命令的拦截,并通过集成使用NLog的例子而不使用Database.log。请继续往下看

低级构造块监听

接口监听

监听代码实际上是监听接口的概念。这些接口来继承自 IDbInterceptor 并且当EF有所行为时其定义的方法会被调用。其意义是在每个对象被截获时都有一个接口。例如,当EF调用ExecuteNonQuery, ExecuteScalar, ExecuteReader等相关的方法时在IDbInterceptor上定义的方法将会被调用。同样,当每个操作完成这些方法也会被调用,上述中的DatabaseLogFormatter类就实现了这个接口来记录命令。

接口存在的意义

(1)为了记录SQL命令

(2)为了支持并实现一些其他特性

处理结果

泛型 DbCommandInterceptionContext<> 类里面包含几个属性称之为Result, OriginalResult, Exception, and OriginalException。这些方法会被设置为空通过在操作被执行之前调用的拦截方法。如果操作被成功执行,那么 Result and OriginalResult会被设置成操作的结果。这些值会被在操作执行完后的监听方法所监控。同理,如果操作抛出异常,那么Exception and OriginalException将会被设置。

禁止执行

如果一个监听者在命令快执行完之前设置了Result属性的话,那么EF实际上是不会试图去执行该命令,但是代替的是仅仅是使用结果集。换言之,这个监听者会禁止命令的执行,但是EF会继续,就好像命令已经被执行了。

 

发生上述禁止执行的示例可能是经过包装的批处理命令,这个监听者会将其储存以便日后将用于批处理但是会装到EF中一如往常的执行该命令。注意监听者需要更多这来实现批处理。

执行后改变结果

如果一个监听者在命令执行完后设置了Result属性,那么EF将会使用改变后的结果而不是通过此操作返回实际的结果。同样,如果一个监听者在命令执行完后设置了Exception属性,那么EF将抛出设置的异常,就好像此操作已经抛出了这个异常一样。

 

一个监听者也可以设置Exception属性为空来表明没有异常应该被抛出。这其实是非常有意义的,如果执行操作失败但是监听者希望EF继续运行就好像操作成功执行了一样。

OriginalResult 和OriginalException

在EF执行完一个操作后,如果该操作未失败将设置Result和OriginalResult属性,或者如果执行失败抛出异常那么将设置Exception和OrigianlException。

 

在实际执行完一个操作之后, OriginalResult和OriginalException会被EF设置为只读的。这些属性不会被监听者所设置。这也就意味着,任何监听者能区分Exception(异常)和Result(结果),那些会通过一些其他的相对于操作被执行时发生的真实的异常(Exception)和结果(Result)的监听者所设置。

 注册监听者

 一旦创建了一个实现了一个或者多个监听接口的类需要通过  DbInterception 注册到EF中。例如:

DbInterception.Add(new NLogCommandInterceptor());

监听者也能够使用基于代码的配置机制(Code-Based DbConfiguration )被注册在应用程序域中。

下面我们将基于以上描述通过使用 IDbCommandInterceptor ,来讲述一个实例: Log To NLog (通过NLog进行日志记录)(关于NLog请看这里:NLog Tutorial

记录如下日志:

(1)执行非异步的命令作为Warning(警告)

(2)执行抛出的异常作为Error(严重错误)

复制代码
    public class NLogCommandInterceptor : IDbCommandInterceptor
    {
        
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        public void NonQueryExecuting(
            DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            LogIfNonAsync(command, interceptionContext);
        }

        public void NonQueryExecuted(
            DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            LogIfError(command, interceptionContext);
        }

        public void ReaderExecuting(
            DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            LogIfNonAsync(command, interceptionContext);
        }

        public void ReaderExecuted(
            DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            LogIfError(command, interceptionContext);
        }

        public void ScalarExecuting(
            DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            LogIfNonAsync(command, interceptionContext);
        }

        public void ScalarExecuted(
            DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            LogIfError(command, interceptionContext);
        }

        private void LogIfNonAsync<TResult>(
            DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
        {
            if (!interceptionContext.IsAsync)
            {
                Logger.Warn("Non-async command used: {0}", command.CommandText);
            }
        }

        private void LogIfError<TResult>(
            DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
        {
            if (interceptionContext.Exception != null)
            {
                Logger.Error("Command {0} failed with exception {1}",
                    command.CommandText, interceptionContext.Exception);
            }
        }

       
    }
复制代码

结果如下:

【注意】上述NLog得安装如下程序包

接下来在NLog.config中rules和target节点下进行简单的配置输出日志即可,如下:

  <rules> 
    <logger name="ConsoleApplication1.NLogCommandInterceptor" minlevel="Info" writeTo="f"></logger> 
/*name为命名空间+实现IDbCommandInterceptor接口的监控类名称,f为写到下面target中的name*/ </rules>
  <targets>
    <target name="f" xsi:type="File" fileName="c:\temp\log.txt"/>
  </targets>

监听中的Dispatch方法

除了在 DbInterception 中注册方法外,它也提供了Dispatch方法,此方法允许代码不是分配到监听的通知的一部分,这是以上已经提到的机制,允许提供者让监听者知道在EF的控制下,一条命令正在被执行。不过对于应用程序开发者去使用Dispatch API这是比较少见的。下面了解下这个方法如何去操作。

DbInterception.Dispatch.Command.NonQueryAsync(myCommand, new DbCommandInterceptionContext());

以上代码做了以下五件事:

(1)确保被设置到监听上下文中的命令是否是  IsAsync  的

(2)调用所有注册在 IDbCommandInterceptors 中的 NonQueryExecuting  方法

(3)除非如以上所说这些 NonQueryExecuting 方法之一被设置了Result属性,否则调用 ExecuteNonQueryAsync  

(4)在异步任务中设置了延续,使得注册在 IDbCommandInterceptors 上的所有 NonQueryExecuted 方法都被调用

(5)使得任务结果中包含了可能已经被监听集合中的方法之一改变了的正确结果

打开日志无需重新编译(EF 6.1)

上述我们是通过手动操作来进行日志的输出,如果你嫌麻烦,大可在配置文件(web.config或App.config)中的 EntityFramework 节点下进行相关配置来完成日志输出工作。

(1)输出到控制台

<interceptors>
  <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"/>
</interceptors>

(2)输出到文件

复制代码
<interceptors>
  <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
    <parameters>
      <parameter value="c:\temp\log.txt"/>
    </parameters>
  </interceptor>
</interceptors>
复制代码

如下结果:

 

【注意】上述默认情况下,当应用程序每次启动会重新生成一个新的log.text,则此前的将被覆盖。如果该文件总是存在的话,为了追加到到日志文件中,可以进行如下操作:

复制代码
<interceptors>
  <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
    <parameters>
      <parameter value="c:\temp\log.txt"/>
      <parameter value="true" type="System.Boolean"/>
    </parameters>
  </interceptor>
</interceptors>
复制代码

上述就是通过注册监听集合下的interceptor(监听者)来进行日志输出。

IDbConfigurationInterceptor

在EF6.1中引入了此接口,它是一个当应用程序启动时允许代码检测或者修改EF configuration的监听接口。通过这个我们能够实现一个关于 DatabaseLogger 的简单版本

复制代码
public class ExampleDatabaseLogger : IDbConfigurationInterceptor
{
    public void Loaded(
        DbConfigurationLoadedEventArgs loadedEventArgs,
        DbConfigurationInterceptionContext interceptionContext)
    {
        var formatterFactory = loadedEventArgs
                .DependencyResolver
                .GetService<Func<DbContext, Action<string>, DatabaseLogFormatter>>();
 
        var formatter = formatterFactory(null, Console.Write);
 
        DbInterception.Add(formatter);
    }
}
复制代码

我们来分析下上述代码:

(1)第一行要求EF去注册 DatabaseLogFormatter 工厂,上述我们只是新建了一个  DatabaseLogFormatter 来代替,当然如果应用程序在此行自定义了格式化 ,那么将通过用此自定义的而不会是默认的

(2)第一行实际上是没有获得DatabaseLogFormatter,它只是获得一个来创建DatabaseLogFormatter实例的一个工厂,第二行调用工厂来获得一个实际意义上的formatter实例。因为我们要记录所有上下文实例,所以为空也是允许的。工厂的第二个参数是对于控制台输出流的委托,因此我们只需将指针指向Console.Write()方法即可。同理如果我们要将日志记录到文件中,则需要通过StreanWrite的实例来实现,上述已经演示。

(3)第三行将DatabaseLogFormatter作为一个监听者来进行注册,在EF中如何就监听者来进行日志记录,上述已经描述。






本文转自Jeffcky博客园博客,原文链接:http://www.cnblogs.com/CreateMyself/p/4753476.html,如需转载请自行联系原作者

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
7月前
|
IDE 数据处理 数据库
【Entity Framework】EF日志-简单日志记录
【Entity Framework】EF日志-简单日志记录
57 0
|
2月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
473 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
25天前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
|
3月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
363 3
|
3天前
|
SQL 关系型数据库 MySQL
MySQL事务日志-Undo Log工作原理分析
事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。
MySQL事务日志-Undo Log工作原理分析
|
5月前
|
Kubernetes Ubuntu Windows
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
149 3
|
1月前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
3月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1751 14
MySQL事务日志-Redo Log工作原理分析
|
2月前
|
存储 监控 安全
什么是日志管理,如何进行日志管理?
日志管理是对IT系统生成的日志数据进行收集、存储、分析和处理的实践,对维护系统健康、确保安全及获取运营智能至关重要。本文介绍了日志管理的基本概念、常见挑战、工具的主要功能及选择解决方案的方法,强调了定义管理目标、日志收集与分析、警报和报告、持续改进等关键步骤,以及如何应对数据量大、安全问题、警报疲劳等挑战,最终实现日志数据的有效管理和利用。
152 0
|
3月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
50 0