- 10 minutes to read

Event Hub Policy

[!DANGER] Azure Trusted Service Connectivity Retirement (March 2026) - Microsoft is retiring the Trusted Services feature, which affects logging from APIM to Azure Event Hubs when network restrictions are enabled. Read the Microsoft announcement and review the alternative solutions below.

Use scalable, policy-based logging for Azure API Management with Nodinite. This guide helps you set up Event Hub logging, configure loggers, and implement policies so you can securely and efficiently track all requests and responses.

✅ Log requests and responses with full payload and headers
✅ Integrate with Azure Event Hub for scalable, asynchronous logging
✅ Monitor and manage loggers directly from Nodinite
✅ Use policy-based logging for flexibility and security
✅ Prevent data loss with robust pickup and monitoring agents

Warning

The Event Hub logging option enforces a 200 KB limit. If you log messages larger than this, the event will not be logged. For large messages, use the Blob Storage Policy.

graph LR subgraph "Payload < 200KB" A[Azure API Management
with Policy 2] A -->|1. Log Event,
payload < 200KB| B(Azure Event Hub) end subgraph "Nodinite" B --> C{Pickup Service} C --> F[Log API] end

Diagram: Event Hub policy-based logging from Azure API Management to Nodinite.

Event Hub Overview

You create a Nodinite Log Event and send it to Azure Event Hub as intermediate storage. The Nodinite Pickup Log Events Service Logging Agent transfers the data to your Nodinite instance for use in self-service Log Views.

To enable Nodinite logging from your APIs in Azure API Management:

When you activate the policy, Azure API Management creates a Nodinite-specific JSON Log Event and posts it to the named Event Hub. The Nodinite Pickup Log Events Service Logging Agent asynchronously moves the JSON Log Event to Nodinite.

Event Hub Logger

To log using Azure Event Hub, create a named Event Hub Logger. Reference this logger by name in your policy. You cannot rename a logger—delete and recreate it if needed. Create it using Nodinite or tools like Insomnia.

Important

Add one or more named Event Hub loggers to create the Event Hub Logger used by the Policy. This enables logging to Nodinite using Azure Event Hub as intermediate storage.

Tip

Use the Nodinite Azure Monitoring Agent to manage and monitor your Event Hub Loggers.

Important

If you use Postman, review Security Risks of Postman.

You can also create the Event Hub Logger by performing a PUT method with a Body; a template is provided below:

Method URL
PUT https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.ApiManagement/service/{{APIMGMTServiceName}}/loggers/{{loggerName}}?api-version=2019-12-01
DELETE https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.ApiManagement/service/{{APIMGMTServiceName}}/loggers/{{loggerName}}?api-version=2019-12-01
  • {{subscriptionId}} is the Subscription ID where your API Management Service is located.
  • {{resourceGroup}} is the Resource Group where your API Management Service is located.
  • {{APIMGMTServiceName}} is the name of your API Management Service.
  • {{loggerName}} is the name you assign to this Event Hub Logger.

The {loggerName} uniquely identifies this logger in your API Management Service ({{APIMGMTServiceName}}). Use a unique name per Event Hub and environment (Prod, QA, Test, ...).

Important

Once you reference a Logger in any APIM Policy, you cannot remove it. To change the name, remove all references, drop the old one, and recreate it with the new name.

{
  "properties": {
    "loggerType": "azureEventHub",
    "description": "{description}",
    "resourceId": "{resourceId}",
    "credentials": {
      "name": "{`eventHubEntityName`}",
      "connectionString": "Endpoint=sb://{namespace}.servicebus.windows.net/;SharedAccessKeyName={sharedAccessKeyName};SharedAccessKey={sharedAccessKey};EntityPath={entityPath}"
    }
  }
}

Use this Body as a template and modify it for your needs.

Field Variable Value Description
loggerType - azureEventHub Hard coded value. DO NOT CHANGE!
description {description} Doing good Add a user-friendly description for this Event Hub Logger
resourceId &#123;resourceId&#125;
/subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.EventHub/namespaces/{EventHubNamespaceName}/eventhubs/{eventhubName}
NOTE: The target Event Hub Entity may be in another subscription/resource group. Enter values accordingly.
name {eventHubEntityName} eventHubEntityName OPTIONAL: Name of the Event Hub Entity if not provided in the connection string
connectionString {namespace} The namespace with the target Event Hub
connectionString {sharedAccessKeyName} Name of the shared access key
connectionString {sharedAccessKey} The shared access key for the provided key name
connectionString {entityPath} Name of the target event hub entity for Nodinite JSON Log Event. If you provide {entityPath}, you do not need to specify {eventHubEntityName}

