← All postsDevelopment ยท 3 November 2026

Telemetry with Application Insights: switch it on, then look at it

Every Business Central tenant can stream operational telemetry to Azure Application Insights: slow queries, locks, errors, page load times, job queue runs, extension updates, sign-ins. Almost nobody switches it on. How to, what the signals mean, the five KQL queries to start with, and how to add your own events from AL.

When a customer says "the system was slow yesterday afternoon", there are two kinds of partner. The one that asks the user to reproduce it, and the one that opens Application Insights, filters to yesterday afternoon, and says "it was the aged debtors report run by two people at once, 40 seconds each, locking Cust. Ledger Entry; here is the query". The difference is one setting in the admin centre.

Switching it on

Create an Application Insights resource in Azure (any subscription, pay-as-you-go pricing; a mid-sized tenant costs a few pounds a month). Copy its connection string. In the Business Central admin centre, on the environment, paste it into the Application Insights field. From then on, the platform sends telemetry for that environment: no code, no extension. Repeat per environment, or point them at one resource and filter by environment name. For an extension's own telemetry, the applicationInsightsConnectionString in app.json sends that app's events to the publisher's resource as well.

What arrives

Each event has an ID (RT0005, AL0000E24 and so on; Microsoft documents them all) and a set of custom dimensions. The ones that matter day to day:

Five queries to start with

Kusto Query Language, in the Logs blade of the Application Insights resource. Microsoft publishes a full set of sample queries and Power BI reports; these five get a new administrator most of the value.

// 1. Slowest SQL in the last 24 hours, by AL object
traces
| where timestamp > ago(24h) and customDimensions.eventId == "RT0005"
| extend obj = tostring(customDimensions.alObjectName), ms = toint(customDimensions.executionTimeInMs)
| summarize count(), avg(ms), max(ms) by obj
| order by max_ms desc

// 2. Lock timeouts and who was waiting
traces
| where timestamp > ago(7d) and customDimensions.eventId in ("RT0012","RT0013")
| project timestamp, customDimensions.alObjectName, customDimensions.alStackTrace, customDimensions.sqlStatement

// 3. Every error users saw this week, counted
traces
| where timestamp > ago(7d) and customDimensions.eventId == "RT0030"
| summarize n = count() by msg = tostring(customDimensions.failureReason)
| order by n desc

// 4. Job queue failures
traces
| where timestamp > ago(7d) and customDimensions.eventId == "AL0000HE7"
| project timestamp, customDimensions.alJobQueueObjectName, customDimensions.alJobQueueStacktrace

// 5. API calls by endpoint and caller
traces
| where timestamp > ago(1d) and customDimensions.eventId == "RT0008"
| summarize n = count(), avg(toint(customDimensions.serverExecutionTime)) by ep = tostring(customDimensions.endpoint), app = tostring(customDimensions.clientAppId)
| order by n desc

Pin them to a dashboard, add an alert on queries 2 and 4, and you have a monitoring setup most businesses do not have after five years.

Adding your own events from AL

var
    ShipmentSentEventIdTok: Label 'CON0001', Locked = true;
    ShipmentSentMsg: Label 'Courier shipment sent', Locked = true;
    ShipmentNoTok: Label 'ShipmentNo', Locked = true;
    CarrierTok: Label 'Carrier', Locked = true;

procedure SendShipment(var Shipment: Record "CON Courier Shipment")
var
    Dim: Dictionary of [Text, Text];
begin
    Dim.Add(ShipmentNoTok, Shipment."No.");
    Dim.Add(CarrierTok, Format(Shipment.Carrier));
    Session.LogMessage(ShipmentSentEventIdTok, ShipmentSentMsg, Verbosity::Normal,
        DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, Dim);
end;

Telemetry text is a Label with Locked = true, as Microsoft's own code does: a label because all text belongs in labels, locked because telemetry must never be translated or the queries stop matching. Give every event an ID with your prefix, a stable message, and dimensions with no personal data (classify accordingly). TelemetryScope::ExtensionPublisher sends to the app.json resource; All also sends to the customer's environment resource. Log the start, end and failure of integrations and long operations, and business-rule failures that are not errors (a shipment rejected by the carrier). Then your extension explains itself in the same place as the platform does. The performance post ends with "look at the telemetry"; this is how.

What it costs and who should own it

Application Insights charges per gigabyte ingested; a typical mid-sized tenant sends a few hundred megabytes a month. Set a daily cap and 90 days' retention and the bill is small. Ownership: the person who administers the environment looks at the dashboard weekly and the alerts land in their inbox; the support partner has read access and uses it on every ticket. If your partner is not asking for it, ask why; it is in our list of what to expect.

Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

More from the blog