APM مرنة في التطبيق

لم أقم بنشر مقالات منذ فترة طويلة والآن مرة أخرى ... هذه المقالة لم تكن كبيرة جدًا ، ولكن ، كما نأمل ، مفيدة. بمجرد أن قررنا استخدام Prometheus لجمع المقاييس ، لكن ... بعد فترة من الوقت ، قررنا التبديل إلى Elastic APM ، لأننا حصلنا بالفعل على رصة Flex بأكملها وقررنا دعم المقاييس داخل هذه المجموعة.


لذلك ، فإن APM المرنة هي أداة للعمل مع المقاييس ، لهذا التطبيق يستخدم Elastic APM Agent - الوكلاء لجمع المقاييس بلغات مختلفة. استخدمنا وكيل APM المرن. يتم دعم الحزمة بواسطة .NET Framework منذ الإصدار 4.6.2.


يرسل الوكلاء المقاييس إلى الخادم (خادم مرن APM). تتم كتابة جميع الإعدادات اللازمة في web.config باستخدام المفاتيح المحددة التي يتوقعها عامل APM المرن.
نحن بحاجة إلى تكوين عنوان url لخادم APM المرن على الأقل. للتقسيم ، عند عرض المقاييس في LC ، حسب التطبيقات والبيئات ، نحتاج إلى المعلمات التالية:


  • ElasticApm: اسم الخدمة - اسم الخدمة ، يجب أن يستوفي القواعد التالية: ^ [a-zA-Z0-9 _-] + $.
  • ElasticApm: البيئة - اسم البيئة يسمح لك بتصفية البيانات على مستوى عالمي في التطبيق. معتمد فقط على Kibana بدءًا من الإصدار 7.2.

لضبط الوقت الذي سيتم بعده إرسال المقاييس ، نحتاج إلى المعلمة التالية:


  • ElasticApm: MetricsInterval - يتيح لك ضبط الوقت الذي سيتم بعده إرسال المقاييس إلى الخادم ، افتراضيًا - 5 ثوانٍ. إذا تم ضبط الوقت على 0 ، فلن يتم إرسال المقاييس إلى الخادم. جميع القياسات لهذه المعلمة في ثوان.

يمكنك أيضًا تكوين مستوى التسجيل: ElasticApm: LogLevel.


مثال لملء web.config:


<?xml version="1.0" encoding="utf-8"?> <!-- ... --> <configuration> <!-- ... --> <appSettings> <!-- ... --> <add key="ElasticApm:ServerUrls" value="https://my-apm-server:8200" /> <add key="ElasticApm:MetricsInterval" value="10" /> <add key="ElasticApm:Environment" value="Stage" /> <add key="ElasticApm:ServiceName" value="Web.Api" /> <!-- ... --> </appSettings> <!-- ... --> </configuration> 

وحدة عمل حزمة وكيل APM المرنة هي المعاملات - كائنات من نوع ITransaction. يتم جمع بيانات المعاملات داخل كائن المراسل وإرسالها إلى الخادم مرة واحدة في وقت محدد. وترد إعدادات وقت الإرسال أعلاه.


تبدأ بداية المعاملة باستدعاء أسلوب StartTransaction ، والانتهاء باستدعاء الأسلوب End (). بالإضافة إلى أسلوب StartTransaction ، يمكنك أيضًا استخدام طريقة CaptureTransaction ، ولكن لم يتم استدعاء الطريقة End () لذلك ، يتم تمرير كل ما تحتاجه كمعلمات للطريقة. إذا لم يكن من الضروري التقاط استثناءات ، فهناك طريقة CaptureException لهذا الغرض.


ستقوم هذه الطريقة بكتابة كائن المعاملة الذي سيحتوي على الاستثناء الذي تم تمريره إليه. تتم كتابة جميع المعلومات الإضافية - بيانات التعريف - إلى كائن المعاملة عن طريق ملء خاصية الملصقات الخاصة بكائن المعاملة.


