One of the easiest ways to accidentally generate unexpected Azure Monitor costs is leaving a Log Analytics workspace without a Daily Cap. You’re only ever one bad code release away from a potentially massive spike in costs.
Microsoft recommends setting a daily cap and configuring an alert when the cap is reached, but this is often forgotten when workspaces are created via IaC, scripts, or pipelines.
In this post I’ll show how to use a PowerShell script to:
- Check current Log Analytics usage
- Calculate a recommended Daily Cap based on real ingestion
- Set the Daily Cap automatically
- Create an alert rule when the cap is hit (Microsoft best practice)
- Use an Action Group for notifications
The script below is useful for automation, landing zones, and cost-control guardrails. Currently it is limited to active on the singular Workspace that you define, but could be easily expanded to cover an entire tenant if required.
Why set a Daily Cap on Log Analytics
Without a cap, ingestion continues indefinitely which means cost continues indefinitely.
Microsoft guidance recommends
- Set a daily cap slightly above normal ingestion
- Alert when the cap is hit
- Investigate unexpected spikes
The script below calculates the cap as Daily usage × 1.25 (25% buffer), this can also be configured if required though. This allows for normal variation without blocking ingestion too early.
Prerequisites
Before running the script, make sure the following are configured.
1.Create an Action Group for alerts
The script requires an Action Group resource ID, this is used to define which Action Group any alerts will be sent to should the Daily Cap be reached.
Portal steps
- Go to Azure Portal
- Search for Monitor
- Select Alerts
- Select Create then Action group
- Give it a name, configure the type of notification to receive and any additional actions you want to trigger.
After creation, you’ll see that the URL of the resource is something along the lines of what’s shown below –
https://portal.azure.com/?feature.customportal=false#@example.com/resource/subscriptions/FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF/resourceGroups/Default-ActivityLogAlerts/providers/microsoft.insights/actiongroups/Alert%20SysadminCentralGuy/overview
We need to copy everything from /subscriptions till the end of the name of the alert (Alert%20SysadminCentralGuy), we also need to remove any spaces in the name such that we end up with something like what’s shown below –
/subscriptions/FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF/resourceGroups/Default-ActivityLogAlerts/providers/microsoft.insights/actiongroups/Alert SysadminCentralGuy
2. Get Log Analytics workspace details
You need to copy the following details, all of which you can get from viewing the Log Analytics Workspace in the Azure Portal.
- Tenant ID
- Subscription name
- Resource group
- Workspace name
3. Populate the Variables in the Script then Run it
Import-Module Az.Monitor
$ErrorActionPreference = 'Stop'
$tenantId = 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF'
$subscriptionName = 'Example Subscription'
$resourceGroupName = 'example-rg'
$workspaceName = 'example-workspace'
$dailyCapMultiplier = 1.25 # Daily Cap is Current Daily Usage * this value
$actionGroupResourceId = '/subscriptions/FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF/resourceGroups/Default-ActivityLogAlerts/providers/microsoft.insights/actiongroups/Alert SysadminCentralGuy'
Connect-AzAccount -TenantId $tenantId -Subscription $subscriptionName
$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $workspaceName
$workspaceUsage = Get-AzOperationalInsightsWorkspaceUsage -ResourceGroupName $resourceGroupName -Name $workspaceName
# Set the daily cap based on usage
if($null -ne $workspace.WorkspaceCapping -and $workspace.WorkspaceCapping.DailyQuotaGb -gt 0) {
Write-Host -ForegroundColor Green "Current Daily Cap: $($workspace.WorkspaceCapping.DailyQuotaGb) GB"
return
} else {
Write-Host -ForegroundColor Yellow "No Daily Cap currently set"
}
if($workspaceUsage.CurrentValue -eq 0) {
$limitToSet = 1
} else {
$limitToSet = [math]::Ceiling($workspaceUsage.CurrentValue * $dailyCapMultiplier)
}
Write-Host "Setting new Daily Cap: $limitToSet GB..."
Set-AzOperationalInsightsWorkspace -ResourceGroupName $resourceGroupName -Name $workspaceName -DailyQuotaGb $limitToSet | Out-Null
Write-Host -ForegroundColor Green "Daily Cap set successfully"
$alertRuleName = "Log Analytics Daily Cap Alert - $workspaceName"
$existingAlertRule = Get-AzScheduledQueryRule -ResourceGroupName $resourceGroupName -Name $alertRuleName -ErrorAction SilentlyContinue
if($null -ne $existingAlertRule) {
Write-Host -ForegroundColor Red "Alert Rule already exists"
return
}
$QueryRuleQuery = @"
_LogOperation
| where Category =~ "Ingestion"
| where Detail contains "OverQuota"
"@
$QueryRuleCondition = New-AzScheduledQueryRuleConditionObject `
-Query $QueryRuleQuery `
-TimeAggregation 'Count' `
-ResourceIdColumn '_ResourceId' `
-Operator 'GreaterThan' `
-Threshold 0 `
-FailingPeriodNumberOfEvaluationPeriod 1 `
-FailingPeriodMinFailingPeriodsToAlert 1
New-AzScheduledQueryRule `
-Name $alertRuleName `
-ResourceGroupName $resourceGroupName `
-Location $workspace.Location `
-ActionGroupResourceId $actionGroupResourceId `
-Description "Triggers when the daily cap is hit" `
-DisplayName $alertRuleName `
-Scope $workspace.ResourceId `
-Severity 2 `
-WindowSize ([System.TimeSpan]::New(0,15,0)) `
-EvaluationFrequency ([System.TimeSpan]::New(0,15,0)) `
-CriterionAllOf $QueryRuleCondition | Out-Null
Write-Host -ForegroundColor Green "Alert Rule created successfully"
How the script works
Step by step
- Connects to Azure
- Gets workspace details
- Gets current ingestion usage
- Checks if a cap already exists
- Calculates recommended cap
- Sets the cap
- Checks if alert exists
- Creates alert rule if missing






Leave a comment