引入redis服务,支持自动化单元测试
大石头 编写于 2022-03-31 22:56:30
X
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using NewLife.Collections;
using NewLife.Data;
using NewLife.Serialization;

namespace NewLife.Log
{
    /// <summary>性能跟踪片段。轻量级APM</summary>
    public interface ISpan : IDisposable
    {
        /// <summary>唯一标识。随线程上下文、Http、Rpc传递,作为内部片段的父级</summary>
        String Id { get; set; }

        /// <summary>父级片段标识</summary>
        String ParentId { get; set; }

        /// <summary>跟踪标识。可用于关联多个片段,建立依赖关系,随线程上下文、Http、Rpc传递</summary>
        String TraceId { get; set; }

        /// <summary>开始时间。Unix毫秒</summary>
        Int64 StartTime { get; set; }

        /// <summary>结束时间。Unix毫秒</summary>
        Int64 EndTime { get; set; }

        /// <summary>数据标签。记录一些附加数据</summary>
        String Tag { get; set; }

        /// <summary>错误信息</summary>
        String Error { get; set; }

        /// <summary>设置错误信息</summary>
        /// <param name="ex">异常</param>
        /// <param name="tag">标签</param>
        void SetError(Exception ex, Object tag);
    }

    /// <summary>性能跟踪片段。轻量级APM</summary>
    /// <remarks>
    /// spanId/traceId采用W3C标准,https://www.w3.org/TR/trace-context/
    /// </remarks>
    public class DefaultSpan : ISpan
    {
        #region 属性
        /// <summary>构建器</summary>
        [XmlIgnore, ScriptIgnore, IgnoreDataMember]
        public ISpanBuilder Builder { get; private set; }

        /// <summary>唯一标识。随线程上下文、Http、Rpc传递,作为内部片段的父级</summary>
        public String Id { get; set; }

        /// <summary>父级片段标识</summary>
        public String ParentId { get; set; }

        /// <summary>跟踪标识。可用于关联多个片段,建立依赖关系,随线程上下文、Http、Rpc传递</summary>
        public String TraceId { get; set; }

        /// <summary>开始时间。Unix毫秒</summary>
        public Int64 StartTime { get; set; }

        /// <summary>结束时间。Unix毫秒</summary>
        public Int64 EndTime { get; set; }

        /// <summary>数据标签。记录一些附加数据</summary>
        public String Tag { get; set; }

        ///// <summary>版本</summary>
        //public Byte Version { get; set; }

        /// <summary>跟踪标识。强制采样,确保链路采样完整,上下文传递</summary>
        public Byte TraceFlag { get; set; }

        /// <summary>错误信息</summary>
        public String Error { get; set; }

        private static readonly AsyncLocal<ISpan> _Current = new();
        /// <summary>当前线程正在使用的上下文</summary>
        public static ISpan Current { get => _Current.Value; set => _Current.Value = value; }

        private ISpan _parent;
        private Boolean _finished;
        //private static Int64 _gid;
        #endregion

        #region 构造
        /// <summary>实例化</summary>
        public DefaultSpan() { }

        /// <summary>实例化</summary>
        /// <param name="builder"></param>
        public DefaultSpan(ISpanBuilder builder)
        {
            Builder = builder;
            StartTime = DateTime.UtcNow.ToLong();
        }

        static DefaultSpan()
        {
            IPAddress ip;
            try
            {
                ip = NetHelper.MyIP();
            }
            catch
            {
                ip = IPAddress.Loopback;
            }
            if (ip == null) ip = IPAddress.Parse("127.0.0.1");
            _myip = ip.GetAddressBytes().ToHex().ToLower().PadLeft(8, '0');
            _pid = Process.GetCurrentProcess().Id.ToString("x4").PadLeft(4, '0');
        }

        /// <summary>释放资源</summary>
        public void Dispose() => Finish();
        #endregion

        #region 方法
        /// <summary>设置跟踪标识</summary>
        public virtual void Start()
        {
            //if (Id.IsNullOrEmpty()) Id = Rand.NextBytes(8).ToHex().ToLower();
            if (Id.IsNullOrEmpty()) Id = CreateId();

            // 设置父级
            var span = Current;
            _parent = span;
            if (span != null && span != this)
            {
                ParentId = span.Id;
                TraceId = span.TraceId;

                // 继承跟踪标识,该TraceId下全量采样,确保链路采样完整
                if (span is DefaultSpan ds) TraceFlag = ds.TraceFlag;
            }

            // 否则创建新的跟踪标识
            if (TraceId.IsNullOrEmpty()) TraceId = CreateTraceId();

            // 设置当前片段
            Current = this;
        }

