NLog整合Exceptionless

前言

    在实际的.Net Core相关项目开发中,很多人都会把NLog作为日志框架的首选,主要是源于它的强大和它的扩展性。同时很多时候我们需要集中式的采集日志,这时候仅仅使用NLog是不够的,NLog主要是负责代码中日志的落地,也就是收集程序中的日志。类似的使用ELK(Elasticsearch+Logstash+Kibana)或EFK(Elasticsearch+Filebeat+Kibana)的集中式日志管理平台负责统一采集各个应用端通过日志框架手机的日志并统一的管理和展示。但是无论是ELK还是EFK,操作都有一定的复杂度,而且这是重型武器,有时候可能还不需要这么大的排场,这时候就需要一种轻量级的解决方案,而Exceptionless正式这种轻量级的分布式日志管理平台。

概念

可能有的同学对于Exceptionless或者是NLog还不是很了解,这里咱们就简单的介绍一下。

Exceptionless

    简单的来说Exceptionless就是一款分布式日志管理框架,它可以统一收集管理并展示出来程序的日志,这样的话减少了传统开发过程中还需要去服务器查找日志的痛苦,大大提升对程序的运维效率。接下来我们先亮出来自学三件套

目前支持JavaScript, Node, .NET Core, .NET相关应用程序的异常信息采集。为何仅支持.Net .Net Core和JS相关的?原因很简单,Exceptionless是基于.NET Core开发的。如果你有别的语言的开发需求也想使用Exceptionless,这个时候不要气馁,因为Exceptionless本质是基于http接口的形式上报数据的,这个可在官方文档上找到如何使用http上报日志信息相关

以上文档有针对Exceptionless通过http接口对接的所有信息,通过它可以封装自己的sdk。

NLog

    相信很多同学对NLog已经相当熟悉了,它是一款日志框架,完美的支持.Net和.Net Core,它在.Net Core的流行度和使用广泛度完全不亚于之前的Log4Net,最重要的它功能很强大,而且扩展起来非常方便,它支持将日志输入到多种target形式,比如txt文件、Sql Server、MySQL、Redis、Mq、MongoDb、ElasticSearch等,几乎能想到的所有存储相关的组件,而且还支持过时日志打包压缩自动删除等高级功能,也是我个人非常推荐的一款日志框架,而且它可以直接对接到.Net Core Logger组件上,废话不多说自学N件套地址

NLog最大的优势就是强大,强大到你能用到的它几乎都支持,而且你想不到的它可能也支持了,而且使用起来也是非常的简单。作为日志框架,我觉得它是最值得一试的一款。

环境搭建

    上面我们已经分别介绍了Exceptionless和NLog知道了他们的概念。Exceptionless支持直接采集日志信息上报到Exceptionless,也就是原始的方式,这个官方文档上都有相关的介绍,这里咱们就不过多介绍这种方式了,使用原始方式的的时候可能会存在许多的问题,比如上报形式单一采集格式的问题等。许多时候我们是使用日志框架记录程序日志相关的,它的优势在于target丰富,而且支持自定义日志格式等等,恰恰NLog足够强大,支持直接将Log数据上报到Exceptionless,接下来我们就来看一下它们之间的整合方式。

Exceptionless搭建

官网提供了两种使用的方式

  • 一种是在官方网站注册账号然后获取apiKey,这样的话不用自己搭建Exceptionless,而是将日志直接收集上报到Exceptionless服务器上。但是,一般基于安全和性能考虑,这种方式并不常用。
  • 另一种则是自建Exceptionless服务,也是本篇我们要使用的方式。之前的低版本支持在window服务器上自建服务,但是高版本已经是基于docker的方式构建了。而使用docker的方式也是我个人日常学习中比较喜欢的方式。

官方也是提供了两种方式去基于docker构建Exceptionless,一种是基于源码自行构建,另一种则是通过官方docker镜像直接运行容器。因为Exceptionless依赖Elasticsearch存储所以官方也是也是直接提供了docker-compose的方式去运行容器。
如果使用基于源码的方式构建,首先是找到Exceptionless的官方GitHub地址https://github.com/exceptionless/Exceptionless去clone源代码,或者直接下载源码的Release包https://github.com/exceptionless/Exceptionless/releases。下载完成之后进入项目根目录找到docker-compose.dev.yml文件,文件内容如下

