Open source

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:

Prepare your Azure environment

Azure Event Hubs exposes different endpoints depending on the protocol:

ProtocolPortWhen to use
Kafka9093 (TLS)Applications send events using Kafka clients or Kafka Connect.
AMQP5671 (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.

  2. Create or reuse an Azure Kubernetes Service (AKS) cluster.

  3. Enable Microsoft Entra Workload ID and OpenID Connect in your AKS cluster.

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

    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
    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
    kubectl create namespace alloy
  8. Create a Kubernetes ServiceAccount with the workload identity annotation.

    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
  9. Create a federated identity credential to link your managed identity with the Kubernetes ServiceAccount.

    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.

  2. Assign the Azure Event Hubs Data Receiver role to your managed identity.

    Get the managed identity principal ID:

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

Install Alloy

  1. Add the Grafana Helm repository.

    shell
    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update
  2. Retrieve the tenant ID for your Azure subscription.

    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
    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
    helm install alloy grafana/alloy \
        --namespace alloy \
        -f values.yaml

Verify the installation

  1. Check that the Alloy Pod is running.

    shell
    kubectl get pods -n alloy

    You should see output similar to:

    text
    NAME      READY   STATUS    RESTARTS   AGE
    alloy-0   1/1     Running   0          1m
  2. Check the Alloy logs for any errors.

    shell
    kubectl logs -n alloy -l app.kubernetes.io/name=alloy
  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
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
helm upgrade alloy grafana/alloy \
    --namespace alloy \
    -f values.yaml