Application Insights SDK 中的筛选和预处理遥测

可以编写代码来筛选、修改或扩充遥测数据,然后再从 SDK 发送遥测数据。 处理包括从标准遥测模块(如 HTTP 请求收集和依赖项收集)发送的数据。

  • 筛选可以先修改或丢弃遥测数据,然后通过实现 ITelemetryProcessor 从 SDK 发送遥测数据。 例如,可以通过排除机器人请求减少遥测量。 与抽样不同,你可以完全控制发送或丢弃的内容,但它会影响基于聚合日志的任何指标。 根据丢弃项的方式,有时你也可能无法在相关项之间导航。

  • 通过实现 ITelemetryInitializer 向从应用发送的任何遥测添加或修改属性。 例如,可以添加计算得出的值或在门户中筛选数据所依据的版本号。

  • 采样可在不影响统计信息的情况下减少遥测量。 它还保留相关数据点,以便你可以在诊断问题时导航这些点。 在门户中,总计相乘以补偿采样。

注意

SDK API 用于发送自定义事件和指标。

先决条件

为应用程序安装相应的 SDK:ASP.NETASP.NET Core用于 .NET/.NET Core 的非 HTTP/辅助角色JavaScript

筛选

使用此技术可以直接控制要在遥测流中包含或排除的内容。 可以通过筛选删除要发送到 Application Insights 的遥测项。 你可以将筛选与采样结合使用,也可以单独使用。

若要筛选遥测,请编写遥测处理器并通过 TelemetryConfiguration 注册它。 所有遥测都通过处理器。 你可以选择从流中删除它或将它提供给链中的下一处理器。 包括来自标准模块的遥测(例如 HTTP 请求收集器和依赖项收集器)以及自行跟踪的遥测。 例如,你可以筛选出有关机器人请求或成功依赖项调用的遥测。

警告

使用处理器筛选从 SDK 发送的遥测会使门户中看到的统计信息出现偏差,并使它难以跟进相关项目。

此时,考虑使用采样

.NET 应用程序

  1. 实现 ITelemetryProcessor

    遥测处理器会构建一个处理链。 当你实例化遥测处理器时,系统会为你提供链中下一个处理器的引用。 将遥测数据点传递到处理方法时,该方法会生效,然后调用(或不调用)链中的下一个遥测处理器。

    using Microsoft.ApplicationInsights.Channel;
    using Microsoft.ApplicationInsights.Extensibility;
    using Microsoft.ApplicationInsights.DataContracts;
    
    public class SuccessfulDependencyFilter : ITelemetryProcessor
    {
        private ITelemetryProcessor Next { get; set; }
    
        // next will point to the next TelemetryProcessor in the chain.
        public SuccessfulDependencyFilter(ITelemetryProcessor next)
        {
            this.Next = next;
        }
    
        public void Process(ITelemetry item)
        {
            // To filter out an item, return without calling the next processor.
            if (!OKtoSend(item)) { return; }
    
            this.Next.Process(item);
        }
    
        // Example: replace with your own criteria.
        private bool OKtoSend (ITelemetry item)
        {
            var dependency = item as DependencyTelemetry;
            if (dependency == null) return true;
    
            return dependency.Success != true;
        }
    }
    
  2. 添加处理器。

    在 ApplicationInsights.config 中插入此代码片段:

    <TelemetryProcessors>
      <Add Type="WebApplication9.SuccessfulDependencyFilter, WebApplication9">
        <!-- Set public property -->
        <MyParamFromConfigFile>2-beta</MyParamFromConfigFile>
      </Add>
    </TelemetryProcessors>
    

    可通过在类中提供公共命名属性,传递 .config 文件中的字符串值。

    警告

    注意将 .config 文件中的类型名称和任何属性名称匹配到代码中的类和属性名称。 如果 .config 文件引用不存在的类型或属性,该 SDK 在发送任何遥测时可能静默失败。

    或者, 可以在代码中初始化筛选器。 在合适的初始化类(例如 Global.asax.cs 中的 AppStart)中,将处理器插入链:

    var builder = TelemetryConfiguration.Active.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
    builder.Use((next) => new SuccessfulDependencyFilter(next));
    
    // If you have more processors:
    builder.Use((next) => new AnotherProcessor(next));
    
    builder.Build();
    

    在此后创建的遥测客户端会使用你的处理器。

