Sdílet prostřednictvím


Monitorování rozhraní API pomocí služby Azure API Management, Event Hubs a Moesif

PLATÍ PRO: Všechny úrovně služby API Management

Služba API Management poskytuje řadu možností, jak vylepšit zpracování požadavků HTTP odesílaných do vašeho rozhraní HTTP API. Existence požadavků a odpovědí je však přechodná. Požadavek se provede a prochází službou API Management do vašeho back-endového rozhraní API. Vaše rozhraní API zpracuje požadavek a odpověď se vrátí zpět příjemci rozhraní API. Služba API Management uchovává některé důležité statistiky o rozhraních API pro zobrazení na řídicím panelu webu Azure Portal, ale kromě toho jsou podrobnosti pryč.

log-to-eventhub Pomocí zásad ve službě API Management můžete odeslat jakékoli podrobnosti z požadavku a odpovědi do služby Azure Event Hubs. Existuje několik důvodů, proč můžete chtít generovat události ze zpráv HTTP odesílaných do vašich rozhraní API. Mezi příklady patří záznam auditu aktualizací, analýzy využití, upozorňování výjimek a integrace třetích stran.

Tento článek ukazuje, jak zachytit celou zprávu požadavku HTTP a odpovědi, odeslat ji do centra událostí a pak tuto zprávu předat službě třetí strany, která poskytuje služby protokolování a monitorování PROTOKOLU HTTP.

Proč odesílat ze služby API Management?

Můžete napsat HTTP middleware, který se může integrovat k rozhraním API HTTP pro zachycení požadavků a odpovědí HTTP a jejich zaznamenávání v systémech protokolování a monitorování. Nevýhodou tohoto přístupu je, že middleware HTTP musí být integrovaný do back-endového rozhraní API a musí odpovídat platformě rozhraní API. Pokud existuje více rozhraní API, musí každý z nich nasadit middleware. Často existují důvody, proč se back-endová rozhraní API nedají aktualizovat.

Použití služby Azure API Management k integraci s infrastrukturou protokolování poskytuje centralizované řešení nezávislé na platformě. Je také škálovatelný, částečně kvůli funkcím geografické replikace služby Azure API Management.

Proč odesílat do centra událostí?

Je vhodné se zeptat: proč vytvořit zásadu specifickou pro Azure Event Hubs? Existuje mnoho různých míst, kde můžete chtít své požadavky protokolovat. Proč žádosti neodesílat přímo do konečného cíle? To je možnost. Při provádění žádostí o protokolování ze služby API Management je však nutné zvážit, jak protokolování zpráv ovlivňuje výkon rozhraní API. Postupné zvýšení zatížení je možné zvládnout zvýšením dostupných instancí systémových komponent nebo využitím geografické replikace. Krátké špičky provozu však můžou způsobit zpoždění požadavků, pokud se požadavky na protokolování infrastruktury začnou zpomalit při zatížení.

Služba Azure Event Hubs je navržena tak, aby mohla přijímat velká množství dat s kapacitou pro zpracování daleko většího počtu událostí, než je počet požadavků HTTP, které většina rozhraní API zpracovává. Centrum událostí funguje jako druh sofistikované vyrovnávací paměti mezi vaší službou API Management a infrastrukturou, která ukládá a zpracovává zprávy. Tím se zajistí, že výkon vašeho rozhraní API nebude trpět kvůli infrastruktuře protokolování.

Jakmile se data předají do centra událostí, setrvají tam a čekají na to, až je příjemci centra událostí zpracují. Centrum událostí se nezajímá, jak se zpracovává, jen se stará o to, aby se zpráva úspěšně doručila.

Event Hubs má možnost streamovat události do více skupin příjemců. To umožňuje zpracování událostí různými systémy. To podporuje mnoho scénářů integrace, aniž by se během zpracování požadavku rozhraní API ve službě správy API způsobovala dodatečná zdržení, protože je třeba vygenerovat pouze jednu událost.

Zásada pro odesílání zpráv typu application/HTTP

Centrum událostí přijímá data událostí jako jednoduchý řetězec. Obsah tohoto řetězce je na vás. Pokud chcete mít možnost zabalit požadavek HTTP a odeslat ho do služby Azure Event Hubs, musíte řetězec naformátovat s informacemi o požadavku nebo odpovědi. V takových situacích, pokud existuje existující formát, který můžete použít opakovaně, nemusíte psát vlastní parsovací kód. Zpočátku můžete zvážit použití HAR pro odesílání požadavků a odpovědí HTTP. Tento formát je však optimalizovaný pro ukládání posloupnosti požadavků HTTP ve formátu založeném na formátu JSON. Obsahoval mnoho povinných prvků, které pro scénář předávání zprávy HTTP přes drát přidaly zbytečnou složitost.