version: '3.7'

services:
  #通过源码自行构建镜像
  app:
    #依赖elasticsearch和redis
    depends_on:
      - elasticsearch
      - redis
    build:
      context: .
      target: app
    image: exceptionless/app:latest
    environment:
      EX_AppMode: Production
      EX_ConnectionStrings__Cache: provider=redis
      EX_ConnectionStrings__Elasticsearch: server=http://elasticsearch:9200
      #redis的作用是消息总线、消息队列和缓存
      EX_ConnectionStrings__MessageBus: provider=redis
      EX_ConnectionStrings__Queue: provider=redis
      EX_ConnectionStrings__Redis: server=redis,abortConnect=false
      EX_RunJobsInProcess: 'false'
    #暴露访问端口
    ports:
      - 5000:80
      - 5001:443
    volumes:
      - appdata:/app/storage
      - ssldata:/https

  jobs:
    depends_on:
      - app
    image: exceptionless/job:latest
    build:
      context: .
      target: job
    environment:
      EX_AppMode: Production
      EX_BaseURL: http://localhost:5000
      EX_ConnectionStrings__Cache: provider=redis
      EX_ConnectionStrings__Elasticsearch: server=http://elasticsearch:9200
      EX_ConnectionStrings__MessageBus: provider=redis
      EX_ConnectionStrings__Queue: provider=redis
      EX_ConnectionStrings__Redis: server=redis,abortConnect=false
      EX_ConnectionStrings__Storage: provider=folder;path=/app/storage
    volumes:
      - appdata:/app/storage

  elasticsearch:
    image: exceptionless/elasticsearch:7.10.0
    environment:
      discovery.type: single-node
      xpack.security.enabled: 'false'
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    ports:
      - 9200:9200
      - 9300:9300
    volumes:
      - esdata7:/usr/share/elasticsearch/data

  kibana:
    depends_on:
      - elasticsearch
    image: docker.elastic.co/kibana/kibana:7.10.0
    ports:
      - 5601:5601

  redis:
    image: redis:6.0-alpine
    ports:
      - 6379:6379

volumes:
  esdata7:
    driver: local
  appdata:
    driver: local
  ssldata:
    driver: local

通过上面的docker-compose文件我们可以看出目前Exceptionless依赖elasticsearch和redis,大致可以看出Exceptionless存储是依赖elasticsearch,而提升性能的则是redis,比如消息总线防止并发的缓冲队列都是依赖redis的,具体实现细节我们这里就不做过多套路了。因为使用dev的方式构建镜像的方式依赖Exceptionless源码,所以不建议移动该docker-compose文件位置,使用docker-compose的指令直接运行该文件

docker-compose -f docker-compose.dev.yml up

上面的方式虽然可以直接依靠源码去构建,但是其实大可不必这么复杂比如kibana这种完全就是多余的,而且他的这种方式是依赖源码的,生产环境我们不可能把代码直接copy过去,所以我们需要精简一下,如下所示

version: '3.7'

services:
  app:
    depends_on:
      - elasticsearch
      - redis
    image: exceptionless/exceptionless:latest
    environment:
      EX_AppMode: Production
      EX_ConnectionStrings__Cache: provider=redis
      EX_ConnectionStrings__Elasticsearch: server=http://elasticsearch:9200
      EX_ConnectionStrings__MessageBus: provider=redis
      EX_ConnectionStrings__Queue: provider=redis
      EX_ConnectionStrings__Redis: server=redis:6379,abortConnect=false
      EX_RunJobsInProcess: 'false'
    ports:
      - 5000:80
    volumes:
      - appdata:/app/storage

  jobs:
    depends_on:
      - app
    image: exceptionless/job:latest
    environment:
      EX_AppMode: Production
      EX_BaseURL: http://localhost:5000
      EX_ConnectionStrings__Cache: provider=redis
      EX_ConnectionStrings__Elasticsearch: server=http://elasticsearch:9200
      EX_ConnectionStrings__MessageBus: provider=redis
      EX_ConnectionStrings__Queue: provider=redis
      EX_ConnectionStrings__Redis: server=redis:6379,abortConnect=false
      EX_ConnectionStrings__Storage: provider=folder;path=/app/storage
    volumes:
      - appdata:/app/storage

  elasticsearch:
    image: exceptionless/elasticsearch:7.10.0
    environment:
      discovery.type: single-node
      xpack.security.enabled: 'false'
      xpack.ml.enabled: 'false'
      ES_JAVA_OPTS: -Xms1g -Xmx1g
    ports:
      - 9200:9200
      - 9300:9300
    volumes:
      - esdata7:/usr/share/elasticsearch/data

  redis:
    image: redis:6.0-alpine
    ports:
      - 6379:6379