示例筛选器

综合请求

筛选出机器人和 Web 测试。 尽管指标资源管理器提供筛选出综合源的选项,但此选项可通过在 SDK 自身中筛选它们减少流量和引入大小。

public void Process(ITelemetry item)
{
    if (!string.IsNullOrEmpty(item.Context.Operation.SyntheticSource)) {return;}
    
    // Send everything else:
    this.Next.Process(item);
}

身份验证失败

筛选出带有“401”响应的请求。

public void Process(ITelemetry item)
{
    var request = item as RequestTelemetry;

    if (request != null &&
    request.ResponseCode.Equals("401", StringComparison.OrdinalIgnoreCase))
    {
        // To filter out an item, return without calling the next processor.
        return;
    }

    // Send everything else
    this.Next.Process(item);
}

筛选出快速远程依赖项调用

如果只想诊断速度较慢的调用,可筛掉速度较快的调用。

注意

这项筛选操作会使门户上看到的统计信息出现偏差。

public void Process(ITelemetry item)
{
    var request = item as DependencyTelemetry;

    if (request != null && request.Duration.TotalMilliseconds < 100)
    {
        return;
    }
    this.Next.Process(item);
}

诊断依赖项问题

本博客介绍了通过自动将常规 Ping 发送到依赖项诊断依赖项问题的项目。

Java 应用程序

若要详细了解遥测数据处理器及其在 Java 中的实现,请参阅 Java 遥测数据处理器文档

JavaScript Web 应用程序

可以使用 ITelemetryInitializer 从 JavaScript Web 应用程序筛选遥测数据。

  1. 创建遥测初始化程序回调函数。 回调函数将 ITelemetryItem 作为参数,该项是正在处理的事件。 从此回调返回 false 将导致筛选掉遥测项。

    var filteringFunction = (envelope) => {
      if (envelope.data.someField === 'tobefilteredout') {
        return false;
      }
      return true;
    };
    
  2. 添加遥测初始化程序回调:

    appInsights.addTelemetryInitializer(filteringFunction);
    

添加/修改属性:ITelemetryInitializer

通过遥测初始化表达式使用其他信息来扩充遥测,或者重写通过标准遥测模块设置的遥测属性。

例如,Web 程序包的 Application Insights 将收集有关 HTTP 请求的遥测。 默认情况下,它会将响应代码 >= 400 的任何请求标记为失败。 但是,如果你希望将 400 视为成功,可以提供一个设置成功属性的遥测初始化表达式。

如果提供了遥测初始化表达式,只要调用任何 Track*() 方法,就会调用它。 此初始化表达式包括由标准遥测模块调用的 Track() 方法。 按照约定,这些模块不会设置已由初始化表达式设置的任何属性。 初始化程序会在调用遥测处理器之前被调用,因此初始化程序执行的任何扩充都对处理器可见。

