Getting data into Azure Monitor — diagnostic settings, data collection rules, Azure Monitor Agent and the Logs Ingestion API
How platform logs, VM data and your own records reach a workspace — diagnostic settings (limits, destinations, category groups, resource-specific tables, errors) in Bicep; data collection rules and Azure Monitor Agent; the Logs Ingestion API with a custom table, a Direct DCR, TypeScript and its 403, 413 and 429 errors.
markdownreferencev1030M-7PH3stage: buildtrust: officialservice: monitorinterface: cliinterface: iacinterface: sdkdiagnostic settingsfive diagnostic settings per resourcedestination must already existEvent Hubs same regioncategory group allLogscategory group auditSQL auditing not enabled by audit categoryresource-specific tablesAzureDiagnostics tablelogAnalyticsDestinationType Dedicatedexport-to-resource-specificAllMetrics onlyMetric category is not supportedactivity log diagnostic setting subscriptionaz monitor diagnostic-settings createaz monitor diagnostic-settings categories listdata collection ruleDCR limits10 data sources20 data flowstransformation 15360 charactersDCR same region as workspacedata collection rule associationdata collection endpoint when neededAzure Monitor AgentAzureMonitorLinuxAgentAzureMonitorWindowsAgentAzure Monitor Agent managed identityLogs Ingestion APIkind DirectlogsIngestion endpointcustom table _CLstreamDeclarations Custom-transformKql@azure/monitor-ingestionLogsIngestionClient uploadisAggregateLogsUploadErrorLogs Ingestion 403 permissions 30 minutesLogs Ingestion 413 ReadyBody_ClientConnectionAbortLogs Ingestion 429 Retry-After1 MB per call
Getting data into Azure Monitor
Diagnostic settings (platform logs and metrics)
Resource logs aren't collected until a diagnostic setting sends them somewhere; metrics and the activity log are collected anyway.
| Rule | Detail |
|---|---|
| Count | Five settings per resource (fixed) |
| Destinations | Log Analytics, storage, Event Hubs, partner solutions; at most one of each type per setting (two workspaces need two settings); the destination must already exist; storage and Event Hubs must be in the resource's region; other subscriptions are fine with RBAC on both |
| Categories | Pick categories, or a category group: allLogs or audit (not both needed; groups follow Microsoft's changes). audit on SQL Database doesn't turn on SQL auditing |
| Collection mode | Resource-specific tables (logAnalyticsDestinationType: 'Dedicated', CLI --export-to-resource-specific true) where the service supports them, rather than the shared AzureDiagnostics table |
| Metrics | Only AllMetrics for most services; metrics with dimensions arrive flattened; some metrics aren't exportable |
| Cost | Ingestion is billed, and so is export of some categories. Collect only needed categories; skip metrics unless you query them in logs |
| Activity log | A subscription-level setting: az monitor diagnostic-settings subscription create (or ARM), not the resource command |
// Send a Key Vault's resource logs and metrics to a workspace, into resource-specific tables
param keyVaultName string
param workspaceId string
resource kv 'Microsoft.KeyVault/vaults@2026-05-15' existing = {
name: keyVaultName
}
resource toWorkspace 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
scope: kv
name: 'to-log-analytics'
properties: {
workspaceId: workspaceId
logAnalyticsDestinationType: 'Dedicated' // resource-specific tables, not AzureDiagnostics
logs: [
{
categoryGroup: 'allLogs'
enabled: true
}
]
metrics: [
{
category: 'AllMetrics'
enabled: true
}
]
}
}
az monitor diagnostic-settings categories list --resource <id> shows what a resource offers. Diagnostic settings have only the 2021-05-01-preview API version.
| Symptom | Cause → fix |
|---|---|
| "Metric category 'xxxx' is not supported" | Use AllMetrics |
| The setting disappears | Non-ASCII characters in the resource ID (not supported): re-create the resource or move it to a group without them |
| No data yet | Allow up to 90 minutes; the table appears with the first record. Nothing after 24 hours: the resource may emit no logs; disable and re-enable the setting |
| Duplicate Application Insights rows | Its diagnostic setting can't target the workspace the resource is based on |
Data collection rules (DCRs)
A DCR says what to collect, how to transform it (KQL transformKql) and where to send it. Create it in the destination workspace's region. Limits per DCR: 10 data sources, 20 data flows and 20 streams, 10 workspaces, 15,360 characters per transformation.
Azure Monitor Agent (extension AzureMonitorLinuxAgent or AzureMonitorWindowsAgent, publisher Microsoft.Azure.Monitor) needs a managed identity on the VM: user-assigned for fleets, system-assigned for tests; Arc servers use system-assigned only. Collection starts when a data collection rule association links the machine to a DCR (many to many). In CLI, DCRs and associations come from the monitor-control-service extension (the monitor data-collection command group), not core. A data collection endpoint is needed only for private link, or for DCRs created before March 31, 2024. The old Log Analytics agent (MMA) and the Azure Diagnostics extension are retired: reference/monitor/what-to-use.md.
Logs Ingestion API (your own records)
It replaces the HTTP Data Collector API (end of support September 14, 2026). You need a custom table (_CL), a DCR whose stream matches what you send, and Monitoring Metrics Publisher on the DCR for the sender. A Direct DCR gets its own logsIngestion endpoint (an existing DCR can't gain one). Keep the DCR, workspace and any DCE in one region.
// A custom table and a Direct DCR for the Logs Ingestion API, with an app identity allowed to send
param location string = resourceGroup().location
param workspaceName string
param senderPrincipalId string // the app's managed identity (object ID)
resource ws 'Microsoft.OperationalInsights/workspaces@2026-03-01' existing = {
name: workspaceName
}
resource table 'Microsoft.OperationalInsights/workspaces/tables@2026-03-01' = {
parent: ws
name: 'AuditEvents_CL' // custom tables must end in _CL
properties: {
schema: {
name: 'AuditEvents_CL'
columns: [
{ name: 'TimeGenerated', type: 'datetime' }
{ name: 'Computer', type: 'string' }
{ name: 'Action', type: 'string' }
{ name: 'Outcome', type: 'string' }
]
}
}
}
resource dcr 'Microsoft.Insights/dataCollectionRules@2024-03-11' = {
name: 'dcr-audit-events'
location: location // same region as the workspace
kind: 'Direct' // gives the DCR its own logsIngestion endpoint
properties: {
streamDeclarations: {
'Custom-AuditEvents': {
columns: [
{ name: 'TimeGenerated', type: 'datetime' }
{ name: 'Computer', type: 'string' }
{ name: 'Action', type: 'string' }
{ name: 'Outcome', type: 'string' }
]
}
}
destinations: {
logAnalytics: [
{ name: 'ws', workspaceResourceId: ws.id }
]
}
dataFlows: [
{
streams: ['Custom-AuditEvents']
destinations: ['ws']
transformKql: 'source | where Action != "heartbeat"'
outputStream: 'Custom-AuditEvents_CL'
}
]
}
dependsOn: [table]
}
var monitoringMetricsPublisher = '3913510d-42f4-4e42-8a64-420c390055eb'
resource canSend 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: dcr
name: guid(dcr.id, senderPrincipalId, monitoringMetricsPublisher)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', monitoringMetricsPublisher)
principalId: senderPrincipalId
principalType: 'ServicePrincipal'
}
}
output immutableId string = dcr.properties.immutableId
output logsIngestionEndpoint string = dcr.properties.endpoints.logsIngestion
import { DefaultAzureCredential } from "@azure/identity";
import { LogsIngestionClient, isAggregateLogsUploadError } from "@azure/monitor-ingestion";
const credential = new DefaultAzureCredential();
// Needs Monitoring Metrics Publisher on the data collection rule.
export async function sendAuditRows(): Promise<void> {
const ingest = new LogsIngestionClient(process.env.DCR_LOGS_INGESTION_ENDPOINT ?? "", credential); // the DCR's logsIngestion endpoint, or a DCE
const rows = [{ TimeGenerated: new Date().toISOString(), Computer: "app-01", Action: "login", Outcome: "success" }];
try {
await ingest.upload(process.env.DCR_IMMUTABLE_ID ?? "", "Custom-AuditEvents", rows); // dcr-…, stream from streamDeclarations
} catch (e) {
if (isAggregateLogsUploadError(e)) {
for (const f of e.errors) console.error(f.cause, f.failedLogs.length); // batches that failed after the SDK's retries
}
throw e;
}
}
| Error | Cause → fix |
|---|---|
| 403 | The sender lacks Monitoring Metrics Publisher on the DCR, or the assignment is new: wait up to 30 minutes |
413, or TimeoutExpired with ReadyBody_ClientConnectionAbort | Over 1 MB per call: send smaller batches |
| 429 | Over the per-DCR limits (2 GB per minute, 12,000 requests per minute): wait for Retry-After |
| No rows | The first send to a new table can take up to 15 minutes |
| Log Analytics doesn't suggest the new table | IntelliSense can lag 24 hours; the query still works |
The hub's reading: a stream or column mismatch between the payload, streamDeclarations and the table is the usual cause when calls succeed but columns stay empty.