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?
Je možné napsat middleware HTTP, který se může připojit k rozhraním ROZHRANÍ API HTTP, aby zachytil požadavky a odpovědi HTTP, a odesílat je do systémů 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 možnostem 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 bych mohl chtít protokolovat své žádosti. 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 zprávy protokolování ovlivňují 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žená tak, aby ingress velké objemy dat s kapacitou pro zpracování mnohem vyššího počtu událostí, než je počet požadavků HTTP, které většina procesů 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í, zachovají se a čekají na zpracování příjemců centra událostí. 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 umožňuje podporu mnoha scénářů integrace bez zpoždění při zpracování požadavku rozhraní API ve službě API Management, protože je potřeba vygenerovat pouze jednu událost.
Zásada pro odesílání zpráv aplikace nebo HTTP
Centrum událostí přijímá data událostí jako jednoduchý řetězec. Obsah tohoto řetězce je na vás. Abychom mohli zabalit požadavek HTTP a odeslat ho do služby Azure Event Hubs, musíme ř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ůžeme znovu použít, nemusíme psát vlastní parsovací kód. Zpočátku jsem uvažoval o 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í bylo použít application/http
typ média, jak je popsáno ve specifikaci HTTP RFC 7230. Tento typ média používá 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 text jako 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#. Tady je zásada, která odešle zprávu požadavku HTTP 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 nazvaný logger-id
, který odkazuje na název protokolovacího nástroje, který byl vytvořen 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ě. Seřazené doručování zpráv je zaručené pouze v rámci oddílu. 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 naše zprávy doručují příjemcům v pořadí a využívají možnosti distribuce zatížení oddílů, rozhodl jsem se odesílat zprávy požadavku HTTP do jednoho oddílu a zpráv odpovědí HTTP do druhého oddílu. Tím se zajistí rovnoměrné rozdělení zatížení a můžeme zaručit, že budou všechny požadavky spotřebovány v pořadí a všechny odpovědi budou spotřebovány v daném pořadí. Odpověď může být spotřebována před odpovídající žádostí, ale protože 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í requestLine
zkontrolujeme, jestli se má text požadavku zkrátit. Text požadavku se zkrátí jenom na 1024. To se může zvýšit, ale jednotlivé zprávy centra událostí jsou omezené na 256 kB, takže je pravděpodobné, že některé tělo zprávy HTTP se nevejde do jedné zprávy. Při protokolování a analýze je možné odvodit značné množství informací pouze z řádku požadavku HTTP a hlaviček. Mnoho rozhraní API také vyžaduje, aby vracely jen malé tělo a ztráta hodnoty informací zkrácením velkých těl je oproti snížení nákladů na přenos, zpracování a ukládání poměrně minimální, 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
As<string>()
metodě, protože čteme obsah těla, ale také chtělo, aby rozhraní API back-endu mohlo číst tělo. Předáním true této metodě způsobíme uložení těla do vyrovnávací paměti, aby se mohl číst podruhé. To je důležité vědět, pokud máte rozhraní API, které provádí nahrávání velkých souborů nebo používá dlouhé dotazování. V těchto případech by bylo nejlepší se vyhnout čtení těla vůbec.
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ě ú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 provést analýzu uživatele a konkrétního produktu, který používá, můžeme to získat z objektu context
a přidat ho do zprávy.
Metadata zpráv
Při vytváření úplné zprávy pro odeslání do centra událostí není frontline 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 jsme vytvořit zprávu požadavku, která byla uložena v proměnné, dokud se odpověď nevrátila a pak požadavek a odpověď odeslala jako jednu zprávu. Odesláním požadavku a odpovědi nezávisle a použitím message-id
korelace těchto dvou ale získáme větší flexibilitu ve velikosti zprávy, možnost využít více oddílů při zachování pořadí zpráv a požadavek se zobrazí na řídicím panelu protokolování dříve. Může se také stát, že se do centra událostí nikdy neodesílají platná odpověď, pravděpodobně 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 log-to-eventhub
zásadami v oddílu, <inbound>
tak 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 přímým příjemcem a druhý používá EventProcessorHost
třídu. Příklady těchto dvou přístupů najdete v průvodci programováním ve službě Event Hubs. Krátká verze rozdílů je, 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
jednoduchost, ale nemusí být nejlepší volbou pro tento konkrétní scénář. 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 jednoduše převedeme zprávu do jiného formátu a předáme ji jiné službě pomocí asynchronní metody. Není potřeba 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
. Podstata této metody je znázorněna zde:
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í předdefinovaných tříd HTTP jsem System.Net.Http
byl schopen využít parsování 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 jsem se rozhodl, že by bylo zajímavé odeslat požadavek HTTP do Služby Moesif API Analytics. Moesif je cloudová služba, která se specializuje na analýzy a ladění HTTP. Mají bezplatnou úroveň, takže je snadné ji vyzkoušet a umožňuje nám 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 Moesif a pak přejdete do nabídky Vpravo nahoře –> Nastavení aplikace.
Kompletní ukázka
Zdrojový kód a testy pro ukázku jsou na GitHubu. Abyste mohli ukázku spustit sami, potřebujete službu API Management, připojené centrum událostí a účet úložiště.
Ukázka je pouze jednoduchá konzolová aplikace, která naslouchá událostem pocházejícím z centra událostí, převede je na Moesif EventRequestModel
a EventResponseModel
objekty a pak je předá do rozhraní API kolekce 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 pak požadavek a odpověď, které se zobrazují ve streamu událostí.
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.
Další kroky
- Další informace o službě Azure Event Hubs
- Další informace o integraci služby API Management a služby Event Hubs