← Back to DevBytes

Monitoring Azure Functions: Metrics, Alarms, and Dashboards

Introduction to Azure Functions Monitoring

Azure Functions is a serverless compute service that runs your code in response to events, scaling automatically as demand increases. But once your functions are deployed, understanding how they perform, whether they're healthy, and how to troubleshoot issues becomes essential. Monitoring Azure Functions means collecting, analyzing, and acting on telemetry data — including metrics, logs, and traces — to ensure reliability, performance, and cost efficiency.

This tutorial covers the complete monitoring story: built-in platform metrics, integrating Application Insights for rich telemetry, configuring alarms and alerts, building dashboards for visualization, and following best practices that keep your serverless applications observable and resilient.

Why Monitoring Matters for Azure Functions

In a serverless model, you don't manage the underlying infrastructure. This shifts operational responsibility upward — you're now responsible for code behavior, invocation patterns, and performance characteristics. Without monitoring, you operate blind. Here's what effective monitoring unlocks:

Built-in Platform Metrics vs. Application Insights

Azure Functions exposes two tiers of monitoring data:

1. Platform Metrics (Host-Level)

These are free, lightweight metrics collected by the Azure Functions runtime itself. They include invocation count, success/failure counts, average execution time, and memory usage. You can view them in the Azure Portal under the "Metrics" blade of your Function App. They don't require any additional configuration but offer limited depth — no distributed tracing, no exception details, no dependency tracking.

2. Application Insights (Application-Level)

Application Insights is Azure's full-featured application performance monitoring (APM) service. When integrated with Azure Functions, it captures rich telemetry: request traces, dependency calls (HTTP, SQL, storage), exceptions with stack traces, custom metrics, and distributed trace correlation. This is the recommended approach for production workloads. It requires connecting your Function App to an Application Insights resource and adding the instrumentation key or connection string.

To enable Application Insights on an existing Function App, you can use the Azure CLI:

# Enable Application Insights on an existing Function App
az functionapp update \
  --resource-group myResourceGroup \
  --name myFunctionApp \
  --set appInsightsInstrumentationKey="your-instrumentation-key"

# Or using the portal-based connection string approach (recommended for Functions v3+)
az functionapp config appsettings set \
  --resource-group myResourceGroup \
  --name myFunctionApp \
  --settings APPINSIGHTS_INSTRUMENTATIONKEY="your-key" \
            APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=your-key;IngestionEndpoint=https://region.live.applicationinsights.azure.com/"

For new Function Apps, you can specify Application Insights integration at creation time:

az functionapp create \
  --resource-group myResourceGroup \
  --name myFunctionApp \
  --storage-account myStorageAccount \
  --runtime dotnet \
  --runtime-version 8 \
  --functions-version 4 \
  --app-insights myAppInsightsResource \
  --app-insights-key "your-instrumentation-key"

Key Metrics to Monitor

Understanding which metrics matter is half the battle. Here are the essential ones organized by concern:

Execution Metrics

Resource Utilization Metrics

Health and Scaling Metrics

Querying Metrics with Kusto (KQL)

Application Insights stores data in Log Analytics workspaces. You query it using Kusto Query Language (KQL). Here are practical queries you can run in the Azure Portal's "Logs" blade or via the Azure Data Explorer:

Retrieve Invocation Counts Per Function

// Count invocations per function over the last hour
requests
| where timestamp >= ago(1h)
| summarize InvocationCount = count() by operation_Name, bin(timestamp, 5m)
| order by timestamp desc
| render timechart

Analyze Failed Requests with Exception Details

// Join failed requests with exceptions for detailed failure analysis
requests
| where timestamp >= ago(1h)
| where success == false
| join kind=leftouter (exceptions | project operation_Id, exceptionType = type, exceptionMessage = outerMessage) on operation_Id
| project timestamp, operation_Name, resultCode, duration, exceptionType, exceptionMessage
| order by timestamp desc

Track Dependency Performance (HTTP, SQL, Storage)

// Show dependency call performance by target service
dependencies
| where timestamp >= ago(1h)
| summarize 
    AvgDurationMs = avg(duration),
    P95DurationMs = percentile(duration, 95),
    FailureCount = countif(success == false)
  by target, dependencyType = type
| order by AvgDurationMs desc

Monitor Memory Usage Per Instance