إذن ماذا حدث؟ أولاً ، أضف نموذجًا يحتوي على الإعدادات اللازمة لكائن ITransaction بالنسبة لنا:


 public class MeasurementData { public static string ApmServerUrl => ConfigurationManager.AppSettings["ElasticApm:ServerUrls"] ?? "http://localhost:8200"; public static string MetricsInterval => ConfigurationManager.AppSettings["ElasticApm:MetricsInterval"] ?? "10"; public static string ApmEnvironment => ConfigurationManager.AppSettings["ElasticApm:Environment"] ?? "local"; public static string ServiceName => ConfigurationManager.AppSettings["ElasticApm:ServiceName"] ?? "Api"; public ITransaction MetricsObject { get; set; } //    public static ITransaction Create(string metricsName) { Environment.SetEnvironmentVariable(ConfigConsts.EnvVarNames.ServerUrls, ApmServerUrl); Environment.SetEnvironmentVariable(ConfigConsts.EnvVarNames.MetricsInterval, MetricsInterval); Environment.SetEnvironmentVariable(ConfigConsts.EnvVarNames.Environment, ApmEnvironment); Environment.SetEnvironmentVariable(ConfigConsts.EnvVarNames.ServiceName, ServiceName); return Agent.Tracer.StartTransaction(metricsName, ApiConstants.TypeRequest); } } 

بعد ذلك ، نقوم بإنشاء منشئ سنقوم بإنشاء كائن معاملة فيه ، وكتابة البيانات الوصفية للمعاملة ، وإنشاء معاملات لحفظ الاستثناء ، واستكمال المعاملة:


 public class MetricsBuilder { private readonly MeasurementData _measurementData = new MeasurementData(); private string _metricName; public void BuildMetrics(string metricName) { //      ,   _metricName = string.IsNullOrEmpty(metricName) ? "api_payment_request_duration" : metricName; CheckAndCreateMetricsObjects(); } //      public void AddMetricsLabels(string key, string value) { CheckAndCreateMetricsObjects(); if (!_measurementData.MetricsObject.Labels.ContainsKey(key)) { _measurementData.MetricsObject.Labels.Add(key, value); return; } _measurementData.MetricsObject.Labels[key] = value; } //    public void CaptureMetricException(Exception exception) { CheckAndCreateMetricsObjects(); _measurementData.MetricsObject.CaptureException(exception); } public void Dispose() { CheckAndCreateMetricsObjects(); _measurementData.MetricsObject.End(); } // ,      , //   -  private void CheckAndCreateMetricsObjects() { if (_measurementData.MetricsObject == null) { _measurementData.MetricsObject = MeasurementData.Create(_metricName); Logger.Info($"{nameof(MetricsBuilder)}: CurrentTransaction: {JsonConvert.SerializeObject(Agent.Tracer.CurrentTransaction)} " + $"ServerUrls: {JsonConvert.SerializeObject(Agent.Config.ServerUrls)} " + $"MetricsIntervalInMilliseconds: {JsonConvert.SerializeObject(Agent.Config.MetricsIntervalInMilliseconds)}"); } } } public interface IMetricsService : IDisposable { void AddMetricsLabels(string key, string value); void CaptureMetricException(string message, Exception exception); } public class MetricsService : IMetricsService { private readonly MetricsBuilder _builder; public MetricsService(string metricName) { _builder = new MetricsBuilder(); Build(metricName); } public void AddMetricsLabels(string key, string value) { try { _builder.AddMetricsLabels(key, value); } catch (Exception exception) { CaptureMetricException("Can't write metrics labels", exception); } } public void CaptureMetricException(string message, Exception exception) { Logger.Error(message, exception); try { _builder.CaptureMetricException(exception); } catch (Exception exec) { Logger.Error("Can't write capture exception of metrics", exec); } } public void Dispose() { try { _builder.Dispose(); } catch (Exception exception) { CaptureMetricException($"Can't to do correct dispose of object: {typeof(MetricsService)}", exception); } } private void Build(string metricName) { try { _builder.BuildMetrics(metricName); } catch (Exception exception) { CaptureMetricException("Can't create metrics object", exception); } } } 

بالنسبة للفئة التي سيتم فيها جمع المقاييس ، نقوم بإنشاء مصمم نستخدم فيه فئة MetricsService. لا تنسى تسجيل ديكور للتنفيذ الصحيح للتبعيات.


 public class TestClientMetricsService : ITestObject { private readonly ITestObject _testObject; public GatewayClientMetricsService(ITestObject testObject) { _testObject = testObject; } public ProcessingResult TestMethod() { return WriteMetrics("Test_Method", () => _testObject.Test()); } private ProcessingResult WriteMetrics(string methodName, Func<Result> testMethod) { using (var metricsService = new MetricsService(methodName)) { try { metricsService.AddMetricsLabels("assemblyName", Assembly.GetCallingAssembly().FullName); var requestResult = testMethod(); metricsService.AddMetricsLabels("success", requestResult?.Success.ToString() ?? "false"); metricsService.AddMetricsLabels("resultCode", requestResult?.GetResultCode().ToString()); return requestResult; } catch (Exception exception) { metricsService.CaptureMetricException("Can't write metrics", exception); throw; } } } } 

آمل أن يكون هذا المقال مفيدًا لأولئك الذين يريدون استخدام تطبيق APM المرن كأداة لجمع المقاييس.

Source: https://habr.com/ru/post/ar485996/


All Articles