可观测是衡量现代应用质量的核心指标之一,我们设计了ITracer/ISpan这一套链路追踪规范。开发者可以根据该规范来编写各种关键性代码埋点,在应用项目注入星尘监控后,实现埋点数据的采集与上报分析。NewLife全系列项目(大于30个)均使用了ITracer埋点。

Nuget包:NewLife.Core

源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Log/ITracer.cs

快速入门

我们来写第一个手工埋点:

private void Ss_Received(Object? sender, ReceivedEventArgs e)
{
    var ns = (this as INetSession).Host;
    var tracer = ns?.Tracer;
    using var span = tracer?.NewSpan($"net:{ns?.Name}:Receive", e.Message);

    try
    {
        OnReceive(e);
    }
    catch (Exception ex)
    {
        span?.SetError(ex, e.Message ?? e.Packet);
        throw;
    }
}

如上,tracer是ITracer实例,通过NewSpan创建了一个埋点(名称是动态生成)。由于使用了using,这个埋点span将在离开作用域时结束。因此,通过该埋点,我们不仅能够记录收到数据包的次数,还能挤出每一次的耗时。

NewSpan创建埋点时,第二个参数使用e.Message消息作为数据标签,方便在星尘监控中查看分析问题。在catch里面,通过span.SetError方法,把该埋点标记为异常埋点,并记录异常信息。因此,借助ITracer,我们还能够记录异常次数。

ITracer接口

性能跟踪器,轻量级APM规范。

常见APM是把收集到的所有埋点数据上传到服务端统一存储分析,数据量极为庞大。而ITracer实现在本地采样后,马上进行一轮统计,仅上报初步统计数据以及一条采样数据,极大的节省了网络传输和数据存储。

该接口定义如下,一般采用星尘监控的完整实现。

#region 属性
/// <summary>采样周期</summary>
Int32 Period { get; set; }

/// <summary>最大正常采样数。采样周期内,最多只记录指定数量的正常事件,用于绘制依赖关系</summary>
Int32 MaxSamples { get; set; }

/// <summary>最大异常采样数。采样周期内,最多只记录指定数量的异常事件,默认10</summary>
Int32 MaxErrors { get; set; }

/// <summary>超时时间。超过该时间时强制采样,毫秒</summary>
Int32 Timeout { get; set; }

/// <summary>最大标签长度。超过该长度时将截断,默认1024字符</summary>
Int32 MaxTagLength { get; set; }

/// <summary>向http/rpc请求注入TraceId的参数名,为空表示不注入,默认W3C标准的traceparent</summary>
String? AttachParameter { get; set; }
#endregion

/// <summary>建立Span构建器</summary>
/// <param name="name"></param>
/// <returns></returns>
ISpanBuilder BuildSpan(String name);

/// <summary>开始一个Span</summary>
/// <param name="name">操作名</param>
/// <returns></returns>
ISpan NewSpan(String name);

/// <summary>开始一个Span,指定数据标签</summary>
/// <param name="name">操作名</param>
/// <param name="tag">数据</param>
/// <returns></returns>
ISpan NewSpan(String name, Object? tag);

/// <summary>截断所有Span构建器数据,重置集合</summary>
/// <returns></returns>
ISpanBuilder[] TakeAll();

这里最常用的就是NewSpan,几个属性参数一般由星尘监控中心来下发调整。

NewSpan得到的每一个ISpan埋点,在结束后加入name为key的一个字典中,累加次数、异常数和耗时,同时计算最大最小值。在每个采样周期结束后,星尘SDK将会把该字典拿走,打包上传给星尘监控中心。

以下是用于控制采样上报的几个参数:

    • Period采样周期。定期上报数据的间隔,默认60秒。
    • MaxSamples最大正常采样数。采样周期内,最多只记录指定数量的正常事件,用于绘制依赖关系,默认1。
    • MaxErrors最大异常采样数。采样周期内,最多只记录指定数量的异常事件,默认10。
    • Timeout超时时间。超过该时间时强制采样,默认5000毫秒
    • MaxTagLength最大标签长度。超过该长度时将截断,默认1024字符
    • AttachParameter。向http/rpc请求注入TraceId的参数名,为空表示不注入,默认W3C标准的traceparent

ISpan接口

性能跟踪片段,轻量级APM规范。

每个ISpan就是一个埋点实例,它在结束后加入ITracer集合,累加调用次数等指标。

ISpan可用成员如下:

/// <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>设置错误信息,ApiException除外</summary>
/// <param name="ex">异常</param>
/// <param name="tag">标签</param>
void SetError(Exception ex, Object? tag = null);

/// <summary>设置数据标签。内部根据长度截断</summary>
/// <param name="tag">标签</param>
void SetTag(Object tag);

埋点可以嵌套,因此Id标识唯一性,ParentId标识上级,而同一个调用链中的TraceId相同。

StartTime/EndTime记录该埋点的开始时间和结束时间,毫秒级已经足够。

Tag/Error记录该埋点的数据标签和错误内容。

需要注意的是,每一个埋点名的ITracer集合,只会累加次数等指标并保存第一个ISpan作为采样样本。而其它ISpan实例在累加后将会被抛弃,其中的Tag/Erro也不再占用内容。

ISpan扩展

ISpan重要扩展方法如下:

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

/// <summary>把片段信息附加到api请求头上</summary>
/// <param name="span">片段</param>
/// <param name="args">api请求参数</param>
/// <returns></returns>
[return: NotNullIfNotNull(nameof(args))]
public static Object? Attach(this ISpan span, Object? args);

/// <summary>从api请求释放片段信息</summary>
/// <param name="span">片段</param>
/// <param name="parameters">参数</param>
public static void Detach(this ISpan span, IDictionary<String, Object?> parameters);

/// <summary>附加Tag信息在原Tag信息后面</summary>
/// <param name="span">片段</param>
/// <param name="tag">Tag信息</param>
public static void AppendTag(this ISpan span, Object tag);

Attach用于把当前调用链信息注入到Http请求头中,服务方收到请求后解析出来,并作为ISpan的延伸,它们具有相同的TraceId。当这些上下游系统都把埋点采样样本上报给星尘监控中心以后,后者将能够根据TraceId形成跨应用系统的完整调用链。