volumes:
  esdata7:
    driver: local
  appdata:
    driver: local

将上面的yml内容直接复制到一个新建的docker-compose.yml的空文件中就可以直运行了,无任何额外的依赖,在yml文件所在路径直接运行以下命令

docker-compose up -d

如果你的服务器已经拥有了elasticsearch和redis服务,也就是不需要使用以上docker-compose的方式进行构建,那么可以直接使用官方docker镜像的方式直接启动Exceptionless容器,可以使用docker原生的方式直接运行

sudo docker run -d -e EX_AppMode=Production -e EX_ConnectionStrings__Cache="provider=redis" -e EX_ConnectionStrings__Elasticsearch="server=http://10.255.198.168:9200" -e EX_ConnectionStrings__MessageBus="provider=redis" -e EX_ConnectionStrings__Queue="provider=redis" -e EX_ConnectionStrings__Redis="server=10.255.198.168:6379,abortConnect=false" -e EX_RunJobsInProcess=false -e EX_Html5Mode=true -p 5000:80 exceptionless/exceptionless:latest

这里注意修改下相关服务的ip地址,因为我粘贴的是我本机的地址,而且注意elasticsearch的版本必须是7.x版本的,否则的话会报错。程序启动完成后再浏览器输输入http://ip:5000后会自动跳转到登录界面NLog整合Exceptionless如果没有登录账户需要注册一个新的用户后,登录到首页如图所示NLog整合Exceptionless因为Exceptionless每个项目的日志信息是根据apiKey去区分的,所以要在Exceptionless中添加你需要采集日志的项目,具体操作如以下步骤

  • 首先,点击所有项目--->创建项目NLog整合Exceptionless
  • 然后,输入组织名称和项目名称NLog整合Exceptionless
  • 然后,选择项目类型,这里以Asp.Net Core程序为例NLog整合Exceptionless
  • 完成之后,点击项目管理,这里的API秘钥正是我们上传到Exceptionless服务所需要的凭证NLog整合Exceptionless

到了这一步Exceptionless搭建基本上就完成了。

集成NLog

新建一个名叫ProductApi的Asp.Net Core的项目,项目名称任意。然后添加Exceptionless.NLog包,这个包就是将NLog数据上报到Exceptionless的包

<PackageReference Include="Exceptionless.NLog" Version="4.6.2" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.10.0" />

Exceptionless.NLog的Github项目地址位于https://github.com/exceptionless/Exceptionless.Net/tree/master/src/Platforms/Exceptionless.NLog这个地址相当隐蔽不太容易被发现,而且说明文档也是很低调几乎没啥内容,可能是觉得NLog的文档写的太完善了,不用多说大家就能知道怎么用。添加完nuget包引用之后,修改Program.cs程序添加NLog的Logging扩展。仅仅添加UseNLog即可,因为我们使用了NLog.Web.AspNetCore扩展包,所以NLog会集成到Asp.Net Core自带的Microsoft.Extensions.Logging中去,不得不说.Net Core的整体扩展性还是非常强的,这样的话我们可以设置默认的Logging的配置即可,几乎感知不到NLog的存在

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).UseNLog();

