APM elástico no aplicativo

Não publico artigos há muito tempo e agora ... Este artigo não era muito grande, mas espero que seja útil. Uma vez que decidimos usar o Prometheus para coletar métricas, mas ... Depois de um tempo, decidimos mudar para o Elastic APM, porque já tínhamos toda a pilha Elastic e decidimos dar suporte às métricas nessa pilha.


Portanto, o Elastic APM é uma ferramenta para trabalhar com métricas. Para isso, o aplicativo usa o Elastic APM Agent - agentes para coletar métricas para vários idiomas. Usamos o Elastic APM .NET Agent. O pacote é suportado pelo .NET Framework desde a versão 4.6.2.


Os agentes enviam métricas para o servidor (Elastic APM Server). Todas as configurações necessárias são gravadas em web.config com as chaves específicas que o Elastic APM Agent espera.
Precisamos configurar pelo menos um URL para o Elastic APM Server. Para dividir, ao exibir métricas no LC, por aplicativos e ambientes, precisamos dos seguintes parâmetros:


  • ElasticApm: ServiceName - o nome do serviço, deve atender às seguintes regras: ^ [a-zA-Z0-9 _-] + $.
  • ElasticApm: Environment - o nome do ambiente permite filtrar dados em nível global no aplicativo. Suportado apenas no Kibana a partir da versão 7.2.

Para definir o tempo após o qual as métricas serão enviadas, precisamos do seguinte parâmetro:


  • ElasticApm: MetricsInterval - permite definir o tempo após o qual as métricas serão enviadas ao servidor, por padrão - 5 s. Se o tempo estiver definido como 0, as métricas não serão enviadas ao servidor. Todas as medidas para este parâmetro estão em segundos.

Você também pode configurar o nível de log: ElasticApm: LogLevel.


Um exemplo de preenchimento 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> 

A unidade de trabalho do pacote do Elastic APM Agent é transações - objetos do tipo ITransaction. Os dados da transação são coletados dentro do objeto Reporter e enviados ao servidor uma vez em um horário especificado. As configurações de horário de envio são fornecidas acima.


O início de uma transação começa com uma chamada para o método StartTransaction, finalizando com uma chamada para o método End (). Além do método StartTransaction, você também pode usar o método CaptureTransaction, mas o método End () não é necessário, tudo o que você precisa é passado como parâmetros para o método. Se não for necessário capturar exceções, existe um método CaptureException para isso.


Este método gravará o objeto de transação que conterá a exceção passada a ele. Todas as informações adicionais - metadados - são gravadas no objeto de transação preenchendo a propriedade Labels do objeto de transação.


Então o que aconteceu? Primeiro, adicione um modelo que conterá as configurações necessárias para o objeto ITransaction para nós:


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

Em seguida, criamos um construtor no qual criaremos um objeto de transação, escreveremos metadados na transação, criaremos transações para salvar a exceção, concluiremos a transação:


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

Para a classe na qual as métricas devem ser coletadas, criamos um decorador no qual usaremos nossa classe MetricsService. Não se esqueça de registrar um decorador para a implementação correta de dependências.


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

Espero que este artigo tenha sido útil para quem deseja usar o Elastic APM como uma ferramenta para coletar métricas.

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


All Articles