---
title: "Collect Azure Event Hubs logs and forward them to Loki | Grafana Alloy documentation"
description: "Learn how to collect Azure Event Hubs logs and forward them to Loki"
---

# 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](/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/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

- [`loki.source.azure_event_hubs`](../../reference/components/loki/loki.source.azure_event_hubs/)
- [`loki.process`](../../reference/components/loki/loki.process/)
- [`loki.write`](../../reference/components/loki/loki.write/)

## Before you begin

Ensure you have the following:

- Azure administrator access with `Microsoft.Authorization/roleAssignments/write` permissions, such as [Role Based Access Control Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator) or [User Access Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#user-access-administrator)
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) installed and authenticated
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) installed and configured to access your AKS cluster
- [Helm](https://helm.sh/docs/intro/install/) installed

## Prepare your Azure environment

Azure Event Hubs exposes different endpoints depending on the protocol:

Expand table

| Protocol | Port         | When to use                                                         |
|----------|--------------|---------------------------------------------------------------------|
| Kafka    | `9093` (TLS) | Applications send events using Kafka clients or Kafka Connect.      |
| AMQP     | `5671` (TLS) | Applications send events using Azure SDKs or AMQP client libraries. |

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`.

1. Use the Azure portal to [create or reuse a resource group](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal).
2. Create or reuse an [Azure Kubernetes Service](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli) (AKS) cluster.
3. Enable [Microsoft Entra Workload ID](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) and [OpenID Connect](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer) in your AKS cluster.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az aks update \
       --resource-group <RESOURCE_GROUP> \
       --name <AKS_CLUSTER_NAME> \
       --enable-oidc-issuer \
       --enable-workload-identity
   ```
   
   Replace the following:
   
   - *`<RESOURCE_GROUP>`* : Your Azure resource group
   - *`<AKS_CLUSTER_NAME>`* : The name of your AKS cluster
4. Retrieve the OIDC issuer URL for your cluster. You need this value when creating the federated credential.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az aks show \
       --resource-group <RESOURCE_GROUP> \
       --name <AKS_CLUSTER_NAME> \
       --query "oidcIssuerProfile.issuerUrl" \
       --output tsv
   ```
   
   Replace the following:
   
   - *`<RESOURCE_GROUP>`* : Your Azure resource group
   - *`<AKS_CLUSTER_NAME>`* : The name of your AKS cluster
5. Create a [user-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity).
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   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
6. Retrieve the client ID for your managed identity. You need this value for the ServiceAccount annotation and Alloy configuration.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az identity show \
       --resource-group <RESOURCE_GROUP> \
       --name <MANAGED_IDENTITY_NAME> \
       --query clientId \
       --output tsv
   ```
   
   Replace the following:
   
   - *`<RESOURCE_GROUP>`* : Your Azure resource group
   - *`<MANAGED_IDENTITY_NAME>`* : The name of your managed identity
7. Create a Kubernetes namespace for Alloy.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   kubectl create namespace alloy
   ```
8. Create a Kubernetes ServiceAccount with the workload identity annotation.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   kubectl apply -f - <<EOF
   apiVersion: v1
   kind: ServiceAccount
   metadata:
     name: alloy
     namespace: alloy
     annotations:
       azure.workload.identity/client-id: "<CLIENT_ID>"
   EOF
   ```
   
   Replace the following:
   
   - *`<CLIENT_ID>`* : The client ID from the previous step
   
   How Azure Workload Identity authentication works
   
   Azure Workload Identity connects three components:
   
   Kubernetes ServiceAccount : The ServiceAccount annotation `azure.workload.identity/client-id` specifies 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 `--subject` value matches `system:serviceaccount:<namespace>:<serviceaccount>`
   - The managed identity `clientId` matches the ServiceAccount annotation
   - The `--audiences` value is `api://AzureADTokenExchange`
9. Create a federated identity credential to link your managed identity with the Kubernetes ServiceAccount.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   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://AzureADTokenExchange
   ```
   
   Replace 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

1. Follow the steps to [Set up Azure Event Hubs](/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/azure/config-azure-logs/#set-up-azure-event-hubs).
2. Assign the `Azure Event Hubs Data Receiver` role to your managed identity.
   
   Get the managed identity principal ID:
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   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:
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   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
   
   Role assignment scope options
   
   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:
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az role assignment create \
       --assignee $PRINCIPAL_ID \
       --role "Azure Event Hubs Data Receiver" \
       --scope /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>
   ```
   
   To verify the assignment:
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az role assignment list --assignee $PRINCIPAL_ID --scope <SCOPE>
   ```

## Install Alloy

1. Add the Grafana Helm repository.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   helm repo add grafana https://grafana.github.io/helm-charts
   helm repo update
   ```
2. Retrieve the tenant ID for your Azure subscription.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   az account show --query tenantId --output tsv
   ```
3. Create a `values.yaml` file with the following configuration.
   
   > Warning
   > 
   > Don’t store sensitive credentials directly in `values.yaml` or commit them to version control. For production environments, use a Kubernetes Secret with `secretKeyRef`, or an external secret manager such as HashiCorp Vault, Azure Key Vault, or the External Secrets Operator.
   
   The configuration uses port `9093` for the Azure Event Hubs Kafka-compatible endpoint. The `loki.source.azure_event_hubs` component in Alloy requires the Kafka-compatible endpoint and doesn’t support AMQP for this integration.
   
   The `authentication` block 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.
   
   YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```yaml
   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 as `https://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
4. Install Alloy using Helm.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   helm install alloy grafana/alloy \
       --namespace alloy \
       -f values.yaml
   ```

## Verify the installation

1. Check that the Alloy Pod is running.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   kubectl get pods -n alloy
   ```
   
   You should see output similar to:
   
   text ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```text
   NAME      READY   STATUS    RESTARTS   AGE
   alloy-0   1/1     Running   0          1m
   ```
2. Check the Alloy logs for any errors.
   
   shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```shell
   kubectl logs -n alloy -l app.kubernetes.io/name=alloy
   ```
   
   Quick validation tips
   
   - Verify authentication and connection:
     
     shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
     
     ```shell
     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:
     
     shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
     
     ```shell
     az role assignment list --assignee <PRINCIPAL_ID> --scope <SCOPE>
     ```
3. 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:

1. **`stage.json`** : Parses the log line as JSON and extracts the `resourceId` and `category` fields.
2. **`stage.regex`** : Parses the `resourceId` to extract resource details like `subscriptionId`, `resourceGroup`, and `resourceName`.
3. **`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.

YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```yaml
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 as `https://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:

shell ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```shell
helm upgrade alloy grafana/alloy \
    --namespace alloy \
    -f values.yaml
```
