Monitoring Azure Functions

Azure Functions presents a unique monitoring challenge. Your function might execute 100,000 times per day, triggered by HTTP requests, queue messages, timer schedules, and event streams. Each invocation is a discrete unit of work that can fail independently. Traditional application monitoring — watching a running process — does not translate directly to serverless. Application Insights was built to handle this, and understanding how to use it with Functions is essential for running serverless workloads reliably.

What the Functions runtime collects automatically

The Azure Functions runtime has a built-in Application Insights integration. When you configure the connection string, it automatically collects:

  • Function invocations — Every execution: start time, end time, duration, success/failure status, trigger type
  • Exceptions — Unhandled exceptions from your function code, including stack traces
  • Dependencies — Outbound calls your function makes: HTTP requests, SQL queries, blob storage operations, queue messages sent
  • Host metrics — Memory usage, process-level performance counters

All of this happens without you writing any monitoring code in your function. Your function code focuses entirely on its business logic.

Configuring monitoring for Azure Functions

# Set Application Insights connection string on a Function App
az functionapp config appsettings set \
  --resource-group myRG \
  --name myfunctionapp \
  --settings APPLICATIONINSIGHTS_CONNECTION_STRING="<connection-string>"

# Verify Application Insights is configured
az functionapp config appsettings list \
  --resource-group myRG \
  --name myfunctionapp \
  --query "[?name=='APPLICATIONINSIGHTS_CONNECTION_STRING']" \
  --output table

# Create a Function App with Application Insights enabled from the start
az functionapp create \
  --resource-group myRG \
  --name myfunctionapp \
  --storage-account mystorageaccount \
  --runtime dotnet-isolated \
  --runtime-version 8 \
  --functions-version 4 \
  --os-type linux \
  --consumption-plan-location eastus \
  --app-insights myapp-insights

Controlling telemetry volume with host.json

High-frequency functions (processing thousands of queue messages per minute) can generate enormous Application Insights telemetry volumes. Configure sampling in host.json to keep costs manageable while retaining full fidelity for errors and exceptions.

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "maxTelemetryItemsPerSecond": 20,
        "excludedTypes": "Exception",
        "includedTypes": "Request;Dependency"
      },
      "enableLiveDiagnosticsChannel": true
    },
    "logLevel": {
      "default": "Warning",
      "Function": "Information",
      "Host.Aggregator": "Trace",
      "Host.Results": "Error"
    }
  }
}

Key settings explained:

  • maxTelemetryItemsPerSecond — Target throughput for sampling. Adaptive sampling adjusts the rate to hit this target.
  • excludedTypes — Types that are never sampled out. Setting Exception here means every exception is always captured.
  • logLevel — Controls which log messages are sent to Application Insights. Setting most categories to Warning reduces trace volume significantly.
Tip

Always exclude Exception from sampling. The cost savings from sampling are in successful invocation traces, not exception records. You need 100% of your exceptions to investigate production failures properly.

Essential KQL queries for Azure Functions

These queries cover the most common monitoring needs for serverless workloads.

// Function invocation success rate over time
AppRequests
| where TimeGenerated > ago(24h)
| where AppRoleName == "myfunctionapp"
| summarize
    Total = count(),
    Successes = countif(Success == true),
    Failures = countif(Success == false)
    by FunctionName = Name, bin(TimeGenerated, 1h)
| extend SuccessRate = round(100.0 * Successes / Total, 1)
| order by TimeGenerated desc, FunctionName asc

// Find functions with the highest failure rate
AppRequests
| where TimeGenerated > ago(7d)
| where AppRoleName == "myfunctionapp"
| summarize
    Total = count(),
    Failures = countif(Success == false)
    by FunctionName = Name
| extend FailureRate = round(100.0 * Failures / Total, 2)
| where Total > 10  // Ignore rarely-run functions
| order by FailureRate desc

// Average and P95 function execution duration
AppRequests
| where TimeGenerated > ago(24h)
| where AppRoleName == "myfunctionapp"
| where Success == true
| summarize
    AvgDuration = avg(DurationMs),
    P95Duration = percentile(DurationMs, 95),
    Invocations = count()
    by FunctionName = Name
| order by P95Duration desc

// Function exceptions with context
AppExceptions
| where TimeGenerated > ago(24h)
| where AppRoleName == "myfunctionapp"
| project TimeGenerated,
    FunctionName = operation_Name,
    ExceptionType,
    OuterMessage,
    InnermostMessage,
    OperationId = operation_Id
