ASP .NET Core 集成 Skywalking 实现链路追踪

Skywalking部署

Skywalking简单介绍

Skywalking是一个应用性能管理(APM)分布式链路跟踪系统,具有服务器性能监测,应用程序间调用关系及性能监测等功能,Skywalking分为服务端、管理界面、以及嵌入到程序中的探针部分,由程序中的探针采集各类调用数据发送给服务端保存,在管理界面上可以查看各类性能数据。本文介绍服务端及管理界面的安装。

参考文档:

官方部署架构图

部署Skywalking

部署Skywalking需要用到以下工具:

工具 备注 版本号
ElasticSearch ES存储 7.13.2
skywalking-oap-server Skywalking服务端 9.2.0
skywalking-ui Skywalking UI管理界面 9.2.0

ES部署参考这篇博客,本次用到的是7.13.2版本的ES,目前的Skywalking不支持8.0版本的ES

docker pull docker.elastic.co/elasticsearch/elasticsearch:7.13.2

Skywalking Server

镜像地址:

https://hub.docker.com/r/apache/skywalking-oap-server

pull镜像

docker pull  apache/skywalking-oap-server:latest

使用ES存储
起站点

docker run --name skywalking-server -d -p 12800:12800 -p 11800:11800    -e  LANG="en_US.UTF-8"    -e SW_STORAGE=elasticsearch    -e SW_STORAGE_ES_CLUSTER_NODES=172.25.84.73:9200   -e JAVA_OPTS="-Xms64m -Xmx512m" -e SW_HEALTH_CHECKER=default  -e  SW_TELEMETRY=prometheus -e SW_ES_USER="elastic" -e SW_ES_PASSWORD="123456"   apache/skywalking-oap-server:latest 
参数 含义
-p 端口映射,Http默认端口12800,gRPC默认端口11800
LANG 语言
SW_STORAGE 指定存储方式为ES
SW_STORAGE_ES_CLUSTER_NODES ES访问地址,这里不需要加http/https,直接绑定IP+Port
JAVA_OPTS -Xms -Xmx 限制程序初始内存最大内存
SW_ES_USER ES账号,如果ES没有开启认证此变量可不加
SW_ES_PASSWORD ES密码,如果ES没有开启认证此变量可不加

使用Mysql存储
创建一个sw的数据库

CREATE DATABASE IF NOT EXISTS sw   DEFAULT CHARACTER SET utf8  DEFAULT COLLATE utf8_unicode_ci;

下载mysql-connector-java

下载地址 选择对应的版本,然后点击jar下载即可。然后拷贝到服务器

起容器

docker run -d --name skywalking-server  \
-p 11800:11800 -p 12800:12800 \
-e LANG="en_US.UTF-8"  \
-e JAVA_OPTS="-Xms64m -Xmx512m" \
-e SW_STORAGE=mysql \
-e SW_JDBC_URL="jdbc:mysql://192.168.1.5:3306/sw?rewriteBatchedStatements=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai" \
-e SW_DATA_SOURCE_USER=root \
-e SW_DATA_SOURCE_PASSWORD=123456 \
-e SW_DATA_SOURCE_CACHE_PREP_STMTS="true" \
-e SW_DATA_SOURCE_PREP_STMT_CACHE_SQL_SIZE=250 \
-e SW_DATA_SOURCE_PREP_STMT_CACHE_SQL_LIMIT=2048 \
-e SW_DATA_SOURCE_USE_SERVER_PREP_STMTS="true" \
-e SW_STORAGE_MYSQL_QUERY_MAX_SIZE=5000 \
-v /dockerdata/mysql-connector-java-8.0.30.jar:/skywalking/oap-libs/mysql-connector-java-8.0.30.jar \
apache/skywalking-oap-server:latest 
参数 含义
-p 端口映射,Http默认端口12800,gRPC默认端口11800
LANG 语言
SW_STORAGE 指定存储方式为mysql
SW_JDBC_URL mysql地址,sw是数据库名称,useSSL是否使用https
SW_DATA_SOURCE_USER mysql用户名
SW_DATA_SOURCE_PASSWORD mysql密码
JAVA_OPTS -Xms -Xmx 限制程序初始内存最大内存
-v mysql-connector-java 使用程序包连接mysql

访问http://172.25.84.73:12800/有界面即可

Skywalking UI

UI 使用文档地址

https://www.cnblogs.com/heihaozi/p/16350702.html

镜像地址

https://hub.docker.com/r/apache/skywalking-ui

pull镜像

