Azure Monitor & Application Insights: Complete Monitoring Guide
Azure Monitor provides comprehensive monitoring capabilities for applications and infrastructure. This guide covers Application Insights, Log Analytics, alerting, and performance optimization for small business environments.
Understanding Azure Monitor
Core Components
- Application Insights: Application performance monitoring (APM)
- Log Analytics: Centralized log collection and analysis
- Metrics: Time-series data for resources
- Alerts: Proactive notifications for issues
- Dashboards: Visualization and reporting
Data Types
- Metrics: Numerical time-series data
- Logs: Text-based event data
- Traces: Request flow tracking
- Dependencies: External service calls
- Exceptions: Error tracking and analysis
Setting Up Application Insights
PowerShell Setup
# Install Azure PowerShell module
Install-Module -Name Az -Force
# Connect to Azure
Connect-AzAccount
# Create resource group
New-AzResourceGroup -Name "Monitoring-RG" -Location "East US"
# Create Application Insights
$appInsights = New-AzApplicationInsights `
-ResourceGroupName "Monitoring-RG" `
-Name "BusinessAppInsights" `
-Location "East US" `
-Kind "web" `
-WorkspaceResourceId "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.OperationalInsights/workspaces/BusinessLogAnalytics"
# Get instrumentation key
$instrumentationKey = $appInsights.InstrumentationKey
$connectionString = $appInsights.ConnectionString
Write-Host "Instrumentation Key: $instrumentationKey"
Write-Host "Connection String: $connectionString"
Azure CLI Setup
# Login to Azure
az login
# Create resource group
az group create --name "Monitoring-RG" --location "eastus"
# Create Log Analytics workspace
az monitor log-analytics workspace create \
--resource-group "Monitoring-RG" \
--workspace-name "BusinessLogAnalytics" \
--location "eastus"
# Create Application Insights
az monitor app-insights component create \
--app "BusinessAppInsights" \
--location "eastus" \
--resource-group "Monitoring-RG" \
--workspace "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.OperationalInsights/workspaces/BusinessLogAnalytics"
Application Insights Integration
.NET Application Integration
// Program.cs for .NET 6+ Web API
using Microsoft.ApplicationInsights.Extensibility;
var builder = WebApplication.CreateBuilder(args);
// Add Application Insights
builder.Services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("ApplicationInsights");
options.EnableAdaptiveSampling = true;
options.EnableHeartbeat = true;
options.EnableActiveTelemetryConfigurationSetup = true;
});
// Add custom telemetry initializer
builder.Services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
// Add controllers
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware
app.UseRouting();
app.MapControllers();
app.Run();
// Custom telemetry initializer
public class CustomTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = "BusinessAPI";
telemetry.Context.GlobalProperties["Environment"] = "Production";
telemetry.Context.GlobalProperties["Version"] = "1.0.0";
}
}
JavaScript/TypeScript Integration
// Install npm package: @microsoft/applicationinsights-web
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
const appInsights = new ApplicationInsights({
config: {
connectionString: 'InstrumentationKey=your-key;IngestionEndpoint=https://eastus-8.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/',
enableAutoRouteTracking: true,
enableRequestHeaderTracking: true,
enableResponseHeaderTracking: true,
enableCorsCorrelation: true,
enableUnhandledPromiseRejectionTracking: true,
distributedTracingMode: 2 // W3C
}
});
appInsights.loadAppInsights();
appInsights.trackPageView();
// Custom event tracking
export class TelemetryService {
static trackEvent(name: string, properties?: { [key: string]: any }) {
appInsights.trackEvent({ name, properties });
}
static trackException(exception: Error, severityLevel?: number) {
appInsights.trackException({
exception,
severityLevel: severityLevel || 2
});
}
static trackMetric(name: string, value: number, properties?: { [key: string]: any }) {
appInsights.trackMetric({ name, value }, properties);
}
static trackDependency(name: string, data: string, success: boolean, duration: number) {
appInsights.trackDependencyData({
name,
data,
success,
duration,
dependencyTypeName: 'HTTP'
});
}
}
// Usage examples
TelemetryService.trackEvent('UserLogin', { userId: '123', loginMethod: 'email' });
TelemetryService.trackMetric('ProcessingTime', 1500, { operation: 'dataProcessing' });
Custom Metrics and Events
// Controller with custom telemetry
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly TelemetryClient _telemetryClient;
private readonly ILogger<OrdersController> _logger;
public OrdersController(TelemetryClient telemetryClient, ILogger<OrdersController> logger)
{
_telemetryClient = telemetryClient;
_logger = logger;
}
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
using var operation = _telemetryClient.StartOperation<RequestTelemetry>("CreateOrder");
try
{
var stopwatch = Stopwatch.StartNew();
// Track custom event
_telemetryClient.TrackEvent("OrderCreationStarted", new Dictionary<string, string>
{
["CustomerId"] = request.CustomerId,
["OrderType"] = request.OrderType,
["Source"] = "API"
});
// Process order
var order = await ProcessOrderAsync(request);
stopwatch.Stop();
// Track custom metrics
_telemetryClient.TrackMetric("OrderProcessingTime", stopwatch.ElapsedMilliseconds, new Dictionary<string, string>
{
["OrderType"] = request.OrderType,
["Status"] = "Success"
});
_telemetryClient.TrackMetric("OrderValue", (double)order.TotalAmount, new Dictionary<string, string>
{
["Currency"] = order.Currency,
["CustomerId"] = request.CustomerId
});
// Track successful completion
_telemetryClient.TrackEvent("OrderCreated", new Dictionary<string, string>
{
["OrderId"] = order.Id,
["CustomerId"] = request.CustomerId,
["Amount"] = order.TotalAmount.ToString()
});
return Ok(order);
}
catch (Exception ex)
{
// Track exception
_telemetryClient.TrackException(ex, new Dictionary<string, string>
{
["CustomerId"] = request.CustomerId,
["OrderType"] = request.OrderType
});
_telemetryClient.TrackMetric("OrderProcessingError", 1, new Dictionary<string, string>
{
["ErrorType"] = ex.GetType().Name,
["OrderType"] = request.OrderType
});
throw;
}
}
private async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
{
// Track dependency
using var dependencyOperation = _telemetryClient.StartOperation<DependencyTelemetry>("ProcessOrder");
dependencyOperation.Telemetry.Type = "Business Logic";
try
{
// Business logic here
await Task.Delay(100); // Simulate processing
return new Order
{
Id = Guid.NewGuid().ToString(),
CustomerId = request.CustomerId,
TotalAmount = request.Items.Sum(i => i.Price * i.Quantity),
Currency = "USD",
CreatedAt = DateTime.UtcNow
};
}
catch (Exception ex)
{
dependencyOperation.Telemetry.Success = false;
dependencyOperation.Telemetry.Data = ex.Message;
throw;
}
}
}
Log Analytics and KQL Queries
Common KQL Queries
// Application performance overview
requests
| where timestamp > ago(24h)
| summarize
RequestCount = count(),
AverageResponseTime = avg(duration),
P95ResponseTime = percentile(duration, 95),
ErrorRate = countif(success == false) * 100.0 / count()
by bin(timestamp, 1h)
| render timechart
// Top slow requests
requests
| where timestamp > ago(24h)
| where duration > 5000 // Slower than 5 seconds
| top 20 by duration desc
| project timestamp, name, duration, url, resultCode
// Exception analysis
exceptions
| where timestamp > ago(7d)
| summarize ExceptionCount = count() by type, method
| order by ExceptionCount desc
| take 20
// Dependency performance
dependencies
| where timestamp > ago(24h)
| summarize
CallCount = count(),
AverageResponseTime = avg(duration),
SuccessRate = countif(success == true) * 100.0 / count()
by name, type
| order by CallCount desc
// User activity analysis
pageViews
| where timestamp > ago(30d)
| summarize
PageViews = count(),
UniqueUsers = dcount(user_Id)
by bin(timestamp, 1d)
| render timechart
// Custom events tracking
customEvents
| where timestamp > ago(7d)
| where name == "OrderCreated"
| extend OrderValue = todouble(customDimensions.Amount)
| summarize
OrderCount = count(),
TotalRevenue = sum(OrderValue),
AverageOrderValue = avg(OrderValue)
by bin(timestamp, 1h)
| render timechart
// Performance counters
performanceCounters
| where timestamp > ago(24h)
| where counter == "% Processor Time"
| summarize avg(value) by bin(timestamp, 5m), instance
| render timechart
// Availability results
availabilityResults
| where timestamp > ago(30d)
| summarize
AvailabilityPercentage = avg(success) * 100,
TotalTests = count()
by bin(timestamp, 1d), name
| render timechart
Advanced Analytics
// Cohort analysis for user retention
let startDate = ago(30d);
let endDate = now();
pageViews
| where timestamp between (startDate .. endDate)
| summarize FirstVisit = min(timestamp) by user_Id
| extend CohortMonth = startofmonth(FirstVisit)
| join kind=inner (
pageViews
| where timestamp between (startDate .. endDate)
| extend ActivityMonth = startofmonth(timestamp)
| summarize by user_Id, ActivityMonth
) on user_Id
| extend MonthsSinceFirstVisit = datediff('month', CohortMonth, ActivityMonth)
| summarize Users = dcount(user_Id) by CohortMonth, MonthsSinceFirstVisit
| order by CohortMonth, MonthsSinceFirstVisit
// Anomaly detection
requests
| where timestamp > ago(7d)
| make-series RequestCount = count() default=0 on timestamp step 1h
| extend AnomalyScore = series_decompose_anomalies(RequestCount, 1.5, 7, 'linefit')
| mv-expand timestamp to typeof(datetime), RequestCount to typeof(double), AnomalyScore to typeof(double)
| where AnomalyScore > 0
| project timestamp, RequestCount, AnomalyScore
// Funnel analysis
let events = customEvents
| where timestamp > ago(30d)
| where name in ("ProductViewed", "AddedToCart", "CheckoutStarted", "OrderCompleted")
| project timestamp, name, user_Id = tostring(customDimensions.UserId);
events
| evaluate funnel_sequence(user_Id, timestamp, 1d,
"ProductViewed", "AddedToCart", "CheckoutStarted", "OrderCompleted")
Alerting and Notifications
Metric Alerts
# Create action group for notifications
$actionGroup = New-AzActionGroup `
-ResourceGroupName "Monitoring-RG" `
-Name "critical-alerts" `
-ShortName "critical" `
-EmailReceiver @(
@{
"name" = "admin"
"emailAddress" = "admin@company.com"
}
) `
-SmsReceiver @(
@{
"name" = "oncall"
"countryCode" = "1"
"phoneNumber" = "5551234567"
}
)
# Create response time alert
$responseTimeAlert = New-AzMetricAlertRuleV2 `
-ResourceGroupName "Monitoring-RG" `
-Name "high-response-time" `
-WindowSize "00:05:00" `
-Frequency "00:01:00" `
-TargetResourceId "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.Insights/components/BusinessAppInsights" `
-TargetResourceType "Microsoft.Insights/components" `
-MetricName "requests/duration" `
-Operator "GreaterThan" `
-Threshold 5000 `
-Severity 2 `
-ActionGroupId $actionGroup.Id
# Create error rate alert
$errorRateAlert = New-AzMetricAlertRuleV2 `
-ResourceGroupName "Monitoring-RG" `
-Name "high-error-rate" `
-WindowSize "00:05:00" `
-Frequency "00:01:00" `
-TargetResourceId "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.Insights/components/BusinessAppInsights" `
-TargetResourceType "Microsoft.Insights/components" `
-MetricName "requests/failed" `
-Operator "GreaterThan" `
-Threshold 5 `
-Severity 1 `
-ActionGroupId $actionGroup.Id
Log-based Alerts
# Create log alert for exceptions
$logAlert = New-AzScheduledQueryRule `
-ResourceGroupName "Monitoring-RG" `
-Name "exception-spike" `
-Location "East US" `
-DisplayName "Exception Spike Alert" `
-Description "Alert when exception rate exceeds threshold" `
-Severity 2 `
-Enabled $true `
-EvaluationFrequency "00:05:00" `
-WindowSize "00:10:00" `
-TargetResourceId "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.Insights/components/BusinessAppInsights" `
-Query "exceptions | where timestamp > ago(10m) | summarize ExceptionCount = count() by bin(timestamp, 5m) | where ExceptionCount > 10" `
-ActionGroupId $actionGroup.Id
# Create availability alert
$availabilityAlert = New-AzScheduledQueryRule `
-ResourceGroupName "Monitoring-RG" `
-Name "availability-degradation" `
-Location "East US" `
-DisplayName "Availability Degradation" `
-Description "Alert when availability drops below threshold" `
-Severity 1 `
-Enabled $true `
-EvaluationFrequency "00:05:00" `
-WindowSize "00:15:00" `
-TargetResourceId "/subscriptions/subscription-id/resourceGroups/Monitoring-RG/providers/Microsoft.Insights/components/BusinessAppInsights" `
-Query "availabilityResults | where timestamp > ago(15m) | summarize AvailabilityPercentage = avg(success) * 100 | where AvailabilityPercentage < 99" `
-ActionGroupId $actionGroup.Id
Custom Dashboards
PowerBI Integration
# Export data to Power BI
$query = @"
requests
| where timestamp > ago(30d)
| summarize
RequestCount = count(),
AverageResponseTime = avg(duration),
ErrorRate = countif(success == false) * 100.0 / count()
by bin(timestamp, 1h)
| order by timestamp desc
"@
# Execute query and export results
$results = Invoke-AzOperationalInsightsQuery -WorkspaceId "workspace-id" -Query $query
$results.Results | Export-Csv -Path "monitoring-data.csv" -NoTypeInformation
Azure Dashboard Creation
{
"properties": {
"lenses": {
"0": {
"order": 0,
"parts": {
"0": {
"position": {
"x": 0,
"y": 0,
"rowSpan": 4,
"colSpan": 6
},
"metadata": {
"inputs": [
{
"name": "ComponentId",
"value": {
"SubscriptionId": "subscription-id",
"ResourceGroup": "Monitoring-RG",
"Name": "BusinessAppInsights"
}
}
],
"type": "Extension/AppInsightsExtension/PartType/AppMapGalPt"
}
},
"1": {
"position": {
"x": 6,
"y": 0,
"rowSpan": 4,
"colSpan": 6
},
"metadata": {
"inputs": [
{
"name": "ComponentId",
"value": {
"SubscriptionId": "subscription-id",
"ResourceGroup": "Monitoring-RG",
"Name": "BusinessAppInsights"
}
}
],
"type": "Extension/AppInsightsExtension/PartType/RequestsTablePt"
}
}
}
}
}
}
}
Performance Optimization
Sampling Configuration
// Configure adaptive sampling
services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = connectionString;
options.EnableAdaptiveSampling = true;
});
// Configure sampling processor
services.Configure<TelemetryConfiguration>(config =>
{
var builder = config.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
// Add sampling processor
builder.Use((next) => new AdaptiveSamplingTelemetryProcessor(next)
{
MaxTelemetryItemsPerSecond = 5,
SamplingPercentageIncreaseTimeout = TimeSpan.FromMinutes(2),
SamplingPercentageDecreaseTimeout = TimeSpan.FromMinutes(30),
EvaluationInterval = TimeSpan.FromSeconds(15),
InitialSamplingPercentage = 25
});
// Add custom filter
builder.Use((next) => new CustomTelemetryFilter(next));
builder.Build();
});
// Custom telemetry filter
public class CustomTelemetryFilter : ITelemetryProcessor
{
private readonly ITelemetryProcessor _next;
public CustomTelemetryFilter(ITelemetryProcessor next)
{
_next = next;
}
public void Process(ITelemetry item)
{
// Filter out health check requests
if (item is RequestTelemetry request && request.Url.AbsolutePath.EndsWith("/health"))
{
return;
}
// Filter out successful dependency calls to reduce noise
if (item is DependencyTelemetry dependency && dependency.Success == true && dependency.Duration < TimeSpan.FromSeconds(1))
{
return;
}
_next.Process(item);
}
}
Monitoring Automation
# Automated monitoring report
param(
[string]$WorkspaceId = "workspace-id",
[string]$ApplicationInsightsId = "appinsights-id",
[string]$EmailRecipient = "admin@company.com",
[int]$DaysBack = 1
)
# Performance metrics query
$performanceQuery = @"
requests
| where timestamp > ago($($DaysBack)d)
| summarize
TotalRequests = count(),
AverageResponseTime = avg(duration),
P95ResponseTime = percentile(duration, 95),
ErrorCount = countif(success == false),
ErrorRate = countif(success == false) * 100.0 / count()
"@
# Execute query
$performanceResults = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceId -Query $performanceQuery
# Exception query
$exceptionQuery = @"
exceptions
| where timestamp > ago($($DaysBack)d)
| summarize ExceptionCount = count() by type
| order by ExceptionCount desc
| take 10
"@
$exceptionResults = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceId -Query $exceptionQuery
# Generate HTML report
$htmlReport = @"
<html>
<head><title>Application Monitoring Report</title></head>
<body>
<h1>Daily Application Monitoring Report</h1>
<h2>Performance Metrics</h2>
<table border="1">
<tr><th>Metric</th><th>Value</th></tr>
<tr><td>Total Requests</td><td>$($performanceResults.Results[0].TotalRequests)</td></tr>
<tr><td>Average Response Time</td><td>$($performanceResults.Results[0].AverageResponseTime)ms</td></tr>
<tr><td>95th Percentile Response Time</td><td>$($performanceResults.Results[0].P95ResponseTime)ms</td></tr>
<tr><td>Error Count</td><td>$($performanceResults.Results[0].ErrorCount)</td></tr>
<tr><td>Error Rate</td><td>$($performanceResults.Results[0].ErrorRate)%</td></tr>
</table>
<h2>Top Exceptions</h2>
<table border="1">
<tr><th>Exception Type</th><th>Count</th></tr>
"@
foreach ($exception in $exceptionResults.Results) {
$htmlReport += "<tr><td>$($exception.type)</td><td>$($exception.ExceptionCount)</td></tr>"
}
$htmlReport += @"
</table>
</body>
</html>
"@
# Send email report
Send-MailMessage -To $EmailRecipient -Subject "Daily Monitoring Report" -Body $htmlReport -BodyAsHtml -From "monitoring@company.com" -SmtpServer "smtp.company.com"
Best Practices
Implementation
- Start with basic telemetry and gradually add custom metrics
- Use structured logging with proper log levels
- Implement health checks for all dependencies
- Monitor business metrics alongside technical metrics
Performance
- Configure sampling to manage costs
- Use telemetry processors to filter unnecessary data
- Implement caching for frequently accessed data
- Monitor query performance in Log Analytics
Security
- Sanitize sensitive data before logging
- Use role-based access for monitoring data
- Implement secure communication for alerts
- Regular review of monitoring permissions
Cost Management
Monitoring Costs
# Check Application Insights usage
$usage = Get-AzApplicationInsightsUsage -ResourceGroupName "Monitoring-RG" -Name "BusinessAppInsights"
$usage | Select-Object MetricName, CurrentValue, Limit
# Estimate monthly costs
$dailyIngestion = 100 # MB per day
$monthlyIngestion = $dailyIngestion * 30
$monthlyCost = $monthlyIngestion * 0.00273 # Per MB pricing
Write-Host "Estimated monthly cost: $${monthlyCost:F2}"
Cost Optimization
// Configure telemetry volume control
services.Configure<TelemetryConfiguration>(config =>
{
// Reduce telemetry volume
config.DefaultTelemetrySink.TelemetryProcessorChainBuilder
.Use((next) => new QuickPulseTelemetryProcessor(next))
.Use((next) => new AdaptiveSamplingTelemetryProcessor(next)
{
MaxTelemetryItemsPerSecond = 2,
SamplingPercentageIncreaseTimeout = TimeSpan.FromMinutes(5),
SamplingPercentageDecreaseTimeout = TimeSpan.FromMinutes(15)
})
.Build();
});
Troubleshooting
Common Issues
# Check Application Insights configuration
Get-AzApplicationInsights -ResourceGroupName "Monitoring-RG" -Name "BusinessAppInsights"
# Verify data ingestion
$query = "requests | where timestamp > ago(1h) | take 10"
$results = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceId -Query $query
# Check alert rules
Get-AzMetricAlertRuleV2 -ResourceGroupName "Monitoring-RG"
Get-AzScheduledQueryRule -ResourceGroupName "Monitoring-RG"
Data Quality Issues
// Check for missing telemetry
requests
| where timestamp > ago(24h)
| make-series RequestCount = count() default=0 on timestamp step 1h
| mv-expand timestamp to typeof(datetime), RequestCount to typeof(double)
| where RequestCount == 0
| project timestamp
// Validate telemetry completeness
requests
| where timestamp > ago(1h)
| summarize
HasUserAgent = countif(isnotempty(client_Browser)),
HasLocation = countif(isnotempty(client_City)),
HasCustomDimensions = countif(isnotempty(customDimensions))
by bin(timestamp, 10m)
Conclusion
Azure Monitor and Application Insights provide comprehensive monitoring capabilities for modern applications. Proper implementation of telemetry, alerting, and dashboards ensures proactive issue detection and optimal application performance.
For professional Azure Monitor implementation and monitoring strategy consulting services in Louisville, contact Tyler on Tech Louisville for expert assistance with your application monitoring needs.