// Track memory working set per instance over time
performanceCounters
| where timestamp >= ago(1h)
| where name == "Private Bytes"
| project timestamp, value, cloud_RoleInstance
| summarize AvgMemoryMB = avg(value / 1024 / 1024) by bin(timestamp, 5m), cloud_RoleInstance
| render timechart

Detect Cold Starts

// Identify cold start traces
traces
| where timestamp >= ago(24h)
| where message contains "JIT compilation" or message contains "Cold start" or message contains "Initializing"
| project timestamp, message, operation_Name, cloud_RoleInstance
| order by timestamp desc

Emitting Custom Metrics from Function Code

Beyond built-in telemetry, you often need business-specific metrics — items processed, external API call latencies, queue depths, or custom dimensions. Application Insights SDKs let you emit custom metrics directly from your function code.

C# (Isolated Worker Process)

using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Metrics;

public class OrderProcessor
{
    private readonly TelemetryClient _telemetryClient;
    private static readonly MetricIdentifier _processedOrdersMetric = 
        new MetricIdentifier("custom", "OrdersProcessed", "Status");

    public OrderProcessor(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    [Function("ProcessOrder")]
    public async Task Run(
        [QueueTrigger("orders-queue")] Order order,
        FunctionContext context)
    {
        var logger = context.GetLogger("ProcessOrder");
        var startTime = DateTime.UtcNow;
        
        try
        {
            // Business logic here
            await ProcessOrderAsync(order);
            
            // Emit custom metric: orders processed with status dimension
            _telemetryClient.GetMetric(_processedOrdersMetric)
                .TrackValue(1, "Success");
            
            // Emit custom execution duration metric
            var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
            _telemetryClient.GetMetric(
                new MetricIdentifier("custom", "ProcessingDurationMs"))
                .TrackValue(elapsed);
            
            logger.LogInformation($"Order {order.Id} processed successfully.");
        }
        catch (Exception ex)
        {
            // Track failed processing metric
            _telemetryClient.GetMetric(_processedOrdersMetric)
                .TrackValue(1, "Failed");
            
            logger.LogError(ex, $"Failed to process order {order.Id}");
            throw;
        }
    }
}

C# (In-Process Model, .NET 6/8)

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.ApplicationInsights;

public class InventoryUpdater
{
    private readonly TelemetryClient _telemetryClient;

    public InventoryUpdater(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    [FunctionName("UpdateInventory")]
    public async Task Run(
        [ServiceBusTrigger("inventory-updates")] string message,
        ILogger log)
    {
        // Track a custom event with properties for richer querying
        var eventProperties = new Dictionary
        {
            ["EventType"] = "InventoryUpdate",
            ["Priority"] = "High",
            ["BatchSize"] = "1"
        };
        
        _telemetryClient.TrackEvent("InventoryUpdateReceived", eventProperties);
        
        // Track numeric metric for throughput
        _telemetryClient.TrackMetric("InventoryItemsProcessed", 1);
        
        // Process the message
        await ProcessInventoryUpdateAsync(message);
        
        log.LogInformation("Inventory updated successfully.");
    }
}

Python (v2 Programming Model)

import logging
import azure.functions as func
from opencensus.ext.azure import metrics_exporter
from opencensus.stats import aggregation, measure, stats

# Set up Application Insights metric exporter
# Requires: pip install opencensus-ext-azure
_exporter = metrics_exporter.new_metrics_exporter(
    connection_string="InstrumentationKey=your-key;IngestionEndpoint=...")
stats.stats.view_manager.register_exporter(_exporter)

# Define custom measures
processed_items_measure = measure.MeasureInt(
    "processed_items", "Items processed count", "items")
processing_time_measure = measure.MeasureFloat(
    "processing_time_ms", "Processing time in milliseconds", "ms")

@app.function_name("ProcessBlob")
@app.blob_trigger(arg_name="myblob", path="input-container/{name}")
def process_blob(myblob: func.InputStream):
    logging.info(f"Processing blob: {myblob.name}")
    
    # Simulate processing
    item_count = len(myblob.read().splitlines())
    
    # Record custom metrics
    stats.stats.record_measure(
        processed_items_measure, item_count,
        {"function": "ProcessBlob", "status": "success"})
    
