前言
在实际的.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 文件所在路径直接运行以下命令
如果你的服务器已经拥有了 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后会自动跳转到登录界面
如果没有登录账户需要注册一个新的用户后,登录到首页如图所示
因为 Exceptionless 每个项目的日志信息是根据 apiKey 去区分的,所以要在 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 菜单展示出来的信息会比较全
可以点击查看详情,详情信息记录的非常详细,不得不说 Exceptionless 还是非常强大非常人性非常实用的
还能查看更详细的信息
到这里为止,关于 NLog 整合 Exceptionless 的操作就全部完成了,感叹一句就是不仅简单而且强大。
总结
通过本次整合 NLog 和 Exceptionless,我们既感受到 Exceptionless 的简单和强大,也感受到了 NLog 的扩展性之强,希望更多地人能够尝试一下 NLog。这一切还是得益于.Net Core 自身的扩展性,特别是它内置的一些抽象,完全成为了构建.Net Core 程序的核心,而且基于这些内置的核心抽象操作可以很轻松的扩展许多操作,使得模块之间的耦合性变得非常低,而这种设计的思想才是我们真正在编程中应该学习的。
👇欢迎扫码关注我的公众号👇
评论