Alternativní možností je použít application/http typ média, jak je popsáno ve specifikaci HTTP RFC 7230. Tento typ média používá přesně stejný formát, který se používá k skutečnému odesílání zpráv HTTP přes drát, ale celá zpráva může být vložena do textu jiného požadavku HTTP. V našem případě použijeme tělo jako naši zprávu k odeslání do služby Event Hubs. Pohodlně existuje analyzátor, který existuje v klientských knihovnách Microsoft ASP.NET Web API 2.2, které můžou analyzovat tento formát a převést ho na nativní HttpRequestMessage objekty a HttpResponseMessage objekty.

Abychom mohli tuto zprávu vytvořit, musíme ve službě Azure API Management využít výrazy zásad založené na jazyce C#. Zde je pravidlo, které odešle HTTP požadavek ve formě zprávy do služby Azure Event Hubs.

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

Deklarace zásad

Existuje několik konkrétních věcí, které stojí za zmínku o tomto výrazu zásad. Zásada log-to-eventhub má atribut s názvem logger-id, který odkazuje na název protokolovacího nástroje vytvořeného ve službě API Management. Podrobnosti o tom, jak nastavit protokolovací nástroj centra událostí ve službě API Management, najdete v dokumentu Postup protokolování událostí do služby Azure Event Hubs ve službě Azure API Management. Druhý atribut je volitelný parametr, který dává službě Event Hubs pokyn, do kterého oddílu se má zpráva uložit. Služba Event Hubs používá oddíly k povolení škálovatelnosti a vyžaduje minimálně dvě. Řadové doručování zpráv je zaručené pouze v rámci partitionu. Pokud službě Azure Event Hubs nedáváme pokyn, do kterého oddílu se má zpráva umístit, použije k distribuci zatížení algoritmus kruhového dotazování. To ale může způsobit zpracování některých zpráv mimo pořadí.

Oddíly

Abychom zajistili, že se zprávy doručují příjemcům v pořadí a využívají možnosti distribuce zatížení oddílů, můžeme odesílat zprávy požadavku HTTP do jednoho oddílu a zpráv odpovědí HTTP do druhého oddílu. Tím zajistíte rovnoměrnou distribuci zatížení a můžete zaručit, že se budou všechny požadavky a všechny odpovědi spotřebovávat v daném pořadí. Odpověď může být spotřebována před odpovídající žádostí, ale to není problém, protože máme jiný mechanismus pro korelaci požadavků na odpovědi a víme, že žádosti vždy přicházejí před odpověďmi.

Datové části HTTP

Po sestavení requestLinezkontrolujte, jestli se má text požadavku zkrátit. Text požadavku se zkrátí jenom na 1024. To by mohlo být zvýšeno; jednotlivé zprávy centra událostí jsou však omezeny na 256 kB, takže je pravděpodobné, že některá těla zpráv HTTP se nevejdou do jedné zprávy. Při protokolování a analýze můžete odvodit značné množství informací pouze z řádku požadavku HTTP a hlaviček. Mnoho rozhraní API také vyžaduje pouze vrácení malých těl, a proto ztráta hodnoty informací zkrácením velkých těl je poměrně minimální oproti snížení nákladů na přenos, zpracování a ukládání, aby se zachoval veškerý obsah těla.

Poslední poznámka ke zpracování těla spočívá v tom, že musíme předat true metodě As<string>(), protože čteme obsah těla, ale také chceme, aby si API back-endu mohlo tělo přečíst. Předáním true této metodě způsobíme, aby tělo bylo uchováváno ve vyrovnávací paměti, aby mohlo být přečteno podruhé. To je důležité, pokud máte rozhraní API, které nahrává velké soubory nebo používá dlouhé dotazování. V těchto případech je nejlepší se vůbec vyhnout čtení obsahu.

Záhlaví HTTP

Hlavičky HTTP lze přenést do formátu zprávy v jednoduchém formátu páru klíč/hodnota. Rozhodli jsme se odstranit určitá pole citlivá na zabezpečení, aby nedocházelo k zbytečnému úniku informací o přihlašovacích údajích. Je nepravděpodobné, že by se klíče rozhraní API a další přihlašovací údaje používaly k analytickým účelům. Pokud chceme analyzovat uživatele a konkrétní produkt, který používá, můžeme ho získat z objektu context a přidat ho do zprávy.

Metadat zpráv

Při vytváření úplné zprávy pro odeslání do centra událostí není frontová linie ve skutečnosti součástí application/http zprávy. První řádek je další metadata skládající se z toho, jestli je zpráva zprávou žádost nebo odpověď a ID zprávy, která se používá ke korelaci požadavků na odpovědi. ID zprávy se vytvoří pomocí jiné zásady, která vypadá takto:

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

Mohli bychom vytvořit zprávu požadavku, uložit ji do proměnné, dokud se odpověď nevrátí, a pak požadavek a odpověď odeslat jako jednu zprávu. Odesláním požadavku a odpovědi nezávisle na sobě a použitím message-id ke korelaci těchto dvou, získáme větší flexibilitu ve velikosti zprávy, možnost využít více particí při zachování pořadí zpráv a rychlejší zaznamenání požadavku na našem řídicím panelu protokolování. V některých scénářích se také může stát, že se do centra událostí nikdy neodesílají platná odpověď (možná kvůli závažné chybě požadavku ve službě API Management), ale stále máme záznam žádosti.

