ITracerResolver解析名称时,支持返回空,提示不创建埋点
智能大石头 authored at 2025-02-10 22:38:40
4.28 KiB
X
using System.Net;

namespace NewLife.Log;

/// <summary>追踪器解析器</summary>
public interface ITracerResolver
{
    /// <summary>从Uri中解析出埋点名称</summary>
    /// <param name="uri"></param>
    /// <param name="userState"></param>
    /// <returns></returns>
    String? ResolveName(Uri uri, Object? userState);

    /// <summary>解析埋点名称</summary>
    /// <param name="name"></param>
    /// <param name="userState"></param>
    /// <returns></returns>
    String? ResolveName(String name, Object? userState);

    /// <summary>创建Http请求埋点</summary>
    ISpan? CreateSpan(ITracer tracer, Uri uri, Object? userState);
}

/// <summary>默认追踪器解析器</summary>
/// <remarks>
/// 解析器给予用户自定义创建Http请求埋点的机会,可以根据需要自定义埋点名称和标签。
/// 在星尘扩展中,再次扩展解析,支持自定义WebApi接口的埋点名称和标签。
/// </remarks>
public class DefaultTracerResolver : ITracerResolver
{
    /// <summary>请求内容是否作为数据标签。默认true</summary>
    /// <remarks>丰富的数据标签可以辅助分析问题,关闭后提升性能</remarks>
    public Boolean RequestContentAsTag { get; set; } = true;

    /// <summary>支持作为标签数据的内容类型</summary>
    public String[] TagTypes { get; set; } = [
        "text/plain", "text/xml", "application/json", "application/xml", "application/x-www-form-urlencoded"
    ];

    /// <summary>标签数据中要排除的头部</summary>
    public String[] ExcludeHeaders { get; set; } = ["traceparent", "Cookie"];

    /// <summary>从Uri中解析出埋点名称</summary>
    /// <param name="uri"></param>
    /// <param name="userState"></param>
    /// <returns></returns>
    public virtual String? ResolveName(Uri uri, Object? userState)
    {
        String name;
        if (uri.IsAbsoluteUri)
        {
            // 太长的Url分段,不适合作为埋点名称
            var segments = uri.Segments.Skip(1).TakeWhile(e => e.Length <= 16).ToArray();
            name = segments.Length > 0
               ? $"{uri.Scheme}://{uri.Authority}/{String.Concat(segments)}"
               : $"{uri.Scheme}://{uri.Authority}";
        }
        else
        {
            name = uri.ToString();
            var p = name.IndexOf('?');
            if (p > 0) name = name[..p];
        }

        return ResolveName(name, userState);
    }

    /// <summary>解析埋点名称</summary>
    /// <param name="name"></param>
    /// <param name="userState"></param>
    /// <returns></returns>
    public virtual String? ResolveName(String name, Object? userState) => name;

    /// <summary>创建Http请求埋点</summary>
    public virtual ISpan? CreateSpan(ITracer tracer, Uri uri, Object? userState)
    {
        var name = tracer.Resolver.ResolveName(uri, userState);
        if (name.IsNullOrEmpty()) return null;

        var span = tracer.NewSpan(name);

        var request = userState as HttpRequestMessage;
        var method = request?.Method.Method ?? (userState as WebRequest)?.Method ?? "GET";
        var tag = $"{method} {uri}";

        if (RequestContentAsTag && tag.Length < tracer.MaxTagLength &&
            span is DefaultSpan ds && ds.TraceFlag > 0 && request != null)
        {
            var maxLength = ds.Tracer?.MaxTagLength ?? 1024;
            if (request.Content is ByteArrayContent content &&
                content.Headers.ContentLength != null &&
                content.Headers.ContentLength < 1024 * 8 &&
                content.Headers.ContentType != null &&
                content.Headers.ContentType.MediaType.StartsWithIgnoreCase(TagTypes))
            {
                // 既然都读出来了,不管多长,都要前面1024字符
                var str = request.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
                if (!str.IsNullOrEmpty()) tag += "\r\n" + (str.Length > maxLength ? str[..maxLength] : str);
            }

            if (tag.Length < 500)
            {
                var vs = request.Headers.Where(e => !e.Key.EqualIgnoreCase(ExcludeHeaders)).ToDictionary(e => e.Key, e => e.Value.Join(";"));
                tag += "\r\n" + vs.Join("\r\n", e => $"{e.Key}: {e.Value}");
            }
        }
        span.SetTag(tag);

        return span;
    }
}