接下来需要在项目根目录中新建nlog.config用来配置nlog相关信息,新建完成之后添加以下配置

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      throwExceptions="true" internalLogFile="internal-nlog.log" internalLogLevel="Debug" >
  <extensions>
    <!--添加扩展Exceptionless​程序集-->
    <add assembly="Exceptionless.NLog"/>
  </extensions>
  <targets async="true">
    <!--写入本地文件-->
    <target name="File" xsi:type="File" fileName="${basedir}/logs/${shortdate}.log"
            layout=" ${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}| ${newline}" >
    </target>
    <!--上报Exceptionless-->
    <!--xsi:type:固定是Exceptionless-->
    <!--apiKey:即我们在Exceptionless中添加完项目后得到的apiKey-->
    <!--serverUrl:Exceptionless的地址-->
    <target xsi:type="Exceptionless" name="Exceptionless" apiKey="d66B6fXD6sz3kAuqdc5Fe04td7iIygunkDa5GoUt"
            serverUrl="http://10.255.52.93:5000/">
      <!--堆栈信息-->
      <field name="StackTrace" layout="${stacktrace}"/>
      <!--Message信息-->
      <field name="Message" layout="${message}"/>
      <field name="LogLevel" layout="${level}"/>
      <field name="CreateDate" layout="${date}"/>
      <!--物理名称-->
      <field name="MachineName" layout="${machinename}" />
      <!--线程ID-->
      <field name="ThreadId" layout="${threadid}"/>
      <!--发生源-->
      <field name="CallSite" layout="${callsite}"/>
      <field name="AppdomainVersion" layout="${assembly-version}"/>
      <field name="Appdomain" layout="${appdomain}"/> 
    </target>
  </targets>
  <rules>
    <!--本地文件-->
    <logger name="*" writeTo="File"/>
    <!--上报Exceptionless-->
    <logger name='*' writeTo='Exceptionless'/>
  </rules>
</nlog>

新建完nlog.config之后不要忘了将右击该文件 属性--->复制到输出路径--->始终复制,或修改该项目的csproj文件添加

<ItemGroup>
    <Content Update="nlog.config">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
</ItemGroup>

到这里为止关于NLog整合Exceptionless的环境搭建就已经完成了,是不是非常的简单,抛开环境搭建工作量其实并不大,这一切都是源于.Net Core的强大和它那灵活的可扩展性。

简单测试一下

通过上面的操作我们已经把NLog整合Exceptionless的环境搭建起来了,接下来我们随便写点代码测试一波随便建个类,就是为了演示异常,代码无任何实质意义,不喜勿喷。。。,这里我是模拟了一个ApiController抛出异常,然后用Logger记录了信息

[Route("productapi/[controller]")]
public class ProductController : ControllerBase
{
    private readonly ILogger _logger;
    public ProductController(ILogger<ProductController> logger)
    {
        _logger = logger;
    }

    [HttpGet("exceptiontest")]
    public string ExceptionTest()
    {
        try
        {
            throw new Exception("发生了未知的异常");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,$"{HttpContext.Connection.RemoteIpAddress}调用了productapi/product/exceptiontest接口返回了失败");
        }
        return "调用失败";
    }
}

运行起来项目调用一下这段代码之后,查看Exceptionless,如果环境配置啥的都是正确的话,会展示出一下效果,点击All菜单展示出来的信息会比较全NLog整合Exceptionless可以点击查看详情,详情信息记录的非常详细,不得不说Exceptionless还是非常强大非常人性非常实用的NLog整合Exceptionless还能查看更详细的信息NLog整合Exceptionless到这里为止,关于NLog整合Exceptionless的操作就全部完成了,感叹一句就是不仅简单而且强大。

总结

    通过本次整合NLog和Exceptionless,我们既感受到Exceptionless的简单和强大,也感受到了NLog的扩展性之强,希望更多地人能够尝试一下NLog。这一切还是得益于.Net Core自身的扩展性,特别是它内置的一些抽象,完全成为了构建.Net Core程序的核心,而且基于这些内置的核心抽象操作可以很轻松的扩展许多操作,使得模块之间的耦合性变得非常低,而这种设计的思想才是我们真正在编程中应该学习的。

上一篇:Nlog


下一篇:日志框架NLog之将日志发送到邮件