Util应用框架基础(六) - 日志记录(四) - 写入 Exceptionless

发布时间 2023-11-12 16:28:46作者: 何镇汐

本文是Util应用框架日志记录的第四篇,介绍安装和写入 Exceptionless 日志系统的配置方法.

Exceptionless 是一个日志管理系统,使用 Asp.Net Core 开发,比 Seq 的模糊搜索能力弱,使用它可能需要一些技巧.

Util应用框架目前主要使用 SeqExceptionless 管理日志.

你可以从中选择一个合适的.

本节介绍使用 Docker 简单安装 Exceptionless,用于开发测试,部署到生产环境请参考官方文档.

安装 Exceptionless

创建 exceptionless-data 卷, 运行命令.

docker volume create exceptionless-data

创建 Exceptionless 容器, 运行命令.

docker run --name exceptionless -d --restart=always -v exceptionless-data:/usr/share/elasticsearch/data -p 5480:80 exceptionless/exceptionless:8.0.0-elasticsearch7

容器名称: exceptionless

连接端口: 5480

安装成功后,Docker容器列表出现 exceptionless 容器.

运行 Exceptionless

打开 http://localhost:5480 ,可以看到 Exceptionless 管理界面.

Exceptionless 启动需要一些时间,请稍后刷新页面.

如果 Exceptionless 持续无法启动,请删除 exceptionless-data 卷,并重新安装.

创建 Exceptionless 用户

点击 Signup 按钮进入注册页面.

使用下面的信息注册,或按你自己的喜好设置.

名称: admin

邮箱: admin@a.com

密码: admin123

点击创建我的帐户按钮,进入管理界面.

创建 Exceptionless 项目

填写组织名称,范例: Util

填写项目名称,范例: Demo

点击创建项目按钮.

选择顶部菜单的所有项目 ,点击Demo项目设置按钮.

在 Demo 项目设置界面,选择 API密钥 选项卡.

项目开发时,配置以下信息将日志写入 Exceptionless.

API密钥: 复制你的API密钥,如上图所示.

Exceptionless服务地址: http://localhost:5480

日志配置

  • 引用Nuget包

    Nuget包名: Util.Logging.Serilog.Exceptionless

  • AddExceptionless

    使用 AddExceptionless 扩展方法启用 Exceptionless 日志操作.

    • 默认不带参数,你可以在配置文件中指定 Exceptionless 的配置信息.

      var builder = WebApplication.CreateBuilder( args );
      builder.AsBuild().AddExceptionless();
      
    • 如果要清除默认设置的日志提供程序,传入 true.

      Asp.Net Core 默认日志提供程序会把消息输出到控制台,你可以清除它们.

      builder.AsBuild().AddExceptionless( true );
      
    • 设置应用程序名称.

      对于微服务应用,记录产生日志的应用名称,能方便排查问题.

      builder.AsBuild().AddExceptionless( "权限服务" );
      
    • 指定Api密钥和服务地址.

      builder.AsBuild().AddExceptionless( t => {
          t.ApiKey = "";
          t.ServerUrl = "";
      } );
      

      指定Api密钥和服务地址并清除默认设置的日志提供程序.

      builder.AsBuild().AddExceptionless( t => {
          t.ApiKey = "";
          t.ServerUrl = "";
      }, true );
      
  • 添加 appsettings 配置节

    appsettings.json 配置文件添加 Exceptionless 配置节.

    {
      "Logging": {
        "LogLevel": {
          "Default": "Trace"
        }
      },
      "Exceptionless": {
        "ApiKey": "8JtknZBV1NRC7bdsv6SF5cbBFrMZipWMkARZxkxo",
        "ServerUrl": "http://localhost:5480"
      }
    }
    

    ApiKey 指定 API 密钥.

    ServerUrl 指定 Exceptionless 服务地址.

    最简化配置仅需设置 API 密钥和 Exceptionless 服务地址.

    本教程 Exceptionless 安装示例使用 5480 端口.

    API 密钥替换成你自己的.

    其它参数请参考 Exceptionless 文档.

查看 Exceptionless

配置完成后,可以启动你的项目,查看 Exceptionless 日志界面.

可以看到由 Asp.Net Core 写入的系统日志.

结构化日志支持

下面的示例比较结构化日志与普通日志的差别.

范例使用 ILog 接口写入日志,你也可以使用 ILogger 替代.

先记录普通日志消息,方便后续比较.

_log.Message( "用户admin已删除" ).LogInformation();

查看扩展属性.

结构化日志语法

  • {}

    • {} 用来定义日志属性.

    • 范例:

      _log.Message( "用户{User}已删除", "admin" ).LogInformation();
      

      {User} 定义了名为 User 的字符串属性.

      查看 Exceptionless 扩展属性界面, 可以看到已经添加了 User 属性.

      识别出了 User 属性,就可以使用它进行搜索.

      搜索框输入下面的表达式.

      data.User:"admin"
      

      User 是一个扩展属性,Exceptionless 要求扩展属性前加上 data.

      属性名与属性值使用 : 分隔,表示相等,即 属性名=属性值 .

      字符串值可以放进双引号中,比如 "admin"

      字符串值也可不带双引号,比如 admin.

      但是不能放在单引号中, 比如 'admin' .

      Exceptionless 的模糊匹配能力有限,只支持头匹配,类似 like 'xx%' ,不能完全模糊搜索.

      Exceptionless 使用 * 进行模糊匹配,只能放在参数值右方, 比如 adm* .

      带 * 的参数值不能放在双引号中 .

      范例:

      以 adm 开头模糊搜索 User 扩展属性.

      data.User:adm*
      

      注意: 不要将结构化日志 {} 与 .Net 字符串语法 $"{}" 混淆.

      var user = "admin";
      _log.Message( $"用户{user}已删除" ).LogInformation();
      

      $"" 中的 {user} 将被 user 变量值 'admin' 替换, 等效于.

      _log.Message( "用户admin已删除" ).LogInformation();
      

      它仅是普通日志消息,不是结构化日志.

    • {@} 用来定义日志属性,并强制序列化对象.

      前面的示例使用简单的字符串参数,如果传入对象参数是什么结果?

    • 范例:

      定义 User 对象.

      namespace Demo;
      
      public class User {
        public int Id { get; set; }
        public string Name { get; set; }
      }
      

      现在传入 User 对象.

      var user = new User { Id = 1, Name = "a" };
      _log.Message( "用户{User}已删除", user ).LogInformation();
      

      {User} 被替换为字符串 "Demo.User" ,这是通过调用 User 对象的 ToString() 方法得到的.

      这与我们的预期不符合,我们希望序列化 User 对象,从而获取对象的结构进行搜索.

      Serilog 对 {} 参数的处理有一套内置规则,如果传入对象,有些结构能序列化,比如字典 Dictionary<string,int> ,有些则不能.

      不应该依赖 Serilog 自动序列化的能力,而是应明确指定是否序列化.

      {@} 强制序列化对象,从而保留对象结构,以方便日志系统展示和搜索.

      var user = new User { Id = 1, Name = "a" };
      _log.Message( "用户{@User}已删除", user ).LogInformation();
      

      遗憾的是, Exceptionless 无法搜索对象属性.

      虽然 Exceptionless 对扩展属性的搜索支持有限,但它内置了很多搜索语法,可以参考官方文档.

    • {$} 用来定义日志属性,并强制字符串化.

      {$} 让 Serilog 以明确的方式显示对象的字符串表示.

      var user = new User { Id = 1, Name = "a" };
      _log.Message( "用户{$User}已删除", user ).LogInformation();
      

      {$User} 调用user对象的 ToString() 方法显示为字符串.