ETJava Beta | Java    注册   登录
  • 搜索:
  • Net8将Serilog日志推送ES,附视频

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/OrcCoCo/p/18334274
    如有侵权 请联系我们删除  (页面底部联系我们)  

    说在前面的话

    自从AI出来之后,学习的曲线瞬间变缓了,学习的路径也有了很大的变化。
    与本人来说以前大多数都先知晓理论再找相关的框架官网或博客,然后去实践Demo,再加入到代码中,也就是理论-》实践-》应用-》技能
    而现在,可以在稍微知晓概念之后,直接找AI要Demo先跑起来,应用到代码中去,然后反向结合代码去问AI每一步实现的逻辑与理论,再消化成自己技能,也就是 概念-》应用-》理论知识-》技能

    故此次尝试不同的写文思路,从拿到源代码的读者角度,开始思考与实践。

    依照惯例,源代码在文末,需要自取~

    环境准备

    • Docker Desktop
    • VS2022 SDK .NET8
    • Elastic Search 8.0+ Kibana(Docker)
    • SqlServer (Docker 或者本地 SqlServer)
    • Chat Gpt (可选)

    效果一览

    实现要求

    1. 分别记录EF、非EF日志,存放至不同的路径
    2. 其中非EF日志以AOP的思想,记录执行方法的前后信息,API接口调用详情与返回结果,
    3. 把所有的消息推送至elastic search。

    实现效果

    Docker

    Docker中启动 ES 和 SqlServer

    再 F5 启动项目

    日志分别存储

    • EF 日志存储在 Logs/EFSql 文件夹下
    • 非EF日志存储在 Logs/AuditLogs 文件夹下

    推送至ES

    所有的日志推送至Elastic Search

    从Program 初见端倪

    OK,相信你现在已经把代码拉下来了,如果没有想生啃代码,不用跳过此节。

    .net 8 与 .net 6 可以使用顶级语句,相对于.net 5 及以前的版本,去掉了Startup作为web的配置程序,也就是上手各大项目的入口, 这里直接看Program 来探究Demo 如何配置Web,来一窥端倪吧!

    using Autofac;
    using Autofac.Extensions.DependencyInjection;
    using Autofac.Extras.DynamicProxy;
    using Microsoft.EntityFrameworkCore;
    using SampleDemo.Yzh.Net.Logger;
    using SampleDemo.Yzh.Net.Repository;
    using SampleDemo.Yzh.Net.Service;
    using SampleDemo.Yzh.Net.Core;
    using Serilog;
    using ILogger = Serilog.ILogger;
    
    var builder = WebApplication.CreateBuilder(args);
    // Config
    builder.Services.AddSingleton(new AppSettings(builder.Configuration));
    // Serilog
    builder.Host.AddCustomLog();
    // EF
    builder.Services.AddDbContextPool<TestContext>
        (o => o.UseSqlServer(builder.Configuration.GetConnectionString("NicoLocaldbStr"))
    #if DEBUG
        .EnableSensitiveDataLogging()
    #endif
        .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddSerilog())
        ));
    // Http Info
    builder.Services.AddHttpContextAccessor();
    // Auto Fac
    builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
    builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
    {
        // 注册AopDemo拦截器为单例模式
        containerBuilder.RegisterType<AopDemo>().SingleInstance();
    
        // InterceptedBy指定拦截器
        containerBuilder.RegisterType<ValuesService>().As<IValuesService>().EnableInterfaceInterceptors().InterceptedBy(typeof(AopDemo));
        // 或者在IValuesService 加标签 [Intercept(typeof(AopDemo))]
        //containerBuilder.RegisterType<ValuesService>().As<IValuesService>().EnableInterfaceInterceptors();
    
        //向 Autofac 容器注册 Log.Logger 这个已经存在的日志记录器实例 ,指定这个实例作为 ILogger 接口的实现 ,此ILogger为 Serilog的ILogger
        //当应用程序中的组件通过依赖注入请求 ILogger 接口的实例时,它们会接收到 Log.Logger 这个单例对象
        containerBuilder.RegisterInstance(Log.Logger).As<ILogger>();
    });
    
    builder.Services.AddControllers();
    var app = builder.Build();
    // Middleware
    app.UseMiddleware<ResponseLoggingMiddleware>(); //返回客户端响应的日志记录
    app.UseAuthorization();
    app.MapControllers();
    
    app.Run();
    

    这里我直接把Program.cs 51 行代码全部拉出来,由此来看大致做了些什么。

    • using 中可以看到 引用了Autofac以及他的扩展包, 引入了 Serilog,并且指定了ILogger为 Serilog.ILogger,作用暂且按下不表

    • builder.Services.AddSingleton(new AppSettings(builder.Configuration)) 这里将配置以单例的形式封装在AppSettings类中,后续取配置直接从AppSettings类中取即可

    • builder.Host.AddCustomLog(); 这里用一个扩展方法,将Serilog的注入写入AddCustomLog()方法中,后续这将是该Demo核心方法,重点看

    builder.Services.AddDbContextPool<TestContext>
        (o => o.UseSqlServer(builder.Configuration.GetConnectionString("NicoLocaldbStr"))
    #if DEBUG
        .EnableSensitiveDataLogging()
    #endif
        .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddSerilog())
        ));
    
    • 上述代码,以DbContext池化的方式注入EF,并且在Debug模式下开启敏感日志,配置Serilog作为日志的记录工具

    • builder.Services.AddHttpContextAccessor(); 添加访问Http请求的信息,用于后续Reponse中间件开发提供HTTP信息

    • builder.Host.UseServiceProviderFactory 以及 builder.Host.ConfigureContainer 配置Auto Fac,注释详解

    • app.UseMiddleware< ResponseLoggingMiddleware >(); ResponseLoggingMiddleware中间件返回客户端响应的日志记录

    再看Serilog拓展

    从上文可知,使用了 AddCustomLog() 这样的一个扩展方法,注入了Serilog,OK! ,Ctrl + F12 进入一探究竟

        public static class CustomLogger
        {
            public static IHostBuilder AddCustomLog(this IHostBuilder builder)
            {
                Log.Logger = new LoggerConfiguration()
                    .ReadFrom.Configuration(AppSettings.Configuration)
                    .Enrich.FromLogContext()
    
                    // 输出到控制台
                    .WriteToConsole()
                    // 输出到文件
                    .WriteToFile()
                    // 输出到Es
                    .WriteToElasticsearch()
                    .CreateLogger();
    
                builder.UseSerilog();
                return builder;
            }
        }
    
    • .ReadFrom.Configuration(AppSettings.Configuration):指示 Serilog 从应用程序的配置文件(如 appsettings.json)中读取配置信息。这里假设有一个 AppSettings 类,它持有应用程序的配置信息。

    • .Enrich.FromLogContext(): 添加从日志上下文中丰富日志事件的能力,例如,可以自动地将关联的 HTTP 请求信息添加到日志中。

    • .WriteToConsole(): 配置 Serilog 将日志输出到控制台。

    • .WriteToFile(): 配置 Serilog 将日志输出到文件。

    • .WriteToElasticsearch(): 配置 Serilog 将日志输出到 Elasticsearch。这对于在 Elasticsearch 中集中管理和分析日志非常有用。

    WriteToConsole与WriteToFile 也是拓展方法,那就继续Ctrl + F12 继续下去咯

    public static class LoggerConfigurationExtensions
    {
        /// <summary>
        /// 输出在控制台
        /// </summary>
        /// <param name="loggerConfiguration"></param>
        /// <returns></returns>
        public static LoggerConfiguration WriteToConsole(this LoggerConfiguration loggerConfiguration)
        {
            // 输出普通日志
            loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg =>
                lg.FilterRemoveSqlLog().WriteTo.Console());
    
            // 输出SQL
            loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg =>
                lg.FilterSqlLog().WriteTo.Console());
    
            return loggerConfiguration;
        }
    
        /// <summary>
        /// 写入文件
        /// </summary>
        /// <param name="loggerConfiguration"></param>
        /// <returns></returns>
        public static LoggerConfiguration WriteToFile(this LoggerConfiguration loggerConfiguration)
        {
            // SQL语句写入 
            loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg =>
                lg.FilterSqlLog()
                    .WriteTo.Async(s => s.File(LogContextStatic.Combine(LogContextStatic.EFSql, @"EFSql.txt"), rollingInterval: RollingInterval.Day,
                        outputTemplate: LogContextStatic.FileMessageTemplate, retainedFileCountLimit: 31)));
    
            // 非SQL写入
            loggerConfiguration = loggerConfiguration.WriteTo.Logger(lg =>
                lg.FilterRemoveSqlLog()
                    .WriteTo.Async(s => s.File(LogContextStatic.Combine(LogContextStatic.AuditLogs, @"AuditLog.txt"), rollingInterval: RollingInterval.Hour,
                    outputTemplate: LogContextStatic.FileMessageTemplate, retainedFileCountLimit: 31)));
            return loggerConfiguration;
        }
    
        /// <summary>
        /// 推送至 ES
        /// </summary>
        /// <param name="loggerConfiguration"></param>
        /// <returns></returns>
        public static LoggerConfiguration WriteToElasticsearch(this LoggerConfiguration loggerConfiguration)
        {
            var esUri = AppSettings.GetValue("ElasticConfiguration:Uri");
            loggerConfiguration = loggerConfiguration.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(
                    new Uri(esUri))
            {
                AutoRegisterTemplate = true,
                IndexFormat = "nico-api-log-{0:yyyy.MM.dd}",
                ModifyConnectionSettings = con => con.ServerCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => true)
                .BasicAuthentication(
                    AppSettings.GetValue("ElasticConfiguration:Es_UserName"),
                    AppSettings.GetValue("ElasticConfiguration:Es_Pwd"))
            });
            return loggerConfiguration;
        }
    
        /// <summary>
        /// 过滤出 SQL语句的日志
        /// </summary>
        /// <param name="lc"></param>
        /// <returns></returns>
        public static LoggerConfiguration FilterSqlLog(this LoggerConfiguration lc)
        {
            return lc.Filter.ByIncludingOnly(e =>
            e.Properties.ContainsKey("SourceContext") &&
            e.Properties["SourceContext"].ToString().Contains("Microsoft.EntityFrameworkCore.Database.Command"));
        }
    
        /// <summary>
        /// 过滤非 SQL语句的日志
        /// </summary>
        /// <param name="lc"></param>
        /// <returns></returns>
        public static LoggerConfiguration FilterRemoveSqlLog(this LoggerConfiguration lc)
        {
            return lc.Filter.ByExcluding(e =>
            e.Properties.ContainsKey("SourceContext") &&
            e.Properties["SourceContext"].ToString().Contains("Microsoft.EntityFrameworkCore.Database.Command"));
        }
    }
    

    注释非常详尽,不再赘述,这里需要解释核心的问题,如何区别的SQL与非SQL的?

    日志上下文 (SourceContext):Serilog 允许通过日志事件的属性来进行过滤。在这个例子中,使用了 SourceContext 属性,这是一个常用于标识日志来源的属性。对于由 Entity Framework Core 生成的日志,SourceContext 通常会包含值 "Microsoft.EntityFrameworkCore.Database.Command",这表示日志条目是由 EF Core 在执行数据库命令时产生的。

    过滤器 (Filter.ByIncludingOnly):这个方法允许配置日志系统仅包含符合特定条件的日志条目。在这里,条件是日志事件的属性中必须包含 SourceContext,并且其值包含 "Microsoft.EntityFrameworkCore.Database.Command"。这样,只有 EF Core 产生的数据库命令日志会被包括进来。

    如果此节看懂了,下面实践绝对难不倒读者。

    部署ES

    本人在部署ES的时候遇到了很多坑,看到一些大佬的博客也是一笔带过,这里用Docker desktop 一步一步全部覆盖到位,跟着文章步骤走,绝对搞得定。

    启动elasticsearch 、 kibana

    首先先贴上 ES官方文档启动教程 https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html

    下载elasticsearch 、 kibana 镜像,本文截至24年7月30日,使用最新的ES版本8.14.3

    镜像列表

    docker创建网络

    docker network create elastic-net
    

    创建好的网络

    docker中启动ES,并且记住登录的密码,加入集群的Token

    docker run --name es01 --net elastic -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -t elasticsearch:8.14.3
    

    启动之后,会在终端显示密码,还有加入集群的Token,待会在启动kibana时要用,如果不小心关掉了,可以在docker desktop中,找到es的容器日志找到。

    包含了密码,Token等

    到这里ES启动完成,接下来启动Kibana

    docker中启动kibana,加入集群

    docker run --name kib01 --net elastic -p 5601:5601 kibana:8.14.3
    

    提示进入后台配置页面

    http://0.0.0.0/5601 可能进不去(改了host文件的原因),用 http://localhost/5601即可

    将token传入 ,静待部署完成, 就进入了登录界面

    账号默认为 elastic 密码(小写) 则为一开始保存的 密码,这里为 _*5O7P+5FKSV7nC_EDEE 记得改为你自己的 ,成功登录之后就进入后台页面

    ES后台创建数据集 (DataView)

    创建 Data view , 此时需要重点注意 , Index pattern这里需要填对应索引的名称,匹配成功才能在Discover中看到日志

    至此 ES 搭建完成,现在要去将 Log 推送到 ES平台来

    启动!!!

    上文中已经将ES部署好了,现在需要将项目跑起来,将日志推送到ES平台中去。

    修改项目配置

    AppSetting.json

    数据库使用的是SQL Server , 需要修改为你自己的SQL 链接字符串即可。

        "ConnectionStrings": {
        // SQL Server
        "NicoLocaldbStr": "Data Source=localhost;Initial Catalog=nicolocaldb;User ID=sa;Password=MyStrongPwd!2#;Trust Server Certificate=True"
      }
    
    

    这里需要加张表

    
    -- SQL Server
    CREATE TABLE TestEFTable (
        Id INT PRIMARY KEY IDENTITY(1,1),        -- Id 列为主键并设置为自增
        Name NVARCHAR(255) NULL,                 -- Name 列,允许 NULL,使用 NVARCHAR 类型来支持 Unicode 字符
        LastModifiedAt DATETIMEOFFSET NULL,      -- LastModifiedAt 列,允许 NULL,使用 DATETIMEOFFSET 类型
        LastModifiedBy INT NOT NULL              -- LastModifiedBy 列,不允许 NULL
    );
    
      "ElasticConfiguration": {
        "Uri": "https://localhost:9200",    // ES 地址
        "Es_UserName": "elastic",           // 账号
        "Es_Pwd": "_*5O7P+5FKSV7nC_EDEE"    // 密码
      }
    

    ES 的配置同样修改为自己的配置。

    原神启动!(bushi

    F5启动! (以下是操作视频)
    https://www.bilibili.com/video/BV1w3vUeWE4n/

    总结

    稍微总结一下,

    • Demo集成了Elasticsearch,Serilog,拦截器,autofac,EF。
    • 区分EF 与非EF日志的记录与存放
    • 记录完整的操作视频,包教包会!

    源代码仓库 [GitHub] https://github.com/OrzCoCo-Y/LogSampleDemo
    [Gitee] https://gitee.com/yi_zihao/LogSampleDemo

    参考资料

    【ChatGPT】
    【严架的Maomi 框架】https://maomi.whuanle.cn 借鉴了其中HTTP日志记录,取代了自己写中间件
    【老八的哲学开源框架】https://github.com/anjoy8/Blog.Core 借鉴了其中服务注册