APM Tips

Application Insights

Application Insights sampling: the types, the defaults, and why it beats filtering

Updated · 7 min read · By APM Tips editorial team

Sampling keeps a representative share of whole operations and records on each kept item how many it stands for, so totals and charts stay statistically correct. The classic .NET SDK 2.x samples adaptively at five items per second per server host; SDK 3.x and the Microsoft OpenTelemetry Distro default to rate-limited sampling at five traces per second; ingestion sampling at the service is a fallback Microsoft labels not recommended. Filtering is different: it drops items without a trace, so request and failure counts come out low.

Sampling is the part of Application Insights that people switch off when numbers look wrong, and then switch back on when the bill arrives. Both reactions come from the same misunderstanding: assuming that a sampled resource holds “some of the data” in the way a filtered one does. It doesn’t. A sampled resource holds a statistically fair subset plus the multiplier needed to restore the totals.

How sampling works

The decision is made per operation, using the operation ID of the incoming request. Either the whole operation is kept (the request, its dependency calls, exceptions and traces) or none of it is, so when you open a kept request in the portal its children are all there. Items with no operation ID, such as work on a background thread without an HTTP context, fall back to a plain percentage per telemetry type.

Every kept item records the rate it was sampled at. In the classic schema that is the itemCount column: a request kept at 20% has itemCount 5. The portal’s charts multiply by it, and your own queries must too.

Three kinds of telemetry are never sampled, by any method: sessions, metrics (custom metrics included) and performance counters.

The four kinds of sampling

KindWhere it runsDefault in
AdaptiveClassic .NET SDK, in the appASP.NET and ASP.NET Core SDK 2.x, Azure Functions
Fixed-rateClassic SDKs, JavaScript SDK, JavaOff unless configured
Rate-limitedSDK 3.x, OpenTelemetry distros, Java agentSDK 3.x, Microsoft OpenTelemetry Distro, Java agent 3.4.0+
IngestionApplication Insights service endpointOff; set in the portal

Adaptive sampling adjusts the percentage to stay near a target rate, recalculated from a moving average. Fixed-rate keeps the percentage you choose. Rate-limited sampling caps the number of traces kept per second. Ingestion sampling drops items as they arrive at the service: it saves storage and cost, but not network traffic, and it only applies to data the SDK didn’t already sample.

Classic SDK 2.x

The .NET Classic API SDK 2.x is deprecated and retires on March 31, 2027, but it still runs a great deal of production code, so its defaults are worth knowing precisely.

Adaptive sampling is on by default in the ASP.NET and ASP.NET Core SDKs. The target is five telemetry items per second on each server host, and the SDK adds two adaptive processors by default: one for Event items and one for everything else, so custom events are sampled separately from requests and dependencies. Each SDK instance decides independently, which means ten instances behind a load balancer aim for ten times the volume.

In ASP.NET Core you switch the default off and register your own sampler on the processor chain. This example keeps a fixed 10%:

using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.ApplicationInsights.Extensibility;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<TelemetryConfiguration>(config =>
{
    var chain = config.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
    chain.UseSampling(10); // percent; choose a value close to 100/N
    chain.Build();
});

builder.Services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
{
    EnableAdaptiveSampling = false,
});

ASP.NET apps on .NET Framework do the same in ApplicationInsights.config by replacing the adaptive processor node with the fixed-rate one:

<TelemetryProcessors>
  <Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.SamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
    <SamplingPercentage>20</SamplingPercentage>
  </Add>
</TelemetryProcessors>

Choose a fixed percentage close to 100/N for a whole number N: 50, 33.33, 25, 20, 10, 1. The ApplicationInsights.config guide covers the rest of that file.

To exempt specific items, the 2.x answer is a telemetry initializer that sets SamplingPercentage to 100 on them. Initializers run before the sampling processor, so every sampling method leaves those items alone.

Today: SDK 3.x and the Microsoft OpenTelemetry Distro