    logging.info(f"Processed {item_count} items from blob.")

Node.js (JavaScript, v4 Programming Model)

const { app } = require('@azure/functions');
const { TelemetryClient } = require('applicationinsights');

const telemetryClient = new TelemetryClient(
    process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
);

app.http('ProcessPayment', {
    handler: async (request, context) => {
        const startTime = Date.now();
        context.log('Processing payment request');
        
        try {
            // Business logic
            const result = await processPayment(request.body);
            
            const duration = Date.now() - startTime;
            
            // Emit custom event with metrics
            telemetryClient.trackEvent({
                name: 'PaymentProcessed',
                properties: {
                    paymentType: request.body.type,
                    amount: request.body.amount.toString()
                },
                measurements: {
                    processingDurationMs: duration,
                    amountValue: request.body.amount
                }
            });
            
            return { status: 200, body: result };
            
        } catch (error) {
            telemetryClient.trackException({
                exception: error,
                properties: { context: 'ProcessPayment' }
            });
            throw error;
        }
    }
});

Setting Up Alerts and Alarms

Metrics alone are passive. Alerts transform monitoring into an active safety net. Azure offers multiple alert types for Functions: metric-based alerts, log-search alerts (KQL queries), and smart detection alerts from Application Insights. Here's how to configure each.

Metric-Based Alerts via Azure CLI

Metric alerts fire when a platform or Application Insights metric crosses a threshold. This example creates an alert for high failure rate:

# Create an alert rule for Function App failure rate exceeding 5%
az monitor metrics alert create \
  --name "HighFailureRate-Alert" \
  --resource-group "myResourceGroup" \
  --scopes "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/myFunctionApp" \
  --condition "Microsoft.Web/sites.Host.FailedRequests > 5" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --description "Alert when function failure rate exceeds threshold" \
  --action "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/microsoft.insights/actionGroups/myActionGroup"

Log-Search Alerts (KQL-Based)

For more sophisticated conditions, use log-search alerts that run a KQL query on a schedule:

# Create a log-search alert for sustained high latency (P95 > 5 seconds over 15 min)
az monitor scheduled-query create \
  --resource-group "myResourceGroup" \
  --name "HighLatency-Alert" \
  --scopes "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/microsoft.insights/components/myAppInsights" \
  --query '
    requests
    | where timestamp >= ago(15m)
    | summarize P95Duration = percentile(duration, 95) by operation_Name
    | where P95Duration > 5000
  ' \
  --threshold 1 \
  --condition "GreaterThan" \
  --evaluation-frequency 5m \
  --window-size 15m \
  --description "Alert on P95 latency exceeding 5 seconds" \
  --action-groups "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/microsoft.insights/actionGroups/myActionGroup"

Smart Detection Alerts

Application Insights includes built-in machine learning-based anomaly detection. These detect unusual patterns in failure rates, response time degradation, and traffic anomalies automatically. Enable them via the portal under Application Insights > Smart Detection, or via ARM/Bicep. No additional configuration is needed beyond having Application Insights connected.

Creating an Action Group

Alerts need action groups to notify you. Here's how to create one that sends email and triggers a Logic App for automated remediation:

az monitor action-group create \
  --resource-group "myResourceGroup" \
  --name "CriticalAlerts-ActionGroup" \
  --short-name "Critical" \
  --action email ops-team-email ops-team@contoso.com \
  --action webhook remediation-hook \
    "https://prod-01.centralus.logic.azure.com:443/workflows/abc123..." \
    "{\"alertType\":\"AzureFunctionAlert\"}"

Building Dashboards for Visualization

Dashboards give you a single-pane-of-glass view of function health. Azure provides several options: Azure Portal dashboards (customizable tiles), Azure Monitor Workbooks (rich KQL-driven reports), and integration with Grafana for cross-platform observability.

Azure Portal Dashboard with Metrics Tiles

You can create a dashboard in the Azure Portal and pin metric charts directly. Here's an ARM-based approach to programmatically define a dashboard:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "type": "Microsoft.Portal/dashboards",
      "apiVersion": "2020-09-01-preview",
      "name": "FunctionMonitoringDashboard",
      "properties": {
        "lenses": [
          {
            "order": 0,
            "parts": [
              {
                "position": { "x": 0, "y": 0, "rowSpan": 4, "colSpan": 6 },
                "metadata": {
                  "inputs": [
                    {
                      "name": "resourceId",
                      "value": "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/myFunctionApp"
                    }
                  ],
                  "type": "Extension/Microsoft_Azure_Monitoring/Monitoring_MetricChart",
                  "chart": {
                    "metrics": [
                      {
                        "resourceId": "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/myFunctionApp",
                        "metricName": "Microsoft.Web/sites.Host.FailedRequests",
                        "aggregationType": "Sum"
                      }
                    ],
                    "title": "Failed Requests (5m)",
                    "duration": "PT1H",
                    "dimension": "Microsoft.Web/sites.Host.FunctionName"
                  }
                }
              },
              {
                "position": { "x": 6, "y": 0, "rowSpan": 4, "colSpan": 6 },
                "metadata": {
                  "inputs": [],
                  "type": "Extension/Microsoft_Azure_Monitoring/Monitoring_MetricChart",
                  "chart": {
                    "metrics": [
                      {
                        "resourceId": "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/myFunctionApp",
                        "metricName": "Microsoft.Web/sites.Host.AverageResponseTime",
                        "aggregationType": "Average"
                      }
                    ],
                    "title": "Average Response Time (ms)",
                    "duration": "PT1H"
                  }
                }
              }
            ]
          }
        ]
      }
    }
  ]
}

