Skip to main content

Monitor your app with Application Insights

When something is slow or broken in production, you need to see what your app is actually doing: which requests fail, how long they take, and where the time goes. This lab connects a web app on Azure App Service to Application Insights, the application performance monitoring (APM) service in Azure Monitor, so you get live metrics, request and failure analytics, and queryable logs with no changes to your app code.

You will connect Application Insights three ways so you can pick the workflow that fits you:

  • Azure Developer CLI (azd) - provision Application Insights in Bicep alongside your app and deploy in one flow.
  • Azure CLI (az) - create the resources explicitly and wire them together with app settings.
  • Azure portal - turn on Application Insights from a blade on your app.

App Service supports auto-instrumentation (also called codeless attach) for .NET, Node.js, and Java. It injects a monitoring agent at runtime, so you collect telemetry without adding an SDK. Python and PHP use SDK-based instrumentation instead. This lab calls out the differences.

App Service Labs complements Microsoft Learn

This lab is a hands-on, end-to-end walkthrough. For reference depth on any concept, follow the "Learn more" links to the official Microsoft Learn documentation.

Estimated time: 45 to 60 minutes

Objectives

By the end of this lab you will be able to:

  • Create a workspace-based Application Insights resource and connect it to an App Service app.
  • Enable auto-instrumentation (codeless) for .NET, Node.js, and Java, and explain where support differs by runtime and OS.
  • Generate traffic and read it in Live Metrics, the failures and performance views, and Logs (KQL).
  • Create a metric alert that fires on a symptom your users would notice.

Prerequisites

Before you begin, you will need an Azure subscription with Owner permissions and a GitHub account.

In addition, you will need the following tools installed on your local machine:

Setup Azure CLI

Start by logging into Azure by run the following command and follow the prompts:

az login --use-device-code
tip

You can log into a different tenant by passing in the --tenant flag to specify your tenant domain or tenant ID.

Choose a region and low-cost tier

This lab uses the East US region and the B1 (Basic) Linux App Service tier, a low-cost option that is ideal for learning (about USD 13 per month if you leave it running). Application Insights bills on the data it ingests; the small volume in this lab costs little, and the first 5 GB per month is free. Delete the resource group when you finish (see Clean up) to stop charges.

How Application Insights connects to your app

Application Insights stores its telemetry in a Log Analytics workspace (a workspace-based resource, the current model). Your app sends telemetry to Application Insights using a connection string, which you supply through the APPLICATIONINSIGHTS_CONNECTION_STRING app setting. For .NET, Node.js, and Java, a second app setting turns on the App Service-managed agent that collects requests, dependencies, exceptions, and traces automatically.

Connection string, not instrumentation key

Always connect with the connection string. Instrumentation keys alone are deprecated because they do not carry the regional ingestion endpoints that newer regions require.

Choose your path

Choose your path
Set these once - every matching step and code sample below follows your choice.
Tooling
Language
OS

This lab assumes you already have a web app on App Service, or it helps you create one. Pick a tooling path, then choose your language.

Provision and connect Application Insights

The Azure Developer CLI provisions your infrastructure and deploys your code together. Here you define Application Insights, a Log Analytics workspace, and the web app in Bicep, with the connection string and the auto-instrumentation setting wired in, so the app is monitored the moment it starts.

1. Sign in

azd auth login

2. Create the project structure

Create a folder with the files below. The sample is Node.js; the notes explain what changes per language.

mkdir monitor-app-insights && cd monitor-app-insights
mkdir infra src

Create azure.yaml in the project root:

# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
name: monitor-app-insights
services:
web:
project: ./src
language: js # change per language: js, dotnet, python, java
host: appservice

Create src/server.js (a tiny app with a home route and a route that fails, so you have both successful and failed requests to look at):

const http = require('http');
const port = process.env.PORT || 3000;
http.createServer((req, res) => {
if (req.url === '/error') {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Simulated failure');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Hello from Azure App Service with Application Insights!</h1>');
}).listen(port);

Create src/package.json:

{
"name": "monitor-app-insights",
"version": "1.0.0",
"main": "server.js",
"scripts": { "start": "node server.js" },
"engines": { "node": ">=20" }
}

Create infra/main.parameters.json:

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": { "value": "${AZURE_ENV_NAME}" },
"location": { "value": "${AZURE_LOCATION}" },
"resourceGroupName": { "value": "${AZURE_RESOURCE_GROUP}" }
}
}

Create infra/main.bicep. It runs at subscription scope so azd creates and owns the resource group for this environment:

targetScope = 'subscription'

@description('Name of the azd environment; used to derive resource names.')
param environmentName string

@description('Azure region for all resources.')
param location string

@description('Resource group to create for this environment.')
param resourceGroupName string

resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = {
name: resourceGroupName
location: location
}

module resources 'resources.bicep' = {
name: 'resources'
scope: rg
params: {
location: location
environmentName: environmentName
}
}

output WEB_URI string = resources.outputs.webUri
output APPLICATIONINSIGHTS_NAME string = resources.outputs.appInsightsName

Create infra/resources.bicep. This creates the workspace, the workspace-based Application Insights resource, the B1 Linux plan, and the web app with the monitoring app settings:

@description('Azure region for all resources.')
param location string

@description('azd environment name used to derive globally unique names.')
param environmentName string

var suffix = uniqueString(subscription().id, resourceGroup().id, environmentName)
var planName = 'plan-${suffix}'
var webName = 'app-${suffix}'
var lawName = 'law-${suffix}'
var appInsightsName = 'appi-${suffix}'

resource law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: lawName
location: location
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 30
}
}

resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: appInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: law.id // makes this a workspace-based resource
}
}

resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: planName
location: location
sku: {
name: 'B1'
}
kind: 'linux'
properties: {
reserved: true // required for Linux plans
}
}

resource web 'Microsoft.Web/sites@2023-12-01' = {
name: webName
location: location
kind: 'app,linux'
tags: {
'azd-service-name': 'web' // links this site to the "web" service in azure.yaml
}
properties: {
serverFarmId: plan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: 'NODE|22-lts'
appSettings: [
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'true'
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~3' // turns on codeless attach on Linux
}
{
name: 'XDT_MicrosoftApplicationInsights_Mode'
value: 'recommended'
}
]
}
}
}

output webUri string = 'https://${web.properties.defaultHostName}'
output appInsightsName string = appInsights.name
Per-language changes

This template uses linuxFxVersion: 'NODE|22-lts'. For another runtime, change that value (for example DOTNETCORE|8.0 or JAVA|17-java17) and update language in azure.yaml. The ApplicationInsightsAgent_EXTENSION_VERSION setting enables codeless attach for .NET, Node.js, and Java. For Python or PHP, remove that setting from the Bicep (it does nothing for those runtimes) and instrument in code - see the Connect by runtime section.

3. Create an environment and deploy

Give the environment a unique suffix so names do not collide with earlier runs:

SUFFIX=$(openssl rand -hex 3) # 6 lowercase hex chars
azd env new "monitor-appi-${SUFFIX}" --location eastus
azd env set AZURE_RESOURCE_GROUP "rg-appi-${SUFFIX}"
azd up

When it finishes, azd prints the app endpoint:

- Endpoint: https://app-<random>.azurewebsites.net/
SUCCESS: Your application was deployed to Azure in 3 minutes 35 seconds.
First azd up can miss the tagged resource

On the very first azd up, azd occasionally reports unable to find a resource tagged with 'azd-service-name: web' because the provisioning outputs are not cached yet. If that happens, run azd deploy once more - the resources already exist and the code deploy completes.

Connect Application Insights by runtime

Auto-instrumentation support differs by language and OS. This section summarizes what to set for each runtime; the app settings go on the web app (the CLI, azd, and portal paths above all set them the same way).

.NET and .NET Core have the broadest codeless support: Windows (.NET Framework and .NET Core) and Linux (.NET Core and later). Set the connection string and the agent version.

App settingValue
APPLICATIONINSIGHTS_CONNECTION_STRINGyour connection string
ApplicationInsightsAgent_EXTENSION_VERSION~3
XDT_MicrosoftApplicationInsights_Moderecommended
SDK-based instrumentation is the deeper option

Auto-instrumentation is the fastest way to get telemetry, but adding the Application Insights or Azure Monitor OpenTelemetry SDK to your code gives you more: custom events and metrics, precise dependency tracking, and control over sampling. You can use both together - the SDK enriches what the agent already collects. See Data collection basics.

Generate some traffic

Telemetry only appears once your app handles requests. Send a burst that includes both successful and failing requests. Replace the hostname with your app's:

APP_URL=https://<your-app-name>.azurewebsites.net
for i in $(seq 1 60); do
curl -s -o /dev/null "$APP_URL/"
curl -s -o /dev/null "$APP_URL/error"
done

Telemetry reaches Application Insights within a minute or two. Live Metrics is near real time; the aggregated charts and Logs have a short ingestion delay.

Explore your telemetry