        private static readonly String _myip;
        private static Int32 _seq;
        private static Int32 _seq2;
        private static readonly String _pid;

        /// <summary>创建分片编号</summary>
        /// <returns></returns>
        protected virtual String CreateId()
        {
            // IPv4(8) + PID(4) + 顺序数(4)
            var id = Interlocked.Increment(ref _seq);
            return _myip + _pid + id.ToString("x4").PadLeft(4, '0');
        }

        /// <summary>创建跟踪编号</summary>
        /// <returns></returns>
        protected virtual String CreateTraceId()
        {
            /*
             * 阿里云EagleEye全链路追踪
             * 7ae122a215982779017518707e
             * IPv4(8) + 毫秒时间(13) + 顺序数(4) + 标识位(1) + PID(4)
             * 7ae122a2 + 1598277901751 + 8707 + e
             */

            var sb = Pool.StringBuilder.Get();
            sb.Append(_myip);
            sb.Append(DateTime.UtcNow.ToLong());
            var id = Interlocked.Increment(ref _seq2);
            sb.Append(id.ToString("x4").PadLeft(4, '0'));
            sb.Append('e');
            sb.Append(_pid);

            //return _myip + DateTime.UtcNow.ToLong() + Interlocked.Increment(ref _seq) + "e" + _pid;
            return sb.Put(true);
        }

        /// <summary>完成跟踪</summary>
        protected virtual void Finish()
        {
            if (_finished) return;
            _finished = true;

            EndTime = DateTime.UtcNow.ToLong();

            // 从本线程中清除跟踪标识
            Current = _parent;

            // Builder这一批可能已经上传,重新取一次,以防万一
            var builder = Builder.Tracer.BuildSpan(Builder.Name);
            builder.Finish(this);

            // 打断对Builder的引用,当前Span可能还被放在AsyncLocal字典中
            // 也有可能原来的Builder已经上传,现在加入了新的builder集合
            Builder = null;
        }

        /// <summary>设置错误信息</summary>
        /// <param name="ex">异常</param>
        /// <param name="tag">标签</param>
        public virtual void SetError(Exception ex, Object tag)
        {
            Error = ex?.GetMessage();
            if (tag is String str)
                Tag = str.Cut(1024);
            else if (tag is StringBuilder builder)
                Tag = builder.Length < 1024 ? builder.ToString() : builder.ToString(0, 1024);
            else if (tag is Packet pk)
                Tag = pk.ToHex(1024 / 2);
            else if (tag != null)
                Tag = tag.ToJson().Cut(1024);
        }

        /// <summary>已重载。</summary>
        /// <returns></returns>
        public override String ToString() => $"00-{TraceId}-{Id}-{TraceFlag:x2}";
        #endregion
    }

    /// <summary>跟踪片段扩展</summary>
    public static class SpanExtension
    {
        #region 扩展方法
        private static String GetAttachParameter(ISpan span)
        {
            var builder = (span as DefaultSpan)?.Builder;
            var tracer = (builder as DefaultSpanBuilder)?.Tracer;
            return tracer?.AttachParameter;
        }

        /// <summary>把片段信息附加到http请求头上</summary>
        /// <param name="span">片段</param>
        /// <param name="request">http请求</param>
        /// <returns></returns>
        public static HttpRequestMessage Attach(this ISpan span, HttpRequestMessage request)
        {
            if (span == null || request == null) return request;

            // 注入参数名
            var name = GetAttachParameter(span);
            if (name.IsNullOrEmpty()) return request;

            var headers = request.Headers;
            if (!headers.Contains(name)) headers.Add(name, span.ToString());

            return request;
        }

        ///// <summary>把片段信息附加到http请求头上</summary>
        ///// <param name="span">片段</param>
        ///// <param name="headers">http请求头</param>
        ///// <returns></returns>
        //public static HttpRequestHeaders Attach(this ISpan span, HttpRequestHeaders headers)
        //{
        //    if (span == null || headers == null) return headers;

        //    // 注入参数名
        //    var name = GetAttachParameter(span);
        //    if (name.IsNullOrEmpty()) return headers;

        //    if (!headers.Contains(name)) headers.Add(name, span.ToString());

        //    return headers;
        //}

        /// <summary>把片段信息附加到http请求头上</summary>
        /// <param name="span">片段</param>
        /// <param name="request">http请求</param>
        /// <returns></returns>
        public static WebRequest Attach(this ISpan span, WebRequest request)
        {
            if (span == null || request == null) return request;

            // 注入参数名
            var name = GetAttachParameter(span);
            if (name.IsNullOrEmpty()) return request;

            var headers = request.Headers;
            if (!headers.AllKeys.Contains(name)) headers.Add(name, span.ToString());

            return request;
        }