Azure Monitor Workbooks

Workbooks offer richer interactivity — parameterized queries, drill-downs, and conditional formatting. Here's a workbook KQL snippet you can embed to show a comprehensive function health summary:

// Workbook query: Function Health Overview
// Parameter: TimeRange (set via workbook parameter)
let timeRange = {TimeRange};

// Summary cards
requests
| where timestamp >= timeRange
| summarize 
    TotalExecutions = count(),
    FailedExecutions = countif(success == false),
    AvgDurationMs = avg(duration),
    P95DurationMs = percentile(duration, 95),
    P99DurationMs = percentile(duration, 99)
| extend SuccessRate = round((1 - (FailedExecutions * 1.0 / TotalExecutions)) * 100, 2)

// Detailed breakdown by function
requests
| where timestamp >= timeRange
| summarize 
    Executions = count(),
    Failures = countif(success == false),
    AvgDuration = avg(duration),
    P95 = percentile(duration, 95)
  by operation_Name
| order by Executions desc

// Dependency failures
dependencies
| where timestamp >= timeRange
| where success == false
| summarize FailureCount = count() by target, dependencyType = type
| order by FailureCount desc

Grafana Integration

For teams already using Grafana, Azure Monitor and Application Insights can serve as data sources. Configure the Azure Monitor data source in Grafana with a service principal, then create panels using KQL or metric queries directly. This is ideal for unified dashboards spanning multiple clouds and services.

# Example Grafana dashboard JSON snippet for Azure Functions panel
{
  "datasource": "AzureMonitor-Insights",
  "queries": [
    {
      "query": "requests | where timestamp >= $__timeFrom() and timestamp <= $__timeTo() | summarize count() by bin(timestamp, 5m), operation_Name | render timechart",
      "resultFormat": "time_series",
      "queryType": "ApplicationInsights",
      "appInsights": {
        "appId": "/subscriptions/sub-id/resourceGroups/myResourceGroup/providers/microsoft.insights/components/myAppInsights"
      }
    }
  ],
  "title": "Function Invocations Over Time",
  "type": "timeseries"
}

Structured Logging and Trace Correlation

Effective monitoring relies on structured, correlated logs. When a function calls another function or external service, you need to trace that entire chain. Application Insights automatically propagates operation_Id (trace ID) across HTTP and Service Bus triggers when using the SDK properly.

Best practices for structured logging in Azure Functions:

// C# example: structured logging with correlation
public async Task Run([HttpTrigger] HttpRequest req, ILogger log)
{
    // Use structured logging with named placeholders
    log.LogInformation("Processing order {OrderId} for customer {CustomerId}", 
        order.Id, order.CustomerId);
    
    // Properties automatically appear in Application Insights customDimensions
    // Query: traces | where customDimensions.OrderId == "12345"
    
    // Track dependency with correlation
    var correlationId = Activity.Current?.Id; // W3C trace context
    await httpClient.GetAsync($"https://api.example.com/orders/{order.Id}");
    // This HTTP call automatically appears in dependencies with same operation_Id
}

For Python, use OpenTelemetry for automatic trace correlation across services:

# Python: structured logging with OpenTelemetry
from opentelemetry import trace
from opentelemetry.instrumentation.requests import RequestsInstrumentor
import logging

