Elastic APM in der App

Ich habe schon lange keine Artikel mehr veröffentlicht ... Dieser Artikel war nicht sehr groß, aber hoffentlich nützlich. Einmal entschieden wir uns, Prometheus zum Sammeln von Metriken zu verwenden, aber ... Nach einer Weile entschieden wir uns, auf Elastic APM umzusteigen, da wir bereits den gesamten Elastic-Stack hatten und beschlossen, die Metriken in diesem Stack zu unterstützen.


Elastic APM ist also ein Tool für die Arbeit mit Metriken. Dazu verwendet die Anwendung Elastic APM Agent - Agenten zum Sammeln von Metriken für verschiedene Sprachen. Wir haben den Elastic APM .NET Agent verwendet. Das Paket wird von .NET Framework seit Version 4.6.2 unterstützt.


Agenten senden Metriken an den Server (Elastic APM Server). Alle erforderlichen Einstellungen werden in web.config mit den spezifischen Schlüsseln geschrieben, die der Elastic APM-Agent erwartet.
Wir müssen mindestens eine URL für Elastic APM Server konfigurieren. Um die Anzeige von Metriken im LC nach Anwendungen und Umgebungen aufzuteilen, benötigen wir die folgenden Parameter:


  • ElasticApm: ServiceName - Der Name des Dienstes muss die folgenden Regeln erfüllen: ^ [a-zA-Z0-9 _-] + $.
  • ElasticApm: Umgebung - Mit dem Namen der Umgebung können Sie Daten auf globaler Ebene in der Anwendung filtern. Wird nur auf Kibana ab Version 7.2 unterstützt.

Um die Zeit festzulegen, nach der Metriken gesendet werden, benötigen wir den folgenden Parameter:


  • ElasticApm: MetricsInterval - Mit dieser Option können Sie die Zeit einstellen, nach der die Metriken standardmäßig an den Server gesendet werden - 5 s. Wenn die Zeit auf 0 gesetzt ist, werden keine Metriken an den Server gesendet. Alle Messungen für diesen Parameter erfolgen in Sekunden.

Sie können auch die Protokollierungsstufe konfigurieren: ElasticApm: LogLevel.


Ein Beispiel zum Füllen von 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> 

Die Arbeitseinheit des Elastic APM Agent-Pakets sind Transaktionen - Objekte vom Typ ITransaction. Transaktionsdaten werden im Reporter-Objekt erfasst und einmal zu einem bestimmten Zeitpunkt an den Server gesendet. Die Einstellungen für die Sendezeit sind oben angegeben.


Der Start einer Transaktion beginnt mit einem Aufruf der StartTransaction-Methode und endet mit einem Aufruf der End () -Methode. Zusätzlich zur StartTransaction-Methode können Sie auch die CaptureTransaction-Methode verwenden, aber die End () -Methode wird nicht dafür aufgerufen, alles, was Sie benötigen, wird als Parameter an die Methode übergeben. Wenn keine Ausnahmen abgefangen werden müssen, gibt es hierfür eine CaptureException-Methode.


Diese Methode schreibt das Transaktionsobjekt, das die übergebene Ausnahme enthält. Alle zusätzlichen Informationen - Metadaten - werden in das Transaktionsobjekt geschrieben, indem die Labels-Eigenschaft des Transaktionsobjekts ausgefüllt wird.


Also, was ist passiert? Fügen Sie zunächst ein Modell hinzu, das die erforderlichen Einstellungen für das ITransaction-Objekt für uns enthält:


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

Als Nächstes erstellen wir einen Builder, in dem wir ein Transaktionsobjekt erstellen, Metadaten in die Transaktion schreiben, Transaktionen zum Speichern der Ausnahme erstellen und die Transaktion abschließen:


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

Für die Klasse, in der die Metriken gesammelt werden sollen, erstellen wir einen Dekorator, in dem wir unsere MetricsService-Klasse verwenden. Vergessen Sie nicht, einen Decorator für die korrekte Implementierung von Abhängigkeiten zu registrieren.


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

Ich hoffe, dieser Artikel war hilfreich für diejenigen, die Elastic APM als Tool zum Sammeln von Metriken verwenden möchten.

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


All Articles