Tip

Use a SAS key with Manage permission to create the Event Hub Logger. The Send permission is not enough. We recommend a dedicated SAS key for each Event Hub Logger for easy revocation and best security.

API Policy Configuration

Decide what to log (Request and/or Response) and add the configuration to your policy in the inbound, outbound, or both sections.

Direction Type
Inbound Request
Outbound Response

The template below logs:

  • Mandatory properties for a Nodinite Log Event (modify as needed for your business)
  • HTTP Headers as context properties

    Info

    Some properties are excluded in the sample code

  • Body

Replace '{loggerName}' in both the API management logger and the code snippets below for the Policy configuration.

The example below creates a basic Nodinite JSON Log Event with common, mandatory, and optional properties.

  • Context is optional
  • Body is optional (note the 200 KB limit on the JSON using the Event Hub Logger; consider Blob Storage Policy[] for larger messages).
<log-to-eventhub logger-id='{loggerName}'>
    @{
    var body =  context.Request.Body.As<string>(preserveContent: true);

    var bodyToLog = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(body));

    var headers = context.Request.Headers
                .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key");

    Dictionary<string, string> contextProperties = new Dictionary<string, string>();
    foreach (var h in headers) {
        contextProperties.Add(string.Format("Header#{0}", h.Key), String.Join(", ", h.Value));
    }
    
    var requestLogMessage = new {
        LogAgentValueId = 15,
        EndPointName = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
        EndPointUri = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
        EndPointDirection = 10,
        EndPointTypeId = 71,
        OriginalMessageTypeName = "Nodinite.Schemas/Nodinite.Demo/5.0#Orders",
        LogDateTime = DateTime.UtcNow,
        Context = contextProperties,
        Body = bodyToLog
    };

    return JsonConvert.SerializeObject(requestLogMessage);
    }
</log-to-eventhub>

Example Azure Event Hub Policy

This complete policy logs both inbound (Request) and outbound (Response) information.

In this example, you add a clientTrackingId (guid) if not provided in the x-ms-client-tracking-id HTTP header, plus other optional properties for a Nodinite Log Event.

