APM élastique dans l'application

Je n'ai pas publié d'articles depuis longtemps et maintenant encore ... Cet article n'était pas très volumineux, mais, je l'espère, utile. Une fois, nous avons décidé d'utiliser Prometheus pour collecter des métriques, mais ... Après un certain temps, nous avons décidé de passer à Elastic APM, car nous avions déjà l'intégralité de la pile Elastic et nous avons décidé de prendre en charge les métriques dans cette pile.


Ainsi, Elastic APM est un outil pour travailler avec des métriques, pour cela l'application utilise Elastic APM Agent - des agents pour collecter des métriques pour différentes langues. Nous avons utilisé l'agent Elastic APM .NET. Le package est pris en charge par le .NET Framework depuis la version 4.6.2.


Les agents envoient des métriques au serveur (Elastic APM Server). Tous les paramètres nécessaires sont écrits dans web.config avec les clés spécifiques attendues par l'agent Elastic APM.
Nous devons configurer au moins une URL pour Elastic APM Server. Afin de diviser, lors de l'affichage des métriques dans le LC, par applications et environnements, nous avons besoin des paramètres suivants:


  • ElasticApm: ServiceName - le nom du service, doit satisfaire aux règles suivantes: ^ [a-zA-Z0-9 _-] + $.
  • ElasticApm: Environment - le nom de l'environnement vous permet de filtrer les données au niveau global dans l'application. Uniquement pris en charge sur Kibana à partir de la version 7.2.

Pour définir l'heure après laquelle les mesures seront envoyées, nous avons besoin du paramètre suivant:


  • ElasticApm: MetricsInterval - vous permet de définir le délai après lequel les métriques seront envoyées au serveur, par défaut - 5 s. Si l'heure est définie sur 0, les mesures ne seront pas envoyées au serveur. Toutes les mesures pour ce paramètre sont en secondes.

Vous pouvez également configurer le niveau de journalisation: ElasticApm: LogLevel.


Un exemple de remplissage de 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> 

L'unité de travail du package Elastic APM Agent est les transactions - objets de type ITransaction. Les données de transaction sont collectées à l'intérieur de l'objet Reporter et envoyées au serveur une fois à une heure spécifiée. Les paramètres d'heure d'envoi sont indiqués ci-dessus.


Le début d'une transaction commence par un appel à la méthode StartTransaction et se termine par un appel à la méthode End (). En plus de la méthode StartTransaction, vous pouvez également utiliser la méthode CaptureTransaction, mais la méthode End () n'est pas appelée pour cela, tout ce dont vous avez besoin est transmis en tant que paramètres à la méthode. S'il n'est pas nécessaire d'intercepter des exceptions, il existe une méthode CaptureException pour cela.


Cette méthode écrira l'objet de transaction qui contiendra l'exception qui lui est transmise. Toutes les informations supplémentaires - les métadonnées - sont écrites dans l'objet de transaction en remplissant la propriété Labels de l'objet de transaction.


Que s'est-il donc passé? Tout d'abord, ajoutez un modèle qui contiendra les paramètres nécessaires pour l'objet ITransaction pour nous:


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

Ensuite, nous créons un générateur dans lequel nous allons créer un objet de transaction, écrire des métadonnées dans la transaction, créer des transactions pour enregistrer l'exception, terminer la transaction:


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

Pour la classe dans laquelle les métriques doivent être collectées, nous créons un décorateur dans lequel nous utiliserons notre classe MetricsService. N'oubliez pas d'enregistrer un décorateur pour l'implémentation correcte des dépendances.


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

J'espère que cet article a été utile à ceux qui souhaitent utiliser Elastic APM comme outil de collecte de métriques.

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


All Articles