Both current .NET paths replaced adaptive sampling with a rate-limited sampler built for OpenTelemetry traces.

Application Insights .NET SDK 3.x removed EnableAdaptiveSampling and both sampling processors. It offers two settings on TelemetryConfiguration, ApplicationInsightsServiceOptions, appsettings.json or applicationinsights.config: TracesPerSecond (the default mode, five per second) or SamplingRatio (0.0 to 1.0; setting it switches rate limiting off). Requests and dependencies share one rate, and per-type rules such as “always keep exceptions” are no longer supported.

The Microsoft OpenTelemetry Distro for .NET, Node.js and Python also defaults to five traces per second. In .NET you switch to a fixed ratio by clearing the rate limit:

using Microsoft.OpenTelemetry;

var builder = WebApplication.CreateBuilder(args);

builder.UseMicrosoftOpenTelemetry(options =>
{
    options.Exporters = ExportTarget.AzureMonitor;
    options.AzureMonitor.TracesPerSecond = null; // turn off rate-limited sampling
    options.AzureMonitor.SamplingRatio = 0.1F;   // keep about 10% of traces
});

Node.js does the same with samplingRatio and tracesPerSecond: 0:

const { useMicrosoftOpenTelemetry } = require("@microsoft/opentelemetry");

useMicrosoftOpenTelemetry({
  azureMonitor: {
    azureMonitorExporterOptions: {
      connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
    },
  },
  samplingRatio: 0.1,
  tracesPerSecond: 0,
});

Python reads the standard OpenTelemetry variables, set before the distro starts; don’t carry over the older sampling_ratio and traces_per_second arguments:

export OTEL_TRACES_SAMPLER="microsoft.fixed_percentage"
export OTEL_TRACES_SAMPLER_ARG=0.1

Java uses APPLICATIONINSIGHTS_SAMPLING_PERCENTAGE or APPLICATIONINSIGHTS_SAMPLING_REQUESTS_PER_SECOND; since agent 3.4.0 the default is rate-limited at about five requests per second, and logs outside a request are always collected.

Two details catch people out. First, logs can follow the trace decision: trace-based log sampling drops log records that belong to an unsampled trace. It is on by default in .NET (EnableTraceBasedLogsSampler) and Java, off by default in Node.js and Python. Second, Live Metrics requires the Azure Monitor sampler; replacing it with a generic OpenTelemetry sampler breaks the live view. See enabling Live Metrics from code.

Count correctly in KQL

With sampling on, count() counts stored rows, not requests. Weight everything by itemCount:

requests
| where timestamp > ago(1d)
| summarize requests = sum(itemCount),
            p95_ms = percentilew(duration, itemCount, 95)
  by operation_Name

To see whether, and how hard, each type is being sampled, use Microsoft’s check:

union requests, dependencies, pageViews, browserTimings, exceptions, traces
| where timestamp > ago(1d)
| summarize RetainedPercentage = 100 / avg(itemCount) by bin(timestamp, 1h), itemType

Anything below 100 is sampled. The latency by operation and failed requests by result code queries in the library are already sampling-aware, and the KQL builder writes the weighting for you.

Why filtering is not a substitute

A telemetry processor (classic) or span processor (OpenTelemetry) that drops items is a filter. Microsoft’s own warning is blunt: filtering skews the statistics you see in the portal and makes it hard to follow related items. Nothing records what was dropped, so log-based request counts, failure rates and any alert built on them come out low, and an operation can lose its request while keeping its dependencies.

Filtering still has a place: health-check probes, synthetic traffic and other items you never want to count at all. Drop those deliberately and document it. For “we send too much”, sample.

Ingestion sampling and the daily cap

Ingestion sampling is set under Usage and estimated costs > Data Sampling. It is a last resort for apps you can’t redeploy, and Microsoft labels it not recommended for OpenTelemetry apps: it has no control over which traces and spans survive, so more traces arrive broken. If incoming data was already sampled below 100%, the service ignores the setting entirely.