Open your Application Insights resource in the Azure portal (search for its name, or open it from the app's Application Insights blade).

Live Metrics

Select Live metrics (under Investigate). While you send traffic, you see request rate, request duration, and failures update in near real time, with a rolling feed of sample requests. Live Metrics is the fastest way to confirm the connection works and to watch a deployment.

Failures and Performance

  • Failures (under Investigate) breaks down failed requests by response code and operation. Your /error requests appear here as HTTP 500 responses; select one to drill into a sample and its exception.
  • Performance shows request duration by operation, so you can find the slowest endpoints and see the distribution of response times.

Logs (KQL)

Select Logs (under Monitoring) to query telemetry with Kusto Query Language (KQL). Run this to count requests by result code:

requests
| summarize count() by resultCode
| order by count_ desc

You should see rows for 200 and 500. To find the slowest requests:

requests
| top 10 by duration desc
| project timestamp, name, resultCode, duration
Workspace table names

Because this resource is workspace-based, the same data is available in the Log Analytics workspace under the AppRequests table (Application Insights presents it as requests in its own Logs blade). Both work; use whichever surface you are in.

You can verify the same thing from the command line by querying the workspace directly:

WS_ID=$(az monitor log-analytics workspace show \
--resource-group $RG_NAME \
--workspace-name $LAW_NAME \
--query customerId -o tsv)

az monitor log-analytics query \
--workspace "$WS_ID" \
--analytics-query "AppRequests | summarize count() by ResultCode"

Create a metric alert

Alerts tell you about a problem before your users report it. Create a rule on a symptom users would notice, such as failed requests or slow responses.

Add the alert to your Bicep so it is part of your infrastructure. Append this to infra/resources.bicep, then run azd provision again. It alerts when the average server response time exceeds 3 seconds over 5 minutes:

resource responseTimeAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: 'alert-response-time-${appInsightsName}'
location: 'global'
properties: {
severity: 3
enabled: true
scopes: [
appInsights.id
]
evaluationFrequency: 'PT1M'
windowSize: 'PT5M'
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: [
{
name: 'ResponseTime'
metricNamespace: 'microsoft.insights/components'
metricName: 'requests/duration'
operator: 'GreaterThan'
threshold: 3000
timeAggregation: 'Average'
criterionType: 'StaticThresholdCriterion'
}
]
}
}
}

Confirm the rule exists and is enabled:

az monitor metrics alert list --resource-group $RG_NAME \
--query "[].{name:name, enabled:enabled, severity:severity}" -o table

Verify

Confirm the app is serving traffic and that telemetry is flowing.

  1. The app returns HTTP 200. Check from the command line:

    curl -I https://<your-app-name>.azurewebsites.net

    Expected output (validated during authoring):

    HTTP/1.1 200 OK
    Content-Type: text/html
  2. Telemetry is arriving. In the Application Insights Logs blade, run:

    requests | summarize count() by resultCode

    You should see a nonzero count for 200 and, if you called /error, for 500. During authoring, this returned 195 requests at 200 and 190 at 500 for the Azure CLI run.

  3. The alert rule is enabled. The az monitor metrics alert list output above shows enabled: True.

Cleanup

To clean up the resources created in this lab, run the following command to delete the resource group. If you want to use the resources again, you can skip this step.

az group delete \
--name ${RG_NAME} \
--yes \
--no-wait

This will delete the resource group and all its contents.

If you used azd

Delete everything azd created (the resource group, resources, and the local environment) with azd down --purge --force instead of the command above.

Summary

In this lab, you connected an App Service web app to Application Insights and confirmed telemetry was flowing. You learned how to:

  • Create a workspace-based Application Insights resource and connect it with the APPLICATIONINSIGHTS_CONNECTION_STRING app setting.
  • Enable auto-instrumentation for .NET, Node.js, and Java, and where support differs by OS, with Python and PHP using SDK-based instrumentation.
  • Explore telemetry in Live Metrics, Failures, Performance, and Logs with KQL.
  • Create a metric alert on server response time or failed requests.

Troubleshooting

  • No data in Application Insights. Confirm APPLICATIONINSIGHTS_CONNECTION_STRING is set on the app and that you restarted it. Codeless attach only takes effect after a restart and a request. Send more traffic and wait a minute or two - Live Metrics updates first, then the aggregated views. See Troubleshoot no data.
  • Data appears in Logs but not in the classic query API. Workspace-based resources store telemetry in the AppRequests table in the Log Analytics workspace. Query the workspace with az monitor log-analytics query (shown above) or use the Application Insights Logs blade, which exposes the requests alias.
  • azd up reports unable to find a resource tagged with 'azd-service-name: web'. This is the first-run output-cache race. Run azd deploy once more; the resources already exist and the code deploy completes.
  • Wrong runtime instrumented, or nothing for Python or PHP. Codeless attach covers .NET, Node.js, and Java only. For Python, use the Azure Monitor OpenTelemetry Distro; for PHP, use the OpenTelemetry SDK. See Connect by runtime.
  • Alert never fires. Metric alerts evaluate on a schedule and need enough data in the window. Generate sustained traffic (or lower the threshold) and confirm the rule shows enabled: True.

Learn more