Azure mapping
The Azure Monitor exporter does not go through a collector: it turns each OpenTelemetry-shaped record into an Application Insights envelope and posts it to the ingestion endpoint itself. This page is the mapping, record by record - the same in the .NET encoder and in the browser script, so a page view from the browser and a request span from the server land in the tables and on the Application Map node you expect.
Endpoint
Envelopes are posted to {IngestionEndpoint}/v2.1/track, with the ingestion endpoint read from the
connection string - https://dc.services.visualstudio.com when the connection string has none. The
body is a JSON array of envelopes with Content-Type: application/json (gzip-compressed with
Compression = BelemetryCompression.Gzip). With an AccessTokenProvider, every request also
carries Authorization: Bearer - and such an exporter is never handed to the browser script.
Envelope
Every record becomes one envelope (a metric becomes one per point). name is
Microsoft.ApplicationInsights.<Type>, baseType is <Type>Data,
and the type-specific fields live in baseData.
{
"ver": 1,
"name": "Microsoft.ApplicationInsights.PageView",
"time": "2026-10-15T09:30:12.3456789Z",
"sampleRate": 100,
"iKey": "00000000-0000-0000-0000-000000000000",
"tags": {
"ai.cloud.role": "[bitplatform]/belemetry-demo",
"ai.cloud.roleInstance": "6f1c...",
"ai.device.type": "Browser",
"ai.internal.sdkVersion": "bitblmjs:10.6.2",
"ai.location.ip": "0.0.0.0",
"ai.operation.id": "4bf92f3577b34da6a3ce929d0e0e4736",
"ai.operation.name": "Checkout",
"ai.session.id": "9c1e..."
},
"data": {
"baseType": "PageViewData",
"baseData": {
"ver": 2,
"id": "00f067aa0ba902b7",
"name": "Checkout",
"url": "https://myapp.example.com/checkout",
"duration": "00:00:00",
"properties": { "url.path": "/checkout", "browser.navigation.type": "push" }
}
}
}Tags
| Tag | Value |
|---|---|
ai.cloud.role | [service.namespace]/service.name, or service.name alone without a namespace - the same naming as the Azure Monitor OpenTelemetry exporter, so both land on one Application Map node |
ai.cloud.roleInstance | service.instance.id |
ai.application.ver | service.version |
ai.operation.id | The trace id |
ai.operation.parentId | The parent span id (span) or the span id (log) |
ai.operation.name | The page name (page view) or the request name (server span: {method} {route} when both are known, else the span name); not set on other records |
ai.session.id | session.id |
ai.user.id | enduser.pseudo.id |
ai.user.authUserId | user.id |
ai.user.agent | user_agent.original from the browser resource |
ai.device.locale | browser.language from the browser resource |
ai.device.type | "Browser" (JS), "Phone" (Android and iOS), "PC" otherwise |
ai.location.ip | "0.0.0.0" when MaskIp (the default), so no IP-based geolocation |
ai.internal.sdkVersion | "bitblm:<version>" (.NET) or "bitblmjs:<version>" (JS) |
session.id, enduser.pseudo.id and user.id are moved into the tags and not
repeated as properties; without UserTracking consent the user tags are simply absent.
Record types
| Source record | Envelope type | Table | Fields |
|---|---|---|---|
| Span, kind SERVER or CONSUMER | Request |
requests |
id, name ({method} {http.route} when the route is known),
duration (d.hh:mm:ss.fffffff), responseCode, success
(no error status and a status code below 500), url |
| Any other span | RemoteDependency |
dependencies |
id, name, resultCode, data (the URL or the query text),
duration, success (below 400), type (Ajax for browser HTTP,
HTTP for .NET, the db.system.name for a database, InProc for internal
spans), target |
Log with exception.* |
Exception |
exceptions |
exceptions:[{id:1,outerId:0,typeName,message,hasFullStack,stack}], severityLevel |
eventName == "browser.page_view" |
PageView |
pageViews |
name (page.title, else url.path), url, id |
eventName == "browser.web_vital" |
Metric |
customMetrics |
metrics:[{name:"<web_vital.name>",kind:0,value,count:1}] - LCP, INP, CLS, FCP, TTFB |
eventName == "browser.page_visit_time" |
Event |
customEvents |
name = PageVisitTime, measurements:{PageVisitTime:<ms>}, properties PageName and PageUrl - the Application Insights JS SDK's autoTrackPageVisitTime shape |
Any other eventName |
Event |
customEvents |
name = the event name |
| Any other log | Message |
traces |
message, severityLevel; the logger category as the CategoryName property |
| Metric point | Metric |
customMetrics |
Gauge: the last value. Sum: the delta. Histogram: the delta sum, count, min and max. The meter name as ns; sums and histograms with nothing new since the last export are skipped |
Application Insights takes deltas, so metrics go to Azure Monitor as deltas whatever
Metrics.Temporality says - that setting applies to OTLP only.
Severity levels
| OTLP severity number | severityLevel |
|---|---|
| 1-8 (TRACE, DEBUG) | 0 Verbose |
| 9-12 (INFO) | 1 Information |
| 13-16 (WARN) | 2 Warning |
| 17-20 (ERROR) | 3 Error |
| 21-24 (FATAL) | 4 Critical |
Properties
Every attribute not consumed by a field becomes a custom property, stringified - so a structured log value, a
TrackEvent attribute or a processor's enrichment is queryable as
customDimensions["key"]. Keys are truncated to 150 characters and values to 8192, the limits the
ingestion service enforces; messages and stack traces may be up to 32768.
Retries
Status codes 408, 429, 439 (daily quota), 500, 502,
503 and 504 are retried with backoff, honouring Retry-After, and kept for
offline persistence when the retries run out. Anything else is a permanent answer and
the batch is dropped.
A 206 is a partial success: the response lists, per item, the index in the array and its status
code. Only the items whose code is one of the retryable ones above are sent again - rebuilt into a new array -
and the rest, accepted or permanently rejected, are not.
API
BelemetryAzureMonitorExporterOptions
Class
Settings of the Azure Monitor (Application Insights) exporter, which posts to the ingestion endpoint (/v2.1/track) directly, so page views, requests, dependencies, exceptions and custom events land in their usual Application Insights tables without a collector in between.
| Name | Type and default | Description |
|---|---|---|
ConnectionString |
string |
The Application Insights connection string (InstrumentationKey=...;IngestionEndpoint=https://...;LiveEndpoint=https://...). Defaults to APPLICATIONINSIGHTS_CONNECTION_STRING when set (outside the browser). |
AccessTokenProvider |
Func<CancellationToken, ValueTask<string>> |
Supplies a Microsoft Entra ID access token (scope https://monitor.azure.com//.default) sent as Authorization: Bearer, for resources with local authentication disabled. Belemetry takes no dependency on Azure.Identity: pass e.g. ct => credential.GetTokenAsync(...). |
MaskIp |
bool= true |
Whether to send ai.location.ip = 0.0.0.0 so the ingestion service does not derive the user's location from the client IP address. Defaults to true. |
Compression |
BelemetryCompression= BelemetryCompression.None |
The request-body compression. Defaults to none. |
Timeout |
TimeSpan= 00:00:30 |
The maximum time one export request may take. Defaults to 30 seconds. |
EnableLiveMetrics |
bool= false |
Whether to stream Live Metrics (requests, dependencies, exceptions, CPU and memory, once per second while someone watches the Live Metrics blade). Not available in the browser. Defaults to false. |
IncludeInBrowser |
bool= true |
Whether the browser script exports to Application Insights too (with BelemetryBrowserTransport.Direct). Defaults to true. |
Signals |
BelemetrySignals= BelemetrySignals.All |
The signals sent to Application Insights. Defaults to all three. |
MinimumLogLevel |
LogLevel? |
The least severe log level this exporter receives from the .NET pipeline (its own logs and the records the browser relays); plain log records below it are left out of this exporter's batches only. Events - page views, interactions, web vitals, custom events, exceptions - are not logs to a log level and always pass, as do spans and metrics. null (the default) sends every collected record. Use it when Application Insights should get warnings and errors only while an OTLP backend gets everything, as a per-provider logging level would with two logging providers. |