ASP.NET Core MVC之Serilog日志处理,你了解多少?

C#

浏览数:125

2019-5-12

AD:资源代下载服务

前言

本节我们来看看ASP.NET Core MVC中比较常用的功能,对于导入和导出目前仍在探索中,项目需要自定义列合并,所以事先探索了如何在ASP.NET Core MVC进行导入、导出,更高级的内容还需等我学习再给出。

EntityFramework Core

在学习ASP.NET Core MVC之前我们来看看在EF Core中如何更新对象指定属性,这个问题之前我们已经探讨过,但是还是存在一点问题,请往下看。

        public void Update(T entity, params Expression<Func<T, object>>[] properties)
        {
            _context.Entry(entity).State = EntityState.Unchanged;
            foreach (var property in properties)
            {
                var propertyName = ExpressionHelper.GetExpressionText(property);
                _context.Entry(entity).Property(propertyName).IsModified = true;
            }
        }

上述方法可以用来更新对象指定属性,使用lambda来指定需要更新的属性,如下:

        [HttpGet("[action]")]
        public IActionResult Index()
        {
            var blog = new Blog() { Id = 1, Name = "Jeffcky1" };
            _blogRepository.Update(blog, d => d.Name);
            _blogRepository.SaveChanges();
        }

但是当更新数值类型时会解析不到该属性,如下:

             var blog = new Blog() { Id = 1, Count = 1 };
            _blogRepository.Update(blog, d => d.Count);
            _blogRepository.SaveChanges();

此时得到该属性名称为空字符串

 

lambda为何解析不到呢?原来它进行了Convert如下:

既然是将其转换成了Convert,那么在表达式树中应该用其节点类型,如下:

此时我们需要判断其节点类型是否已经经过Convert即可,最终代码如下:

        public void Update(T entity, params Expression<Func<T, object>>[] properties)
        {

            EntityEntry<T> entry = _context.Entry(entity);
            entry.State = EntityState.Unchanged;
            foreach (var property in properties)
            {
                string propertyName = "";
                Expression bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = ExpressionHelper.GetExpressionText(property);
                }
                entry.Property(propertyName).IsModified = true;
            }
       }

此时将能正确解析到数值类型名称,如下:

为何要封装这一层,只是不想首先查询出对象然后再来进行更新属性,其两步操作合并为一步岂不爽哉。当然我们也可以不通过lambda来实现,直接给出属性集合,然后遍历即可,大概如下:

        public void Update(T entity, params object[] properties)
        {
            _context.ChangeTracker.TrackGraph(entity, e =>
            {
                e.Entry.State = EntityState.Unchanged;
                if ((e.Entry.Entity as T) != null)
                {
                    foreach (var p in properties)
                    {
                        _context.Entry(e.Entry.Entity as T).Property(p.ToString()).IsModified = true;
                    }

                }
            });
        }

Serilog日志输出 

目前比较流行的日志框架则是Log4net、NLog,之前也一直用的Log4net,但是在.net core中已经内置了日志框架Serilog,在github上也已加星不少,想必比较强大,既然这样为何不使用内置的呢,免去再用其他日志框架还要配置的麻烦。

创建默认项目在Startup中已经注入日志,如下:

loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

当利用dotnet.exe调试时在控制台会显示调试各种信息,要是直接运行那该如何呢?在web中的配置文件web.config中,如下:

 <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
  </system.webServer>

我们需要设置 stdoutLogEnabled=”true” 来启动调试,此时运行项目你会发现屁都没有,此时我们查看windows日志发现根本无法创建文件夹,想必是没有权限的缘故。

此时我们需要手动创建logs文件夹,最终运行程序则会自动将所有信息写入到日志文件中,如下:

是不是这样就完了呢,这样设置则会将所有信息都会写入日志那样岂不是额外做些无用功,看看如下生成的日志。

 

