This is not the first time I’ll be writing about event-driven architecture, nor will it probably be the last time 😊. I was checking my previous blog posts about event-driven architecture, and it grabbed my attention that I haven’t written anything yet about monitoring. Well, what a good moment for creating a first blog post about real-time monitoring with some ‘event-driven sauce on top’!

In modern (cloud) environments, real-time awareness of your workloads is critical. Whether you’re running production workloads or managing infrastructure, being notified when something goes wrong can prevent major outages or performance degradation.

In this blog, we’ll walk through how you can set up an alert for an Azure App Service using Azure Monitor, Logic App, and Slack! Time for some serverless event-driven monitoring automation!

During this post, you’ll see this icon 🎬, which indicates that action is required so you get the full benefit out of this post.


And I’ve introduced the 📒, which indicates that the subsequent part explains the technical details in the context of this post.

When to utilize this

Why would you go “Custom” when Azure Monitor already supports alert actions?

Well.. Azure Monitor indeed provides a rich set of built-in features such as alerts, notifications, webhooks, and more. However, there are scenarios where a more tailored approach is needed. For example, integrating with internal systems, enforcing business-specific logic before triggering an action, or complex workflows across services.

🧠 And! If you want to further explore event-driven architecture from a learning perspective, this is a practical example! So always consider your business case to determine the most appropriate way forward.

In this blog, I’m utilizing Slack, but feel free to experiment, using GRAPH for sending mail is also absolutely fine. It’s about the principle of showing how you can achieve an alerting goal by utilizing Azure Logic Apps with tags and a specific alerting carrier (in this case, Slack).

Setting up Slack

If you don’t have a Slack account yet, set one up. After the account is set up, we need to add a webhook app.

🎬 Add a webhook to a channel where you want to receive the messages

  1. In Slack, go to ‘apps’ à ‘Add app’
  2. Choose for incoming webhook
  • Choose the channel where you want the messages to be delivered.

Store the generated webhook URL (you need this for later)

Setting up the Azure components

I will add the code for the LogicApp to this post so you can create the logic for yourself. If you decide to deploy everything manually make sure the resources below are deployed

  • App Service
  • Logic App
  • Activity log alert rule
  • Action group
  • Tag “AlwaysOn: True” on the app service

Or let me deploy it for you:

Deploy to Azure

Granting permissions

The managed identity of the LogicApp needs to have permissions to read resources. As managed identity is enabled (if you followed the ‘let me deploy it for you’) we can easily add reader permissions on the resource group of our webapp which we want to include in our monitoring.

🎬 If you haven’t enabled managed identity through manual deployment you should now enable this.

🎬 Now grant websites contributor permission for the LogicApp managed identity on the resource group

📒 There are special permissions like websites.read which also cover the required permission. If you want to go forward to a production environment, always take the least amount of privileges available!

In the case of this blog, we’ll let the logic app restart the App Service, so we need to have the correct permissions on the managed identity in order to do so.

Information can be found for the built-in roles on: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles

Configuring alert rule

Basically we want to configure the alert rule according to the settings below:

This checks the alert log. It searches for events in the category “Administrative” to see if a operation  ‘stopped’ has performed for any of the app services.

If so it triggers a webhook by going to the action group and calling from that level on our logic app

📒 The URL should be the value of the Slack webhook you copied before in the earlier steps.

To visualize this the flow looks as below:

Lets test

When everything is configured, it’s time to test! Let’s open the App Service and stop it.

🎬 Click the stop button.

Now the app service is getting stopped and eventually the log will be written to the activity logs that the app service is now stopped by the person who pressed the button.

This will make sure that our triggers conditions are met and that they’ll perform the action configured on them.

🎬 Now open the LogicApp and check the runs (this sometimes can take a bit time)

Your run should be visible:

And when we check the Slack channel where the webhook is pointing to we can also see that the message has been delivered there:

Summary

That’s a wrap! 🎉

In this blog, we explored how to create a real-time monitoring and alerting setup for Azure App Services, event-driven style, of course 😎.

We kicked things off by talking about why sometimes you need to go custom, even when Azure Monitor gives you solid built-in options. Think business logic, internal system integration, or just a more flexible workflow.

Then we rolled up our sleeves and:

Set up a Slack webhook to receive alert messages.

Created the necessary Azure components: App Service, Logic App, Alert Rule, Action Group, and tagged our App Service to keep it “AlwaysOn”.

Built an alert rule that watches for “App Service stopped” events and triggers a Logic App that sends a Slack notification.

🎬 If you followed along step-by-step, you now have a lightweight, scalable, and serverless alerting pipeline running in real-time.

📒 This post is all about combining Azure Monitor and Logic Apps to build clean, event-driven observability into your cloud workloads, without adding complexity you don’t need.

Thanks again for reading! I hope this inspires you to build your own real-time monitoring workflows, and maybe plug in Teams, email, or even auto-remediation logic next time.

