- 10 minutes to read

Logging the Request/Response from the Azure API Management platform

In this guide, you will learn how to apply a Nodinite specific policy to enable logging from the Azure API Management platform

There are several ways to log Nodinite Log Events to Nodinite from your Azure API Management platform. This user guide describes the following options:

  1. Azure Event Hub (recommended)
  2. API Call (supported but not recommended)
graph LR A[Azure API Management
with Policy] -->|1. Log Event| B(Azure Event Hub) B --> C{Pickup Service} C --> F[Nodinite's Log API] A -.-> |2. Log Event|F

Logging from API management is enabled using a Policy. This policy is provided as Templates in this user guide.

Logging using Azure Event Hub

Applying the policy means that the Azure API Management platform will create a Nodinite specific JSON Log Event and post it to the named EventHub. Then the Nodinite Pickup LogEvents Service Logging Agent will asynchronously fetch this JSON Log Event and eventually transfer it to Nodinite.

Depending on what you want to log (Request &/ Response) you will need to put the following configuration into your policy either in the inbound, outbound or in both configurations.

Direction Type
Inbound Request
Outbound Response

Before logging to Nodinite using an Azure Event Hub, make sure you have set up an Event Hub logger - follow this guide.

Replace the '' in both API management logger and the code snippet below for the Policy configuration

Policy configuration

The policy configuration below logs the:

  • Mandatory properties for a Nodinite Log Event
    • feel free to add as required by your business requirements
  • HTTP Headers as context properties (note not all as excluded by code)
  • Body
<log-to-eventhub logger-id='{new logger name}'>
    @{
    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 = 0,
        EndPointTypeId = 71,
        OriginalMessageTypeName = "Azure.ApiManagement.Request",
        LogDateTime = DateTime.UtcNow,
        Context = contextProperties,
        Body = bodyToLog
    };

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

Example Azure Event Hub Policy

This policy includes a configuration to log both the inbound and the outbound information.

In this example, we will add a clientTrackingId (guid) if it was not provided in the x-ms-client-tracking-id HTTP header and some other optional properties for a Nodinite Log Event.

<policies>
    <inbound>
        <!-- creating a tracking/correlation id -->
        <set-variable name="clientTrackingId" value="@{ 
            var guidBinary = new byte[16];
            Array.Copy(Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 10);
            long time = DateTime.Now.Ticks;
            byte[] bytes = new byte[6];
            unchecked
            {
                    bytes[5] = (byte)(time >> 40);
                    bytes[4] = (byte)(time >> 32);
                    bytes[3] = (byte)(time >> 24);
                    bytes[2] = (byte)(time >> 16);
                bytes[1] = (byte)(time >> 8);
                bytes[0] = (byte)(time);
            }
            Array.Copy(bytes, 0, guidBinary, 10, 6);
            return new Guid(guidBinary).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='{new logger name}'>
            @{
            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");

            Dictionary<string, string> contextProperties = new Dictionary<string, string>();
            contextProperties.Add("CorrelationId", context.Variables.GetValueOrDefault<string>("clientTrackingId"));
            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 = 0,
                EndPointTypeId = 71,
                OriginalMessageTypeName = "Azure.ApiManagement.Request",
                LogDateTime = DateTime.UtcNow,
                ApplicationInterchangeId = context.Variables.GetValueOrDefault<string>("clientTrackingId"),
                Context = contextProperties,
                Body = bodyToLog
            };

            return JsonConvert.SerializeObject(requestLogMessage);
            }
        </log-to-eventhub>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound>
        <set-header name="x-ms-client-tracking-id" exists-action="override">
            <value>@(context.Variables.GetValueOrDefault<string>("clientTrackingId"))</value>
        </set-header>
       
        <log-to-eventhub logger-id='{new logger name}'>
            @{
            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");

            Dictionary<string, string> contextProperties = new Dictionary<string, string>();
            contextProperties.Add("CorrelationId", context.Variables.GetValueOrDefault<string>("clientTrackingId"));
            contextProperties.Add("StatusCode", context.Response.StatusCode.ToString());
            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 = 1,
                EndPointTypeId = 71,
                OriginalMessageTypeName = "Azure.ApiManagement.Response",
                LogDateTime = DateTime.UtcNow,
                ApplicationInterchangeId = context.Variables.GetValueOrDefault<string>("clientTrackingId"),
                Context = contextProperties,
                Body = bodyToLog,
                LogText = context.Response.StatusCode.ToString()
            };
            return JsonConvert.SerializeObject(requestLogMessage);
            }
        </log-to-eventhub>
    </outbound>
    <on-error />
</policies>

Logging using Rest API Call

This approach is typically used when you are using a consumption based API mgmt