光说不练假把式,来,我们来实现一个,让你见识见识Serilog的强大。先定义一个自定义输出格式的Option并创建输出日志文件夹。

    public class JeffckyLogOptions
    {
        public string LogPath { get; set; } = @"C:\Jeffcky_StudyEFCore\logs";

        public string PathFormat { get; set; }

        public static void EnsurePreConditions(JeffckyLogOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (string.IsNullOrWhiteSpace(options.LogPath))
            {
                throw new ArgumentException("系统日志文件存储路径未配置", nameof(options.LogPath));
            }

            if (string.IsNullOrWhiteSpace(options.PathFormat))
            {
                throw new ArgumentException("系统日志文件名称未配置", nameof(options.PathFormat));
            }

            if (!Directory.Exists(options.LogPath))
            {
                Directory.CreateDirectory(options.LogPath);
            }
        }
    }

内置实现日志的接口.net core已经给出,所以我们需要获取内置日志接口服务并实现自定义扩展方法。

app.ApplicationServices.GetService(typeof(ILoggerFactory));

此时上述app来源于 IApplicationBuilder 此时我们则只需要实现该接口的自定义扩展方法。我们开始下载Serilog程序包

此时我们需要对日志通过 LoggerConfiguration 类进行初始化配置。

接下来则是将日志写到文件中我们可以自定义格式,此时再下载如下程序包。

里面有个 RollingFile 方法来自定输出日志模板,自此我们就有了如下代码:

          var serilog = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .Enrich.FromLogContext()
                .WriteTo.RollingFile(Path.Combine(options.LogPath, options.PathFormat),
                    outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {Message}{NewLine}{Exception}");

我们获取.net core内置的日志接口服务。

 ILoggerFactory loggerFactory = (ILoggerFactory)app.ApplicationServices.GetService(typeof(ILoggerFactory));

同时设置我们输出日志的路径选项。

JeffckyLogOptions.EnsurePreConditions(options);

接下来将Serilog创建的日志添加到内置日志服务中,如下:

 loggerFactory.AddSerilog(serilog.CreateLogger());

当然上述AddSerilog方法时扩展方法,此时我们仍需要添加如下扩展程序包:

最后还差一步则是日志的生命周期,灰常重要,为什么说灰常重要,我们要确保当程序关闭时或者系统出问题时所有在内存缓冲区的日志都将输送到日志文件夹中,如下:

            // Ensure any buffered events are sent at shutdown
            IApplicationLifetime appLifetime = (IApplicationLifetime)app.ApplicationServices.GetService(typeof(IApplicationLifetime));
            if (appLifetime != null)
            {
                appLifetime.ApplicationStopped.Register(Log.CloseAndFlush);
            }

一气呵成,最终我们在Startup的如下方法中进行调用即可:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {...}

利用Serilog添加自定日志格式输出,进行如下调用:

            app.UseJeffckySelfDefineLog(new JeffckyLogOptions()
            {
                LogPath = Configuration[nameof(JeffckyLogOptions.LogPath)],
                PathFormat = "Jeffcky_StudyEFCore_{Date}.log"
            });

当然还有其他强大功能,比如Serilog中可以获取到上下文,这样的话我们就可以过滤对于那个应用程序使用对应的日志输出级别以及其他,如下:

                serilog = serilog.Filter.ByIncludingOnly((e) =>
                {
                    var context = e.Properties["SourceContext"].ToString();

                    return (context.StartsWith("\"Your Applicion Name") ||
                            e.Level == LogEventLevel.Warning ||
                            e.Level == LogEventLevel.Error ||
                            e.Level == LogEventLevel.Fatal);
                });

说一千到一万,出现结果才是真理,我们一起来看看。

 

看看日志输出信息,你会看到干净的日志输出。

总结

本节我们详细讲解了.net core中新生代日志框架-Serilog,别抱着Log4net不放了,Serilog你值得拥有,跟着老大一直学习中,涨涨见识!因为项目中会用到批量导入和导出,所以在研究导出、导入,下节可能会讲到导入、导出在ASP.NET Core MVC中,也有可能会讲SQL Server中的最后几节关于死锁的内容,不管怎样都是在积累知识,你说呢,敬请期待!

作者:Jeffcky