Collect Azure Event Hubs logs and forward them to Loki
You can configure Alloy to collect logs from Azure Event Hubs and forward them to Loki. For more information about monitoring Azure resources in Grafana Cloud, refer to Monitor Microsoft Azure.
This topic describes how to:
- Prepare your Azure environment with Workload Identity authentication.
- Configure Azure Event Hubs and install Alloy.
- Optionally extract labels from Azure resource logs.
Components used in this topic
Before you begin
Ensure you have the following:
- Azure administrator access with
Microsoft.Authorization/roleAssignments/writepermissions, such as Role Based Access Control Administrator or User Access Administrator - Azure CLI installed and authenticated
kubectlinstalled and configured to access your AKS cluster- Helm installed
Prepare your Azure environment
Azure Event Hubs exposes different endpoints depending on the protocol:
Both protocols use the hostname <EVENTHUB_NAMESPACE>.servicebus.windows.net.
The loki.source.azure_event_hubs component uses the Kafka protocol, so the examples in this procedure use port 9093.
Use the Azure portal to create or reuse a resource group.
Create or reuse an Azure Kubernetes Service (AKS) cluster.
Enable Microsoft Entra Workload ID and OpenID Connect in your AKS cluster.
az aks update \ --resource-group <RESOURCE_GROUP> \ --name <AKS_CLUSTER_NAME> \ --enable-oidc-issuer \ --enable-workload-identityReplace the following:
<RESOURCE_GROUP>: Your Azure resource group<AKS_CLUSTER_NAME>: The name of your AKS cluster
Retrieve the OIDC issuer URL for your cluster. You need this value when creating the federated credential.
az aks show \ --resource-group <RESOURCE_GROUP> \ --name <AKS_CLUSTER_NAME> \ --query "oidcIssuerProfile.issuerUrl" \ --output tsvReplace the following:
<RESOURCE_GROUP>: Your Azure resource group<AKS_CLUSTER_NAME>: The name of your AKS cluster
Create a user-assigned managed identity.
az identity create \ --resource-group <RESOURCE_GROUP> \ --name <MANAGED_IDENTITY_NAME>Replace the following:
<RESOURCE_GROUP>: Your Azure resource group<MANAGED_IDENTITY_NAME>: A name for your managed identity
Retrieve the client ID for your managed identity. You need this value for the ServiceAccount annotation and Alloy configuration.
az identity show \ --resource-group <RESOURCE_GROUP> \ --name <MANAGED_IDENTITY_NAME> \ --query clientId \ --output tsvReplace the following:
<RESOURCE_GROUP>: Your Azure resource group<MANAGED_IDENTITY_NAME>: The name of your managed identity
Create a Kubernetes namespace for Alloy.
kubectl create namespace alloyCreate a Kubernetes ServiceAccount with the workload identity annotation.
kubectl apply -f - <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: alloy namespace: alloy annotations: azure.workload.identity/client-id: "<CLIENT_ID>" EOFReplace the following:
<CLIENT_ID>: The client ID from the previous step
Azure Workload Identity connects three components:
Kubernetes ServiceAccount : The ServiceAccount annotation
azure.workload.identity/client-idspecifies which managed identity the Pod can impersonate.Federated credential : The federated credential on the managed identity trusts tokens from your AKS cluster’s OIDC issuer for a specific ServiceAccount (
system:serviceaccount:<namespace>:<name>).Runtime token exchange : When the Pod runs, AKS issues a token for the ServiceAccount. Azure validates this token against the federated credential and returns an access token for the managed identity. Alloy uses this token to authenticate to Event Hubs without a connection string.
If authentication fails, verify:
- The OIDC issuer URL matches your cluster
- The
--subjectvalue matchessystem:serviceaccount:<namespace>:<serviceaccount> - The managed identity
clientIdmatches the ServiceAccount annotation - The
--audiencesvalue isapi://AzureADTokenExchange
Create a federated identity credential to link your managed identity with the Kubernetes ServiceAccount.
az identity federated-credential create \ --name alloy-federated-credential \ --identity-name <MANAGED_IDENTITY_NAME> \ --resource-group <RESOURCE_GROUP> \ --issuer <OIDC_ISSUER_URL> \ --subject system:serviceaccount:alloy:alloy \ --audiences api://AzureADTokenExchangeReplace the following:
<RESOURCE_GROUP>: Your Azure resource group<MANAGED_IDENTITY_NAME>: The name of your managed identity<OIDC_ISSUER_URL>: The OIDC issuer URL
Configure Azure Event Hubs
Follow the steps to Set up Azure Event Hubs.
Assign the
Azure Event Hubs Data Receiverrole to your managed identity.Get the managed identity principal ID:
PRINCIPAL_ID=$(az identity show \ --resource-group <RESOURCE_GROUP> \ --name <MANAGED_IDENTITY_NAME> \ --query principalId \ --output tsv)Assign the role at namespace scope for least-privilege access:
az role assignment create \ --assignee $PRINCIPAL_ID \ --role "Azure Event Hubs Data Receiver" \ --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.EventHub/namespaces/<EVENTHUB_NAMESPACE>Replace the following:
<MANAGED_IDENTITY_NAME>: The name of your managed identity<RESOURCE_GROUP>: Your Azure resource group<SUBSCRIPTION_ID>: Your Azure subscription ID<EVENTHUB_NAMESPACE>: The name of your Event Hub namespace
Assign the role at the smallest scope that meets your requirements:
Namespace scope (recommended) : Grants access only to the specific Event Hub namespace. Use this for least-privilege access.
Resource group or subscription scope : Grants access to all Event Hubs in the resource group or subscription. Use this only if Alloy must read from multiple namespaces.
Example resource group scope:
az role assignment create \ --assignee $PRINCIPAL_ID \ --role "Azure Event Hubs Data Receiver" \ --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>To verify the assignment:
az role assignment list --assignee $PRINCIPAL_ID --scope <SCOPE>
Install Alloy
Add the Grafana Helm repository.
helm repo add grafana https://grafana.github.io/helm-charts helm repo updateRetrieve the tenant ID for your Azure subscription.
az account show --query tenantId --output tsvCreate a
values.yamlfile with the following configuration.Warning
Don’t store sensitive credentials directly in
values.yamlor commit them to version control. For production environments, use a Kubernetes Secret withsecretKeyRef, or an external secret manager such as HashiCorp Vault, Azure Key Vault, or the External Secrets Operator.The configuration uses port
9093for the Azure Event Hubs Kafka-compatible endpoint. Theloki.source.azure_event_hubscomponent in Alloy requires the Kafka-compatible endpoint and doesn’t support AMQP for this integration.The
authenticationblock uses OAuth 2.0 with Azure Workload Identity through the federated credential you created earlier. Kafka-compatible endpoints use SASL/OAUTHBEARER with Microsoft Entra ID tokens, so you don’t need an Event Hub connection string.serviceAccount: create: false name: alloy controller: type: statefulset replicas: 1 podLabels: azure.workload.identity/use: "true" alloy: extraEnv: - name: "AZURE_CLIENT_ID" value: "<CLIENT_ID>" - name: "AZURE_TENANT_ID" value: "<TENANT_ID>" configMap: content: | loki.source.azure_event_hubs "azure" { fully_qualified_namespace = "<EVENTHUB_NAMESPACE>.servicebus.windows.net:9093" event_hubs = ["<EVENTHUB_NAME>"] authentication { mechanism = "oauth" } use_incoming_timestamp = true labels = { job = "integrations/azure_event_hubs", } forward_to = [loki.write.grafana_cloud.receiver] } loki.write "grafana_cloud" { endpoint { url = "<GRAFANA_CLOUD_LOKI_URL>" basic_auth { username = "<GRAFANA_CLOUD_LOKI_USERNAME>" password = "<GRAFANA_CLOUD_API_KEY>" } } }Replace the following:
<CLIENT_ID>: Your managed identity client ID<TENANT_ID>: Your Azure tenant ID<EVENTHUB_NAMESPACE>: Your Event Hub namespace name<EVENTHUB_NAME>: Your Event Hub name<GRAFANA_CLOUD_LOKI_URL>: Your Grafana Cloud Loki endpoint, such ashttps://logs-prod-us-central1.grafana.net/loki/api/v1/push<GRAFANA_CLOUD_LOKI_USERNAME>: Your Grafana Cloud Loki username<GRAFANA_CLOUD_API_KEY>: Your Grafana Cloud API key
Install Alloy using Helm.
helm install alloy grafana/alloy \ --namespace alloy \ -f values.yaml
Verify the installation
Check that the Alloy Pod is running.
kubectl get pods -n alloyYou should see output similar to:
NAME READY STATUS RESTARTS AGE alloy-0 1/1 Running 0 1mCheck the Alloy logs for any errors.
kubectl logs -n alloy -l app.kubernetes.io/name=alloyVerify authentication and connection:
kubectl logs -n alloy -l app.kubernetes.io/name=alloy | grep -E -i "authenticated|connected|sasl"Push a test event to the Event Hub and confirm a matching log appears in Grafana Explore within approximately one minute.
If errors occur, verify the role assignment:
az role assignment list --assignee <PRINCIPAL_ID> --scope <SCOPE>
In Grafana Cloud, navigate to Explore and select your Loki data source to view the incoming logs.
Optional: Configure Alloy to extract labels from Azure Event Hubs
By default, the Alloy configuration doesn’t extract labels from the Event Hubs log lines.
You can configure Alloy to use loki.process to extract labels such as resourceId, category, and resourceGroup from Azure resource logs.
The loki.process component uses three stages to transform the logs:
stage.json: Parses the log line as JSON and extracts theresourceIdandcategoryfields.stage.regex: Parses theresourceIdto extract resource details likesubscriptionId,resourceGroup, andresourceName.stage.labels: Creates Loki labels from the extracted values for easier querying.
Update the alloy.configMap.content section in your values.yaml file with the following configuration.
When set to true, the use_incoming_timestamp setting uses the event’s timestamp, such as Event Hubs EnqueuedTimeUtc or a timestamp field in the payload.
The default is false, which uses Alloy ingestion time.
Keep the default if your events lack reliable timestamps.
alloy:
configMap:
content: |
loki.source.azure_event_hubs "azure" {
fully_qualified_namespace = "<EVENTHUB_NAMESPACE>.servicebus.windows.net:9093"
event_hubs = ["<EVENTHUB_NAME>"]
authentication {
mechanism = "oauth"
}
use_incoming_timestamp = true
labels = {
job = "integrations/azure_event_hubs",
}
forward_to = [loki.process.azure_logs.receiver]
}
loki.process "azure_logs" {
stage.json {
expressions = {
resourceId = "resourceId",
category = "category",
}
}
stage.regex {
expression = "(?i)/subscriptions/(?P<subscriptionId>[^/]+)/resourcegroups/(?P<resourceGroup>[^/]+)/providers/(?P<providerNamespace>[^/]+)/(?P<resourceType>[^/]+)/(?P<resourceName>[^/]+)"
source = "resourceId"
}
stage.labels {
values = {
category = "",
resourceId = "",
resourceGroup = "",
service_name = "resourceName",
}
}
forward_to = [loki.write.grafana_cloud.receiver]
}
loki.write "grafana_cloud" {
endpoint {
url = "<GRAFANA_CLOUD_LOKI_URL>"
basic_auth {
username = "<GRAFANA_CLOUD_LOKI_USERNAME>"
password = "<GRAFANA_CLOUD_API_KEY>"
}
}
}Replace the following:
<EVENTHUB_NAMESPACE>: Your Event Hub namespace name<EVENTHUB_NAME>: Your Event Hub name<GRAFANA_CLOUD_LOKI_URL>: Your Grafana Cloud Loki endpoint, such ashttps://logs-prod-us-central1.grafana.net/loki/api/v1/push<GRAFANA_CLOUD_LOKI_USERNAME>: Your Grafana Cloud Loki username<GRAFANA_CLOUD_API_KEY>: Your Grafana Cloud API key
After updating the configuration, upgrade the Helm release:
helm upgrade alloy grafana/alloy \
--namespace alloy \
-f values.yaml


