Megosztás a következőn keresztül:


API-k monitorozása az Azure API Management, az Event Hubs és a Moesif használatával

A KÖVETKEZŐRE VONATKOZIK: Minden API Management-szint

Az API Management szolgáltatás számos lehetőséget kínál a HTTP API-nak küldött HTTP-kérések feldolgozásának javítására. A kérések és válaszok megléte azonban átmeneti. A kérés meg lett állítva, és az API Management szolgáltatáson keresztül halad át a háttér API-ba. Az API feldolgozza a kérést, és a válasz visszafolyik az API-fogyasztóhoz. Az API Management szolgáltatás megőriz néhány fontos statisztikát az API-król az Azure Portal irányítópultján való megjelenítéshez, de ezen túl a részletek el is tűntek.

Az API Management szolgáltatásban a log-to-eventhub szabályzat használatával bármilyen adatot elküldhet a kérésből és a válaszból egy Azure Event Hubnak. Számos oka lehet annak, hogy az API-knak küldött HTTP-üzenetekből szeretne eseményeket generálni. Ilyenek például a frissítések naplózási nyomvonala, a használati elemzések, a kivételriasztások és a külső integrációk.

Ez a cikk bemutatja, hogyan rögzítheti a teljes HTTP-kérés- és válaszüzenetet, hogyan küldheti el az eseményközpontba, majd továbbíthatja azt egy külső szolgáltatásnak, amely HTTP-naplózási és monitorozási szolgáltatásokat nyújt.

Miért érdemes az API Management Service-ből küldeni?

HTTP köztes szoftvereket írhat, amelyek HTTP API-keretrendszerekhez csatlakoztathatók a HTTP-kérések és válaszok rögzítéséhez, valamint a naplózási és monitorozási rendszerekbe való betáplálásukhoz. Ennek a megközelítésnek a hátránya, hogy a HTTP köztes szoftvernek integrálva kell lennie a háttér API-ba, és meg kell felelnie az API platformjának. Ha több API van, mindegyiknek üzembe kell helyeznie a köztes szoftvereket. Gyakran előfordul, hogy a háttérbeli API-k nem frissíthetők.

Az Azure API Management szolgáltatás használata a naplózási infrastruktúrával való integrációhoz központosított és platformfüggetlen megoldást biztosít. Ez is méretezhető, részben az Azure API Management georeplikációs képességeinek köszönhetően.

Miért érdemes az Azure Event Hubba küldeni?

Ésszerű kérdés, hogy miért hozzon létre olyan szabályzatot, amely az Azure Event Hubsra jellemző? Számos különböző helyen szeretném naplózni a kéréseimet. Miért nem küldi el a kéréseket közvetlenül a végső célhelyre? Ez egy lehetőség. Ha azonban naplózási kéréseket küld egy API felügyeleti szolgáltatásból, figyelembe kell venni, hogy a naplózási üzenetek hogyan befolyásolják az API teljesítményét. A terhelés fokozatos növelését a rendszerösszetevők rendelkezésre álló példányainak növelésével vagy a georeplikációs lehetőségek kihasználásával lehet kezelni. A forgalom rövid kiugrása azonban késleltetheti a kérelmeket, ha a naplózási infrastruktúrára irányuló kérelmek lassan indulnak be.

Az Azure Event Hubs úgy lett kialakítva, hogy nagy mennyiségű adatot inigráljon, és sokkal több esemény kezelésére képes, mint a legtöbb API-folyamat HTTP-kéréseinek száma. Az Event Hub egyfajta kifinomult pufferként működik az API felügyeleti szolgáltatás és az üzeneteket tároló és feldolgozó infrastruktúra között. Ez biztosítja, hogy az API teljesítménye ne szenvedjen a naplózási infrastruktúra miatt.

Miután az adatokat átadta egy eseményközpontnak, az megmarad, és megvárja, amíg az Event Hub felhasználói feldolgozzák azokat. Az Event Hub nem érdekli a feldolgozás módját, csak az üzenet sikeres kézbesítésének biztosításával foglalkozik.

Az Event Hubs több fogyasztói csoportnak is képes eseményeket streamelni. Ez lehetővé teszi, hogy az eseményeket különböző rendszerek dolgozzák fel. Ez lehetővé teszi számos integrációs forgatókönyv támogatását anélkül, hogy az API-kérés feldolgozását késve kellene feldolgozni az API Management szolgáltatásban, mivel csak egy eseményt kell létrehozni.

Alkalmazás-/http-üzenetek küldésére szolgáló szabályzat

Az Event Hub egyszerű sztringként fogadja el az eseményadatokat. A sztring tartalma önön múlik. Ahhoz, hogy egy HTTP-kérést csomagolhassunk be, és elküldjük az Event Hubsnak, formáznunk kell a sztringet a kérelem- vagy válaszinformációkkal. Ilyen helyzetekben, ha van olyan formátum, amelyet újra felhasználhatunk, akkor előfordulhat, hogy nem kell saját elemzési kódot írnunk. Kezdetben úgy gondoltam, hogy a HAR-t használom HTTP-kérések és válaszok küldéséhez. Ez a formátum azonban jSON-alapú HTTP-kérések sorozatának tárolására van optimalizálva. Számos kötelező elemet tartalmazott, amelyek szükségtelen bonyolultságot adtak hozzá a HTTP-üzenet vezetéken keresztüli továbbításának forgatókönyvéhez.

Egy másik lehetőség az application/http RFC 7230 HTTP-specifikációban leírt adathordozótípus használata volt. Ez a médiatípus pontosan ugyanazt a formátumot használja, mint a http-üzenetek átvitelére a vezetéken keresztül, de a teljes üzenet egy másik HTTP-kérés törzsébe helyezhető. A mi esetünkben csak a törzset fogjuk használni üzenetként az Event Hubsnak való küldéshez. Kényelmesen létezik egy elemző, amely a Microsoft ASP.NET Web API 2.2 ügyfélkódtárakban található, amelyek elemezhetik ezt a formátumot, és átalakíthatják natív HttpRequestMessage és HttpResponseMessage objektummá.

Az üzenet létrehozásához ki kell használnunk a C#-alapú szabályzatkifejezéseket az Azure API Managementben. Itt található a szabályzat, amely HTTP-kérési üzenetet küld az Azure Event Hubsnak.

<log-to-eventhub logger-id="conferencelogger" 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>

Szabályzat deklarációja

Van néhány konkrét dolog, amit érdemes megemlíteni erről a szabályzatkifejezésről. A log-to-eventhub szabályzat egy logger-id nevű attribútummal rendelkezik, amely az API Management szolgáltatásban létrehozott naplózó nevére hivatkozik. Az Event Hub-naplózók API Management szolgáltatásban való beállításának részleteit az Események naplózása az Azure Event Hubsba az Azure API Managementben című dokumentumban találja. A második attribútum egy opcionális paraméter, amely arra utasítja az Event Hubsot, hogy melyik partíción tárolja az üzenetet. Az Event Hubs partíciókkal teszi lehetővé a méretezhetőséget, és legalább kettőre van szükség. Az üzenetek megrendelt kézbesítése csak egy partíción belül garantált. Ha nem utasítjuk az Event Hubot arra, hogy melyik partíción helyezze el az üzenetet, az egy ciklikus időszeleteléses algoritmussal osztja el a terhelést. Ez azonban előfordulhat, hogy egyes üzeneteink feldolgozása nem megfelelő.

Partíciók

Annak érdekében, hogy az üzeneteink a felhasználókhoz érkezhessenek, és kihasználhassák a partíciók terheléselosztási képességét, úgy döntöttem, hogy HTTP-kérési üzeneteket küldünk egy partícióra, és HTTP-válaszüzeneteket egy második partícióra. Ez egyenletes terheléselosztást biztosít, és garantálhatjuk, hogy az összes kérést sorrendben használjuk fel, és az összes választ sorrendben használjuk fel. Lehetséges, hogy a válasz a megfelelő kérés előtt lesz felhasználva, de mivel ez nem probléma, mivel a kérések válaszokhoz való korrelációjának más mechanizmusa van, és tudjuk, hogy a kérések mindig a válaszok előtt érkeznek.

HTTP hasznos adatok

A létrehozása requestLineután ellenőrizzük, hogy a kérelem törzsét csonkolja-e. A kérelem törzse csak 1024-et csonkít. Ez növelhető, azonban az egyes Event Hub-üzenetek száma 256 KB, ezért valószínű, hogy egyes HTTP-üzenettestek nem férnek el egyetlen üzenetben. A naplózás és az elemzés során jelentős mennyiségű információ származhat csak a HTTP-kérelemsorból és a fejlécekből. Emellett számos API-kérés csak kistesteket ad vissza, így a nagy méretű testek csonkolásával az információs érték elvesztése meglehetősen minimális az átviteli, feldolgozási és tárolási költségek csökkenéséhez képest, hogy az összes törzstartalmat megtartsa. A test feldolgozásának egyik utolsó megjegyzése, hogy át kell adnunk true a As<string>() metódusnak, mert a törzs tartalmát olvassuk, de azt is szerettük volna, hogy a háttér API képes legyen olvasni a törzset. Ennek a módszernek az igaz átadásával a test pufferelt állapotba kerül, hogy másodszor is olvasható legyen. Ezt fontos figyelembe venni, ha olyan API-val rendelkezik, amely nagy fájlokat tölt fel, vagy hosszú lekérdezést használ. Ezekben az esetekben a legjobb lenne, ha egyáltalán nem olvasnánk a testet.

HTTP-fejlécek

A HTTP-fejlécek egyszerű kulcs/érték pár formátumban továbbíthatók az üzenetformátumba. Úgy döntöttünk, hogy eltávolítunk bizonyos biztonsági bizalmas mezőket, hogy elkerüljük a hitelesítő adatok szükségtelen kiszivárgását. Nem valószínű, hogy az API-kulcsokat és más hitelesítő adatokat elemzési célokra használnák. Ha elemzést szeretnénk végezni a felhasználóról és az általuk használt termékről, akkor ezt lekérhetjük az context objektumból, és hozzáadhatjuk az üzenethez.

Üzenet metaadatai

Az eseményközpontba küldendő teljes üzenet létrehozásakor az első sor valójában nem része az application/http üzenetnek. Az első sor további metaadatokból áll, amelyek azt tartalmazzák, hogy az üzenet egy kérés vagy válaszüzenet, valamint egy üzenetazonosító, amely a kérések válaszokhoz való korrelációjára szolgál. Az üzenetazonosító egy másik, az alábbihoz hasonló szabályzattal jön létre:

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

Létrehozhattuk volna a kérési üzenetet, amelyet egy változóban tárolhattunk volna, amíg a válasz vissza nem érkezik, majd egyetlen üzenetként elküldtük a kérést és a választ. Ha azonban egymástól függetlenül küldi el a kérést és a választ, és egy üzenetazonosító használatával korrelálja a kettőt, egy kicsit rugalmasabbá válik az üzenet mérete, több partíció is kihasználható, miközben megtartja az üzenetsorrendet, és a kérés hamarabb megjelenik a naplózási irányítópulton. Előfordulhatnak olyan esetek is, amikor a rendszer soha nem küld érvényes választ az eseményközpontnak, valószínűleg az API Management szolgáltatásban történt végzetes kéréshiba miatt, de a kérésről még van feljegyzésünk.

A válasz HTTP-üzenetet küldő szabályzat a kéréshez hasonlóan néz ki, így a teljes szabályzatkonfiguráció a következőképpen néz ki:

<policies>
  <inbound>
      <set-variable name="message-id" value="@(Guid.NewGuid())" />
      <log-to-eventhub logger-id="conferencelogger" 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="conferencelogger" 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>

A set-variable szabályzat létrehoz egy értéket, amely a szakaszban és <outbound> a log-to-eventhub <inbound> szakaszban lévő szabályzat által is elérhető.

Események fogadása az Event Hubsból

Az Azure Event Hub eseményei az AMQP protokoll használatával érkeznek. A Microsoft Service Bus csapata elérhetővé tette az ügyfélkódtárakat, hogy megkönnyítse a fogyasztó eseményeket. Két különböző megközelítés támogatott, az egyik közvetlen fogyasztó , a másik pedig az EventProcessorHost osztályt használja. Erre a két megközelítésre példákat az Event Hubs programozási útmutatójában talál. A különbségek rövid verziója teljes ellenőrzést biztosít, Direct Consumer és a EventProcessorHost vízvezetékek némelyike működik Az Ön számára, de bizonyos feltételezéseket tesz arról, hogyan dolgozza fel ezeket az eseményeket.

EventProcessorHost

