Azure Activity Log
Automatically capture every resource change in your Azure subscription and forward it to ChangeVault.
Overview
Azure Activity Log records every control-plane operation in your subscription — resources being created, modified, deleted, role assignments changed, and more. By connecting it to ChangeVault you get a complete, automatic audit trail without anyone needing to manually log a change.
Architecture
Azure Subscription (Activity Log)
↓ Diagnostic Settings
Azure Event Hub
↓ trigger
Azure Function
↓ POST /api/changes
ChangeVaultWhat gets captured
| Operation type | Example | Risk level |
|---|---|---|
| Resource created | New VM, storage account, database | Medium |
| Resource updated | VM resized, firewall rule changed | Medium |
| Resource deleted | VM deleted, subnet removed | High |
| Action | Start/stop VM, rotate key | Medium |
| Failed operation | Deployment failed | Rolled back |
Read-only operations (/read) and "Started" events (before completion) are filtered out.
Prerequisites
- An Azure subscription with Owner or Contributor access
- Azure CLI installed, or access to the Azure Portal
- Node.js 18+ (for the Azure Function)
Step 1 — Create an Event Hub
# Create resource group (skip if you have one)
az group create --name changevault-rg --location westeurope
# Create Event Hub namespace
az eventhubs namespace create \
--name changevault-eventhub \
--resource-group changevault-rg \
--sku Standard
# Create the event hub
az eventhubs eventhub create \
--name insights-operational-logs \
--namespace-name changevault-eventhub \
--resource-group changevault-rg \
--partition-count 4 \
--retention-time 1
# Create a consumer group for the Function
az eventhubs eventhub consumer-group create \
--name changevault \
--eventhub-name insights-operational-logs \
--namespace-name changevault-eventhub \
--resource-group changevault-rgStep 2 — Enable Diagnostic Settings
Export the Activity Log to the Event Hub. Replace <subscription-id>, <rg> and <ns> with your values.
az monitor diagnostic-settings create \
--name "changevault-export" \
--resource /subscriptions/<subscription-id> \
--event-hub-rule /subscriptions/<subscription-id>/resourceGroups/changevault-rg/providers/Microsoft.EventHub/namespaces/changevault-eventhub/authorizationRules/RootManageSharedAccessKey \
--event-hub "insights-operational-logs" \
--logs '[
{"category": "Administrative", "enabled": true},
{"category": "Security", "enabled": true},
{"category": "Policy", "enabled": true}
]'You can also do this in the Portal under Monitor → Activity log → Export Activity Logs → Add diagnostic setting.
Step 3 — Deploy the Azure Function
Create the Function App
az functionapp create \
--name changevault-forwarder \
--resource-group changevault-rg \
--storage-account <your-storage-account> \
--consumption-plan-location westeurope \
--runtime node \
--runtime-version 20 \
--functions-version 4Set environment variables
# Get the Event Hub connection string
EVENTHUB_CONN=$(az eventhubs namespace authorization-rule keys list \
--resource-group changevault-rg \
--namespace-name changevault-eventhub \
--name RootManageSharedAccessKey \
--query primaryConnectionString -o tsv)
az functionapp config appsettings set \
--name changevault-forwarder \
--resource-group changevault-rg \
--settings \
"EventHubConnectionString=$EVENTHUB_CONN" \
"CHANGEVAULT_API_KEY=cvk_prod_••••••••" \
"CHANGEVAULT_URL=https://app.changevault.dev"Function code — src/functions/index.js
const { app } = require("@azure/functions");
const API_KEY = process.env.CHANGEVAULT_API_KEY;
const API_URL = process.env.CHANGEVAULT_URL ?? "https://app.changevault.dev";
function shouldForward(record) {
if (record.category !== "Administrative") return false;
if (record.resultType === "Start") return false; // skip "started", only log completions
const op = (record.operationName ?? "").toLowerCase();
return op.endsWith("/write") || op.endsWith("/delete") || op.endsWith("/action");
}
function toChange(record) {
const op = record.operationName ?? "";
const resourceName = (record.resourceId ?? "").split("/").pop() ?? "unknown";
const resourceType = op.split("/").slice(0, 2).join("/");
const isDelete = op.toLowerCase().endsWith("/delete");
return {
title: `Azure: ${isDelete ? "Deleted" : "Updated"} ${resourceType} — ${resourceName}`,
description: `Caller: ${record.caller ?? "unknown"}\nOperation: ${op}\nResource: ${record.resourceId}`,
risk_level: isDelete ? "high" : "medium",
status: record.resultType === "Failed" ? "rolled_back" : "completed",
tags: ["azure", "activity-log", resourceType.toLowerCase().replace(/[^a-z0-9-]/g, "-")],
};
}
app.eventHub("activityLogForwarder", {
connection: "EventHubConnectionString",
eventHubName: "insights-operational-logs",
consumerGroup: "changevault",
cardinality: "many",
handler: async (messages) => {
const all = Array.isArray(messages) ? messages : [messages];
const records = all.flatMap((m) => m.records ?? [m]);
await Promise.all(
records.filter(shouldForward).map((record) =>
fetch(`${API_URL}/api/changes`, {
method: "POST",
headers: {
"x-api-key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(toChange(record)),
})
)
);
},
});package.json
{
"name": "changevault-forwarder",
"version": "1.0.0",
"main": "src/functions/index.js",
"dependencies": {
"@azure/functions": "^4.0.0"
}
}Deploy
cd changevault-forwarder
npm install
func azure functionapp publish changevault-forwarderFiltering specific operations
To limit which operations get forwarded, modify the shouldForward function. For example, to only log compute and network changes:
const ALLOWED_PROVIDERS = ["microsoft.compute", "microsoft.network", "microsoft.sql"];
function shouldForward(record) {
if (record.category !== "Administrative") return false;
if (record.resultType === "Start") return false;
const op = (record.operationName ?? "").toLowerCase();
if (!op.endsWith("/write") && !op.endsWith("/delete")) return false;
return ALLOWED_PROVIDERS.some((p) => op.startsWith(p));
}Estimated cost
| Resource | Estimated monthly cost |
|---|---|
| Event Hub (Standard, 1 TU) | ~€9 |
| Azure Function (Consumption plan) | Free for first 1M executions |
| Storage account | < €1 |
For most subscriptions the total cost is under €10/month.
API access requires the Pro plan or higher.