| order by TimeGenerated desc
| limit 50

Adding custom telemetry to function code

The automatic telemetry tells you that a function ran and how long it took. Custom telemetry tells you what it accomplished — how many records it processed, how many were skipped, what business decisions it made.

# In your function code (C# example using ILogger for structured logging):
# The Functions runtime routes ILogger calls to Application Insights automatically.

# Log at different levels with structured properties:
# _logger.LogInformation("Processed {RecordCount} records from {QueueName}",
#     processedCount, queueName);
#
# _logger.LogWarning("Skipped {RecordCount} records due to validation errors",
#     skippedCount);
#
# _logger.LogError(ex, "Failed to process record {RecordId} after {RetryCount} retries",
#     recordId, retryCount);

# For custom metrics, inject TelemetryClient:
# _telemetryClient.TrackMetric("RecordsProcessed", processedCount,
#     new Dictionary<string, string> {
#         { "FunctionName", context.FunctionName },
#         { "QueueName", queueName }
#     });
#
# _telemetryClient.TrackEvent("BatchCompleted",
#     new Dictionary<string, string> {
#         { "BatchId", batchId },
#         { "QueueName", queueName }
#     },
#     new Dictionary<string, double> {
#         { "RecordsProcessed", processedCount },
#         { "DurationMs", sw.ElapsedMilliseconds }
#     });

Monitoring Durable Functions

Durable Functions (orchestrations, activities, entities) add workflow-level monitoring on top of regular function monitoring. Each orchestration instance gets an instance ID that you can use to track the workflow’s progress.

# Query the Durable Task Framework status via the management API
curl -X GET \
  "https://myfunctionapp.azurewebsites.net/runtime/webhooks/durabletask/instances?code=<host-key>&top=50&createdTimeFrom=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%MZ)" \
  -H "Content-Type: application/json"

# Check the status of a specific orchestration instance
curl -X GET \
  "https://myfunctionapp.azurewebsites.net/runtime/webhooks/durabletask/instances/<instanceId>?code=<host-key>" \
  -H "Content-Type: application/json"
// Track Durable Functions orchestration health in KQL
AppTraces
| where TimeGenerated > ago(24h)
| where AppRoleName == "myfunctionapp"
| where Message contains "orchestration"
| extend
    InstanceId = tostring(customDimensions["prop__instanceId"]),
    Status = tostring(customDimensions["prop__status"])
| summarize LastStatus = arg_max(TimeGenerated, Status) by InstanceId
| summarize StatusCount = count() by Status

Common mistakes

  1. Not configuring log levels in host.json. By default, the Functions runtime emits extremely verbose logs — including framework-internal messages — to Application Insights. Without explicit log level configuration, a moderately busy function app can incur significant ingestion costs. Always configure logLevel in host.json to suppress framework-internal Debug and Trace messages.
  2. Sampling out exceptions on high-frequency functions. The default adaptive sampling may sample out exception telemetry when invocation rate is very high. Explicitly exclude Exception from sampling in your samplingSettings configuration.
  3. Forgetting that timer-triggered functions generate telemetry even when doing nothing important. A timer function that runs every minute generates over 500,000 invocation records per year. If it does nothing meaningful on most runs, configure it to log only when it actually processes something, or exclude its invocations from tracing when the result is trivially empty.
  4. Using the same Application Insights resource for Functions and web applications. Different workloads have different telemetry volumes and sampling needs. A high-frequency Functions app can overwhelm the shared resource’s sampling configuration, affecting the fidelity of your web application’s telemetry.

Frequently asked questions

Does Application Insights work with Azure Functions automatically?

Yes. Application Insights is integrated into the Azure Functions runtime. When you set the APPLICATIONINSIGHTS_CONNECTION_STRING app setting, the runtime automatically tracks all function invocations, exceptions, and dependencies.

How do I reduce Application Insights costs for high-frequency Functions?

Configure adaptive sampling or set a fixed sampling rate in host.json. For timer-triggered functions that run thousands of times per hour, sampling 10% of successful invocations and 100% of failures is a common cost-control strategy.

What is the difference between function execution logs and application logs?

Function execution logs (start, success, failure, duration) are automatically emitted by the Functions runtime. Application logs are messages your function code writes via ILogger. Both go to Application Insights but to different tables: execution data to AppRequests, application logs to AppTraces.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.