Before logging to Nodinite's Log API, make sure that Nodinite's Log API is available to your Azure API Management. It could be part of the vnet or the Log API is available on a public endpoint.

graph LR A[Azure API Management] -->|Log Event| B(Nodinite's Log API)
<send-request mode="new" response-variable-name="nodiniteRequestResponse" timeout="60" ignore-error="true">
    <set-url>@("{Your public nodinite log api url")</set-url>
    <set-method>POST</set-method>
    <set-header name="content-type" exists-action="override">
        <value>application/json</value>
    </set-header>
    <set-body>@{

    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");

    Dictionary<string, string> contextProperties = new Dictionary<string, string>();
    contextProperties.Add("CorrelationId", context.Variables.GetValueOrDefault<string>("clientTrackingId"));
    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 = 0,
        EndPointTypeId = 71,
        OriginalMessageTypeName = "Azure.ApiManagement.Request",
        LogDateTime = DateTime.UtcNow,
        ApplicationInterchangeId = context.Variables.GetValueOrDefault<string>("clientTrackingId"),
        //Context = new {
            //CorrelationId = context.Variables.GetValueOrDefault<string>("clientTrackingId")
        //},
        Context = contextProperties,
        Body = bodyToLog
    };

    
    return JsonConvert.SerializeObject(requestLogMessage);
    }</set-body>
</send-request>

Example API call

<policies>
    <inbound>
        <!-- creating a tracking/correlation id -->
        <set-variable name="clientTrackingId" value="@{ 
            var guidBinary = new byte[16];
            Array.Copy(Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 10);
            long time = DateTime.Now.Ticks;
            byte[] bytes = new byte[6];
            unchecked
            {
                    bytes[5] = (byte)(time >> 40);
                    bytes[4] = (byte)(time >> 32);
                    bytes[3] = (byte)(time >> 24);
                    bytes[2] = (byte)(time >> 16);
                bytes[1] = (byte)(time >> 8);
                bytes[0] = (byte)(time);
            }
            Array.Copy(bytes, 0, guidBinary, 10, 6);
            return new Guid(guidBinary).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>
        <!-- Send request -->
        <send-request mode="new" response-variable-name="nodiniteRequestResponse" timeout="60" ignore-error="true">
            <set-url>@("{Your public nodinite log api url")</set-url>
            <set-method>POST</set-method>
            <set-header name="content-type" exists-action="override">
                <value>application/json</value>
            </set-header>
            <set-body>@{

            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");

            Dictionary<string, string> contextProperties = new Dictionary<string, string>();
            contextProperties.Add("CorrelationId", context.Variables.GetValueOrDefault<string>("clientTrackingId"));
            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 = 0,
                EndPointTypeId = 71,
                OriginalMessageTypeName = "Azure.ApiManagement.Request",
                LogDateTime = DateTime.UtcNow,
                ApplicationInterchangeId = context.Variables.GetValueOrDefault<string>("clientTrackingId"),
                //Context = new {
                  //CorrelationId = context.Variables.GetValueOrDefault<string>("clientTrackingId")
                //},
                Context = contextProperties,
                Body = bodyToLog
            };

            
            return JsonConvert.SerializeObject(requestLogMessage);
            }</set-body>
        </send-request>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound>
        <set-header name="x-ms-client-tracking-id" exists-action="override">
            <value>@(context.Variables.GetValueOrDefault<string>("clientTrackingId"))</value>
        </set-header>
        <send-request mode="new" response-variable-name="nodiniteRequestResponse" timeout="60" ignore-error="true">
            <set-url>@("{Your public nodinite log api url")</set-url>
            <set-method>POST</set-method>
            <set-header name="content-type" exists-action="override">
                <value>application/json</value>
            </set-header>
            <set-body>@{
            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");

            Dictionary<string, string> contextProperties = new Dictionary<string, string>();
            contextProperties.Add("CorrelationId", context.Variables.GetValueOrDefault<string>("clientTrackingId"));
            contextProperties.Add("StatusCode", context.Response.StatusCode.ToString());
            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 = 1,
                EndPointTypeId = 71,
                OriginalMessageTypeName = "Azure.ApiManagement.Response",
                LogDateTime = DateTime.UtcNow,
                ApplicationInterchangeId = context.Variables.GetValueOrDefault<string>("clientTrackingId"),
                Context = contextProperties,
                Body = bodyToLog,
                LogText = context.Response.StatusCode.ToString()
            };
            return JsonConvert.SerializeObject(requestLogMessage);
            }</set-body>
        </send-request>
    </outbound>
    <on-error />
</policies>

Next Step

JSON Log Event

Pickup LogEvents Service Logging Agent