Zásada pro odeslání zprávy HTTP odpovědi vypadá podobně jako požadavek, takže úplná konfigurace zásad vypadá takto:

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

Zásada set-variable vytvoří hodnotu, která je přístupná jak zásadě log-to-eventhub v oddílu <inbound>, tak v oddílu <outbound>.

Příjem událostí ze služby Event Hubs

Události ze služby Azure Event Hubs se přijímají pomocí protokolu AMQP. Tým Microsoft Service Bus zpřístupnil klientské knihovny, aby byly náročné události jednodušší. Existují dva různé přístupy, jeden je být přímým spotřebitelem a druhý je použití EventProcessorHost třídy. Příklady těchto dvou přístupů najdete v úložišti ukázek služby Event Hubs. Krátká verze rozdílů: Direct Consumer poskytuje úplnou kontrolu a EventProcessorHost dělá některé z instalatérských prací za vás, ale dělá určité předpoklady o způsobu zpracování těchto událostí.

EventProcessorHost

V této ukázce použijeme EventProcessorHost pro zjednodušení, ale pro tento konkrétní scénář nemusí být nejlepší volbou. EventProcessorHost pracuje na tom, abyste se ujistili, že se nemusíte starat o problémy s vlákny v rámci konkrétní třídy procesoru událostí. V našem scénáři ale zprávu převedeme do jiného formátu a předáme ji jiné službě pomocí asynchronní metody. Není nutné aktualizovat sdílený stav, a proto žádné riziko problémů s vlákny. Pro většinu scénářů EventProcessorHost je pravděpodobně nejlepší volbou a určitě je to jednodušší možnost.

IEventProcessor

Centrální koncept při použití EventProcessorHost je vytvořit implementaci IEventProcessor rozhraní, která obsahuje metodu ProcessEventAsync. Tady je podstata této metody:

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

Do metody se předá seznam objektů EventData a budeme iterovat přes tento seznam. Bajty každé metody jsou analyzovány do HttpMessage objektu a tento objekt je předán instanci IHttpMessageProcessor.

HttpMessage

Instance HttpMessage obsahuje tři části dat:

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

}

Instance HttpMessage obsahuje MessageId identifikátor GUID, který nám umožňuje připojit požadavek HTTP k odpovídající odpovědi HTTP a logickou hodnotu, která identifikuje, zda objekt obsahuje instanci HttpRequestMessage a HttpResponseMessage. Pomocí vestavěných tříd HTTP System.Net.Http jsem mohl využít analýzu application/http kódu, který je součástí System.Net.Http.Formatting.

IHttpMessageProcessor

Instance HttpMessage se pak přepošla na implementaci IHttpMessageProcessor, což je rozhraní, které jsem vytvořil pro oddělení příjmu a interpretace události od Azure Event Hubs a skutečné zpracování.

Přeposílání zprávy HTTP

Pro tuto ukázku jsme se rozhodli odeslat požadavek HTTP do analýzy rozhraní Moesif API. Moesif je cloudová služba, která se specializuje na analýzy a ladění HTTP. Mají bezplatnou úroveň, takže se to dá snadno vyzkoušet. Moesif nám umožňuje zobrazit požadavky HTTP v reálném čase procházející přes naši službu API Management.

Implementace IHttpMessageProcessor vypadá takto:

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

Využívá MoesifHttpMessageProcessor knihovnu rozhraní API jazyka C# pro Moesif , která usnadňuje odesílání dat událostí HTTP do jejich služby. Abyste mohli odesílat data HTTP do rozhraní API kolekce Moesif, potřebujete účet a ID aplikace. ID aplikace Moesif získáte tak, že vytvoříte účet na webu Moesifu a pak přejdete do pravé horní nabídky a vyberete Nastavení aplikace.

Kompletní ukázka

Zdrojový kód a testy pro ukázku jsou na GitHubu. K tomu, abyste si mohli sami spustit ukázku, potřebujete službu API Management, připojené Event Hub a Storage Account.

Ukázka je pouze jednoduchá konzolová aplikace, která naslouchá událostem pocházejícím z Event Hubu, převádí je na objekty Moesif EventRequestModel a EventResponseModel, a pak je předává do rozhraní API kolektoru Moesif.

Na následujícím animovaném obrázku uvidíte požadavek na rozhraní API na portálu pro vývojáře, konzolovou aplikaci zobrazující přijetí, zpracování a přeposílání zprávy a následné zobrazení požadavku a odpovědi v eventstreamu.

Animovaný obrázek ukázky žádosti přeposílané do Runscopeu

Shrnutí

Služba Azure API Management poskytuje ideální místo pro zachycení provozu HTTP přenášeného do a z vašich rozhraní API. Azure Event Hubs je vysoce škálovatelné a nízkonákladové řešení pro zachycení provozu a jeho předávání do sekundárních systémů zpracování pro protokolování, monitorování a další sofistikované analýzy. Připojení k systémům monitorování provozu třetích stran, jako je Moesif, je stejně jednoduché jako několik desítek řádků kódu.