docker pull apache/skywalking-ui:latest

起站点指定skywalking-oap-server地址

docker run --name skywalking-ui -d -p 8080:8080  -e SW_OAP_ADDRESS=http://172.25.84.73:12800  apache/skywalking-ui:latest

访问http://172.25.84.73:8080/

.Net Core集成Skywalking

Netget安装包

Install-Package  SkyAPM.Agent.AspNetCore

新建一个skyapm.json文件,文件属性输出目录选择如果较新则复制,并填入以下内容
文件模板地址

{
  "SkyWalking": {
    "ServiceName": "asp-net-core-skyapmsample1",
    "Namespace": "",
    "HeaderVersions": [
      "sw8"
    ],
    "Sampling": {
      "SamplePer3Secs": -1,
      "Percentage": -1.0,
      "LogSqlParameterValue": false
    },
    "Logging": {
      "Level": "Information",
      "FilePath": "logs/skyapm-{Date}.log"
    },
    "Transport": {
      "Interval": 3000,
      "ProtocolVersion": "v8",
      "QueueSize": 30000,
      "BatchSize": 3000,
      "gRPC": {
        //Skywalking 地址
        "Servers": "172.25.84.73:11800",
        "Timeout": 100000,
        "ConnectTimeout": 100000,
        "ReportTimeout": 600000
      }
    }
  }
}

使用以下代码添加环境变量,要写在所有代码之前

Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "SkyAPM.Agent.AspNetCore");

或者展开项目的Properties,打开launchSettings.json文件,在其中的环境变量中加入以下内容

"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore"


项目启动访问API

然后在Skywalking UI中查看就可以看到API的链路追踪。如果找不到数据可以把时间范围拉长点,可能有时区问题

参考文档地址:

https://www.cnblogs.com/sunyuliang/p/11424848.html
https://www.cnblogs.com/savorboard/p/asp-net-core-skywalking.html

添加Tag和Log

using Microsoft.AspNetCore.Mvc;
using SkyApm.Tracing;
using System.Xml.Linq;

namespace WebApplication1.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IEntrySegmentContextAccessor _segContext;
        public WeatherForecastController(IEntrySegmentContextAccessor segContext) => _segContext = segContext;

        [HttpGet]
        public void Get()
        {
            _segContext.Context.Span.AddLog(SkyApm.Tracing.Segments.LogEvent.Message("自定义日志1"));
            _segContext.Context.Span.AddTag("tagkey", "tagvalue");
        }
    }
}


添加Tag和Log帮助类

using Microsoft.AspNetCore.Razor.TagHelpers;
using SkyApm.Tracing;
using SkyApm.Tracing.Segments;

namespace WebApplication1
{
    public class SkyWalkingTraceService
    {
        /// <summary>
        /// ITracingContext
        /// </summary>
        private readonly ITracingContext _tracingContext;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="tracingContext"></param>
        public SkyWalkingTraceService(ITracingContext tracingContext)
        {
            _tracingContext = tracingContext;
        }

        /// <summary>
        /// SkyWalking方法级埋点
        /// </summary>
        public void SkyWalkingTrace()
        {
            //记录调用方法来源
            System.Diagnostics.StackFrame frame = new System.Diagnostics.StackFrame(1);
            System.Reflection.MethodBase method = frame.GetMethod();
            string namspace = method.DeclaringType.Namespace;
            string clasName = method.ReflectedType.Name;
            string FullName = $"{clasName}.{method.Name}";
            var context = _tracingContext.CreateLocalSegmentContext(FullName);
            context.Span.Component = SkyApm.Common.Components.ASPNETCORE;
            context.Span.AddTag("namspace", namspace);
            context.Span.AddTag("clasName", clasName);
            context.Span.AddLog(LogEvent.Message($"Worker running at: {DateTime.Now}"));
            _tracingContext.Release(context);
        }
    }
}

Program.cs注入

builder.Services.AddSingleton<SkyWalkingTraceService>();

Api调用

using Microsoft.AspNetCore.Mvc;
using SkyApm.Tracing;
using System.Xml.Linq;

namespace WebApplication1.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly SkyWalkingTraceService _skyWalkingTraceService;

        public WeatherForecastController(SkyWalkingTraceService skyWalkingTraceService)
        {
            _skyWalkingTraceService = skyWalkingTraceService;
        }

        [HttpGet]
        public void Get() => _skyWalkingTraceService.SkyWalkingTrace();
    }
}

Api过滤器记录错误日志

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Text;
using SkyApm.Tracing;
using System.Text.Json;