# Instrument requests library for automatic HTTP dependency tracking
RequestsInstrumentor().instrument()

tracer = trace.get_tracer(__name__)

@app.function_name("ProcessOrder")
@app.http_trigger()
def process_order(req: func.HttpRequest) -> func.HttpResponse:
    with tracer.start_as_current_span("process-order") as span:
        span.set_attribute("order.id", req.params.get("orderId"))
        logging.info({
            "event": "OrderProcessingStarted",
            "orderId": req.params.get("orderId"),
            "traceId": span.get_span_context().trace_id
        })
        # All outgoing HTTP calls within this span are automatically correlated
        response = requests.get("https://api.example.com/inventory/check")
        return func.HttpResponse("OK", status_code=200)

Monitoring Cost and Performance on Different Plans

Monitoring considerations vary by hosting plan:

Consumption Plan

Premium Plan

Dedicated (App Service) Plan

Best Practices for Azure Functions Monitoring

Configuring Sampling in host.json

{
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "maxTelemetryItemsPerSecond": 20,
        "excludedTypes": "Request;Exception"
      },
      "enableLiveMetrics": true,
      "enableDependencyTracking": true
    },
    "logLevel": {
      "default": "Warning",
      "Function": "Information",
      "Host.Results": "Error"
    }
  }
}

Automating Monitoring Setup with Bicep

For production deployments, automate your entire monitoring configuration using Infrastructure as Code. Here's a Bicep snippet that provisions a Function App with Application Insights, an alert rule, and a dashboard:

// main.bicep - Complete monitoring setup for Azure Functions
param location string = resourceGroup().location
param functionAppName string = 'myfuncapp-${uniqueString(resourceGroup().id)}'
param storageAccountName string = 'st${uniqueString(resourceGroup().id)}'

// Application Insights for telemetry
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
  name: 'appinsights-${functionAppName}'
  location: location
  kind: 'web'
  properties: {
    Application_Type: 'web'
    WorkspaceResourceId: logAnalyticsWorkspace.id
  }
}

// Log Analytics workspace (required for log-search alerts)
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
  name: 'law-${functionAppName}'
  location: location
  properties: {
    sku: { name: 'PerGB2018' }
    retentionInDays: 30
  }
}

// Function App with Application Insights connection
resource functionApp 'Microsoft.Web/sites@2022-03-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp'
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        { name: 'APPINSIGHTS_INSTRUMENTATIONKEY', value: appInsights.properties.InstrumentationKey }
        { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: appInsights.properties.ConnectionString }
      ]
    }
  }
}

// Action group for notifications
resource actionGroup 'microsoft.insights/actionGroups@2022-04-01' = {
  name: 'function-alerts-actiongroup'
  location: 'Global'
  properties: {
    groupShortName: 'FuncAlert'
    enabled: true
    emailReceivers: [
      {
        name: 'ops-team'
        emailAddress: 'ops-team@contoso.com'
        useCommonAlertSchema: true
      }
    ]
  }
}

// Metric alert: high failure rate
resource failureAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  name: 'high-failure-rate-alert'
  location: 'Global'
  properties: {
    description: 'Alert when function failure rate exceeds 5%'
    severity: 2
    enabled: true
    scopes: [functionApp.id]
    evaluationFrequency: 'PT1M'
    windowSize: 'PT5M'
    criteria: {
      'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
      allOf: [
        {
          name: 'Metric1'
          metricName: 'Microsoft.Web/sites.Host.FailedRequests'
          operator: 'GreaterThan'
          threshold: 5
          timeAggregation: 'Average'
          criterionType: 'StaticThresholdCriterion'
        }
      ]
    }
    actions: [
      { actionGroupId: actionGroup.id }
    ]
  }
}

Conclusion

Monitoring Azure Functions is not optional — it's foundational to running reliable, cost-effective serverless applications. By combining platform metrics for quick health checks, Application Insights for deep telemetry and distributed tracing, and a well-configured alerting strategy, you gain full observability into your function estate. The code examples and patterns in this tutorial give you a practical starting point: emit custom metrics from your functions, query them with KQL, build dashboards that serve your team's specific needs, and automate everything with infrastructure as code. Start with Application Insights integration on day one, configure sampling thoughtfully, and iterate on your alerts and dashboards as your application evolves. With these practices in place, you'll catch issues before they impact users and continuously improve the performance and reliability of your serverless workloads.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles