专业的移动网站建设公司排名,wordpress 老版本,备案网站域名查询,什么是关键词排名优化在上上篇博客通过对aspnetcore启动前配置做了一些更改#xff0c;以及对nlog进行了自定义字段#xff0c;可以把请求记录输送到mysql#xff0c;正式情况可能不会这么部署。因为近期也在学习elk#xff0c;所以就打算做一个实例#xff0c;结合nlog把日志输送到logstash以及对nlog进行了自定义字段可以把请求记录输送到mysql正式情况可能不会这么部署。因为近期也在学习elk所以就打算做一个实例结合nlog把日志输送到logstash当然现在有开源的.netcore性能监控系统但是本文的重点是nlog的拓展以及如何拓展。现有的工作方式在nlog中我们在配置文件targets节点下添加一个target就可以定义一个日志输出目标当我们需要把日志输送到logstash时需要添加一个target节点其type为Network填上addresslayout等等一般有如下配置targets target xsi:typeNetwork namelogstash_apiinsight keepConnectionfalse layout${customer-ip} ${customer-method} ${customer-path} ${customer-bytes} ${customer-duration} address tcp://192.168.93.135:8102 /target/targetsrules logger nameWebApp.* minlevelTrace writeTologstash_apiinsight //rules我们可以在layout中定义很多个字段然后在当logstash接受到数据包时通过grok来依次解析每一个字段如果对正则比较熟悉这种方式确实能够工作但是当日志记录的字段数越来越多时其实是很麻烦的。我个人比较喜欢json日志通过json发送时数据更加语义化在logstash的处理也会容易很多组织成json发送有什么缺点吗现在能想到的只有它会多发送属性的名称从而浪费一些资源因为如果按上面配置的layout日志的传输过程中是不会发送 message,date,level 这些属性的名称的在logstash做稍作处理就可以解析这些字段。要注意的是上面的 layout中声明的字段是不存在的为了方便测试我们可以直接填数据layout127.0.0.1 GET /home/index 2000 50此时只要你在WebApp命名空间下记录日志输送到logstash的始终都是这一行内容如果您是通过rpm来安装的logstash那么在 /etc/logstash/conf.d 下新建一个 test.conf 输入下面的内容同时打开服务器的 8012端口input { tcp { port 8102 }}filter { grok { match { message %{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration} } }}output { elasticsearch { hosts localhost:9200 index sample }}启动.netcore程序多记几次日志观察kibana就会有如下输出这里虽然是假数据但是想要真的也很简单在我的上上一片博客中就多次使用了NLog中LayoutRenderer这个父类来自定义字段[LayoutRenderer(customer-ip)]public class ProtocolApiInsightRenderer : LayoutRenderer{ protected override void Append(StringBuilder builder, LogEventInfo logEvent) { builder.Append(127.0.0.1); }}现有方式有一些问题是很难解决的。如果日志本身有空格呢我们该寻找哪个字符作为分隔符再比如当部分字段可空部分字段不可空的时候当要传三个数字到logstash结果只传了两个那grok该怎么解析呢虽然不一定会遇到这种情况但也反映了语义化的json是更容易处理的。拓展NLog思考的过程其实到这里我们不难发当我们需要向服务器发送tcp数据包时现现有的工作方式是存在不足的准确来说是向logstash发送数据包是存在不足的因为这里本身就是一个 NLog.Targets.NetworkTarget它的原本目标就是向TCP发送日志在使用nlog的过程中我们知道在向数据库服务器发送日志的时候可以通过配置文件中的parameter节点表明字段但是NetworkTarget是不支持这样的方式的target name db_log xsi:typeDatabase dbProviderMySql.Data.MySqlClient.MySqlConnection, MySql.Data connectionString${var:connectionString} commandText insert into log( application, logged, level, message, logger, callsite, exception, ip, user, servername, url ) values( application, logged, level, message, logger, callsite, exception, ip, user, servername, url ); /commandText parameter name application layout${apiinsight-application} / parameter name logged layout${date} / parameter name level layout${level} / parameter name message layout${message} / parameter name logger layout${logger} / parameter name callSite layout${callsite:filenametrue} / parameter name exception layout${apiinsight-request-exception} / parameter name IP layout${aspnet-Request-IP}/ parameter name User layout${apiinsight-request-user}/ parameter name serverName layout${apiinsight-request-servername} / parameter name url layout${apiinsight-request-url} //target通过观察源码还可以发现这里所有的type都是通过继承Target这个抽象类来定义的所以现在很容易想到一种方式来拓展那就是写一个 LogstashTarget 继承Target但是你会发现这要要做的东西非常多并且可能需要进一步的阅读NLog的源码NLog代码量相对其他框架来说以及很少了但是至少还要做这些工作1、添加类似parameter的节点2、TCP连接池和消息发送队列虽然这两项都可以借鉴 NetworkTarget 和 DatabaseTarget 他们的工作方式但是很明显这样的代码在你不修改源码的情况下你会写两遍软件设计的基本原则就是尽量拓展少修改。所以我们很快会想到第二种方法既然要结合NetworkTarget 和 DatabaseTarget他们两的特点那我是否可以写一个LogstashTarget继承自NetworkTarget就避免了了 TCP 连接池和消息发送队列的重复代码柑橘哪不对因为还要重复DatabaseTarget的一部分代码这里是我的思考过程其实最主要的原因还是想少些一些代码吧所以我考虑到了第三种方法拓展不行将就用着类型LayoutRenderer从出现到现在我一直都是认为它就是用来自定义字段的但是观察源码就会发现它的子类非常多并没有去研究每一个子类都做了什么但是现在他可以最快的帮助我实现想要的功能。这里我定义了一个抽象类/// summary/// 由于 see crefNLog.Targets.NetworkTarget/ 没有提供 parameter 字段/// 为了更好的把数据组织到 logstash我们可以在这里自定义字段最终以 json 传输到 logstash/// /summarypublic abstract class LogstashLayoutRenderer : LayoutRenderer{ protected HttpContext httpContext HttpContextProvider.Current; protected async override void Append(StringBuilder builder, LogEventInfo logEvent) { builder.Append(await ProviderJson()); } protected abstract Taskstring ProviderJson();}抽象类LogstashLayoutRenderer通过子类实现的方法ProviderJson向builder写入数据这种方法最简单是因为我的根本需求是想给Logstash发一个json格式的日志所以这样也比较好理解至于接下来我想发这个格式的json还是那个格式的json都可以通过实现该类型来达到我的目标所以现在的方法依然使用NetworkTarget作为输出目标。 此外这里把 HttpContext放进来似乎有点奇怪可能当时懒得写那么长HttpContextProvider.Current这样去拿吧可以在这里看它的代码是怎么实现的 https://www.cnblogs.com/cheesebar/p/9078207.html 。不过这样的做法还有一个缺点就是不能在配置文件中定义想要的字段Logstash的字段/// summary/// 给 see crefNetworkTarget/ 优雅的自定义字段/// /summarypublic abstract class LayoutFieldBase{ public abstract Taskstring ProviderField(); public abstract string ProviderFieldName { get; } public HttpContext httpContext HttpContextProvider.Current;}在给这个类取名字的时候是LogstashFieldBase好呢还是NetworkFieldBase好呢。纠结中就叫LayoutFieldBase吧其实他是有两个作用的一方面LogstashLayoutRenderer的ProviderJson方法会搜集所有的字段组织成json这些字段都是继承自该接口的另一方面如果我不仅要把这个相同的日志记录到Logstash可能还要记录到db或者文件。LogstashLayoutRenderer的实现者总是包含很多个LayoutFieldBase这个是写死的同时因为可能还要记录到db那我还为每一个LayoutFieldBase的实现者定义了一个LayoutRendererBase。可以看到Append方法调用子类的实现方法来填写响应的字段值也就是说子类提供一个LayoutFieldBase就可以避免同样的代码写两遍/// summary/// 已知的是这里通过 see crefLayoutFieldBase/ 给 see crefNetworkTarget/ 优雅的自定义字段/// 但是考虑到有些字段可能同事也要输入到 see crefDatabaseTarget/但是相同字段的值获取方式是一样的/// Append 方法通过代理接口 see crefILayoutProxy/ 提供的 see crefLayoutFieldBase/ 取值/// /summarypublic abstract class LayoutRendererBase : LayoutRenderer, ILayoutProxy{ public abstract Type LayoutType { get; } private LayoutFieldBase _layout; public LayoutFieldBase Layout { get { if (_layout null) { if (HttpContextProvider.Current ! null) { _layout HttpContextProvider.Current.RequestServices.GetServicesLayoutFieldBase().First(t t.GetType() LayoutType); } } return _layout; } } protected async override void Append(StringBuilder builder, LogEventInfo logEvent) { builder.Append(await Layout?.ProviderField()); }}当输出目标非Network的时候依然可以通LayoutRendererBase的实现者的LayoutRenderer特性在配置文件中应用它这里看一个例子[LayoutRenderer(apiinsight-application)]public class ApplicationApiInsightRenderer : LayoutRendererBase
{ public override Type LayoutType typeof(AppLayout);
}预先定义了一些LayoutFieldBase和LayoutRendererBase真正进行字段值计算的是左边的这些类型右边的这些类型通过代理使用左边的类型来提供字段所有的LayoutFieldBase都在开始时注入到容器/// summary/// 应用程序名称/// /summarypublic class AppLayout : LayoutFieldBase{ public override string ProviderFieldName app; public async override Taskstring ProviderField() { if (httpContext ! null) { var env httpContext.RequestServices?.GetServiceIHostingEnvironment(); return env.ApplicationName; } return string.Empty; }}[LayoutRenderer(apiinsight-application)]public class ApplicationApiInsightRenderer : LayoutRendererBase{ public override Type LayoutType typeof(AppLayout);}/// summary/// 配合 NLog Target Network 注入自定义字段/// 自定义字段都继承自 see crefLayoutFieldBase//// /summarypublic static class LogstashLayoutBaseServiceCollectionExtensions{ public static void AddLayoutBase(this IServiceCollection services) { var layouts AppDomain.CurrentDomain.GetAssemblies().SelectMany(t t.GetTypes()) .Where(t typeof(LayoutFieldBase).IsAssignableFrom(t) !t.IsAbstract); foreach (var item in layouts) { services.AddSingleton(typeof(LayoutFieldBase), item); } }} 拓展完成对于拓展这件事其实已经做完了因为接下来的事情是业务相关的在回想一下通过自定义的LogstashLayoutRenderer组织Json到Logstash。在拓展中已经定义了一些可能用到的字段比如说应用程序名称AppLayout请求方法MethodLayout等等asp.netcore接口请求统计新增的start和time字段回顾之前我写的博客发现只有请求开始时间和请求消耗时间没有在之前的拓展写进来所在在这里加进来/// summary/// 请求到达的时间/// /summarypublic class StartLayout : LayoutFieldBase{ public override string ProviderFieldName start; public async override Taskstring ProviderField() { if (httpContext ! null) { var _apiInsightsKeys httpContext.RequestServices.GetServiceIApiInsightsKeys(); if (httpContext ! null) { if (httpContext.Items.TryGetValue(_apiInsightsKeys.StartTimeName, out var start) true) { return ((DateTime)start).ToString(yyyy/MM/dd hh:mm:ss); } } } return string.Empty; }}/// summary/// 请求消耗的时间/// /summarypublic class TimeLayout : LayoutFieldBase{ public override string ProviderFieldName interval; public async override Taskstring ProviderField() { if (httpContext ! null) { var _apiInsightsKeys httpContext.RequestServices.GetServiceIApiInsightsKeys(); if (httpContext ! null) { if (httpContext.Items.TryGetValue(_apiInsightsKeys.StopWatchName, out var stopWatch) true) { return (stopWatch as Stopwatch).ElapsedMilliseconds.ToString(); } } } return string.Empty; }}测试的时候可能我也要看统计有没有成功记录需要对比数据库和elk所以数据库依然要写这里定义相应的LayoutRenderer[LayoutRenderer(apiinsight-start)]public class StartApiInsightRenderer : LayoutRendererBase{ public override Type LayoutType typeof(StartLayout);}[LayoutRenderer(apiinsight-time)]public class TimeApiInsightRenderer : LayoutRendererBase{ public override Type LayoutType typeof(TimeLayout);} 核心ApiInsightLogstashLayoutRenderer在调试的时候发现所有的LayoutRenderer都是单例的所以这边的Layouts其实都只会创建一次所以性能会比想象的好很多json就是通过newtonsoft这个裤来创建的。/// summary/// 在 NLog 配置文件中Network 我们只需要注册一个 Layout名称就是 logstash-apiinsight/// /summary[LayoutRenderer(logstash-apiinsight)]public class ApiInsightLogstashLayoutRenderer : LogstashLayoutRenderer{ static readonly Type[] LayoutTypes new[] { typeof(StartLayout), typeof(TimeLayout), typeof(ProtocolLayout), typeof(HostLayout), typeof(PortLayout), typeof(PathLayout), typeof(QueryLayout), typeof(ClientIPLayout), typeof(ServerIPLayout), typeof(AuthLayout), typeof(HttpStatusLayout), typeof(AppLayout), typeof(MethodLayout), }; static LayoutFieldBase[] Layouts; void Init(IServiceProvider serviceProvider) { var services serviceProvider.GetServicesLayoutFieldBase(); Layouts services.Where(t LayoutTypes.Contains(t.GetType())).ToArray(); if (Layouts.Length ! LayoutTypes.Length) { throw new Exception(nameof(ApiInsightLogstashLayoutRenderer) 的 Layouts 和预定义数目的不匹配); } } protected async override Taskstring ProviderJson() { if (Layouts null) { Init(httpContext.RequestServices); } var dic new Dictionarystring, string(); foreach (var item in Layouts) { dic.Add(item.ProviderFieldName, await item.ProviderField()); } var json JObjectHelper.CreateSimpleJson(dic).Replace(Environment.NewLine, string.Empty); return json; }} logstash配置input { tcp { port 8102 }}filter{ json { source message } date { match [ start, yyyy/MM/dd HH:mm:ss ] } mutate{ convert { statusCode integer interval integer port integer } }}output { elasticsearch { hosts localhost:9200 index core-%{YYYY.MM.dd} }}这里就做了一些字段的类型转换因为默认的所有字段都是string类型是不方便统计的。 nlog配置target xsi:typeNetwork namelogstash_apiinsight keepConnectionfalse layout${logstash-apiinsight} address tcp://192.168.93.135:8103 /target小结因为近两天公司事情也比较少事情做完了就乱捣鼓在使用nlog的Network向Logstash发送数据的时候发现确实不大好用所以就思考了这样的一个实现方式基本的是可以了但是还有一些功能比如说层级json怎么定义这其实就是单纯的写代码很有意思的一件事如果您也有想法或者觉得我的代码有不好的地方或者可以改进的地方欢迎一起讨论。 程序地址https://github.com/cheesebar/ApiInsights