.NET 应用程序

  1. 定义初始值设定项

    using System;
    using Microsoft.ApplicationInsights.Channel;
    using Microsoft.ApplicationInsights.DataContracts;
    using Microsoft.ApplicationInsights.Extensibility;
    
    namespace MvcWebRole.Telemetry
    {
      /*
       * Custom TelemetryInitializer that overrides the default SDK
       * behavior of treating response codes >= 400 as failed requests
       *
       */
        public class MyTelemetryInitializer : ITelemetryInitializer
        {
            public void Initialize(ITelemetry telemetry)
            {
                var requestTelemetry = telemetry as RequestTelemetry;
                // Is this a TrackRequest() ?
                if (requestTelemetry == null) return;
                int code;
                bool parsed = Int32.TryParse(requestTelemetry.ResponseCode, out code);
                if (!parsed) return;
                if (code >= 400 && code < 500)
                {
                    // If we set the Success property, the SDK won't change it:
                    requestTelemetry.Success = true;
    
                    // Allow us to filter these requests in the portal:
                    requestTelemetry.Properties["Overridden400s"] = "true";
                }
                // else leave the SDK to set the Success property
            }
        }
    }
    
  2. 加载初始值设定项

    在 ApplicationInsights.config 中:

    <ApplicationInsights>
      <TelemetryInitializers>
        <!-- Fully qualified type name, assembly name: -->
        <Add Type="MvcWebRole.Telemetry.MyTelemetryInitializer, MvcWebRole"/>
        ...
      </TelemetryInitializers>
    </ApplicationInsights>
    

    或者,你可以在代码中实例化初始化表达式,例如在 Global.aspx.cs 中:

    protected void Application_Start()
    {
        // ...
        TelemetryConfiguration.Active.TelemetryInitializers.Add(new MyTelemetryInitializer());
    }
    

    查看此示例的详细信息。

JavaScript 遥测初始值设定项

如果需要,请插入 JavaScript 遥测初始值设定项。 有关 Application Insights JavaScript SDK 的遥测初始值设定项的详细信息,请参阅遥测初始值设定项

通过在 JavaScript SDK (Web) 加载程序脚本配置中添加 onInit 回调函数来插入遥测初始化程序:

<script type="text/javascript">
!(function (cfg){function e(){cfg.onInit&&cfg.onInit(n)}var x,w,D,t,E,n,C=window,O=document,b=C.location,q="script",I="ingestionendpoint",L="disableExceptionTracking",j="ai.device.";"instrumentationKey"[x="toLowerCase"](),w="crossOrigin",D="POST",t="appInsightsSDK",E=cfg.name||"appInsights",(cfg.name||C[t])&&(C[t]=E),n=C[E]||function(g){var f=!1,m=!1,h={initialize:!0,queue:[],sv:"8",version:2,config:g};function v(e,t){var n={},i="Browser";function a(e){e=""+e;return 1===e.length?"0"+e:e}return n[j+"id"]=i[x](),n[j+"type"]=i,n["ai.operation.name"]=b&&b.pathname||"_unknown_",n["ai.internal.sdkVersion"]="javascript:snippet_"+(h.sv||h.version),{time:(i=new Date).getUTCFullYear()+"-"+a(1+i.getUTCMonth())+"-"+a(i.getUTCDate())+"T"+a(i.getUTCHours())+":"+a(i.getUTCMinutes())+":"+a(i.getUTCSeconds())+"."+(i.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z",iKey:e,name:"Microsoft.ApplicationInsights."+e.replace(/-/g,"")+"."+t,sampleRate:100,tags:n,data:{baseData:{ver:2}},ver:undefined,seq:"1",aiDataContract:undefined}}var n,i,t,a,y=-1,T=0,S=["js.monitor.azure.com","js.cdn.applicationinsights.io","js.cdn.monitor.azure.com","js0.cdn.applicationinsights.io","js0.cdn.monitor.azure.com","js2.cdn.applicationinsights.io","js2.cdn.monitor.azure.com","az416426.vo.msecnd.net"],o=g.url||cfg.src,r=function(){return s(o,null)};function s(d,t){if((n=navigator)&&(~(n=(n.userAgent||"").toLowerCase()).indexOf("msie")||~n.indexOf("trident/"))&&~d.indexOf("ai.3")&&(d=d.replace(/(\/)(ai\.3\.)([^\d]*)$/,function(e,t,n){return t+"ai.2"+n})),!1!==cfg.cr)for(var e=0;e<S.length;e++)if(0<d.indexOf(S[e])){y=e;break}var n,i=function(e){var a,t,n,i,o,r,s,c,u,l;h.queue=[],m||(0<=y&&T+1<S.length?(a=(y+T+1)%S.length,p(d.replace(/^(.*\/\/)([\w\.]*)(\/.*)$/,function(e,t,n,i){return t+S[a]+i})),T+=1):(f=m=!0,s=d,!0!==cfg.dle&&(c=(t=function(){var e,t={},n=g.connectionString;if(n)for(var i=n.split(";"),a=0;a<i.length;a++){var o=i[a].split("=");2===o.length&&(t[o[0][x]()]=o[1])}return t[I]||(e=(n=t.endpointsuffix)?t.location:null,t[I]="https://"+(e?e+".":"")+"dc."+(n||"services.visualstudio.com")),t}()).instrumentationkey||g.instrumentationKey||"",t=(t=(t=t[I])&&"/"===t.slice(-1)?t.slice(0,-1):t)?t+"/v2/track":g.endpointUrl,t=g.userOverrideEndpointUrl||t,(n=[]).push((i="SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details)",o=s,u=t,(l=(r=v(c,"Exception")).data).baseType="ExceptionData",l.baseData.exceptions=[{typeName:"SDKLoadFailed",message:i.replace(/\./g,"-"),hasFullStack:!1,stack:i+"\nSnippet failed to load ["+o+"] -- Telemetry is disabled\nHelp Link: https://go.microsoft.com/fwlink/?linkid=2128109\nHost: "+(b&&b.pathname||"_unknown_")+"\nEndpoint: "+u,parsedStack:[]}],r)),n.push((l=s,i=t,(u=(o=v(c,"Message")).data).baseType="MessageData",(r=u.baseData).message='AI (Internal): 99 message:"'+("SDK LOAD Failure: Failed to load Application Insights SDK script (See stack for details) ("+l+")").replace(/\"/g,"")+'"',r.properties={endpoint:i},o)),s=n,c=t,JSON&&((u=C.fetch)&&!cfg.useXhr?u(c,{method:D,body:JSON.stringify(s),mode:"cors"}):XMLHttpRequest&&((l=new XMLHttpRequest).open(D,c),l.setRequestHeader("Content-type","application/json"),l.send(JSON.stringify(s)))))))},a=function(e,t){m||setTimeout(function(){!t&&h.core||i()},500),f=!1},p=function(e){var n=O.createElement(q),e=(n.src=e,t&&(n.integrity=t),n.setAttribute("data-ai-name",E),cfg[w]);return!e&&""!==e||"undefined"==n[w]||(n[w]=e),n.onload=a,n.onerror=i,n.onreadystatechange=function(e,t){"loaded"!==n.readyState&&"complete"!==n.readyState||a(0,t)},cfg.ld&&cfg.ld<0?O.getElementsByTagName("head")[0].appendChild(n):setTimeout(function(){O.getElementsByTagName(q)[0].parentNode.appendChild(n)},cfg.ld||0),n};p(d)}cfg.sri&&(n=o.match(/^((http[s]?:\/\/.*\/)\w+(\.\d+){1,5})\.(([\w]+\.){0,2}js)$/))&&6===n.length?(d="".concat(n[1],".integrity.json"),i="@".concat(n[4]),l=window.fetch,t=function(e){if(!e.ext||!e.ext[i]||!e.ext[i].file)throw Error("Error Loading JSON response");var t=e.ext[i].integrity||null;s(o=n[2]+e.ext[i].file,t)},l&&!cfg.useXhr?l(d,{method:"GET",mode:"cors"}).then(function(e){return e.json()["catch"](function(){return{}})}).then(t)["catch"](r):XMLHttpRequest&&((a=new XMLHttpRequest).open("GET",d),a.onreadystatechange=function(){if(a.readyState===XMLHttpRequest.DONE)if(200===a.status)try{t(JSON.parse(a.responseText))}catch(e){r()}else r()},a.send())):o&&r();try{h.cookie=O.cookie}catch(k){}function e(e){for(;e.length;)!function(t){h[t]=function(){var e=arguments;f||h.queue.push(function(){h[t].apply(h,e)})}}(e.pop())}var c,u,l="track",d="TrackPage",p="TrackEvent",l=(e([l+"Event",l+"PageView",l+"Exception",l+"Trace",l+"DependencyData",l+"Metric",l+"PageViewPerformance","start"+d,"stop"+d,"start"+p,"stop"+p,"addTelemetryInitializer","setAuthenticatedUserContext","clearAuthenticatedUserContext","flush"]),h.SeverityLevel={Verbose:0,Information:1,Warning:2,Error:3,Critical:4},(g.extensionConfig||{}).ApplicationInsightsAnalytics||{});return!0!==g[L]&&!0!==l[L]&&(e(["_"+(c="onerror")]),u=C[c],C[c]=function(e,t,n,i,a){var o=u&&u(e,t,n,i,a);return!0!==o&&h["_"+c]({message:e,url:t,lineNumber:n,columnNumber:i,error:a,evt:C.event}),o},g.autoExceptionInstrumented=!0),h}(cfg.cfg),(C[E]=n).queue&&0===n.queue.length?(n.queue.push(e),n.trackPageView({})):e();})({
src: "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js",
crossOrigin: "anonymous", // When supplied this will add the provided value as the cross origin attribute on the script tag
onInit: function (sdk) {
    sdk.addTelemetryInitializer(function (envelope) {
    envelope.data = envelope.data || {};
    envelope.data.someField = 'This item passed through my telemetry initializer';
    });
}, // Once the application insights instance has loaded and initialized this method will be called
// sri: false, // Custom optional value to specify whether fetching the snippet from integrity file and do integrity check
cfg: { // Application Insights Configuration
    connectionString: "YOUR_CONNECTION_STRING"
}});
</script>

有关遥测项上可用的非自定义属性摘要,请参阅 Application Insights 导出数据模型

可添加任意数量的初始值设定项。 系统按它们的添加顺序对其进行调用。

OpenCensus Python 遥测处理器

OpenCensus Python 遥测处理器是简单的回调函数,可在导出遥测之前调用这些函数来处理遥测。 回调函数必须接受信封数据类型作为其参数。 若要筛掉不想导出的遥测,请确保回调函数返回 False。 可在 GitHub 上查看信封中 Azure Monitor 数据类型的架构。

注意

可以通过更改 tags 字段中的 ai.cloud.role 属性来修改 cloud_RoleName

def callback_function(envelope):
    envelope.tags['ai.cloud.role'] = 'new_role_name'
# Example for log exporter
import logging

from opencensus.ext.azure.log_exporter import AzureLogHandler

logger = logging.getLogger(__name__)

# Callback function to append '_hello' to each log message telemetry
def callback_function(envelope):
    envelope.data.baseData.message += '_hello'
    return True

handler = AzureLogHandler(connection_string='InstrumentationKey=<your-instrumentation_key-here>')
handler.add_telemetry_processor(callback_function)
logger.addHandler(handler)
logger.warning('Hello, World!')
# Example for trace exporter
import requests

from opencensus.ext.azure.trace_exporter import AzureExporter
from opencensus.trace import config_integration
from opencensus.trace.samplers import ProbabilitySampler
from opencensus.trace.tracer import Tracer

config_integration.trace_integrations(['requests'])

# Callback function to add os_type: linux to span properties
def callback_function(envelope):
    envelope.data.baseData.properties['os_type'] = 'linux'
    return True

exporter = AzureExporter(
    connection_string='InstrumentationKey=<your-instrumentation-key-here>'
)
exporter.add_telemetry_processor(callback_function)
tracer = Tracer(exporter=exporter, sampler=ProbabilitySampler(1.0))
with tracer.span(name='parent'):
response = requests.get(url='https://www.wikipedia.org/wiki/Rabbit')
# Example for metrics exporter
import time

from opencensus.ext.azure import metrics_exporter
from opencensus.stats import aggregation as aggregation_module
from opencensus.stats import measure as measure_module
from opencensus.stats import stats as stats_module
from opencensus.stats import view as view_module
from opencensus.tags import tag_map as tag_map_module

stats = stats_module.stats
view_manager = stats.view_manager
stats_recorder = stats.stats_recorder

CARROTS_MEASURE = measure_module.MeasureInt("carrots",
                                            "number of carrots",
                                            "carrots")
CARROTS_VIEW = view_module.View("carrots_view",
                                "number of carrots",
                                [],
                                CARROTS_MEASURE,
                                aggregation_module.CountAggregation())

# Callback function to only export the metric if value is greater than 0
def callback_function(envelope):
    return envelope.data.baseData.metrics[0].value > 0

def main():
    # Enable metrics
    # Set the interval in seconds in which you want to send metrics
    exporter = metrics_exporter.new_metrics_exporter(connection_string='InstrumentationKey=<your-instrumentation-key-here>')
    exporter.add_telemetry_processor(callback_function)
    view_manager.register_exporter(exporter)

    view_manager.register_view(CARROTS_VIEW)
    mmap = stats_recorder.new_measurement_map()
    tmap = tag_map_module.TagMap()

    mmap.measure_int_put(CARROTS_MEASURE, 1000)
    mmap.record(tmap)
    # Default export interval is every 15.0s
    # Your application should run for at least this amount
    # of time so the exporter will meet this interval
    # Sleep can fulfill this
    time.sleep(60)

    print("Done recording metrics")

if __name__ == "__main__":
    main()

可添加任意数量的处理器。 系统按它们的添加顺序对其进行调用。 如果某个处理器引发异常,则它不会影响后面的处理器。

示例 TelemetryInitializer

添加自定义属性

以下初始值设定项示例将自定义属性添加到每个被跟踪的遥测。

public void Initialize(ITelemetry item)
{
    var itemProperties = item as ISupportProperties;
    if(itemProperties != null && !itemProperties.Properties.ContainsKey("customProp"))
    {
        itemProperties.Properties["customProp"] = "customValue";
    }
}

添加云角色名称

以下初始化表达式示例将云角色名称设置为每个被跟踪的遥测。

public void Initialize(ITelemetry telemetry)
{
    if (string.IsNullOrEmpty(telemetry.Context.Cloud.RoleName))
    {
        telemetry.Context.Cloud.RoleName = "MyCloudRoleName";
    }
}

控制用于地理位置映射的客户端 IP 地址

以下示例初始化程序设置了在遥测引入期间用于地理位置映射的客户端 IP,而不是客户端套接字 IP 地址。

public void Initialize(ITelemetry telemetry)
{
    var request = telemetry as RequestTelemetry;
    if (request == null) return true;
    request.Context.Location.Ip = "{client ip address}"; // Could utilize System.Web.HttpContext.Current.Request.UserHostAddress;   
    return true;
}

ITelemetryProcessor 和 ITelemetryInitializer

遥测处理器和遥测初始值设定项之间的区别是什么?

  • 它们的功能有部分重叠。 二者都可用于添加或修改遥测的属性,但我们建议你使用初始化表达式执行这项操作。
  • 遥测初始化表达式始终在遥测处理器之前运行。
  • 遥测初始化表达式可以多次调用。 按照约定,它们不会设置任何已设置的属性。
  • 利用遥测处理器,你可以完全替换或丢弃遥测项。
  • 会针对每个遥测项调用所有已注册的遥测初始化表达式。 对于遥测处理器,SDK 可保证调用第一个遥测处理器。 是否调用余下的处理器取决于前面的遥测处理器。
  • 通过遥测初始化表达式使用更多属性来扩充遥测,或者重写现有的遥测。 使用遥测处理器筛掉遥测。

注意

JavaScript 只包含可使用 ITelemetryInitializer 筛选出事件的遥测初始值设定项

ApplicationInsights.config 故障排除

  • 确认完全限定的类型名称和程序集名称是正确的。
  • 确认 applicationinsights.config 文件在输出目录中并且包含所有最新更改。

Azure Monitor 遥测数据类型参考

参考文档

SDK 代码

后续步骤