Ebben a példában az EventProcessorHost egyszerűséget használjuk, de lehet, hogy nem ez a legjobb választás ehhez az esethez. EventProcessorHost nem a kemény munka, hogy győződjön meg arról, hogy nem kell aggódnia szálkezelés problémák egy adott eseményfeldolgozó osztályban. A forgatókönyvünkben azonban egyszerűen átalakítjuk az üzenetet egy másik formátumra, és aszinkron módszerrel továbbítjuk azt egy másik szolgáltatásnak. Nincs szükség a megosztott állapot frissítésére, ezért nincs szükség a szálkezelés problémáinak kockázatára. A legtöbb forgatókönyv esetében valószínűleg ez a legjobb választás, EventProcessorHost és minden bizonnyal ez a könnyebb megoldás.

IEventProcessor

A használat EventProcessorHost során a központi koncepció az interfész implementációjának IEventProcessor létrehozása, amely tartalmazza a metódust ProcessEventAsync. Ennek a módszernek a lényege itt látható:

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

A rendszer átadja az EventData-objektumok listáját a metódusnak, és iteráljuk a listát. Az egyes metódusok bájtjai egy HttpMessage-objektumba lesznek elemezve, és a rendszer átadja az objektumot az IHttpMessageProcessor egy példányának.

HttpMessage

A HttpMessage példány három adatrészt tartalmaz:

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

}

A HttpMessage példány tartalmaz egy MessageId GUID-t, amely lehetővé teszi a HTTP-kérés csatlakoztatását a megfelelő HTTP-válaszhoz, valamint egy logikai értéket, amely azonosítja, hogy az objektum tartalmaz-e HttpRequestMessage és HttpResponseMessage példányt. A beépített HTTP-osztályok System.Net.Httphasználatával kihasználhattam a application/http benne található System.Net.Http.Formattingelemzési kódot.

IHttpMessageProcessor

Ezt HttpMessage követően a rendszer továbbítja a példányt annak implementálására IHttpMessageProcessor, amely egy olyan felület, amelyet azért hoztam létre, hogy leválasztsam az esemény fogadását és értelmezését az Azure Event Hubról, valamint annak tényleges feldolgozását.

A HTTP-üzenet továbbítása

Ebben a mintában úgy döntöttem, hogy érdekes lenne leküldni a HTTP-kérést a Moesif API Analyticsnek. A Moesif egy felhőalapú szolgáltatás, amely HTTP-elemzésre és hibakeresésre specializálódott. Ingyenes szinttel rendelkeznek, így könnyen kipróbálható, és lehetővé teszi, hogy valós időben láthassuk a HTTP-kéréseket az API Management szolgáltatáson keresztül.

A IHttpMessageProcessor megvalósítás így néz ki:

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

A MoesifHttpMessageProcessor Moesifhez készült C# API-kódtár segítségével egyszerűen leküldheti a HTTP-eseményadatokat a szolgáltatásba. Ahhoz, hogy HTTP-adatokat küldjön a Moesif Collector API-nak, szüksége van egy fiókra és egy alkalmazásazonosítóra. A Moesif alkalmazásazonosítóját úgy szerezheti be, hogy létrehoz egy fiókot a Moesif webhelyén, majd a jobb felső menü alkalmazásbeállítási> menüjébe lép.

Teljes minta

A minta forráskódja és tesztjei a GitHubon találhatók. A minta futtatásához szüksége van egy API Management Szolgáltatásra, egy csatlakoztatott Eseményközpontra és egy Tárfiókra .

A minta csak egy egyszerű konzolalkalmazás, amely figyeli az Event Hubból érkező eseményeket, moesif-lá EventRequestModel és EventResponseModel objektummá alakítja őket, majd továbbítja őket a Moesif Collector API-nak.

Az alábbi animált képen egy API-nak küldött kérés látható a fejlesztői portálon, a konzolalkalmazásban, amely a fogadott, feldolgozott és továbbított üzenetet jeleníti meg, majd megjelenik a kérés és a válasz az Eseménystreamben.

A Runscopenak továbbított kérések bemutatása

Összegzés

Az Azure API Management szolgáltatás ideális hely az API-k felé és onnan érkező HTTP-forgalom rögzítésére. Az Azure Event Hubs egy nagymértékben skálázható, alacsony költségű megoldás a forgalom rögzítésére és másodlagos feldolgozási rendszerekbe való betáplálására naplózáshoz, monitorozáshoz és egyéb kifinomult elemzésekhez. Csatlakozás olyan külső forgalomfigyelő rendszerekre, mint a Moesif, olyan egyszerű, mint néhány tucat sornyi kód.

Következő lépések