x-ms-client-tracking-id
<policies>
    <inbound>
        <!-- creating a tracking/correlation id -->
        <set-variable name="clientTrackingId" value="@{ 
            return Guid.NewGuid().ToString();
        }" />
        <!-- if x-ms-client-tracking-id exists use it, if not, use clientTrackingId -->
        <set-header name="x-ms-client-tracking-id" exists-action="skip">
            <value>@(context.Variables.GetValueOrDefault<string>("clientTrackingId"))</value>
        </set-header>
        <!-- Put on logger -->
        <log-to-eventhub logger-id="{loggerName}">
            @{
                var body =  context.Request.Body.As<string>(preserveContent: true); // in case we need it later...

                var bodyToLog = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(body));

                var headers = context.Request.Headers
                            .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key");

                var correlationId = headers.Any(h=>h.Key == "x-ms-client-tracking-id") ? headers.First(h => h.Key == "x-ms-client-tracking-id").Value[0] : context.Variables.GetValueOrDefault<string>("clientTrackingId"); 

                Dictionary<string, string> contextProperties = new Dictionary<string, string>();
                contextProperties.Add("Correlation Id", correlationId);
            
                foreach (var h in headers) {
                    contextProperties.Add(string.Format("Header#{0}", h.Key), String.Join(", ", h.Value));
                }
                
                var requestLogMessage = new {
                    LogAgentValueId = 15,
                    EndPointName = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
                    EndPointUri = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
                    EndPointDirection = 10,
                    EndPointTypeId = 71,
                    EventDirection=21,
                    OriginalMessageTypeName = "Nodinite.Schemas/Nodinite.Demo/5.0#Orders",
                    LogDateTime = DateTime.UtcNow,
                    LogStatus = 0,
                    ApplicationInterchangeId = correlationId,
                    Context = contextProperties,
                    LogText = "Hello World from Nodinite API mgmt logging policy",
                    Body = bodyToLog
                };

                return JsonConvert.SerializeObject(requestLogMessage);
            }
        </log-to-eventhub>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound>
        <!-- if x-ms-client-tracking-id exists use it, if not, use clientTrackingId -->
        <set-header name="x-ms-client-tracking-id" exists-action="skip">
            <value>@(context.Variables.GetValueOrDefault<string>("Correlation Id"))</value>
        </set-header>
        <log-to-eventhub logger-id="{loggerName}">
            @{
                var body =  context.Response.Body.As<string>(preserveContent: true); // in case we need it later...

                var bodyToLog = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(body));

                var headers = context.Response.Headers
                            .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key");
                
                var correlationId = headers.Any(h=>h.Key == "x-ms-client-tracking-id") ? headers.First(h => h.Key == "x-ms-client-tracking-id").Value[0] : context.Variables.GetValueOrDefault<string>("clientTrackingId"); 

                Dictionary<string, string> contextProperties = new Dictionary<string, string>();
                contextProperties.Add("Correlation Id", correlationId);
                foreach (var h in headers) {
                    contextProperties.Add(string.Format("Header#{0}", h.Key), String.Join(", ", h.Value));
                }
                string logText = "";

                if (context.Response.StatusCode > 300)
                {
                    logText = "Guru meditation";
                }
                
                var requestLogMessage = new {
                    LogAgentValueId = 15,
                    EndPointName = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
                    EndPointUri = context.Deployment.ServiceName + "/" + context.Api.Name + "/" + context.Operation.Name,
                    EndPointDirection = 10,
                    EndPointTypeId = 71,
                    EventDirection=25,
                    OriginalMessageTypeName = "Nodinite.Schemas/Nodinite.Demo/5.0#OrderResponses",
                    LogDateTime = DateTime.UtcNow,
                    ApplicationInterchangeId = correlationId,
                    Context = contextProperties,
                    Body = bodyToLog,
                    LogStatus = context.Response.StatusCode,
                    LogText = logText
                };
                return JsonConvert.SerializeObject(requestLogMessage);
            }
        </log-to-eventhub>
    </outbound>
    <on-error />
</policies>

Redacting Sensitive Data

Important

Protect sensitive information in your logs - By default, the policy examples exclude Authorization and Ocp-Apim-Subscription-Key headers to prevent logging credentials. Review your headers and request/response bodies to ensure no PII (Personally Identifiable Information), secrets, passwords, or API keys are logged.

Best practices:

  • Filter headers: Exclude headers containing sensitive data (authentication tokens, API keys, secrets)
  • Redact body content: Hash or remove PII fields from request/response bodies before logging
  • Use allowlists: Log only known-safe headers instead of denylisting sensitive ones
  • Audit regularly: Review logged data to ensure compliance with data protection regulations

Example header filtering (already in policy samples):

var headers = context.Request.Headers
    .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key");

For advanced redaction techniques, custom body filtering, and security hardening options, see [Network Restrictions & Alternative Solutions - Redact Sensitive Data][Network Restrictions].


Troubleshooting

Contact our Support if you need help implementing Logging.

Network Restrictions & Troubleshooting

Important

Are you experiencing issues with network-restricted Event Hubs? Due to Azure's Trusted Services retirement (March 2026), logging from APIM to Event Hubs with firewall restrictions requires alternative solutions. See our comprehensive guide: Network Restrictions & Alternative Solutions.

Quick summary of available options:

  1. Private Endpoint with VNet Integration ⭐ - Best for production (requires Developer/Premium/Standard v2 tier)
  2. Direct Log API Policy - Works on all APIM tiers including Consumption
  3. Switch to Blob Storage Policy - Better for large messages (>200 KB)
  4. Azure Function/Logic App Intermediary - Flexible middle-ground solution
  5. Upgrade to Developer Tier - Cost-effective option for dev/test (~$50/month)

Note

Event Hub has a 200 KB message size limit. For larger payloads, consider switching to Blob Storage Policy combined with one of the alternative solutions.

👉 Read the full troubleshooting guide for detailed implementation steps, pricing information, and recommendation matrix.


Next Step

Search Fields
Log Views

Azure Application Access
JSON Log Event
Managing
Monitoring
Non Events Agent
Pickup Log Events Service Logging Agent

Interested in logging from other Azure Related Services?