Let me know what you create! 🚀

Logic app code

{
    "definition": {
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "contentVersion": "1.0.0.0",
        "triggers": {
            "manual": {
                "type": "Request",
                "kind": "Http",
                "inputs": {
                    "schema": {
                        "type": "object",
                        "properties": {
                            "schemaId": {
                                "type": "string"
                            },
                            "data": {
                                "type": "object",
                                "properties": {
                                    "essentials": {
                                        "type": "object",
                                        "properties": {
                                            "alertRule": {
                                                "type": "string"
                                            },
                                            "targetResourceGroup": {
                                                "type": "string"
                                            },
                                            "severity": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "alertContext": {
                                        "type": "object",
                                        "properties": {
                                            "authorization": {
                                                "type": "object",
                                                "properties": {
                                                    "action": {
                                                        "type": "string"
                                                    },
                                                    "scope": {
                                                        "type": "string"
                                                    }
                                                }
                                            },
                                            "caller": {
                                                "type": "string"
                                            },
                                            "eventTimestamp": {
                                                "type": "string"
                                            },
                                            "operationName": {
                                                "type": "string"
                                            },
                                            "status": {
                                                "type": "string"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "actions": {
            "Parse_Activity_Log_Data": {
                "type": "Compose",
                "inputs": {
                    "resourceId": "@triggerBody()?['data']?['alertContext']?['authorization']?['scope']",
                    "operationName": "@triggerBody()?['data']?['alertContext']?['operationName']",
                    "eventTime": "@triggerBody()?['data']?['alertContext']?['eventTimestamp']",
                    "caller": "@triggerBody()?['data']?['alertContext']?['caller']",
                    "status": "@triggerBody()?['data']?['alertContext']?['status']",
                    "resourceGroupName": "@triggerBody()?['data']?['essentials']?['targetResourceGroup']",
                    "appName": "@if(empty(triggerBody()?['data']?['alertContext']?['authorization']?['scope']), 'Unknown', last(split(triggerBody()?['data']?['alertContext']?['authorization']?['scope'], '/')))"
                },
                "runAfter": {}
            },
            "Get_App_Service_Tags": {
                "type": "Http",
                "inputs": {
                    "uri": "https://management.azure.com@{outputs('Parse_Activity_Log_Data')?['resourceId']}?api-version=2022-03-01",
                    "method": "GET",
                    "authentication": {
                        "type": "ManagedServiceIdentity"
                    }
                },
                "runAfter": {
                    "Parse_Activity_Log_Data": [
                        "Succeeded"
                    ]
                }
            },
            "Check_AlwaysOn_Tag": {
                "type": "If",
                "expression": {
                    "equals": [
                        "@body('Get_App_Service_Tags')?['tags']?['AlwaysOn']",
                        "true"
                    ]
                },
                "actions": {
                    "Restart_App_Service": {
                        "type": "Http",
                        "inputs": {
                            "uri": "https://management.azure.com@{outputs('Parse_Activity_Log_Data')?['resourceId']}/start?api-version=2022-03-01",
                            "method": "POST",
                            "authentication": {
                                "type": "ManagedServiceIdentity"
                            }
                        }
                    },
                    "Send_Slack_Restart": {
                        "type": "Http",
                        "inputs": {
                            "uri": "@parameters('slackWebhookUrl')",
                            "method": "POST",
                            "headers": {
                                "Content-Type": "application/json"
                            },
                            "body": {
                                "text": "🔄 App Service Auto-Restarted: @{outputs('Parse_Activity_Log_Data')?['appName']}",
                                "attachments": [
                                    {
                                        "color": "good",
                                        "title": "✅ Auto-Restart Successful",
                                        "text": "Your App Service '@{outputs('Parse_Activity_Log_Data')?['appName']}' was automatically stopped but has been restarted because it has the 'AlwaysOn' tag set to 'true'.\n\n📋 Details:\n• App Name: @{outputs('Parse_Activity_Log_Data')?['appName']}\n• Stopped At: @{outputs('Parse_Activity_Log_Data')?['eventTime']}\n• Stopped By: @{outputs('Parse_Activity_Log_Data')?['caller']}\n• Restarted At: @{utcNow()}\n• Action Taken: Automatically restarted\n\n✅ Your app is now running again!"
                                    }
                                ]
                            }
                        },
                        "runAfter": {
                            "Restart_App_Service": [
                                "Succeeded"
                            ]
                        }
                    },
                    "Log_Restart_Details": {
                        "type": "Compose",
                        "inputs": {
                            "message": "✅ Successfully restarted App Service: @{outputs('Parse_Activity_Log_Data')?['appName']}",
                            "details": {
                                "appName": "@{outputs('Parse_Activity_Log_Data')?['appName']}",
                                "stoppedBy": "@{outputs('Parse_Activity_Log_Data')?['caller']}",
                                "stoppedAt": "@{outputs('Parse_Activity_Log_Data')?['eventTime']}",
                                "restartedAt": "@{utcNow()}",
                                "action": "Auto-restarted due to AlwaysOn tag"
                            }
                        },
                        "runAfter": {
                            "Send_Slack_Restart": [
                                "Succeeded",
                                "Failed"
                            ]
                        }
                    },
                    "Log_Restart_Success": {
                        "type": "Compose",
                        "inputs": {
                            "message": "✅ Successfully restarted App Service: @{outputs('Parse_Activity_Log_Data')?['appName']}",
                            "timestamp": "@{utcNow()}",
                            "appName": "@{outputs('Parse_Activity_Log_Data')?['appName']}",
                            "originalStopTime": "@{outputs('Parse_Activity_Log_Data')?['eventTime']}",
                            "restartedBy": "Auto-Restart Logic App",
                            "notificationSent": "Yes"
                        },
                        "runAfter": {
                            "Log_Restart_Details": [
                                "Succeeded",
                                "Failed"
                            ]
                        }
                    }
                },
                "else": {
                    "actions": {
                        "Send_Slack_No_Action": {
                            "type": "Http",
                            "inputs": {
                                "uri": "@parameters('slackWebhookUrl')",
                                "method": "POST",
                                "headers": {
                                    "Content-Type": "application/json"
                                },
                                "body": {
                                    "text": "⚠️ App Service Stopped (No Auto-Restart): @{outputs('Parse_Activity_Log_Data')?['appName']}",
                                    "attachments": [
                                        {
                                            "color": "warning",
                                            "title": "⚠️ App Service Stopped - No Auto-Restart",
                                            "text": "Your App Service '@{outputs('Parse_Activity_Log_Data')?['appName']}' was stopped but will NOT be automatically restarted because it does not have the 'AlwaysOn' tag set to 'true'.\n\n📋 Details:\n• App Name: @{outputs('Parse_Activity_Log_Data')?['appName']}\n• Stopped At: @{outputs('Parse_Activity_Log_Data')?['eventTime']}\n• Stopped By: @{outputs('Parse_Activity_Log_Data')?['caller']}\n• Action Taken: No auto-restart (missing AlwaysOn tag)\n\n💡 To enable auto-restart, add the tag 'AlwaysOn' with value 'true' to this App Service."
                                        }
                                    ]
                                }
                            }
                        },
                        "Log_No_Action_Details": {
                            "type": "Compose",
                            "inputs": {
                                "message": "⚠️ App Service stopped but no restart needed",
                                "details": {
                                    "appName": "@{outputs('Parse_Activity_Log_Data')?['appName']}",
                                    "stoppedBy": "@{outputs('Parse_Activity_Log_Data')?['caller']}",
                                    "stoppedAt": "@{outputs('Parse_Activity_Log_Data')?['eventTime']}",
                                    "reason": "AlwaysOn tag not set to 'true'",
                                    "action": "No restart performed"
                                }
                            },
                            "runAfter": {
                                "Send_Slack_No_Action": [
                                    "Succeeded",
                                    "Failed"
                                ]
                            }
                        },
                        "Log_No_Action_Needed": {
                            "type": "Compose",
                            "inputs": {
                                "message": "App Service @{outputs('Parse_Activity_Log_Data')?['appName']} was stopped but does not have AlwaysOn tag set to 'true'. No action taken.",
                                "timestamp": "@{utcNow()}",
                                "appName": "@{outputs('Parse_Activity_Log_Data')?['appName']}",
                                "originalStopTime": "@{outputs('Parse_Activity_Log_Data')?['eventTime']}",
                                "notificationSent": "Yes"
                            },
                            "runAfter": {
                                "Log_No_Action_Details": [
                                    "Succeeded",
                                    "Failed"
                                ]
                            }
                        }
                    }
                },
                "runAfter": {
                    "Get_App_Service_Tags": [
                        "Succeeded"
                    ]
                }
            }
        },
        "outputs": {
            "appName": {
                "type": "String",
                "value": "@{outputs('Parse_Activity_Log_Data')?['appName']}"
            },
            "actionTaken": {
                "type": "String",
                "value": "@{if(equals(body('Get_App_Service_Tags')?['tags']?['AlwaysOn'], 'true'), 'Restarted', 'No action - AlwaysOn tag not set')}"
            }
        },
        "parameters": {
            "slackWebhookUrl": {
                "type": "String"
            },
            "$connections": {
                "type": "Object",
                "defaultValue": {}
            }
        }
    },
    "parameters": {
        "slackWebhookUrl": {
            "value": "https://hooks.slack.com/services/[YOUR URL GOES HERE]"
        },
        "$connections": {
            "type": "Object",
            "value": {}
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *