Teilen über


Überwachen von APIs mit Azure API Management, Event Hubs und Moesif

GILT FÜR: Alle API Management-Ebenen

Für den API Management-Dienst werden viele Funktionen bereitgestellt, mit denen die Verarbeitung von HTTP-Anforderungen verbessert werden kann, die an Ihre HTTP-API gesendet werden. Die Anforderungen und Antworten sind aber nur vorübergehend vorhanden. Die Anforderung erfolgt und fließt über den API-Verwaltungsdienst zu Ihrer Back-End-API. Ihre API verarbeitet die Anforderung, und eine Antwort fließt zurück an den API-Consumer. Der API Management-Dienst führt einige wichtige Statistiken zu den APIs für die Anzeige im Azure-Portal-Dashboard, aber darüber hinaus gehen die Details verloren.

Mithilfe der Richtlinie log-to-eventhub im API Management-Dienst können Sie alle Details der Anforderung und Antwort an einen Azure Event Hub senden. Es gibt mehrere Gründe, warum Sie Ereignisse aus HTTP-Nachrichten generieren möchten, die an Ihre APIs gesendet werden. Beispiele hierfür sind der Überwachungspfad für Updates, Nutzungsanalysen, Warnungen zu Ausnahmen und Drittanbieterintegrationen.

In diesem Artikel wird veranschaulicht, wie Sie die gesamte HTTP-Anforderungs- und Antwortnachricht erfassen, an einen Event Hub senden und diese Nachricht dann an einen Drittanbieterdienst weiterleiten, der HTTP-Protokollierungs- und Überwachungsdienste bereitstellt.

Warum vom API-Verwaltungsdienst senden?

Sie können HTTP-Middleware schreiben, die in HTTP-API-Frameworks eingebunden werden kann, um HTTP-Anforderungen und -Antworten zu erfassen und in Protokollierungs- und Überwachungssysteme zu übertragen. Der Nachteil bei diesem Ansatz ist, dass die HTTP-Middleware in die Back-End-API integriert werden muss und mit der API-Plattform übereinstimmen muss. Falls mehrere APIs vorhanden sind, muss die Middleware von jeder API bereitgestellt werden. Häufig liegen Gründe vor, warum Back-End-APIs nicht aktualisiert werden können.

Indem der Azure API Management-Dienst zum Integrieren in die Protokollierungsinfrastruktur verwendet wird, wird eine zentralisierte und plattformunabhängige Lösung bereitgestellt. Es ist auch skalierbar, teilweise aufgrund der Georeplikationsfunktionen von Azure API Management.

Gründe für das Senden an einen Event Hub

Es ist sinnvoll zu fragen: Warum eine Richtlinie erstellen, die für Azure Event Hubs spezifisch ist? Es gibt viele verschiedene Orte, an denen Sie Ihre Anforderungen möglicherweise protokollieren möchten. Warum können die Anforderungen nicht einfach direkt an das endgültige Ziel gesendet werden? Dies ist eine Option. Wenn Sie jedoch Protokollierungsanforderungen von einem API-Verwaltungsdienst vornehmen, müssen Sie berücksichtigen, wie sich die Protokollierung von Nachrichten auf die Leistung der API auswirkt. Einem allmählichen Auslastungsanstieg kann begegnet werden, indem vermehrt verfügbare Instanzen von Systemkomponenten bereitgestellt werden oder indem die Georeplikation eingesetzt wird. Kurzzeitige Spitzen beim Datenverkehr können aber dazu führen, dass Anforderungen verzögert werden, wenn sich die Geschwindigkeit von Anforderungen an die Protokollierungsinfrastruktur bei höherer Auslastung verringert.

Azure Event Hubs ist so konzipiert, dass es große Volumen von Daten mit einer wesentlich höheren Anzahl von Ereignissen verarbeiten kann als die Anzahl der API-Anforderungen, die die meisten APIs verarbeiten. Der Event Hub fungiert wie eine Art anspruchsvoller Puffer zwischen Ihrem API Management-Dienst und der Infrastruktur, von der die Nachrichten gespeichert und verarbeitet werden. So wird sichergestellt, dass Ihre API-Leistung aufgrund der Protokollierungsinfrastruktur nicht beeinträchtigt wird.