namespace WebApplication1
{
    public class SkyAPMFilter : ActionFilterAttribute
    {
        private string ActionArguments { get; set; }
        private readonly IEntrySegmentContextAccessor _segContext;
        private readonly ITracingContext _tracingContext;
        private readonly ILogger<ApiFilter> _logger;
        private readonly IServiceProvider _serviceProvider;

        public SkyAPMFilter(IEntrySegmentContextAccessor segContext, ILogger<ApiFilter> logger, ITracingContext tracingContext, IServiceProvider serviceProvider)
        {
            _segContext = segContext;
            _logger = logger;
            _tracingContext = tracingContext;
            _serviceProvider = serviceProvider;
        }

        /// <summary>
        /// 执行方法体之后,返回result前
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context)
        { 
            if (context.Exception != null)
            {
                LoggerError(context, context.Exception);
            }

            _segContext.Context.Span.AddLog(SkyApm.Tracing.Segments.LogEvent.Message("自定义日志123456"));
            _segContext.Context.Span.AddTag("tagkey", "tagvalue");

            base.OnActionExecuted(context);

        }



        /// <summary>
        /// 执行方法体之前
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            try
            {
                if (context.ActionArguments != null && context.ActionArguments.Count > 0)
                {
                    ActionArguments = JsonSerializer.Serialize(context.ActionArguments);
                }
                else
                {
                    ActionArguments = string.Empty;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.StackTrace);
            }

            base.OnActionExecuting(context);
        }

        private void LoggerError(ActionExecutedContext context, Exception exception)
        {
            try
            {
                var segmentContext = _tracingContext.CreateLocalSegmentContext("GlobalExceptionFilter");
                segmentContext.Span.AddLog(SkyApm.Tracing.Segments.LogEvent.Message(exception.Message));
                segmentContext.Span.SpanLayer = SkyApm.Tracing.Segments.SpanLayer.HTTP;
                segmentContext.Span.ErrorOccurred(exception);
                _tracingContext.Release(segmentContext);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
            try
            {
                string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
                string method = context.HttpContext.Request.Method;
                string message = $"\n" + $"地址:{url} \n " +
                $"方式:{method} \n " +
                $"参数:{ActionArguments}\n " +
                $"错误描述:{context.Exception.Message}\n " +
                $"错误堆栈:{context.Exception.StackTrace}\n ";
                _logger.LogError(message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.StackTrace);
            }
        }
    }
}

Program.cs添加Filters

builder.Services.AddControllers(options =>
{
    options.Filters.Add<SkyAPMFilter>();
});

.NET Core DiagnosticSource 诊断跟踪

SkyAPM-dotnet 是 SkyWalking 在 .Net Core 端的探针实现,其主要的收集日志的手段就是基于DiagnosticSource来进行诊断跟踪的。

概念介绍

关于DiagnosticSource它本身是一个基于发布订阅模式的工作模式,由于它本身的实现方式是异步的,所以不仅仅可以把它用到日志上,还可以用它实现异步操作,或者用它简化实现发布订阅的功能。
DiagnosticSource本身是一个抽象类,我们最常用到的是它的子类DiagnosticListener,通过DiagnosticSource的Write方法实现发布一条有具体名称的消息,然后通过IObserver去订阅消息。
这里用一张图表示

微软内置诊断信息

在.Net Core的源码中,微软默认在涉及到网络请求或处理请求等许多重要的节点都使用了DiagnosticListener来发布拦截的消息。

诊断器名称 发布消息名称 描述
Microsoft.AspNetCore Hosting.BeginRequest 开始请求信息
Microsoft.AspNetCore Hosting.EndRequest 结束请求
Microsoft.AspNetCore Hosting.UnhandledException 未处理的异常
HttpHandlerDiagnosticListener System.Net.Http.Request http请求信息
HttpHandlerDiagnosticListener System.Net.Http.Exception http请求异常
HttpHandlerDiagnosticListener System.Net.Http.Response http返回信息

Skyapm 诊断解析

参考资料

https://www.cnblogs.com/wucy/p/13532534.html
https://www.cnblogs.com/yyfh/p/13769743.html
https://juejin.cn/post/6844904034063613966
https://www.jianshu.com/p/a1eb1c0aa1ef
https://www.cnblogs.com/whuanle/p/14141213.html
https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-diagnostic-in-dotnet.html

Skywalking DiagnosticSource Neget包

posted @ 2022-11-07 14:49  雨水的命运  阅读(1069)  评论(0编辑  收藏  举报