The daily cap is a different tool. For a workspace-based resource the effective cap is the lower of the Application Insights and Log Analytics caps; when it’s hit, collection stops for the rest of a 24-hour period, data that arrives over the cap is still billed, and the reset hour can’t be changed. Treat it as a fuse. To turn a cap into a sampling percentage, use the sampling and cost calculator, and see Application Insights pricing for what each GB costs.

Troubleshooting

SymptomLikely causeFix
Request counts far below the load balancer’scount() instead of sum(itemCount), or a filterWeight by itemCount; review processors that drop items
Volume still too high after setting ingestion samplingThe SDK already samples, so ingestion sampling is ignoredLower the SDK rate instead
Volume grows with every new instanceAdaptive and rate-limited targets apply per instanceDivide the target by the instance count
Traces broken across servicesEach app samples independentlyUse the same sampler and rate in every service
Logs missing for some requestsTrace-based log samplingExpected; turn it off only if you need every log
“Throttled” data at peakOver 32,000 events per second, measured over a minuteSample harder; the limit applies per connection string
Live Metrics empty after a sampler changeNot using the Azure Monitor samplerRestore the distro’s sampler

Queries for this topic

From the KQL library: explained queries for the Application Insights tables, ready to copy.

  • Sampling rate in effect, per telemetry type

    requestsdependenciespageViewsbrowserTimingsexceptionstracescustomEventsavailabilityResults cost

    Returns: One row per telemetry type with the rows stored, the items they stand for, and the percentage of items kept by sampling over the last 24 hours.

  • Telemetry volume by table, in GB per day

    requestsdependenciesexceptionstracescustomEventspageViewsavailabilityResultsperformanceCounterscustomMetricsbrowserTimings cost

    Returns: One row per table with the billed gigabytes over the last 7 complete days, the stored rows, the items they stand for after sampling, and the average gigabytes per day, largest first.

  • Returns: One row per operation name with its request count and its p50, p95 and p99 duration in milliseconds, slowest p95 first.

Frequently asked questions

Is Application Insights sampling on by default?

On the server side, yes. The classic ASP.NET and ASP.NET Core SDK 2.x and Azure Functions use adaptive sampling at five items per second per host. SDK 3.x, the Microsoft OpenTelemetry Distro for .NET, Node.js and Python, and the Java agent from 3.4.0 use rate-limited sampling at about five traces or requests per second.

Are custom metrics and performance counters sampled?

No. Microsoft states that sessions, metrics (including custom metrics) and performance counters are never sampled by any of the sampling types, and OpenTelemetry metrics are never sampled either. That makes metrics the right signal for alerts that must not miss anything.

Why does the portal show a different sampling rate from the one I set?

The Data Sampling setting on Usage and estimated costs is ingestion sampling, and the service ignores it when the SDK already sampled the data. The tile shows the value you set, not the rate in effect. Query itemCount to see what is really retained.

Can I keep every exception while sampling requests?

In the classic 2.x SDK, yes: exclude the Exception type from the sampling processor, or set SamplingPercentage to 100 in a telemetry initializer for the items you must keep. SDK 3.x removed per-type sampling; one decision applies to all trace telemetry.

Sources, checked

  1. Telemetry sampling in Azure Application Insights (Classic API)learn.microsoft.com · checked
  2. Sampling in Azure Application Insights with OpenTelemetrylearn.microsoft.com · checked
  3. Configure OpenTelemetry in Application Insights (Enable sampling)learn.microsoft.com · checked
  4. Migrate Application Insights Classic API SDKs to OpenTelemetrylearn.microsoft.com · checked
  5. Monitor .NET and Node.js applications with Application Insights (Classic API 2.x)learn.microsoft.com · checked
  6. Set daily cap on Log Analytics workspacelearn.microsoft.com · checked
  7. Azure Monitor service limits (Application Insights)learn.microsoft.com · checked