        /// <summary>把片段信息附加到api请求头上</summary>
        /// <param name="span">片段</param>
        /// <param name="args">api请求参数</param>
        /// <returns></returns>
        public static Object Attach(this ISpan span, Object args)
        {
            if (span == null || args == null || args is Packet || args is Byte[] || args is IAccessor) return args;
            if (Type.GetTypeCode(args.GetType()) != TypeCode.Object) return args;

            // 注入参数名
            var name = GetAttachParameter(span);
            if (name.IsNullOrEmpty()) return args;

            var headers = args.ToDictionary();
            if (!headers.ContainsKey(name)) headers.Add(name, span.ToString());

            return headers;
        }

        /// <summary>从http请求头释放片段信息</summary>
        /// <param name="span">片段</param>
        /// <param name="headers">http请求头</param>
        public static void Detach(this ISpan span, NameValueCollection headers)
        {
            if (span == null || headers == null || headers.Count == 0) return;

            // 不区分大小写比较头部
            var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
            foreach (var item in headers.AllKeys)
            {
                dic[item] = headers[item];
            }

            Detach2(span, dic);
        }

        /// <summary>从api请求释放片段信息</summary>
        /// <param name="span">片段</param>
        /// <param name="parameters">参数</param>
        public static void Detach(this ISpan span, IDictionary<String, Object> parameters)
        {
            if (span == null || parameters == null || parameters.Count == 0) return;

            // 不区分大小写比较头部
            var dic = parameters.ToDictionary(e => e.Key, e => e.Value, StringComparer.OrdinalIgnoreCase);
            Detach2(span, dic);
        }

        /// <summary>从api请求释放片段信息</summary>
        /// <param name="span">片段</param>
        /// <param name="parameters">参数</param>
        public static void Detach<T>(this ISpan span, IDictionary<String, T> parameters)
        {
            if (span == null || parameters == null || parameters.Count == 0) return;

            // 不区分大小写比较头部
            var dic = parameters.ToDictionary(e => e.Key, e => e.Value, StringComparer.OrdinalIgnoreCase);
            Detach2(span, dic);
        }

        private static void Detach2<T>(ISpan span, IDictionary<String, T> dic)
        {
            // 不区分大小写比较头部
            if (dic.TryGetValue("traceparent", out var tid))
            {
                var ss = (tid + "").Split('-');
                if (ss.Length > 1) span.TraceId = ss[1];
                if (ss.Length > 2) span.ParentId = ss[2];
                if (ss.Length > 3 && !ss[3].IsNullOrEmpty() && span is DefaultSpan ds && ds.TraceFlag == 0)
                {
                    var buf = ss[3].ToHex(0, 2);
                    if (buf.Length > 0) ds.TraceFlag = buf[0];
                }
            }
            else if (dic.TryGetValue("Request-Id", out tid))
            {
                // HierarchicalId编码取最后一段作为父级
                var ss = (tid + "").Split('.', '_');
                if (ss.Length > 0) span.TraceId = ss[0].TrimStart('|');
                if (ss.Length > 1) span.ParentId = ss[^1];
            }
            else if (dic.TryGetValue("Eagleeye-Traceid", out tid))
            {
                var ss = (tid + "").Split('-');
                if (ss.Length > 0) span.TraceId = ss[0];
                if (ss.Length > 1) span.ParentId = ss[1];
            }
            else if (dic.TryGetValue("TraceId", out tid))
            {
                span.Detach(tid + "");
            }
        }

        /// <summary>从数据流traceId中释放片段信息</summary>
        /// <param name="span">片段</param>
        /// <param name="traceId">W3C标准TraceId,可以是traceparent</param>
        public static void Detach(this ISpan span, String traceId)
        {
            if (span == null || traceId.IsNullOrEmpty()) return;

            var ss = traceId.Split('-');
            if (ss.Length > 1) span.TraceId = ss[1];
            if (ss.Length > 2) span.ParentId = ss[2];

            if (ss.Length > 3 && !ss[3].IsNullOrEmpty() && span is DefaultSpan ds && ds.TraceFlag == 0)
            {
                // 识别跟踪标识,该TraceId之下,全量采样,确保链路采样完整
                var buf = ss[3].ToHex(0, 2);
                if (buf.Length > 0) ds.TraceFlag = buf[0];
            }
        }
        #endregion
    }
}