راقب واجهات برمجة التطبيقات الخاصة بك باستخدام Azure APIM ومراكز الأحداث وMoesif
ينطبق على: جميع مستويات إدارة واجهة برمجة التطبيقات
توفر خدمة APIM العديد من الإمكانات لتحسين معالجة طلبات HTTP المرسلة إلى واجهة برمجة تطبيقات HTTP الخاصة بك. ومع ذلك، فإن وجود الطلبات والردود أمر عابر. يتم إجراء الطلب ويتدفق من خلال خدمة APIM إلى واجهة برمجة التطبيقات الخلفية. تعالج واجهة برمجة التطبيقات الخاصة بك الطلب وتتدفق الاستجابة مرة أخرى إلى مستهلك واجهة برمجة التطبيقات. تحتفظ خدمة APIM ببعض الإحصائيات المهمة حول واجهات برمجة التطبيقات للعرض في لوحة معلومات مدخل Microsoft Azure، غير أن التفاصيل اختفت بعدها.
باستخدام نهج log-to-eventhub في خدمة APIM، يمكنك إرسال أي تفاصيل من الطلب والاستجابة إلى Azure Event Hub. هناك عدة أسباب وراء رغبتك في إنشاء أحداث من رسائل HTTP التي يتم إرسالها إلى واجهات برمجة التطبيقات الخاصة بك. تتضمن بعض الأمثلة تدقيقًا للتحديثات وتحليلات الاستخدام وتنبيه الاستثناءات وتكاملات الجهات الخارجية.
توضح هذه المقالة كيفية التقاط طلب HTTP ورسالة الاستجابة بالكامل، وإرسالها إلى Event Hub ثم ترحيل هذه الرسالة إلى خدمة جهة خارجية توفر خدمات تسجيل ومراقبة HTTP.
لماذا ترسل من خدمة APIM؟
من الممكن كتابة برمجية وسيطة HTTP يمكن أن تدخل في أطر عمل HTTP API لالتقاط طلبات واستجابات HTTP وإدخالها في أنظمة التسجيل والمراقبة. الجانب السلبي لهذا النهج هو أن برمجية HTTP الوسيطة يجب أن يتم دمجها في واجهة API الخلفية ويجب أن تتطابق مع النظام الأساسي لواجهة برمجة التطبيقات. إذا كان هناك العديد من واجهات برمجة التطبيقات، فيجب على كل واحدة نشر البرمجيات الوسيطة. غالبًا ما تكون هناك أسباب لعدم إمكانية تحديث واجهات برمجة التطبيقات الخلفية.
يوفر استخدام خدمة إدارة APIM للتكامل مع البنية الأساسية للتسجيل حلاً مركزيًا ومستقلًا عن النظام الأساسي. كما أنه قابل للتطوير، ويرجع ذلك جزئيًا إلى إمكانات النسخ المتماثل الجغرافيالخاصة بـAPIM.
ما أسباب الإرسال إلى مركز الأحداث ؟
من المنطقي نطرح أن سؤالًا وهو، ما أسباب إنشاء سياسة خاصة بمراكز الأحداث؟ هناك العديد من الأماكن المختلفة حيث قد أرغب في تسجيل طلباتي. لماذا لا تكتفي بإرسال الطلبات مباشرة إلى الوجهة النهائية؟ يمثل ذلك الأمر خيارًا. ولكن عند إجراء طلبات تسجيل من خدمة إدارة واجهة برمجة التطبيقات، من الضروري النظر في كيفية تأثير رسائل التسجيل على أداء واجهة برمجة التطبيقات. يمكن التعامل مع الزيادات التدريجية في الحمل عن طريق زيادة الحالات المتاحة لمكونات النظام أو من خلال الاستفادة من التكرار الجغرافي. ولكن يمكن أن تؤدي الارتفاعات القصيرة في حركة المرور إلى تأخير الطلبات إذا بدأت طلبات تسجيل البنية التحتية في التباطؤ تحت الحمل.
تم تصميم مراكز الأحداث لإدخال كميات ضخمة من البيانات، مع القدرة على التعامل مع عدد أكبر بكثير من الأحداث من عدد طلبات HTTP التي تقوم بمعالجتها معظم واجهات برمجة التطبيقات. يعمل Event Hub كنوع من المخزن المؤقت المعقد بين خدمة إدارة واجهة برمجة التطبيقات والبنية التحتية التي تخزن الرسائل وتعالجها. هذا يضمن أن أداء API الخاص بك لن يتأثر بسبب البنية التحتية للتسجيل.
بمجرد أن يتم تمرير البيانات إلى مركز الأحداث، فإنها تستمر وستنتظر حتى يقوم مستهلكو المركز بمعالجتها. لا يهتم مركز الأحداث بكيفية معالجتها، فهو يهتم فقط بالتأكد من تسليم الرسالة بنجاح.
تمتلك مراكز الأحداث القدرة على بث الأحداث إلى مجموعات متعددة من المستهلكين. هذا يسمح للأحداث أن تتم معالجتها بواسطة أنظمة مختلفة. يتيح ذلك دعم العديد من سيناريوهات التكامل دون وضع تأخيرات إضافية في معالجة طلب واجهة برمجة التطبيقات داخل خدمة APIM حيث يلزم إنشاء حدث واحد فقط.
سياسة لإرسال رسائل التطبيق / http
يقبل Event Hub بيانات الحدث كسلسلة بسيطة. محتويات تلك السلسلة متروك لك. لكي نتمكن من حزم طلب HTTP وإرساله إلى مراكز الأحداث، نحتاج إلى تنسيق السلسلة مع معلومات الطلب أو الاستجابة. في مثل هذه الحالات، إذا كان هناك تنسيق موجود يمكننا إعادة استخدامه، فقد لا نضطر إلى كتابة كود التحليل الخاص بنا. في البداية فكرت في استخدام HAR لإرسال طلبات واستجابات HTTP. ومع ذلك، تم تحسين هذا التنسيق لتخزين سلسلة من طلبات HTTP بتنسيق قائم على JSON. احتوت على عدد من العناصر الإلزامية التي أضافت تعقيدًا غير ضروري لسيناريو تمرير رسالة HTTP عبر السلك.
كان الخيار البديل هو استخدام application/http
نوع الوسائط كما هو موضح في مواصفات RFC 7230. يستخدم نوع الوسائط هذا التنسيق نفسه تمامًا المستخدم لإرسال رسائل HTTP عبر السلك، ولكن يمكن وضع الرسالة بأكملها في نص طلب HTTP آخر. في حالتنا، سنستخدم الجسد كرسالة لإرسالها إلى مراكز الأحداث. لتحقيق التلاؤم يوجد محلل موجود في مكتبات Microsoft ASP.NET Web API 2.2 Client يمكنه تحليل هذا التنسيق وتحويله إلى أصلي HttpRequestMessage
وعناصرHttpResponseMessage
.
لكي نتمكن من إنشاء هذه الرسالة، نحتاج إلى الاستفادة من تعبيرات السياسة المستندة إلى C # في Azure APIM. فيما يلي السياسة التي ترسل رسالة طلب HTTP إلى مراكز الأحداث.
<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>
إعلان توضيح السياسة
هناك بعض الأشياء المعينة الجديرة بالذكر حول تعبير السياسة هذا. تشتمل سياسة log-to-eventhub على سمة تسمى مُعرِّف المُسجِّل، والتي تشير إلى اسم المُسجِّل الذي تم إنشاؤه داخل خدمة APIM. يمكن العثور على تفاصيل كيفية إعداد مسجل مراكز الأحداث في APIM في المستند كيفية تسجيل الأحداث في Azure Event Hubs في Azure API Management. السمة الثانية هي معلمة اختيارية ترشد مراكز الأحداث إلى القسم الذي سيتم تخزين الرسالة فيه. تستخدم مراكز الأحداث أقسامًا لتمكين قابلية التوسع وتتطلب اثنين على الأقل. يتم ضمان التسليم المطلوب للرسائل داخل القسم فقط. إذا لم نوجه مركز الأحداث إلى أي قسم لوضع الرسالة فيه، فإنه يستخدم خوارزمية round-robin لتوزيع الحمل. ومع ذلك، قد يتسبب ذلك في معالجة بعض رسائلنا خارج الترتيب.
الأقسام
لضمان تسليم رسائلنا للمستهلكين بالترتيب والاستفادة من قدرة توزيع التحميل على الأقسام، اخترت إرسال رسائل طلب HTTP إلى قسم واحد ورسائل استجابة HTTP إلى قسم ثان. يضمن هذا توزيعًا متساويًا للحمل ويمكننا ضمان استهلاك جميع الطلبات بالترتيب واستهلاك جميع الردود بالترتيب. من الممكن أن يتم استهلاك الرد قبل الطلب المقابل، ولكن هذه ليست مشكلة لأن لدينا آلية مختلفة لربط الطلبات بالردود ونعلم أن الطلبات تأتي دائمًا قبل الردود.
حمولات خاصة بـHTTP
بعد بناءrequestLine
، نتحقق لمعرفة ما إذا كان يجب اقتطاع نص الطلب. يتم تقسيم نص الطلب إلى 1024 فقط. يمكن زيادتها، على الرغم من أن رسائل مركز الأحداث الفردية تقتصر على 256 كيلوبايت، لذا فمن المحتمل أن بعض نصوص رسائل HTTP لن تتناسب مع رسالة واحدة. عند إجراء التسجيل والتحليلات، يمكن اشتقاق قدر كبير من المعلومات من سطر طلب HTTP وعناوينه فقط. أيضًا، تطلب العديد من واجهات برمجة التطبيقات إعادة الأجسام الصغيرة فقط، وبالتالي فإن فقدان قيمة المعلومات عن طريق اقتطاع الأجسام الكبيرة يكون ضئيلًا إلى حد ما مقارنةً بتخفيض تكاليف النقل والمعالجة والتخزين للاحتفاظ بجميع محتويات الجسم. ملاحظة أخيرة حول معالجة الجسم هي أننا نحتاج إلى المرورtrue
إلى الطريقةAs<string>()
لأننا نقرأ محتويات الجسم، ولكننا أردنا أيضًا أن تكون الواجهة الخلفية API قادرة على قراءة الجسم. من خلال تمرير هذه الطريقة بشكل صحيح، فإننا نتسبب في تخزين الجسم مؤقتًا بحيث يمكن قراءته مرة أخرى. من المهم أن تكون على دراية بما إذا كان لديك واجهة برمجة تطبيقات تقوم بتحميل ملفات كبيرة أو تستخدم استطلاعات طويلة. في هذه الحالات، من الأفضل تجنب قراءة الجسد على الإطلاق.
رؤوس عناوين HTTP
يمكن نقل رؤوس HTTP إلى تنسيق الرسالة بتنسيق زوج مفتاح / قيمة بسيط. لقد اخترنا استبعاد بعض الحقول الأمنية الحساسة لتجنب تسريب معلومات الاعتماد دون داعٍ. من غير المحتمل أن يتم استخدام مفاتيح API وبيانات الاعتماد الأخرى لأغراض التحليل. إذا كنا نرغب في إجراء تحليل للمستخدم والمنتج المعين الذي يستخدمونه، فيمكننا الحصول على ذلك من العنصر context
وإضافة ذلك إلى الرسالة.
بيانات تعريف رسالة
عند إنشاء الرسالة الكاملة لإرسالها إلى مركز الحدث، فإن السطر الأول ليس في الواقع جزءًا من الرسالة application/http
. السطر الأول عبارة عن بيانات تعريف إضافية تتكون مما إذا كانت الرسالة عبارة عن طلب أم رسالة استجابة ومعرف الرسالة، والذي يتم استخدامه لربط الطلبات بالاستجابات. يتم إنشاء معرّف الرسالة باستخدام سياسة أخرى تبدو كالتالي:
<set-variable name="message-id" value="@(Guid.NewGuid())" />
كان بإمكاننا إنشاء رسالة الطلب وتخزينها في متغير حتى يتم إرجاع الاستجابة ثم إرسال الطلب والرد كرسالة واحدة. ولكن من خلال إرسال الطلب والرد بشكل مستقل واستخدام معرف الرسالة للربط بين الاثنين، نحصل على مزيد من المرونة في حجم الرسالة، والقدرة على الاستفادة من الأقسام المتعددة مع الحفاظ على ترتيب الرسائل وسيظهر الطلب في التسجيل لدينا لوحة القيادة عاجلاً. قد تكون هناك أيضًا بعض السيناريوهات التي لا يتم فيها إرسال استجابة صالحة أبدًا إلى مركز الحدث، ربما بسبب خطأ فادح في الطلب في خدمة APIM، ولكن لا يزال لدينا سجل للطلب.
تبدو سياسة إرسال استجابة HTTP مماثلة للطلب ولذا تبدو تهيئة السياسة الكاملة على النحو التالي:
<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>
تنشئ السياسةset-variable
قيمة يمكن الوصول إليها بواسطة كل من السياسةlog-to-eventhub
في القسم<inbound>
والقسم<outbound>
.
استقبال الأحداث من استقبال الأحداث من مراكز الأحداث
يتم تلقي الأحداث من Azure Event Hub باستخدام بروتوكول AMQP. جعل فريق Microsoft Service Bus مكتبات العملاء متاحة لتسهيل الأحداث المستهلكة. هناك طريقتان مختلفتان مدعومتان، أحدهما هو المستهلك المباشروالآخر يستخدم الفصلEventProcessorHost
. يمكن العثور على أمثلة على هاتين الطريقتين في دليل برمجة مراكز الأحداث. الإصدار المختصر من الاختلافات هوDirect Consumer
، يمنحك التحكم الكامل ويقومEventProcessorHost
ببعض أعمال السباكة نيابة عنك ولكنه يضع افتراضات معينة حول كيفية معالجة هذه الأحداث.
EventProcessorHost
في هذه العينة، نستخدم EventProcessorHost
البساطة، ولكن قد لا يكون الخيار الأفضل لهذا السيناريو المحدد. EventProcessorHost
يقوم بالعمل الشاق للتأكد من أنك لست مضطرًا للقلق بشأن مشكلات الترابط داخل فئة معينة لمعالج الأحداث. ومع ذلك في السيناريو الخاص بنا، نقوم ببساطة بتحويل الرسالة إلى تنسيق آخر ونقلها إلى خدمة أخرى باستخدام طريقة غير متزامنة. ليست هناك حاجة لتحديث الحالة المشتركة وبالتالي لا يوجد خطر من مشاكل الترابط. بالنسبة لمعظم السيناريوهات، EventProcessorHost
ربما يكون هو الخيار الأفضل وهو بالتأكيد الخيار الأسهل.
معالج خاص بـIEvent
المفهوم المركزي عند الاستخدامEventProcessorHost
هو إنشاء تنفيذ للواجهةIEventProcessor
التي تحتوي على الطريقةProcessEventAsync
. يظهر جوهر هذه الطريقة هنا:
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 ...
}
يتم تمرير قائمة عناصر EventData إلى الطريقة ونحن نكرر هذه القائمة. يتم تحليل وحدات البايت الخاصة بكل أسلوب في عنصر HttpMessage ويتم تمرير هذا العنصر إلى مثيل IHttpMessageProcessor.
رسائل خاصة بـHttp
يحتوي المثيل HttpMessage
على ثلاثة أجزاء من البيانات:
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 ...
}
يحتوي المثيلHttpMessage
على GUID MessageId
الذي يسمح لنا بتوصيل طلب HTTP باستجابة HTTP المقابلة وقيمة منطقية تحدد ما إذا كان الكائن يحتوي على مثيل HttpRequestMessage و HttpResponseMessage. باستخدام فئات HTTP المضمنة من System.Net.Http
، تمكنت من الاستفادة من كود التحليل application/http
المضمن في System.Net.Http.Formatting
.
معالج رسائل IHttp
يتم بعد ذلك إعادة توجيه المثيل HttpMessage
إلى التنفيذ IHttpMessageProcessor
، وهو واجهة قمت بإنشائها لفصل تلقي الحدث وتفسيره من Azure Event Hub والمعالجة الفعلية له.
إعادة توجيه رسالة HTTP
بالنسبة لهذه العينة، قررت أنه سيكون من المثير للاهتمام دفع طلب HTTP إلى Moesif API Analytics. Moesif هي خدمة سحابية متخصصة في تحليلات وتصحيح أخطاء HTTP. لديهم مستوى مجاني، لذلك من السهل المحاولة ويسمح لنا برؤية طلبات HTTP في الوقت الفعلي المتدفقة من خلال خدمة APIM.
يبدو التنفيذIHttpMessageProcessor
مثل هذا،
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));
}
}
يستفيد MoesifHttpMessageProcessor
من مكتبة C # API الخاصة بـMoesif التي تجعل من السهل دفع بيانات حدث HTTP إلى خدمتهم. لإرسال بيانات HTTP إلى Moesif Collector API، ستكون بحاجة إلى حساب ومعرّف التطبيق، وتحصل على معرف تطبيق Moesif عن طريق إنشاء حساب على موقع Moesif ثم الانتقال إلى إعداد التطبيق ->أعلى القائمة اليُسرى.
عينة كاملة
التعليمة البرمجيةللمصدر واختبارات العينة موجودة على GitHub. أنت بحاجة إلى خدمة APIM ومركز أحداث متصل وحساب تخزين لتشغيل العينة بنفسك.
النموذج هو مجرد تطبيق بسيط لوحدة التحكم يستمع إلى الأحداث القادمة من مركز الأحداث، ويحولها إلى Moesif وEventRequestModel
عناصرEventResponseModel
ثم يعيد توجيهها إلى Moesif Collector API.
في الصورة المتحركة التالية، يمكنك رؤية طلب يتم إجراؤه لواجهة برمجة تطبيقات في مدخل مطور فهرس الفيديو، ويظهر تطبيق وحدة التحكم الرسالة التي يتم استلامها ومعالجتها وإعادة توجيهها، ثم يظهر الطلب والاستجابة في Event Stream.
الملخص
توفر خدمة Azure APIM مكانًا مثاليًا لجمع بيانات HTTP التي تنتقل من وإلى واجهات برمجة التطبيقات الخاصة بك. مراكز الأحداث يعتبر حلًا قابلًا للتطوير بدرجة كبيرة ومنخفض التكلفة لالتقاط حركة المرور هذه وإدخالها في أنظمة المعالجة الثانوية للتسجيل والمراقبة والتحليلات المعقدة الأخرى. يعد الاتصال بأنظمة مراقبة حركة المرور التابعة لجهات خارجية، مثل Moesif أمرًا بسيطًا مثل بضع عشرات من سطور التعليمات البرمجية.
الخطوات التالية
- تعرف على المزيد حول Azure Event Hubs
- تعرف على المزيد حول إدارة واجهة برمجة التطبيقات وتكامل مراكز الأحداث