Sobald die Daten an einen Event Hub übergeben wurden, werden sie beibehalten und warten darauf, dass die Event Hub-Consumer sie verarbeiten. Für den Event Hub ist es nicht wichtig, wie die Verarbeitung erfolgt. Er stellt lediglich sicher, dass die Nachricht erfolgreich zugestellt wird.

Für Event Hubs ist es möglich, Ereignisse an mehrere Consumergruppen zu streamen. So können Ereignisse von unterschiedlichen Systemen verarbeitet werden. Dies unterstützt viele Integrationsszenarien, ohne die Verarbeitung der API-Anforderung innerhalb des API-Verwaltungsdiensts zu verzögern, da nur ein Ereignis generiert werden muss.

Eine Richtlinie zum Senden von Anwendungs-/HTTP-Nachrichten

Ein Event Hub akzeptiert Ereignisdaten als einfache Zeichenfolge. Der Inhalt dieser Zeichenfolge bleibt Ihnen überlassen. Um eine HTTP-Anforderung verpacken und an Azure Event Hubs senden zu können, müssen Sie die Zeichenfolge mit den Anforderungs- oder Antwortinformationen formatieren. In solchen Situationen müssen Sie möglicherweise keinen eigenen Analysecode schreiben, wenn ein vorhandenes Format vorhanden ist, das Sie wiederverwenden können. Zunächst sollten Sie die HAR zum Senden von HTTP-Anforderungen und -Antworten verwenden. Dieses Format ist zum Speichern einer Sequenz von HTTP-Anforderungen in einem JSON-basierten Format optimiert. Es enthält viele obligatorische Elemente, die für das Szenario zum Übergeben der HTTP-Nachricht per Übertragung mit unnötiger Komplexität verbunden ist.

Eine alternative Option besteht darin, den application/http Medientyp zu verwenden, wie in der HTTP-Spezifikation RFC 7230 beschrieben. Dieser Medientyp verwendet das gleiche Format, das zum tatsächlichen Senden von HTTP-Nachrichten über das Draht verwendet wird, aber die gesamte Nachricht kann im Textkörper einer anderen HTTP-Anforderung platziert werden. In unserem Fall verwenden wir lediglich den Textkörper als Nachricht zum Senden an Event Hubs. Glücklicherweise ist in Microsoft ASP.NET Web API 2.2-Client-Bibliotheken ein Parser vorhanden, mit dem dieses Format analysiert und in systemeigene HttpRequestMessage- und HttpResponseMessage-Objekte konvertiert werden kann.

Zum Erstellen dieser Nachricht müssen wir die C#-basierten Richtlinienausdrücke in Azure API Management verwenden. Hier ist die Richtlinie, die eine HTTP-Anforderungsnachricht an Azure Event Hubs sendet.

<log-to-eventhub logger-id="myapilogger" partition-id="0">
@{
   var requestLine = string.Format("{0} {1} HTTP/1.1\r\n",
                                               context.Request.Method,
                                               context.Request.Url.Path + context.Request.Url.QueryString);

   var body = context.Request.Body?.As<string>(true);
   if (body != null && body.Length > 1024)
   {
       body = body.Substring(0, 1024);
   }

   var headers = context.Request.Headers
                          .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key")
                          .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                          .ToArray<string>();

   var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;

   return "request:"   + context.Variables["message-id"] + "\n"
                       + requestLine + headerString + "\r\n" + body;
}
</log-to-eventhub>

Richtliniendeklaration

Zu diesem Richtlinienausdruck gibt es einige Anmerkungen. Die log-to-eventhub Richtlinie hat ein Attribut namens logger-id, das auf den Namen des Loggers verweist, der im API-Verwaltungsdienst erstellt wurde. Details zum Einrichten eines Event Hub-Loggers im API-Verwaltungsdienst finden Sie im Dokument "Protokollieren von Ereignissen bei Azure Event Hubs in Azure API Management". Das zweite Attribut ist ein optionaler Parameter, mit dem Event Hubs angewiesen werden, unter welcher Partition die Nachricht gespeichert werden soll. Event Hubs verwenden Partitionen zum Aktivieren der Skalierbarkeit, und es sind immer mindestens zwei Partitionen erforderlich. Die geordnete Zustellung von Nachrichten ist nur innerhalb einer Partition garantiert. Wenn wir Azure Event Hubs nicht anweisen, in welcher Partition die Nachricht angeordnet werden soll, wird zum Verteilen der Last ein Roundrobin-Algorithmus verwendet. Dies kann jedoch dazu führen, dass einige Nachrichten nicht mehr ordnungsgemäß verarbeitet werden.

Partitionen

Um sicherzustellen, dass Nachrichten an Verbraucher übermittelt werden, um die Lastverteilungsfunktion von Partitionen nutzen zu können, können wir HTTP-Anforderungsnachrichten an eine Partition und HTTP-Antwortnachrichten an eine zweite Partition senden. Dadurch wird eine gleichmäßige Lastverteilung sichergestellt und garantiert, dass alle Anforderungen und alle Antworten in der richtigen Reihenfolge abgewickelt werden. Es ist möglich, dass eine Antwort vor der entsprechenden Anforderung verwendet wird, aber das ist kein Problem, da wir einen anderen Mechanismus zum Korrelieren von Anforderungen mit Antworten haben und wissen, dass Anforderungen immer vor Antworten kommen.

HTTP-Nutzlasten

Überprüfen Sie nach dem Erstellen des requestLine , ob der Anforderungstext eingeschränkt werden soll. Der Anforderungstext wird auf 1024 Zeichen abgeschnitten. Dies könnte erhöht werden; Einzelne Event Hub-Nachrichten sind jedoch auf 256 KB beschränkt, daher ist es wahrscheinlich, dass einige HTTP-Nachrichtentexte nicht in eine einzelne Nachricht passen. Bei der Protokollierung und Analyse können Sie aus der HTTP-Anforderungszeile und den Headern eine erhebliche Menge an Informationen ableiten. Außerdem geben viele API-Anforderungen nur kleine Textkörper zurück. Daher ist der Verlust des Informationswerts durch das Abschneiden großer Textkörper im Vergleich zur Reduzierung des Aufwands für die Übertragung, Verarbeitung und Speicherung für die Beibehaltung des gesamten Textkörperinhalts relativ gering.

Eine letzte Notiz zur Verarbeitung des Textkörpers besteht darin, dass wir true an die As<string>() Methode übergeben müssen, da wir den Textkörper lesen, aber die Back-End-API soll ebenfalls in der Lage sein, diesen zu lesen. Indem wir „true“ an diese Methode übergeben, erreichen wir, dass der Text gepuffert wird. Er kann dann ein zweites Mal gelesen werden. Dies ist wichtig, wenn Sie über eine API verfügen, die große Dateien hochlädt oder lange Abrufe verwendet. In diesen Fällen ist es am besten, den Inhalt überhaupt nicht zu lesen.

HTTP-Header

HTTP-Header können in einem einfachen Schlüssel-Wert-Paar-Format in das Nachrichtenformat übertragen werden. Wir haben beschlossen, bestimmte sicherheitsrelevante Felder zu entfernen, um unnötiges Auslaufen von Anmeldeinformationen zu vermeiden. Es ist unwahrscheinlich, dass API-Schlüssel und andere Anmeldeinformationen zu Analysezwecken verwendet werden. Wenn wir den Benutzer und das jeweilige Produkt analysieren möchten, das er verwendet, können wir dies aus dem context Objekt abrufen und der Nachricht hinzufügen.

Metadaten von Nachrichten

Bei der Erstellung der vollständigen Nachricht für das Senden an den Event Hub ist die vorderste Zeile nicht Bestandteil der application/http-Nachricht. Die erste Zeile umfasst zusätzliche Metadaten. Hiermit wird angegeben, ob die Nachricht eine Anforderungs- oder Antwortnachricht ist, und eine Nachrichten-ID wird verwendet, um Anforderungen und Antworten zu korrelieren. Die Nachrichten-ID wird mit einer anderen Richtlinie erstellt, die wie folgt aussieht:

<set-variable name="message-id" value="@(Guid.NewGuid())" />

Wir konnten die Anforderungsnachricht erstellen, diese in einer Variablen speichern, bis die Antwort zurückgegeben wurde, und dann die Anforderung und Antwort als einzelne Nachricht senden. Durch das unabhängige Senden von Anfragen und Antworten und die Verwendung eines message-id, um die beiden zu korrelieren, erhalten wir jedoch etwas mehr Flexibilität im Nachrichtenumfang, die Möglichkeit, mehrere Partitionen zu nutzen, während die Nachrichtenreihenfolge beibehalten wird, und eine frühere Ankunft der Anfragen in unserem Protokoll-Dashboard. Es kann auch einige Szenarien geben, in denen eine gültige Antwort nie an den Event Hub gesendet wird (möglicherweise aufgrund eines schwerwiegenden Anforderungsfehlers im API-Verwaltungsdienst), aber wir haben immer noch einen Datensatz der Anforderung.

Die Richtlinie zum Senden der HTTP-Antwortnachricht ähnelt der Anforderung, und die vollständige Richtlinienkonfiguration sieht wie folgt aus:

<policies>
  <inbound>
      <set-variable name="message-id" value="@(Guid.NewGuid())" />
      <log-to-eventhub logger-id="myapilogger" partition-id="0">
      @{
          var requestLine = string.Format("{0} {1} HTTP/1.1\r\n",
                                                      context.Request.Method,
                                                      context.Request.Url.Path + context.Request.Url.QueryString);

          var body = context.Request.Body?.As<string>(true);
          if (body != null && body.Length > 1024)
          {
              body = body.Substring(0, 1024);
          }

          var headers = context.Request.Headers
                               .Where(h => h.Key != "Authorization" && h.Key != "Ocp-Apim-Subscription-Key")
                               .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                               .ToArray<string>();

          var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;

          return "request:"   + context.Variables["message-id"] + "\n"
                              + requestLine + headerString + "\r\n" + body;
      }
  </log-to-eventhub>
  </inbound>
  <backend>
      <forward-request follow-redirects="true" />
  </backend>
  <outbound>
      <log-to-eventhub logger-id="myapilogger" partition-id="1">
      @{
          var statusLine = string.Format("HTTP/1.1 {0} {1}\r\n",
                                              context.Response.StatusCode,
                                              context.Response.StatusReason);

          var body = context.Response.Body?.As<string>(true);
          if (body != null && body.Length > 1024)
          {
              body = body.Substring(0, 1024);
          }

          var headers = context.Response.Headers
                                          .Select(h => string.Format("{0}: {1}", h.Key, String.Join(", ", h.Value)))
                                          .ToArray<string>();

          var headerString = (headers.Any()) ? string.Join("\r\n", headers) + "\r\n" : string.Empty;

          return "response:"  + context.Variables["message-id"] + "\n"
                              + statusLine + headerString + "\r\n" + body;
     }
  </log-to-eventhub>
  </outbound>
</policies>

Die set-variable-Richtlinie erstellt einen Wert, auf den sowohl die log-to-eventhub-Richtlinie im <inbound>-Abschnitt als auch die <outbound>-Richtlinie zugreifen können.

Empfangen von Ereignissen von Event Hubs

Ereignisse von Azure Event Hubs werden über das AMQP-Protokoll empfangen. Das Microsoft Service Bus-Team hat Clientbibliotheken bereitgestellt, um die Nutzung von Ereignissen zu vereinfachen. Es werden zwei unterschiedliche Ansätze unterstützt: einer mit einem direkten Consumer und ein anderer mit Verwendung der EventProcessorHost-Klasse. Beispiele für diese beiden Ansätze finden Sie im Event Hubs-Beispiel-Repository. Die kurze Version der Unterschiede: Direct Consumer bietet Ihnen die vollständige Kontrolle und EventProcessorHost führt einige der Sanitärinstallationen für Sie aus, macht jedoch bestimmte Annahmen darüber, wie Sie diese Ereignisse verarbeiten.

EventProcessorHost

In diesem Beispiel verwenden wir die EventProcessorHost Einfachheit halber, aber es ist möglicherweise nicht die beste Wahl für dieses bestimmte Szenario. EventProcessorHost wird sichergestellt, dass Sie sich über Threadingprobleme in einer bestimmten Ereignisprozessorklasse keine Sorgen machen müssen. In unserem Szenario konvertieren wir die Nachricht jedoch in ein anderes Format und übergeben sie mithilfe einer asynchronen Methode an einen anderen Dienst. Es ist nicht erforderlich, den gemeinsamen Zustand zu aktualisieren, und daher besteht kein Risiko für Threadingprobleme. In den meisten Fällen ist EventProcessorHost wahrscheinlich die beste Wahl, und es ist sicherlich die einfachere Option.

IEventProcessor

Das zentrale Konzept beim Verwenden von EventProcessorHost ist die Erstellung einer Implementierung der IEventProcessor-Schnittstelle, in der die ProcessEventAsync-Methode enthalten ist. Dies ist das Wesen dieser Methode:

async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{

    foreach (EventData eventData in messages)
    {
        _Logger.LogInfo(string.Format("Event received from partition: {0} - {1}", context.Lease.PartitionId,eventData.PartitionKey));

        try
        {
            var httpMessage = HttpMessage.Parse(eventData.GetBodyStream());
            await _MessageContentProcessor.ProcessHttpMessage(httpMessage);
        }
        catch (Exception ex)
        {
            _Logger.LogError(ex.Message);
        }
    }
    ... checkpointing code snipped ...
}

Eine Liste mit EventData-Objekten wird an die Methode übergeben, und diese Liste wird dann durchlaufen. Die Bytes jeder einzelnen Methode werden in ein HttpMessage-Objekt analysiert, und dieses Objekt wird an eine Instanz von „IHttpMessageProcessor“ übergeben.

HttpMessage

Die HttpMessage-Instanz enthält drei Datenelemente:

public class HttpMessage
{
    public Guid MessageId { get; set; }
    public bool IsRequest { get; set; }
    public HttpRequestMessage HttpRequestMessage { get; set; }
    public HttpResponseMessage HttpResponseMessage { get; set; }

... parsing code snipped ...

}

Die HttpMessage-Instanz enthält eine MessageId-GUID, die uns für die HTTP-Anforderung das Herstellen einer Verbindung mit der entsprechenden HTTP-Antwort ermöglicht, sowie einen booleschen Wert zum Identifizieren, ob das Objekt eine Instanz von HttpRequestMessage und HttpResponseMessage enthält. Indem die integrierten HTTP-Klassen aus System.Net.Http verwendet wurden, konnte der application/http-Analysecode genutzt werden, der in System.Net.Http.Formatting enthalten ist.

IHttpMessageProcessor

Die HttpMessage-Instanz wird dann an die Implementierung von IHttpMessageProcessor weitergeleitet. Diese Schnittstelle habe ich erstellt, um den Empfang und die Interpretation des Ereignisses aus Azure Event Hubs von der eigentlichen Verarbeitung abzukoppeln.

Weiterleiten der HTTP-Meldung

Für dieses Beispiel haben wir beschlossen, die HTTP-Anforderung an Moesif API Analytics zu übertragen. Moesif ist ein cloudbasierter Dienst, der sich auf HTTP-Analysen und Debugging spezialisiert hat. Sie haben eine kostenlose Stufe, damit es einfach zu versuchen ist. Moesif ermöglicht es uns, die HTTP-Anforderungen in Echtzeit zu sehen, die über unseren API-Verwaltungsdienst fließen.

Die IHttpMessageProcessor -Implementierung sieht wie folgt aus:

public class MoesifHttpMessageProcessor : IHttpMessageProcessor
{
    private readonly string RequestTimeName = "MoRequestTime";
    private MoesifApiClient _MoesifClient;
    private ILogger _Logger;
    private string _SessionTokenKey;
    private string _ApiVersion;
    public MoesifHttpMessageProcessor(ILogger logger)
    {
        var appId = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-APP-ID", EnvironmentVariableTarget.Process);
        _MoesifClient = new MoesifApiClient(appId);
        _SessionTokenKey = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-SESSION-TOKEN", EnvironmentVariableTarget.Process);
        _ApiVersion = Environment.GetEnvironmentVariable("APIMEVENTS-MOESIF-API-VERSION", EnvironmentVariableTarget.Process);
        _Logger = logger;
    }

    public async Task ProcessHttpMessage(HttpMessage message)
    {
        if (message.IsRequest)
        {
            message.HttpRequestMessage.Properties.Add(RequestTimeName, DateTime.UtcNow);
            return;
        }

        EventRequestModel moesifRequest = new EventRequestModel()
        {
            Time = (DateTime) message.HttpRequestMessage.Properties[RequestTimeName],
            Uri = message.HttpRequestMessage.RequestUri.OriginalString,
            Verb = message.HttpRequestMessage.Method.ToString(),
            Headers = ToHeaders(message.HttpRequestMessage.Headers),
            ApiVersion = _ApiVersion,
            IpAddress = null,
            Body = message.HttpRequestMessage.Content != null ? System.Convert.ToBase64String(await message.HttpRequestMessage.Content.ReadAsByteArrayAsync()) : null,
            TransferEncoding = "base64"
        };

        EventResponseModel moesifResponse = new EventResponseModel()
        {
            Time = DateTime.UtcNow,
            Status = (int) message.HttpResponseMessage.StatusCode,
            IpAddress = Environment.MachineName,
            Headers = ToHeaders(message.HttpResponseMessage.Headers),
            Body = message.HttpResponseMessage.Content != null ? System.Convert.ToBase64String(await message.HttpResponseMessage.Content.ReadAsByteArrayAsync()) : null,
            TransferEncoding = "base64"
        };

        Dictionary<string, string> metadata = new Dictionary<string, string>();
        metadata.Add("ApimMessageId", message.MessageId.ToString());

        EventModel moesifEvent = new EventModel()
        {
            Request = moesifRequest,
            Response = moesifResponse,
            SessionToken = _SessionTokenKey != null ? message.HttpRequestMessage.Headers.GetValues(_SessionTokenKey).FirstOrDefault() : null,
            Tags = null,
            UserId = null,
            Metadata = metadata
        };

        Dictionary<string, string> response = await _MoesifClient.Api.CreateEventAsync(moesifEvent);

        _Logger.LogDebug("Message forwarded to Moesif");
    }

    private static Dictionary<string, string> ToHeaders(HttpHeaders headers)
    {
        IEnumerable<KeyValuePair<string, IEnumerable<string>>> enumerable = headers.GetEnumerator().ToEnumerable();
        return enumerable.ToDictionary(p => p.Key, p => p.Value.GetEnumerator()
                                                         .ToEnumerable()
                                                         .ToList()
                                                         .Aggregate((i, j) => i + ", " + j));
    }
}

Der MoesifHttpMessageProcessor nutzt eine C#-API-Bibliothek für Moesif, die es erleichtert, HTTP-Ereignisdaten per Push in ihren Dienst zu verschieben. Um HTTP-Daten an die Moesif Collector-API zu senden, benötigen Sie ein Konto und eine Anwendungs-ID. Sie erhalten eine Moesif-Anwendungs-ID, indem Sie ein Konto auf der Website von Moesif erstellen und dann zum oberen rechten Menü wechseln und App-Setup auswählen.

Vollständiges Beispiel

Den Quellcode und die Tests für das Beispiel finden Sie bei GitHub. Sie benötigen einen API Management-Dienst, einen verbundenen Event Hub und ein Speicherkonto, um das Beispiel selbst auszuführen.

Das Beispiel ist lediglich eine einfache Konsolenanwendung, die auf Ereignisse lauscht, die aus dem Event Hub kommen, sie in Moesif EventRequestModel und EventResponseModel-Objekte umwandelt und diese dann an die Moesif Collector-API weiterleitet.

In der folgenden animierten Abbildung können Sie sehen, dass eine Anforderung an eine API im Entwicklerportal gestellt wird, die Konsolenanwendung mit der Nachricht, die empfangen, verarbeitet und weitergeleitet wird, und dann die Anforderung und Antwort, die im Eventstream angezeigt wird.

Animierte Bilddemonstration einer Anforderung, die an Runscope weitergeleitet wird

Zusammenfassung

Der Azure API Management-Dienst ist ein idealer Ort zum Erfassen des HTTP-Datenverkehrs, der an Ihre APIs fließt und von den APIs gesendet wird. Azure Event Hubs ist eine hoch skalierbare, kostengünstige Lösung zum Erfassen dieses Datenverkehrs und zum Bereitstellen für sekundäre Verarbeitungssysteme für die Protokollierung und Überwachung sowie andere anspruchsvolle Analysevorgänge. Das Verbinden mit Datenverkehr-Überwachungssystemen von Drittanbietern wie Moesif ist so einfach wie das Schreiben einiger Dutzend Codezeilen.