From 93769aac4f67879a557316e1f04f440760a92dba Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Mon, 19 Feb 2024 17:25:56 +0600 Subject: [PATCH 01/17] Group metrics to improve prometheus performance + removed not working metrics from MetricsHttpClientWrapper.cs --- .../HttpWrapper/MetricsHttpClientWrapper.cs | 794 +++++++++--------- ATI.Services.Common/Metrics/MetricsFactory.cs | 66 +- 2 files changed, 432 insertions(+), 428 deletions(-) diff --git a/ATI.Services.Common/Metrics/HttpWrapper/MetricsHttpClientWrapper.cs b/ATI.Services.Common/Metrics/HttpWrapper/MetricsHttpClientWrapper.cs index 07eb715..13bfab7 100644 --- a/ATI.Services.Common/Metrics/HttpWrapper/MetricsHttpClientWrapper.cs +++ b/ATI.Services.Common/Metrics/HttpWrapper/MetricsHttpClientWrapper.cs @@ -15,537 +15,525 @@ using JetBrains.Annotations; using NLog; -namespace ATI.Services.Common.Metrics.HttpWrapper +namespace ATI.Services.Common.Metrics.HttpWrapper; + +/// +/// Для удобства лучше использовать ConsulMetricsHttpClientWrapper из ATI.Services.Consul +/// Он внутри себя инкапсулирует ConsulServiceAddress и MetricsFactory +/// +[PublicAPI] +public class MetricsHttpClientWrapper { - /// - /// Для удобства лучше использовать ConsulMetricsHttpClientWrapper из ATI.Services.Consul - /// Он внутри себя инкапсулирует ConsulServiceAddress и MetricsFactory - /// - [PublicAPI] - public class MetricsHttpClientWrapper + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + private readonly Func _logLevelOverride; + + private const string LogMessageTemplate = + "Сервис:{0} в ответ на запрос [HTTP {1} {2}] вернул ответ с статус кодом {3}."; + + public MetricsHttpClientWrapper(MetricsHttpClientConfig config) { - private readonly ILogger _logger; - private readonly HttpClient _httpClient; - private readonly MetricsFactory _metricsFactory; - private readonly Func _logLevelOverride; + Config = config; + _logger = LogManager.GetLogger(Config.ServiceName); + _httpClient = CreateHttpClient(config.Headers, config.PropagateActivity); + _logLevelOverride = Config.LogLevelOverride; + } - private const string LogMessageTemplate = - "Сервис:{0} в ответ на запрос [HTTP {1} {2}] вернул ответ с статус кодом {3}."; + public MetricsHttpClientConfig Config { get; } - public MetricsHttpClientWrapper(MetricsHttpClientConfig config) + private HttpClient CreateHttpClient(Dictionary additionalHeaders, bool propagateActivity = true) + { + HttpClient httpClient; + if (propagateActivity) { - Config = config; - _logger = LogManager.GetLogger(Config.ServiceName); - _httpClient = CreateHttpClient(config.Headers, config.PropagateActivity); - _metricsFactory = MetricsFactory.CreateExternalHttpMetricsFactory(); - _logLevelOverride = Config.LogLevelOverride; + httpClient = new HttpClient(); } - - public MetricsHttpClientConfig Config { get; } - - private HttpClient CreateHttpClient(Dictionary additionalHeaders, bool propagateActivity = true) + else { - HttpClient httpClient; - if (propagateActivity) - { - httpClient = new HttpClient(); - } - else + httpClient = new HttpClient(new SocketsHttpHandler { - httpClient = new HttpClient(new SocketsHttpHandler - { - ActivityHeadersPropagator = DistributedContextPropagator.CreateNoOutputPropagator() - }); - } + ActivityHeadersPropagator = DistributedContextPropagator.CreateNoOutputPropagator() + }); + } - httpClient.Timeout = Config.Timeout; - httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - if (!ServiceVariables.ServiceAsClientName.IsNullOrEmpty() && - !ServiceVariables.ServiceAsClientHeaderName.IsNullOrEmpty()) - { - httpClient.DefaultRequestHeaders.Add( - ServiceVariables.ServiceAsClientHeaderName, - ServiceVariables.ServiceAsClientName); - } + httpClient.Timeout = Config.Timeout; + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + if (!ServiceVariables.ServiceAsClientName.IsNullOrEmpty() && + !ServiceVariables.ServiceAsClientHeaderName.IsNullOrEmpty()) + { + httpClient.DefaultRequestHeaders.Add( + ServiceVariables.ServiceAsClientHeaderName, + ServiceVariables.ServiceAsClientName); + } - if (additionalHeaders != null && additionalHeaders.Count > 0) + if (additionalHeaders != null && additionalHeaders.Count > 0) + { + foreach (var header in additionalHeaders) { - foreach (var header in additionalHeaders) - { - httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); - } + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); } - - return httpClient; } + return httpClient; + } - public async Task> GetAsync(Uri fullUrl, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, fullUrl, headers)); + public async Task> GetAsync(Uri fullUrl, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, fullUrl, headers)); - public async Task> GetAsync(string serviceAddress, string metricName, - string url, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Get, FullUri(serviceAddress, url), headers)); + public async Task> GetAsync(string serviceAddress, string metricName, + string url, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Get, FullUri(serviceAddress, url), headers)); - public async Task> GetAsync(string serviceAddress, string metricName, string url, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, FullUri(serviceAddress, url), headers)); + public async Task> GetAsync(string serviceAddress, string metricName, string url, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, FullUri(serviceAddress, url), headers)); - public async Task> GetAsync(Uri fullUrl, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, fullUrl, headers)); + public async Task> GetAsync(Uri fullUrl, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Get, fullUrl, headers)); - public async Task> PostAsync(string serviceAddress, string metricName, - string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PostAsync(string serviceAddress, string metricName, + string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PostAsync(Uri fullUrl, string metricName, - TModel model, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PostAsync(Uri fullUrl, string metricName, + TModel model, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PostAsync(string serviceAddress, string metricName, - string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PostAsync(string serviceAddress, string metricName, + string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PostAsync(Uri fullUrl, string metricName, TModel model, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PostAsync(Uri fullUrl, string metricName, TModel model, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PostAsync(string serviceAddress, string metricName, - string url, string rawContent, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) - { - Content = rawContent - }); + public async Task> PostAsync(string serviceAddress, string metricName, + string url, string rawContent, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) + { + Content = rawContent + }); - public async Task> PostAsync(Uri fullUrl, string metricName, - string rawContent, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) - { - Content = rawContent - }); + public async Task> PostAsync(Uri fullUrl, string metricName, + string rawContent, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers) + { + Content = rawContent + }); - public async Task> PostAsync(string serviceAddress, string metricName, - string url, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers)); + public async Task> PostAsync(string serviceAddress, string metricName, + string url, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers)); - public async Task> PostAsync(Uri fullUrl, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers)); + public async Task> PostAsync(Uri fullUrl, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUrl, headers)); - public async Task> PostAsync(string serviceAddress, string metricName, string url, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers)); + public async Task> PostAsync(string serviceAddress, string metricName, string url, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers)); - public async Task> PostAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUri, headers)); + public async Task> PostAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUri, headers)); - public async Task> PostAsync(string serviceAddress, string metricName, string url, - string rawContent, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) { Content = rawContent }); + public async Task> PostAsync(string serviceAddress, string metricName, string url, + string rawContent, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Post, FullUri(serviceAddress, url), headers) { Content = rawContent }); - public async Task> PostAsync(Uri fullUri, string metricName, string rawContent, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUri, headers) { Content = rawContent }); + public async Task> PostAsync(Uri fullUri, string metricName, string rawContent, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Post, fullUri, headers) { Content = rawContent }); - public async Task> PutAsync(string serviceAddress, string metricName, - string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PutAsync(string serviceAddress, string metricName, + string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PutAsync(Uri fullUri, string metricName, - TModel model, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PutAsync(Uri fullUri, string metricName, + TModel model, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PutAsync(string serviceAddress, string metricName, - string url, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers)); + public async Task> PutAsync(string serviceAddress, string metricName, + string url, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers)); - public async Task> PutAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers)); + public async Task> PutAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers)); - public async Task> PutAsync(string serviceAddress, string metricName, string url, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers)); + public async Task> PutAsync(string serviceAddress, string metricName, string url, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, FullUri(serviceAddress, url), headers)); - public async Task> PutAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers)); + public async Task> PutAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Put, fullUri, headers)); - public async Task> DeleteAsync(string serviceAddress, - string metricName, string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> DeleteAsync(string serviceAddress, + string metricName, string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> DeleteAsync(string serviceAddress, string metricName, - string url, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers)); - public async Task> DeleteAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, fullUri, headers)); + public async Task> DeleteAsync(string serviceAddress, string metricName, + string url, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers)); + public async Task> DeleteAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, fullUri, headers)); - public async Task> DeleteAsync(string serviceAddress, string metricName, string url, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers)); + public async Task> DeleteAsync(string serviceAddress, string metricName, string url, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, FullUri(serviceAddress, url), headers)); - public async Task> DeleteAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, fullUri, headers)); + public async Task> DeleteAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Delete, fullUri, headers)); - public async Task> PatchAsync(string serviceAddress, - string metricName, - string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PatchAsync(string serviceAddress, + string metricName, + string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PatchAsync(Uri fullUri, string metricName, - TModel model, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PatchAsync(Uri fullUri, string metricName, + TModel model, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PatchAsync(string serviceAddress, string metricName, - string url, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers)); + public async Task> PatchAsync(string serviceAddress, string metricName, + string url, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers)); - public async Task> PatchAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers)); + public async Task> PatchAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers)); - public async Task> PatchAsync(string serviceAddress, string metricName, - string url, TModel model, Dictionary headers = null) - => await SendAsync(metricName, - new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PatchAsync(string serviceAddress, string metricName, + string url, TModel model, Dictionary headers = null) + => await SendAsync(metricName, + new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PatchAsync(Uri fullUri, string metricName, - TModel model, Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers) - { - Content = Config.Serializer.Serialize(model) - }); + public async Task> PatchAsync(Uri fullUri, string metricName, + TModel model, Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers) + { + Content = Config.Serializer.Serialize(model) + }); - public async Task> PatchAsync(string serviceAddress, string metricName, string url, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers)); + public async Task> PatchAsync(string serviceAddress, string metricName, string url, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, FullUri(serviceAddress, url), headers)); - public async Task> PatchAsync(Uri fullUri, string metricName, - Dictionary headers = null) - => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers)); + public async Task> PatchAsync(Uri fullUri, string metricName, + Dictionary headers = null) + => await SendAsync(metricName, new HttpMessage(HttpMethod.Patch, fullUri, headers)); - public async Task>> SendAsync(Uri fullUri, - string metricName, TModel model, - Dictionary headers = null, - HttpMethod method = null) + + public async Task>> SendAsync(Uri fullUri, + string metricName, TModel model, + Dictionary headers = null, + HttpMethod method = null) + { + try { - try + if (fullUri == null) + return new OperationResult>(ActionStatus.InternalServerError, + "Адрес сообщения не указан (fullUri==null)"); + + var message = new HttpMessage(method ?? HttpMethod.Put, fullUri, headers) { - if (fullUri == null) - return new OperationResult>(ActionStatus.InternalServerError, - "Адрес сообщения не указан (fullUri==null)"); + Content = model != null ? Config.Serializer.Serialize(model) : "" + }; - var message = new HttpMessage(method ?? HttpMethod.Put, fullUri, headers) - { - Content = model != null ? Config.Serializer.Serialize(model) : "" - }; + using var requestMessage = message.ToRequestMessage(Config); - using (_metricsFactory.CreateMetricsTimer(metricName, message.Content)) - { - using var requestMessage = message.ToRequestMessage(Config); - - using var responseMessage = await _httpClient.SendAsync(requestMessage); - var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); - - if (!responseMessage.IsSuccessStatusCode) - { - var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, - message.FullUri, responseMessage.StatusCode); - - var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError - ? _logLevelOverride(LogLevel.Error) - : _logLevelOverride(LogLevel.Warn); - _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); - } - - var result = new HttpResponseMessage - { - StatusCode = responseMessage.StatusCode, - Headers = responseMessage.Headers, - TrailingHeaders = responseMessage.TrailingHeaders, - ReasonPhrase = responseMessage.ReasonPhrase, - Version = responseMessage.Version, - RawContent = responseContent - }; - - try - { - result.Content = !string.IsNullOrEmpty(result.RawContent) - ? Config.Serializer.Deserialize(result.RawContent) - : default; - } - catch (TaskCanceledException e) when (e.InnerException is TimeoutException) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Warn), - e, - logObjects: new - { - MetricName = metricName, FullUri = fullUri, Model = model, - Headers = headers - }); - return new OperationResult>(ActionStatus.Timeout); - } - catch (Exception e) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: - new - { - MetricName = metricName, FullUri = fullUri, Model = model, - Headers = headers, - ResponseBody = result.RawContent - }); - } - - return new(result, OperationResult.GetActionStatusByHttpStatusCode(result.StatusCode)); - } + using var responseMessage = await _httpClient.SendAsync(requestMessage); + var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (!responseMessage.IsSuccessStatusCode) + { + var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, + message.FullUri, responseMessage.StatusCode); + + var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError + ? _logLevelOverride(LogLevel.Error) + : _logLevelOverride(LogLevel.Warn); + _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); + } + + var result = new HttpResponseMessage + { + StatusCode = responseMessage.StatusCode, + Headers = responseMessage.Headers, + TrailingHeaders = responseMessage.TrailingHeaders, + ReasonPhrase = responseMessage.ReasonPhrase, + Version = responseMessage.Version, + RawContent = responseContent + }; + + try + { + result.Content = !string.IsNullOrEmpty(result.RawContent) + ? Config.Serializer.Deserialize(result.RawContent) + : default; + } + catch (TaskCanceledException e) when (e.InnerException is TimeoutException) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Warn), + e, + logObjects: new + { + MetricName = metricName, FullUri = fullUri, Model = model, + Headers = headers + }); + return new OperationResult>(ActionStatus.Timeout); } catch (Exception e) { _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: new { MetricName = metricName, FullUri = fullUri, Model = model, Headers = headers }); - return new OperationResult>(e); + e, + logObjects: + new + { + MetricName = metricName, FullUri = fullUri, Model = model, + Headers = headers, + ResponseBody = result.RawContent + }); } + + return new(result, OperationResult.GetActionStatusByHttpStatusCode(result.StatusCode)); + } + catch (Exception e) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Error), + e, + logObjects: new { MetricName = metricName, FullUri = fullUri, Model = model, Headers = headers }); + return new OperationResult>(e); } + } - private async Task> SendAsync(string methodName, HttpMessage message) + private async Task> SendAsync(string methodName, HttpMessage message) + { + try { - using (_metricsFactory.CreateMetricsTimer(methodName, message.Content)) - { - try - { - if (message.FullUri == null) - return new OperationResult(ActionStatus.InternalServerError, - "Адрес сообщения не указан (message.FullUri==null)"); + if (message.FullUri == null) + return new OperationResult(ActionStatus.InternalServerError, + "Адрес сообщения не указан (message.FullUri==null)"); - using var requestMessage = message.ToRequestMessage(Config); + using var requestMessage = message.ToRequestMessage(Config); - using var responseMessage = await _httpClient.SendAsync(requestMessage); + using var responseMessage = await _httpClient.SendAsync(requestMessage); - if (responseMessage.IsSuccessStatusCode) - { - var stream = await responseMessage.Content.ReadAsStreamAsync(); - var result = await Config.Serializer.DeserializeAsync(stream); - return new OperationResult(result, OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); - } + if (responseMessage.IsSuccessStatusCode) + { + var stream = await responseMessage.Content.ReadAsStreamAsync(); + var result = await Config.Serializer.DeserializeAsync(stream); + return new OperationResult(result, OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); + } - var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, - message.FullUri, responseMessage.StatusCode); - var responseContent = await responseMessage.Content.ReadAsStringAsync(); + var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, + message.FullUri, responseMessage.StatusCode); + var responseContent = await responseMessage.Content.ReadAsStringAsync(); - var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError - ? _logLevelOverride(LogLevel.Error) - : _logLevelOverride(LogLevel.Warn); - _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); + var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError + ? _logLevelOverride(LogLevel.Error) + : _logLevelOverride(LogLevel.Warn); + _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); - return new OperationResult(OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); - } - catch (TaskCanceledException e) when (e.InnerException is TimeoutException) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: new { Method = methodName, Message = message }); - return new OperationResult(ActionStatus.Timeout); - } - catch (Exception e) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: new { Method = methodName, Message = message }); - return new OperationResult(e); - } - } + return new OperationResult(OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); + } + catch (TaskCanceledException e) when (e.InnerException is TimeoutException) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Error), + e, + logObjects: new { Method = methodName, Message = message }); + return new OperationResult(ActionStatus.Timeout); } + catch (Exception e) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Error), + e, + logObjects: new { Method = methodName, Message = message }); + return new OperationResult(e); + } + } - private async Task> SendAsync(string methodName, HttpMessage message) + private async Task> SendAsync(string methodName, HttpMessage message) + { + try { - using (_metricsFactory.CreateMetricsTimer(methodName, message.Content)) - { - try - { - if (message.FullUri == null) - return new OperationResult(ActionStatus.InternalServerError); + if (message.FullUri == null) + return new OperationResult(ActionStatus.InternalServerError); - using var requestMessage = message.ToRequestMessage(Config); + using var requestMessage = message.ToRequestMessage(Config); - using var responseMessage = await _httpClient.SendAsync(requestMessage); - var responseContent = await responseMessage.Content.ReadAsStringAsync(); + using var responseMessage = await _httpClient.SendAsync(requestMessage); + var responseContent = await responseMessage.Content.ReadAsStringAsync(); - if (responseMessage.IsSuccessStatusCode) - return new OperationResult(responseContent, OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); + if (responseMessage.IsSuccessStatusCode) + return new OperationResult(responseContent, OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); - var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, - message.FullUri, responseMessage.StatusCode); + var logMessage = string.Format(LogMessageTemplate, Config.ServiceName, message.Method, + message.FullUri, responseMessage.StatusCode); - var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError - ? _logLevelOverride(LogLevel.Error) - : _logLevelOverride(LogLevel.Warn); - _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); + var logLevel = responseMessage.StatusCode == HttpStatusCode.InternalServerError + ? _logLevelOverride(LogLevel.Error) + : _logLevelOverride(LogLevel.Warn); + _logger.LogWithObject(logLevel, ex: null, logMessage, logObjects: responseContent); - return new OperationResult(responseContent, - OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); - } - catch (TaskCanceledException e) when (e.InnerException is TimeoutException) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: new { Method = methodName, Message = message }); - return new OperationResult(ActionStatus.Timeout); - } - catch (Exception e) - { - _logger.LogWithObject(_logLevelOverride(LogLevel.Error), - e, - logObjects: new { Method = methodName, Message = message }); - return new OperationResult(e); - } - } + return new OperationResult(responseContent, + OperationResult.GetActionStatusByHttpStatusCode(responseMessage.StatusCode)); + } + catch (TaskCanceledException e) when (e.InnerException is TimeoutException) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Error), + e, + logObjects: new { Method = methodName, Message = message }); + return new OperationResult(ActionStatus.Timeout); } + catch (Exception e) + { + _logger.LogWithObject(_logLevelOverride(LogLevel.Error), + e, + logObjects: new { Method = methodName, Message = message }); + return new OperationResult(e); + } + } - private Uri FullUri(string serviceAddress, string url) => - serviceAddress != null ? new Uri(new Uri(serviceAddress), url ?? "") : null; + private Uri FullUri(string serviceAddress, string url) => + serviceAddress != null ? new Uri(new Uri(serviceAddress), url ?? "") : null; - private class HttpMessage - { - private readonly string ContentTypeHeaderName = "Content-Type"; + private class HttpMessage + { + private readonly string ContentTypeHeaderName = "Content-Type"; - public HttpMessage(HttpMethod method, Uri fullUri, Dictionary headers) - { - Method = method; - FullUri = fullUri; - Headers = new Dictionary(); - ContentType = "application/json"; + public HttpMessage(HttpMethod method, Uri fullUri, Dictionary headers) + { + Method = method; + FullUri = fullUri; + Headers = new Dictionary(); + ContentType = "application/json"; - if (headers == null) - return; + if (headers == null) + return; - foreach (var header in headers) + foreach (var header in headers) + { + if (string.Equals(header.Key, ContentTypeHeaderName, + StringComparison.InvariantCultureIgnoreCase)) { - if (string.Equals(header.Key, ContentTypeHeaderName, - StringComparison.InvariantCultureIgnoreCase)) - { - ContentType = header.Value; - } - else - { - Headers.Add(header.Key, header.Value); - } + ContentType = header.Value; + } + else + { + Headers.Add(header.Key, header.Value); } } + } - public HttpMethod Method { get; } - public string Content { get; init; } - public Uri FullUri { get; } - public Dictionary Headers { get; } - private string ContentType { get; } - - internal HttpRequestMessage ToRequestMessage(MetricsHttpClientConfig config) - { - var msg = new HttpRequestMessage(Method, FullUri); + public HttpMethod Method { get; } + public string Content { get; init; } + public Uri FullUri { get; } + public Dictionary Headers { get; } + private string ContentType { get; } - if (config.HeadersToProxy.Count != 0) - Headers.AddRange(AppHttpContext.HeadersAndValuesToProxy(config.HeadersToProxy)); + internal HttpRequestMessage ToRequestMessage(MetricsHttpClientConfig config) + { + var msg = new HttpRequestMessage(Method, FullUri); - foreach (var header in Headers) - msg.Headers.TryAddWithoutValidation(header.Key, header.Value); + if (config.HeadersToProxy.Count != 0) + Headers.AddRange(AppHttpContext.HeadersAndValuesToProxy(config.HeadersToProxy)); - string acceptLanguage; - if (config.AddCultureToRequest - && (acceptLanguage = FlowContext.Current.AcceptLanguage) != null) - msg.Headers.Add("Accept-Language", acceptLanguage); + foreach (var header in Headers) + msg.Headers.TryAddWithoutValidation(header.Key, header.Value); + string acceptLanguage; + if (config.AddCultureToRequest + && (acceptLanguage = FlowContext.Current.AcceptLanguage) != null) + msg.Headers.Add("Accept-Language", acceptLanguage); - if (string.IsNullOrEmpty(Content) == false) - { - msg.Content = new StringContent(Content, Encoding.UTF8, ContentType); - } - return msg; + if (string.IsNullOrEmpty(Content) == false) + { + msg.Content = new StringContent(Content, Encoding.UTF8, ContentType); } + + return msg; } } } \ No newline at end of file diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index 9c51316..a382196 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -14,11 +14,12 @@ public class MetricsFactory private Summary Summary { get; } private static string _serviceName = "default_name"; private static readonly string MachineName = Environment.MachineName; + private readonly string _className; private readonly string _externalHttpServiceName; private readonly LogSource _logSource; private static TimeSpan _defaultLongRequestTime = TimeSpan.FromSeconds(1); - private static readonly QuantileEpsilonPair[] _summaryQuantileEpsilonPairs = { + private static readonly QuantileEpsilonPair[] SummaryQuantileEpsilonPairs = { new(0.5, 0.05), new(0.9, 0.05), new(0.95, 0.01), @@ -38,7 +39,7 @@ public static void Init(string serviceName, TimeSpan? defaultLongRequestTime = n } public static MetricsFactory CreateHttpClientMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, string externalHttpServiceName, TimeSpan? longRequestTime = null, params string[] additionalSummaryLabels) @@ -51,15 +52,16 @@ public static MetricsFactory CreateHttpClientMetricsFactory( additionalSummaryLabels); return new MetricsFactory( + className, LogSource.HttpClient, - _serviceName + summaryName, + $"{_serviceName}.http_client", externalHttpServiceName, longRequestTime, labels); } public static MetricsFactory CreateRedisMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, TimeSpan? longRequestTime = null, params string[] additionalSummaryLabels) { @@ -71,28 +73,34 @@ public static MetricsFactory CreateRedisMetricsFactory( additionalSummaryLabels); return new MetricsFactory( + className, LogSource.Redis, - _serviceName + summaryName, + $"{_serviceName}.redis", longRequestTime ?? _defaultLongRequestTime, labels); } public static MetricsFactory CreateMongoMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, params string[] additionalSummaryLabels) { - var labels = ConcatLabelNames("method_name", "entity_name", null, MetricsLabelsAndHeaders.UserLabels, + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, additionalSummaryLabels); return new MetricsFactory( + className, LogSource.Mongo, - _serviceName + summaryName, + $"{_serviceName}.mongo", _defaultLongRequestTime, labels); } public static MetricsFactory CreateSqlMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, TimeSpan? longTimeRequest = null, params string[] additionalSummaryLabels) { @@ -104,14 +112,15 @@ public static MetricsFactory CreateSqlMetricsFactory( additionalSummaryLabels); return new MetricsFactory( + className, LogSource.Sql, - _serviceName + summaryName, + $"{_serviceName}.sql", _defaultLongRequestTime, labels); } public static MetricsFactory CreateControllerMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, params string[] additionalSummaryLabels) { var labels = ConcatLabelNames( @@ -122,14 +131,15 @@ public static MetricsFactory CreateControllerMetricsFactory( additionalSummaryLabels); return new MetricsFactory( + className, LogSource.Controller, - _serviceName + summaryName, + $"{_serviceName}.controller", _defaultLongRequestTime, labels); } public static MetricsFactory CreateRepositoryMetricsFactory( - [NotNull] string summaryName, + [NotNull] string className, TimeSpan? requestLongTime = null, params string[] additionalSummaryLabels) { @@ -141,27 +151,22 @@ public static MetricsFactory CreateRepositoryMetricsFactory( additionalSummaryLabels); return new MetricsFactory( + className, LogSource.Repository, - _serviceName + summaryName, + $"{_serviceName}.repository", requestLongTime ?? _defaultLongRequestTime, labels); } - public static MetricsFactory CreateExternalHttpMetricsFactory(TimeSpan? longRequestTime = null) - { - return new MetricsFactory( - LogSource.ExternalHttpClient, - null, - longRequestTime ?? _defaultLongRequestTime); - } - private MetricsFactory( + string className, LogSource logSource, string summaryServiceName, string externalHttpServiceName, TimeSpan? longRequestTime = null, params string[] summaryLabelNames) { + _className = className; _externalHttpServiceName = externalHttpServiceName; _longRequestTime = longRequestTime ?? _defaultLongRequestTime; _logSource = logSource; @@ -171,7 +176,7 @@ private MetricsFactory( var options = new SummaryConfiguration { MaxAge = TimeSpan.FromMinutes(1), - Objectives = _summaryQuantileEpsilonPairs + Objectives = SummaryQuantileEpsilonPairs }; Summary = Prometheus.Metrics.CreateSummary( @@ -183,11 +188,13 @@ private MetricsFactory( } private MetricsFactory( + string className, LogSource logSource, [CanBeNull] string summaryServiceName, TimeSpan longRequestTime, params string[] summaryLabelNames) { + _className = className; _longRequestTime = longRequestTime; _logSource = logSource; @@ -196,7 +203,7 @@ private MetricsFactory( var options = new SummaryConfiguration { MaxAge = TimeSpan.FromMinutes(1), - Objectives = _summaryQuantileEpsilonPairs + Objectives = SummaryQuantileEpsilonPairs }; Summary = Prometheus.Metrics.CreateSummary( @@ -220,6 +227,7 @@ public IDisposable CreateMetricsTimerWithLogging( } var labels = ConcatLabelValues( + _className, actionName, entityName, _externalHttpServiceName, @@ -257,6 +265,7 @@ public MetricsTimer CreateMetricsTimerWithDelayedLogging( } var labels = ConcatLabelValues( + _className, actionName, entityName, _externalHttpServiceName, @@ -280,6 +289,7 @@ public IDisposable CreateLoggingMetricsTimer( params string[] additionalLabels) { var labels = ConcatLabelValues( + _className, actionName, entityName, _externalHttpServiceName, @@ -300,6 +310,7 @@ public IDisposable CreateMetricsTimer( params string[] additionalLabels) { var labels = ConcatLabelValues( + _className, actionName, entityName, _externalHttpServiceName, @@ -313,6 +324,7 @@ public IDisposable CreateMetricsTimer( /// Метод управляющий порядком значений лэйблов /// private static string[] ConcatLabelValues( + string className, string actionName, string entityName = null, string externHttpService = null, @@ -320,6 +332,7 @@ private static string[] ConcatLabelValues( params string[] additionalLabels) { return ConcatLabels( + className, MachineName, actionName, entityName, @@ -339,6 +352,7 @@ private static string[] ConcatLabelNames( params string[] additionalLabels) { return ConcatLabels( + "class_name", "machine_name", actionName, entityName, @@ -352,6 +366,7 @@ private static string[] ConcatLabelNames( /// Указывать только при объявлении лейблов. Записывается он в таймере, так как нужен для трейсинга /// private static string[] ConcatLabels( + string className, string machineName, string actionName, string entityName, @@ -361,9 +376,10 @@ private static string[] ConcatLabels( { var labels = new List { + className, actionName }; - + if (machineName != null) labels.Add(machineName); From 1e01f37ff15e3d314cdda6b547799b8370d94b1e Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Mon, 19 Feb 2024 18:16:49 +0600 Subject: [PATCH 02/17] Fix prometheus metric naming --- ATI.Services.Common/Metrics/MetricsFactory.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index a382196..2eb7ee8 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -54,7 +54,7 @@ public static MetricsFactory CreateHttpClientMetricsFactory( return new MetricsFactory( className, LogSource.HttpClient, - $"{_serviceName}.http_client", + $"{_serviceName}_http_client", externalHttpServiceName, longRequestTime, labels); @@ -75,7 +75,7 @@ public static MetricsFactory CreateRedisMetricsFactory( return new MetricsFactory( className, LogSource.Redis, - $"{_serviceName}.redis", + $"{_serviceName}_redis", longRequestTime ?? _defaultLongRequestTime, labels); } @@ -94,7 +94,7 @@ public static MetricsFactory CreateMongoMetricsFactory( return new MetricsFactory( className, LogSource.Mongo, - $"{_serviceName}.mongo", + $"{_serviceName}_mongo", _defaultLongRequestTime, labels); } @@ -114,7 +114,7 @@ public static MetricsFactory CreateSqlMetricsFactory( return new MetricsFactory( className, LogSource.Sql, - $"{_serviceName}.sql", + $"{_serviceName}_sql", _defaultLongRequestTime, labels); } @@ -133,7 +133,7 @@ public static MetricsFactory CreateControllerMetricsFactory( return new MetricsFactory( className, LogSource.Controller, - $"{_serviceName}.controller", + $"{_serviceName}_controller", _defaultLongRequestTime, labels); } @@ -153,7 +153,7 @@ public static MetricsFactory CreateRepositoryMetricsFactory( return new MetricsFactory( className, LogSource.Repository, - $"{_serviceName}.repository", + $"{_serviceName}_repository", requestLongTime ?? _defaultLongRequestTime, labels); } From 82c1cb988dbcc92086953d08f3aa76ed8175cbb0 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 03:41:13 +0600 Subject: [PATCH 03/17] Prefix "common" for metrics --- ATI.Services.Common/Initializers/MetricsInitializer.cs | 5 +---- ATI.Services.Common/Metrics/MetricsFactory.cs | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ATI.Services.Common/Initializers/MetricsInitializer.cs b/ATI.Services.Common/Initializers/MetricsInitializer.cs index e37e990..7da301b 100644 --- a/ATI.Services.Common/Initializers/MetricsInitializer.cs +++ b/ATI.Services.Common/Initializers/MetricsInitializer.cs @@ -25,10 +25,7 @@ public Task InitializeAsync() return Task.CompletedTask; } - if (_metricsOptions.MetricsServiceName != null ) - { - MetricsFactory.Init(_metricsOptions.MetricsServiceName, _metricsOptions.DefaultLongRequestTime); - } + MetricsFactory.Init(_metricsOptions.MetricsServiceName, _metricsOptions.DefaultLongRequestTime); _initialized = true; return Task.CompletedTask; diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index 2eb7ee8..e954eaf 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -12,7 +12,7 @@ namespace ATI.Services.Common.Metrics public class MetricsFactory { private Summary Summary { get; } - private static string _serviceName = "default_name"; + private static string _serviceName = "common_default"; private static readonly string MachineName = Environment.MachineName; private readonly string _className; private readonly string _externalHttpServiceName; @@ -31,7 +31,9 @@ public class MetricsFactory public static void Init(string serviceName, TimeSpan? defaultLongRequestTime = null) { - _serviceName = serviceName; + if (serviceName != null) + _serviceName = $"common_{serviceName}"; + if (defaultLongRequestTime != null) { _defaultLongRequestTime = defaultLongRequestTime.Value; From 2b513114cd49b0620b06e44a0097594b94e80e94 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 04:00:21 +0600 Subject: [PATCH 04/17] Alias for AddMetrics(IServiceCollection) --- .../Metrics/MetricsExtensions.cs | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/ATI.Services.Common/Metrics/MetricsExtensions.cs b/ATI.Services.Common/Metrics/MetricsExtensions.cs index 42288da..d23f03a 100644 --- a/ATI.Services.Common/Metrics/MetricsExtensions.cs +++ b/ATI.Services.Common/Metrics/MetricsExtensions.cs @@ -8,35 +8,39 @@ using Microsoft.Extensions.DependencyInjection; using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; -namespace ATI.Services.Common.Metrics +namespace ATI.Services.Common.Metrics; + +[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] +public static class MetricsExtensions { - [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] - public static class MetricsExtensions + /// + /// Alias to distinguish from Microsoft.Extensions.DependencyInjection.MetricsServiceExtensions.AddMetrics(IServiceCollection) + /// + public static void AddAtiMetrics(this IServiceCollection services) => services.AddMetrics(); + + public static void AddMetrics(this IServiceCollection services) { - public static void AddMetrics(this IServiceCollection services) - { - services.ConfigureByName(); - services.AddTransient(); + services.ConfigureByName(); + services.AddTransient(); - InitializeExceptionsMetrics(); + InitializeExceptionsMetrics(); - MetricsLabelsAndHeaders.LabelsStatic = ConfigurationManager.GetSection(nameof(MetricsOptions))?.Get()?.LabelsAndHeaders ?? new Dictionary(); - MetricsLabelsAndHeaders.UserLabels = MetricsLabelsAndHeaders.LabelsStatic.Keys.ToArray(); - MetricsLabelsAndHeaders.UserHeaders = MetricsLabelsAndHeaders.LabelsStatic.Values.ToArray(); - } + MetricsLabelsAndHeaders.LabelsStatic = ConfigurationManager.GetSection(nameof(MetricsOptions))?.Get()?.LabelsAndHeaders ?? new Dictionary(); + MetricsLabelsAndHeaders.UserLabels = MetricsLabelsAndHeaders.LabelsStatic.Keys.ToArray(); + MetricsLabelsAndHeaders.UserHeaders = MetricsLabelsAndHeaders.LabelsStatic.Values.ToArray(); + } - private static void InitializeExceptionsMetrics() - { - var exceptionCollector = new ExceptionsMetricsCollector(); - var registry = Prometheus.Metrics.DefaultRegistry; + private static void InitializeExceptionsMetrics() + { + var exceptionCollector = new ExceptionsMetricsCollector(); + var registry = Prometheus.Metrics.DefaultRegistry; - exceptionCollector.RegisterMetrics(registry); - registry.AddBeforeCollectCallback(exceptionCollector.UpdateMetrics); - } + exceptionCollector.RegisterMetrics(registry); + registry.AddBeforeCollectCallback(exceptionCollector.UpdateMetrics); + } - public static void UseMetrics(this IApplicationBuilder app) - { - app.UseMiddleware(); - } + public static void UseMetrics(this IApplicationBuilder app) + { + app.UseMiddleware(); } } \ No newline at end of file From 7d2ad91c58e0f947767e722fb69b0ba75c24051b Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 04:17:12 +0600 Subject: [PATCH 05/17] ExceptionMetrics naming convention --- ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs index 0dd4f9e..2ad7d05 100644 --- a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs +++ b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs @@ -18,10 +18,11 @@ public class ExceptionsMetricsCollector : EventListener private const string ExceptionsMetricName = "Exceptions"; private Gauge _gauge; - private string ServiceName { get; } + private string ServiceName { get; } = "common_default"; public ExceptionsMetricsCollector() { - ServiceName = ConfigurationManager.GetSection(nameof(MetricsOptions)).Get().MetricsServiceName; + if(ConfigurationManager.GetSection(nameof(MetricsOptions)).Get().MetricsServiceName is { } serviceName) + ServiceName = $"common_{serviceName}"; } protected override void OnEventSourceCreated(EventSource eventSource) @@ -44,7 +45,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) public void RegisterMetrics(CollectorRegistry registry) { _metricFactory = Prometheus.Metrics.WithCustomRegistry(registry); - _gauge = _metricFactory.CreateGauge(ServiceName + ExceptionsMetricName, "", "machine_name", "exception_type"); + _gauge = _metricFactory.CreateGauge($"{ServiceName}_{ExceptionsMetricName}", "", "machine_name", "exception_type"); } public void UpdateMetrics() From c0692a3869df17531e4ce522323e18f6dba25a8f Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 14:10:34 +0600 Subject: [PATCH 06/17] Common naming for httpStatusCodeCounter --- .../MetricsStatusCodeCounterMiddleware.cs | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs index f0bd43a..2a08fdf 100644 --- a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs +++ b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs @@ -2,31 +2,40 @@ using System.Threading.Tasks; using ATI.Services.Common.Variables; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Prometheus; -namespace ATI.Services.Common.Metrics +namespace ATI.Services.Common.Metrics; + +public class MetricsStatusCodeCounterMiddleware { - public class MetricsStatusCodeCounterMiddleware - { - private static Counter counter = Prometheus.Metrics.CreateCounter("HttpStatusCodeCounter", "", - new CounterConfiguration - { - LabelNames = new[] {"http_status_code"}.Concat(MetricsLabelsAndHeaders.UserLabels).ToArray() - }); + private readonly Counter _counter; + + private readonly RequestDelegate _next; - private readonly RequestDelegate _next; + public MetricsStatusCodeCounterMiddleware(RequestDelegate next, IConfiguration configuration) + { + var prefix = configuration.GetSection(nameof(MetricsOptions)) + .Get().MetricsServiceName is { } serviceName + ? $"common_{serviceName}" + : "common_default"; - public MetricsStatusCodeCounterMiddleware(RequestDelegate next) - { - _next = next; - } + _counter = Prometheus.Metrics.CreateCounter($"{prefix}_HttpStatusCodeCounter", + "", + new CounterConfiguration + { + LabelNames = new[] { "http_status_code" } + .Concat(MetricsLabelsAndHeaders.UserLabels) + .ToArray() + }); + _next = next; + } - public async Task InvokeAsync(HttpContext context) - { - await _next(context); - var param = new[] {context.Response.StatusCode.ToString()}.Concat(AppHttpContext.MetricsHeadersValues) - .ToArray(); - counter.WithLabels(param).Inc(); - } + public async Task InvokeAsync(HttpContext context) + { + await _next(context); + var param = new[] {context.Response.StatusCode.ToString()}.Concat(AppHttpContext.MetricsHeadersValues) + .ToArray(); + _counter.WithLabels(param).Inc(); } } \ No newline at end of file From fb36fc9de54439555f6f783c996ef282c1db32ca Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 18:40:33 +0600 Subject: [PATCH 07/17] Factory methods for rabbitmq and custom metrics --- ATI.Services.Common/Logging/LogSource.cs | 3 +- ATI.Services.Common/Metrics/MetricsFactory.cs | 44 +++++++++++++++++++ .../Metrics/RabbitMetricsDirection.cs | 7 +++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 ATI.Services.Common/Metrics/RabbitMetricsDirection.cs diff --git a/ATI.Services.Common/Logging/LogSource.cs b/ATI.Services.Common/Logging/LogSource.cs index 5b3c800..0941e86 100644 --- a/ATI.Services.Common/Logging/LogSource.cs +++ b/ATI.Services.Common/Logging/LogSource.cs @@ -13,6 +13,7 @@ public enum LogSource Sql = 4, Controller = 5, Repository = 6, - ExternalHttpClient = 8 + RabbitMq = 9, + Custom = 10 } } diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index e954eaf..471a1fd 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -159,6 +159,50 @@ public static MetricsFactory CreateRepositoryMetricsFactory( requestLongTime ?? _defaultLongRequestTime, labels); } + + public static MetricsFactory CreateRabbitMqMetricsFactory( + RabbitMetricsDirection direction, + [NotNull] string className, + TimeSpan? requestLongTime = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "exchange_routing_key_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.RabbitMq, + $"{_serviceName}_{direction.ToString().ToLower()}_rabbitmq", + requestLongTime ?? _defaultLongRequestTime, + labels); + } + + public static MetricsFactory CreateCustomMetricsFactory( + [NotNull] string className, + string customMetricName, + TimeSpan? requestLongTime = null, + params string[] additionalSummaryLabels) + { + if(customMetricName is null) throw new ArgumentNullException(nameof(customMetricName)); + + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Custom, + $"{_serviceName}_{customMetricName}", + requestLongTime ?? _defaultLongRequestTime, + labels); + } private MetricsFactory( string className, diff --git a/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs b/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs new file mode 100644 index 0000000..dc1cd6b --- /dev/null +++ b/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs @@ -0,0 +1,7 @@ +namespace ATI.Services.Common.Metrics; + +public enum RabbitMetricsDirection +{ + In, + Out +} \ No newline at end of file From 49c408b166042d1ce1bc58203fe5519efcc5300c Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 21:07:49 +0600 Subject: [PATCH 08/17] Removed LangVersion, now it's bound to dotnet version --- ATI.Services.Common/ATI.Services.Common.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/ATI.Services.Common/ATI.Services.Common.csproj b/ATI.Services.Common/ATI.Services.Common.csproj index 12a642c..e3d1e3c 100644 --- a/ATI.Services.Common/ATI.Services.Common.csproj +++ b/ATI.Services.Common/ATI.Services.Common.csproj @@ -4,7 +4,6 @@ linux-x64 Team Services ATI - 10 true true atisu.services.common From c827de5890c30c1fa99f93fc209aa8c6cecac39e Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 20 Feb 2024 21:08:09 +0600 Subject: [PATCH 09/17] Null reference check --- ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs | 2 +- .../Metrics/MetricsStatusCodeCounterMiddleware.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs index 2ad7d05..430b20b 100644 --- a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs +++ b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs @@ -21,7 +21,7 @@ public class ExceptionsMetricsCollector : EventListener private string ServiceName { get; } = "common_default"; public ExceptionsMetricsCollector() { - if(ConfigurationManager.GetSection(nameof(MetricsOptions)).Get().MetricsServiceName is { } serviceName) + if(ConfigurationManager.GetSection(nameof(MetricsOptions))?.Get()?.MetricsServiceName is { } serviceName) ServiceName = $"common_{serviceName}"; } diff --git a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs index 2a08fdf..4f93c20 100644 --- a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs +++ b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Prometheus; +using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; + namespace ATI.Services.Common.Metrics; @@ -13,10 +15,10 @@ public class MetricsStatusCodeCounterMiddleware private readonly RequestDelegate _next; - public MetricsStatusCodeCounterMiddleware(RequestDelegate next, IConfiguration configuration) + public MetricsStatusCodeCounterMiddleware(RequestDelegate next) { - var prefix = configuration.GetSection(nameof(MetricsOptions)) - .Get().MetricsServiceName is { } serviceName + var prefix = ConfigurationManager.GetSection(nameof(MetricsOptions)) + ?.Get()?.MetricsServiceName is { } serviceName ? $"common_{serviceName}" : "common_default"; From 8d685cca643b55931bddd346decc5986f619d135 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Wed, 21 Feb 2024 15:24:02 +0600 Subject: [PATCH 10/17] Changed metrics prefix, removed service name from it --- .../Initializers/MetricsInitializer.cs | 2 +- .../Metrics/ExceptionsMetricsCollector.cs | 79 +- ATI.Services.Common/Metrics/MetricsFactory.cs | 792 +++++++++--------- ATI.Services.Common/Metrics/MetricsOptions.cs | 26 +- .../MetricsStatusCodeCounterMiddleware.cs | 11 +- 5 files changed, 440 insertions(+), 470 deletions(-) diff --git a/ATI.Services.Common/Initializers/MetricsInitializer.cs b/ATI.Services.Common/Initializers/MetricsInitializer.cs index 7da301b..e553676 100644 --- a/ATI.Services.Common/Initializers/MetricsInitializer.cs +++ b/ATI.Services.Common/Initializers/MetricsInitializer.cs @@ -25,7 +25,7 @@ public Task InitializeAsync() return Task.CompletedTask; } - MetricsFactory.Init(_metricsOptions.MetricsServiceName, _metricsOptions.DefaultLongRequestTime); + MetricsFactory.Init(_metricsOptions.DefaultLongRequestTime); _initialized = true; return Task.CompletedTask; diff --git a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs index 430b20b..eb63171 100644 --- a/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs +++ b/ATI.Services.Common/Metrics/ExceptionsMetricsCollector.cs @@ -1,60 +1,49 @@ using System; using System.Collections.Concurrent; using System.Diagnostics.Tracing; -using Microsoft.Extensions.Configuration; using Prometheus; -using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; -namespace ATI.Services.Common.Metrics -{ - public class ExceptionsMetricsCollector : EventListener - { - // Да, даже под linux - Microsoft-Windows - private const string ExceptionSourceName = "Microsoft-Windows-DotNETRuntime"; - private const int ExceptionBit = 0x8000; - private const int ExceptionNameIndex = 0; - private readonly ConcurrentDictionary _exceptionCounters = new(); - private MetricFactory _metricFactory; - private const string ExceptionsMetricName = "Exceptions"; - private Gauge _gauge; +namespace ATI.Services.Common.Metrics; - private string ServiceName { get; } = "common_default"; - public ExceptionsMetricsCollector() - { - if(ConfigurationManager.GetSection(nameof(MetricsOptions))?.Get()?.MetricsServiceName is { } serviceName) - ServiceName = $"common_{serviceName}"; - } +public class ExceptionsMetricsCollector : EventListener +{ + // Да, даже под linux - Microsoft-Windows + private const string ExceptionSourceName = "Microsoft-Windows-DotNETRuntime"; + private const int ExceptionBit = 0x8000; + private const int ExceptionNameIndex = 0; + private readonly ConcurrentDictionary _exceptionCounters = new(); + private MetricFactory _metricFactory; + private Gauge _gauge; - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name != ExceptionSourceName) - return; + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name != ExceptionSourceName) + return; - EnableEvents( - eventSource, - EventLevel.Error, - (EventKeywords) ExceptionBit); - } + EnableEvents( + eventSource, + EventLevel.Error, + (EventKeywords) ExceptionBit); + } - protected override void OnEventWritten(EventWrittenEventArgs eventData) - { - var exceptionType = eventData.Payload[ExceptionNameIndex].ToString(); - _exceptionCounters.AddOrUpdate(exceptionType, _ => 1, (_, oldValue) => oldValue + 1); - } + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + var exceptionType = eventData.Payload[ExceptionNameIndex].ToString(); + _exceptionCounters.AddOrUpdate(exceptionType, _ => 1, (_, oldValue) => oldValue + 1); + } - public void RegisterMetrics(CollectorRegistry registry) - { - _metricFactory = Prometheus.Metrics.WithCustomRegistry(registry); - _gauge = _metricFactory.CreateGauge($"{ServiceName}_{ExceptionsMetricName}", "", "machine_name", "exception_type"); - } + public void RegisterMetrics(CollectorRegistry registry) + { + _metricFactory = Prometheus.Metrics.WithCustomRegistry(registry); + _gauge = _metricFactory.CreateGauge($"{MetricsFactory.Prefix}_Exceptions", "", "machine_name", "exception_type"); + } - public void UpdateMetrics() + public void UpdateMetrics() + { + foreach (var exceptionType in _exceptionCounters.Keys) { - foreach (var exceptionType in _exceptionCounters.Keys) - { - _exceptionCounters.TryRemove(exceptionType, out var count); - _gauge.WithLabels(Environment.MachineName, exceptionType).Set(count); - } + _exceptionCounters.TryRemove(exceptionType, out var count); + _gauge.WithLabels(Environment.MachineName, exceptionType).Set(count); } } } \ No newline at end of file diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index 471a1fd..1bb668f 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -6,442 +6,438 @@ using JetBrains.Annotations; using Prometheus; -namespace ATI.Services.Common.Metrics +namespace ATI.Services.Common.Metrics; + +[PublicAPI] +public class MetricsFactory { - [PublicAPI] - public class MetricsFactory + private Summary Summary { get; } + public const string Prefix = "common_metric"; + private static readonly string MachineName = Environment.MachineName; + private readonly string _className; + private readonly string _externalHttpServiceName; + private readonly LogSource _logSource; + private static TimeSpan _defaultLongRequestTime = TimeSpan.FromSeconds(1); + + private static readonly QuantileEpsilonPair[] SummaryQuantileEpsilonPairs = { + new(0.5, 0.05), + new(0.9, 0.05), + new(0.95, 0.01), + new(0.99, 0.005), + }; + + //Время запроса считающегося достаточно долгим, что бы об этом доложить в кибану + private readonly TimeSpan _longRequestTime; + + public static void Init(TimeSpan? defaultLongRequestTime = null) { - private Summary Summary { get; } - private static string _serviceName = "common_default"; - private static readonly string MachineName = Environment.MachineName; - private readonly string _className; - private readonly string _externalHttpServiceName; - private readonly LogSource _logSource; - private static TimeSpan _defaultLongRequestTime = TimeSpan.FromSeconds(1); - - private static readonly QuantileEpsilonPair[] SummaryQuantileEpsilonPairs = { - new(0.5, 0.05), - new(0.9, 0.05), - new(0.95, 0.01), - new(0.99, 0.005), - }; - - //Время запроса считающегося достаточно долгим, что бы об этом доложить в кибану - private readonly TimeSpan _longRequestTime; - - public static void Init(string serviceName, TimeSpan? defaultLongRequestTime = null) + if (defaultLongRequestTime != null) { - if (serviceName != null) - _serviceName = $"common_{serviceName}"; - - if (defaultLongRequestTime != null) - { - _defaultLongRequestTime = defaultLongRequestTime.Value; - } + _defaultLongRequestTime = defaultLongRequestTime.Value; } + } - public static MetricsFactory CreateHttpClientMetricsFactory( - [NotNull] string className, - string externalHttpServiceName, - TimeSpan? longRequestTime = null, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "route_template", - "entity_name", - "external_http_service_name", - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.HttpClient, - $"{_serviceName}_http_client", - externalHttpServiceName, - longRequestTime, - labels); - } + public static MetricsFactory CreateHttpClientMetricsFactory( + [NotNull] string className, + string externalHttpServiceName, + TimeSpan? longRequestTime = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "route_template", + "entity_name", + "external_http_service_name", + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.HttpClient, + $"{Prefix}_http_client", + externalHttpServiceName, + longRequestTime, + labels); + } - public static MetricsFactory CreateRedisMetricsFactory( - [NotNull] string className, - TimeSpan? longRequestTime = null, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "method_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Redis, - $"{_serviceName}_redis", - longRequestTime ?? _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateRedisMetricsFactory( + [NotNull] string className, + TimeSpan? longRequestTime = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Redis, + $"{Prefix}_redis", + longRequestTime ?? _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateMongoMetricsFactory( - [NotNull] string className, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "method_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Mongo, - $"{_serviceName}_mongo", - _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateMongoMetricsFactory( + [NotNull] string className, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Mongo, + $"{Prefix}_mongo", + _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateSqlMetricsFactory( - [NotNull] string className, - TimeSpan? longTimeRequest = null, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "procedure_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Sql, - $"{_serviceName}_sql", - _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateSqlMetricsFactory( + [NotNull] string className, + TimeSpan? longTimeRequest = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "procedure_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Sql, + $"{Prefix}_sql", + _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateControllerMetricsFactory( - [NotNull] string className, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "route_template", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Controller, - $"{_serviceName}_controller", - _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateControllerMetricsFactory( + [NotNull] string className, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "route_template", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Controller, + $"{Prefix}_controller", + _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateRepositoryMetricsFactory( - [NotNull] string className, - TimeSpan? requestLongTime = null, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "method_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Repository, - $"{_serviceName}_repository", - requestLongTime ?? _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateRepositoryMetricsFactory( + [NotNull] string className, + TimeSpan? requestLongTime = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Repository, + $"{Prefix}_repository", + requestLongTime ?? _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateRabbitMqMetricsFactory( - RabbitMetricsDirection direction, - [NotNull] string className, - TimeSpan? requestLongTime = null, - params string[] additionalSummaryLabels) - { - var labels = ConcatLabelNames( - "exchange_routing_key_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.RabbitMq, - $"{_serviceName}_{direction.ToString().ToLower()}_rabbitmq", - requestLongTime ?? _defaultLongRequestTime, - labels); - } + public static MetricsFactory CreateRabbitMqMetricsFactory( + RabbitMetricsDirection direction, + [NotNull] string className, + TimeSpan? requestLongTime = null, + params string[] additionalSummaryLabels) + { + var labels = ConcatLabelNames( + "exchange_routing_key_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.RabbitMq, + $"{Prefix}_rabbitmq_{direction.ToString().ToLower()}", + requestLongTime ?? _defaultLongRequestTime, + labels); + } - public static MetricsFactory CreateCustomMetricsFactory( - [NotNull] string className, - string customMetricName, - TimeSpan? requestLongTime = null, - params string[] additionalSummaryLabels) - { - if(customMetricName is null) throw new ArgumentNullException(nameof(customMetricName)); + public static MetricsFactory CreateCustomMetricsFactory( + [NotNull] string className, + string customMetricName, + TimeSpan? requestLongTime = null, + params string[] additionalSummaryLabels) + { + if(customMetricName is null) throw new ArgumentNullException(nameof(customMetricName)); - var labels = ConcatLabelNames( - "method_name", - "entity_name", - null, - MetricsLabelsAndHeaders.UserLabels, - additionalSummaryLabels); - - return new MetricsFactory( - className, - LogSource.Custom, - $"{_serviceName}_{customMetricName}", - requestLongTime ?? _defaultLongRequestTime, - labels); - } - - private MetricsFactory( - string className, - LogSource logSource, - string summaryServiceName, - string externalHttpServiceName, - TimeSpan? longRequestTime = null, - params string[] summaryLabelNames) - { - _className = className; - _externalHttpServiceName = externalHttpServiceName; - _longRequestTime = longRequestTime ?? _defaultLongRequestTime; - _logSource = logSource; + var labels = ConcatLabelNames( + "method_name", + "entity_name", + null, + MetricsLabelsAndHeaders.UserLabels, + additionalSummaryLabels); + + return new MetricsFactory( + className, + LogSource.Custom, + $"{Prefix}_{customMetricName}", + requestLongTime ?? _defaultLongRequestTime, + labels); + } - if (summaryServiceName != null) - { - var options = new SummaryConfiguration - { - MaxAge = TimeSpan.FromMinutes(1), - Objectives = SummaryQuantileEpsilonPairs - }; - - Summary = Prometheus.Metrics.CreateSummary( - summaryServiceName, - string.Empty, - summaryLabelNames, - options); - } - } + private MetricsFactory( + string className, + LogSource logSource, + string summaryServiceName, + string externalHttpServiceName, + TimeSpan? longRequestTime = null, + params string[] summaryLabelNames) + { + _className = className; + _externalHttpServiceName = externalHttpServiceName; + _longRequestTime = longRequestTime ?? _defaultLongRequestTime; + _logSource = logSource; - private MetricsFactory( - string className, - LogSource logSource, - [CanBeNull] string summaryServiceName, - TimeSpan longRequestTime, - params string[] summaryLabelNames) + if (summaryServiceName != null) { - _className = className; - _longRequestTime = longRequestTime; - _logSource = logSource; - - if (summaryServiceName != null) + var options = new SummaryConfiguration { - var options = new SummaryConfiguration - { - MaxAge = TimeSpan.FromMinutes(1), - Objectives = SummaryQuantileEpsilonPairs - }; - - Summary = Prometheus.Metrics.CreateSummary( - summaryServiceName, - string.Empty, - summaryLabelNames, - options); - } - } + MaxAge = TimeSpan.FromMinutes(1), + Objectives = SummaryQuantileEpsilonPairs + }; - public IDisposable CreateMetricsTimerWithLogging( - string entityName, - [CallerMemberName] string actionName = null, - object requestParams = null, - TimeSpan? longRequestTime = null, - params string[] additionalLabels) - { - if (Summary == null) - { - throw new NullReferenceException($"{nameof(Summary)} is not initialized"); - } - - var labels = ConcatLabelValues( - _className, - actionName, - entityName, - _externalHttpServiceName, - AppHttpContext.MetricsHeadersValues, - additionalLabels); - - return new MetricsTimer( - Summary, - labels, - longRequestTime ?? _longRequestTime, - requestParams, - _logSource); + Summary = Prometheus.Metrics.CreateSummary( + summaryServiceName, + string.Empty, + summaryLabelNames, + options); } + } - /// - /// В случае создания данного экземпляра таймер для метрик стартует не сразу, а только после вызова метода Restart() - /// - /// - /// - /// - /// - /// - /// - /// - public MetricsTimer CreateMetricsTimerWithDelayedLogging( - string entityName, - [CallerMemberName] string actionName = null, - object requestParams = null, - TimeSpan? longRequestTime = null, - params string[] additionalLabels) + private MetricsFactory( + string className, + LogSource logSource, + [CanBeNull] string summaryServiceName, + TimeSpan longRequestTime, + params string[] summaryLabelNames) + { + _className = className; + _longRequestTime = longRequestTime; + _logSource = logSource; + + if (summaryServiceName != null) { - if (Summary == null) + var options = new SummaryConfiguration { - throw new NullReferenceException($"{nameof(Summary)} is not initialized"); - } - - var labels = ConcatLabelValues( - _className, - actionName, - entityName, - _externalHttpServiceName, - AppHttpContext.MetricsHeadersValues, - additionalLabels); - - return new MetricsTimer( - Summary, - labels, - longRequestTime ?? _longRequestTime, - requestParams, - _logSource, - false); - } + MaxAge = TimeSpan.FromMinutes(1), + Objectives = SummaryQuantileEpsilonPairs + }; - public IDisposable CreateLoggingMetricsTimer( - string entityName, - [CallerMemberName] string actionName = null, - object requestParams = null, - TimeSpan? longRequestTime = null, - params string[] additionalLabels) - { - var labels = ConcatLabelValues( - _className, - actionName, - entityName, - _externalHttpServiceName, - AppHttpContext.MetricsHeadersValues, - additionalLabels); - - return new MetricsTimer( - Summary, - labels, - longRequestTime ?? _longRequestTime, - requestParams, - _logSource); + Summary = Prometheus.Metrics.CreateSummary( + summaryServiceName, + string.Empty, + summaryLabelNames, + options); } + } - public IDisposable CreateMetricsTimer( - string entityName, - [CallerMemberName] string actionName = null, - params string[] additionalLabels) + public IDisposable CreateMetricsTimerWithLogging( + string entityName, + [CallerMemberName] string actionName = null, + object requestParams = null, + TimeSpan? longRequestTime = null, + params string[] additionalLabels) + { + if (Summary == null) { - var labels = ConcatLabelValues( - _className, - actionName, - entityName, - _externalHttpServiceName, - AppHttpContext.MetricsHeadersValues, - additionalLabels); - - return new MetricsTimer(Summary, labels); + throw new NullReferenceException($"{nameof(Summary)} is not initialized"); } - /// - /// Метод управляющий порядком значений лэйблов - /// - private static string[] ConcatLabelValues( - string className, - string actionName, - string entityName = null, - string externHttpService = null, - string[] userLabels = null, - params string[] additionalLabels) - { - return ConcatLabels( - className, - MachineName, - actionName, - entityName, - externHttpService, - userLabels, - additionalLabels); - } + var labels = ConcatLabelValues( + _className, + actionName, + entityName, + _externalHttpServiceName, + AppHttpContext.MetricsHeadersValues, + additionalLabels); + + return new MetricsTimer( + Summary, + labels, + longRequestTime ?? _longRequestTime, + requestParams, + _logSource); + } - /// - /// Метод управляющий порядком названий лэйблов - /// - private static string[] ConcatLabelNames( - string actionName, - string entityName = null, - string externHttpService = null, - string[] userLabels = null, - params string[] additionalLabels) + /// + /// В случае создания данного экземпляра таймер для метрик стартует не сразу, а только после вызова метода Restart() + /// + /// + /// + /// + /// + /// + /// + /// + public MetricsTimer CreateMetricsTimerWithDelayedLogging( + string entityName, + [CallerMemberName] string actionName = null, + object requestParams = null, + TimeSpan? longRequestTime = null, + params string[] additionalLabels) + { + if (Summary == null) { - return ConcatLabels( - "class_name", - "machine_name", - actionName, - entityName, - externHttpService, - userLabels, - additionalLabels); + throw new NullReferenceException($"{nameof(Summary)} is not initialized"); } - /// - /// Метод управляющий порядком лэйблов и их значений - /// Указывать только при объявлении лейблов. Записывается он в таймере, так как нужен для трейсинга - /// - private static string[] ConcatLabels( - string className, - string machineName, - string actionName, - string entityName, - string externHttpService, - string[] userLabels, - params string[] additionalLabels) + var labels = ConcatLabelValues( + _className, + actionName, + entityName, + _externalHttpServiceName, + AppHttpContext.MetricsHeadersValues, + additionalLabels); + + return new MetricsTimer( + Summary, + labels, + longRequestTime ?? _longRequestTime, + requestParams, + _logSource, + false); + } + + public IDisposable CreateLoggingMetricsTimer( + string entityName, + [CallerMemberName] string actionName = null, + object requestParams = null, + TimeSpan? longRequestTime = null, + params string[] additionalLabels) + { + var labels = ConcatLabelValues( + _className, + actionName, + entityName, + _externalHttpServiceName, + AppHttpContext.MetricsHeadersValues, + additionalLabels); + + return new MetricsTimer( + Summary, + labels, + longRequestTime ?? _longRequestTime, + requestParams, + _logSource); + } + + public IDisposable CreateMetricsTimer( + string entityName, + [CallerMemberName] string actionName = null, + params string[] additionalLabels) + { + var labels = ConcatLabelValues( + _className, + actionName, + entityName, + _externalHttpServiceName, + AppHttpContext.MetricsHeadersValues, + additionalLabels); + + return new MetricsTimer(Summary, labels); + } + + /// + /// Метод управляющий порядком значений лэйблов + /// + private static string[] ConcatLabelValues( + string className, + string actionName, + string entityName = null, + string externHttpService = null, + string[] userLabels = null, + params string[] additionalLabels) + { + return ConcatLabels( + className, + MachineName, + actionName, + entityName, + externHttpService, + userLabels, + additionalLabels); + } + + /// + /// Метод управляющий порядком названий лэйблов + /// + private static string[] ConcatLabelNames( + string actionName, + string entityName = null, + string externHttpService = null, + string[] userLabels = null, + params string[] additionalLabels) + { + return ConcatLabels( + "class_name", + "machine_name", + actionName, + entityName, + externHttpService, + userLabels, + additionalLabels); + } + + /// + /// Метод управляющий порядком лэйблов и их значений + /// Указывать только при объявлении лейблов. Записывается он в таймере, так как нужен для трейсинга + /// + private static string[] ConcatLabels( + string className, + string machineName, + string actionName, + string entityName, + string externHttpService, + string[] userLabels, + params string[] additionalLabels) + { + var labels = new List { - var labels = new List - { - className, - actionName - }; + className, + actionName + }; - if (machineName != null) - labels.Add(machineName); + if (machineName != null) + labels.Add(machineName); - if (entityName != null) - labels.Add(entityName); + if (entityName != null) + labels.Add(entityName); - if (externHttpService != null) - labels.Add(externHttpService); + if (externHttpService != null) + labels.Add(externHttpService); - if (userLabels != null && userLabels.Length != 0) - labels.AddRange(userLabels); + if (userLabels != null && userLabels.Length != 0) + labels.AddRange(userLabels); - if (additionalLabels.Length != 0) - labels.AddRange(additionalLabels); + if (additionalLabels.Length != 0) + labels.AddRange(additionalLabels); - return labels.ToArray(); - } + return labels.ToArray(); } } \ No newline at end of file diff --git a/ATI.Services.Common/Metrics/MetricsOptions.cs b/ATI.Services.Common/Metrics/MetricsOptions.cs index 4b2b162..66cdce9 100644 --- a/ATI.Services.Common/Metrics/MetricsOptions.cs +++ b/ATI.Services.Common/Metrics/MetricsOptions.cs @@ -1,22 +1,16 @@ using System; using System.Collections.Generic; -namespace ATI.Services.Common.Metrics +namespace ATI.Services.Common.Metrics; + +public class MetricsOptions { - public class MetricsOptions - { - /// - /// User's additional labels and headers - /// Keys are labels' names - /// Values are headers' names - /// - public Dictionary LabelsAndHeaders { get; set; } - - public TimeSpan? DefaultLongRequestTime { get; set; } + /// + /// User's additional labels and headers + /// Keys are labels' names + /// Values are headers' names + /// + public Dictionary LabelsAndHeaders { get; set; } - /// - /// Название сервиса для метрик, без точек и прочих символов - /// - public string MetricsServiceName { get; set; } - } + public TimeSpan? DefaultLongRequestTime { get; set; } } \ No newline at end of file diff --git a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs index 4f93c20..eabbb31 100644 --- a/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs +++ b/ATI.Services.Common/Metrics/MetricsStatusCodeCounterMiddleware.cs @@ -2,27 +2,18 @@ using System.Threading.Tasks; using ATI.Services.Common.Variables; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Prometheus; -using ConfigurationManager = ATI.Services.Common.Behaviors.ConfigurationManager; - namespace ATI.Services.Common.Metrics; public class MetricsStatusCodeCounterMiddleware { private readonly Counter _counter; - private readonly RequestDelegate _next; public MetricsStatusCodeCounterMiddleware(RequestDelegate next) { - var prefix = ConfigurationManager.GetSection(nameof(MetricsOptions)) - ?.Get()?.MetricsServiceName is { } serviceName - ? $"common_{serviceName}" - : "common_default"; - - _counter = Prometheus.Metrics.CreateCounter($"{prefix}_HttpStatusCodeCounter", + _counter = Prometheus.Metrics.CreateCounter($"{MetricsFactory.Prefix}_HttpStatusCodeCounter", "", new CounterConfiguration { From e323b4f29c9b93b344179c06a7653672a5bdeda8 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Wed, 21 Feb 2024 17:04:23 +0600 Subject: [PATCH 11/17] Removed client-header-name from metrics --- .../Metrics/MeasureAttribute.cs | 114 +++++++----------- 1 file changed, 46 insertions(+), 68 deletions(-) diff --git a/ATI.Services.Common/Metrics/MeasureAttribute.cs b/ATI.Services.Common/Metrics/MeasureAttribute.cs index 84b8976..0dec142 100644 --- a/ATI.Services.Common/Metrics/MeasureAttribute.cs +++ b/ATI.Services.Common/Metrics/MeasureAttribute.cs @@ -1,91 +1,69 @@ using System; using System.Collections.Concurrent; using System.Threading.Tasks; -using ATI.Services.Common.Behaviors; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; -namespace ATI.Services.Common.Metrics -{ - [PublicAPI] - public class MeasureAttribute : ActionFilterAttribute - { - private string _metricEntity; - private readonly TimeSpan? _longRequestTime; - private readonly bool _longRequestLoggingEnabled = true; - - private static readonly ConcurrentDictionary ControllerNameMetricsFactories - = new(); +namespace ATI.Services.Common.Metrics; - public MeasureAttribute() - { - } - - public MeasureAttribute(string metricEntity) - { - _metricEntity = metricEntity; - } +[PublicAPI] +public class MeasureAttribute : ActionFilterAttribute +{ + private string _metricEntity; + private readonly TimeSpan? _longRequestTime; + private readonly bool _longRequestLoggingEnabled = true; - /// Имя метрики - /// Время ответа после которого запрос считается достаточно долгим для логирования. В секундах - public MeasureAttribute(string metricEntity, double longRequestTimeSeconds) : this(metricEntity) - { - _longRequestTime = TimeSpan.FromSeconds(longRequestTimeSeconds); - } + private static readonly ConcurrentDictionary ControllerNameMetricsFactories + = new(); - public MeasureAttribute(string metricEntity, bool longRequestLoggingEnabled) : this(metricEntity) - { - _longRequestLoggingEnabled = longRequestLoggingEnabled; - } + public MeasureAttribute() { } - public MeasureAttribute(double longRequestTimeSeconds) - { - _longRequestTime = TimeSpan.FromSeconds(longRequestTimeSeconds); - } + public MeasureAttribute(string metricEntity) => _metricEntity = metricEntity; - public MeasureAttribute(bool longRequestLoggingEnabled) - { - _longRequestLoggingEnabled = longRequestLoggingEnabled; - } + /// Имя метрики + /// Время ответа после которого запрос считается достаточно долгим для логирования. В секундах + public MeasureAttribute(string metricEntity, double longRequestTimeSeconds) : this(metricEntity) => + _longRequestTime = TimeSpan.FromSeconds(longRequestTimeSeconds); - public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) - { - var controllerActionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor; - var actionName = - $"{context.HttpContext.Request.Method}:{controllerActionDescriptor.AttributeRouteInfo.Template}"; - var controllerName = controllerActionDescriptor.ControllerName + "Controller"; + public MeasureAttribute(string metricEntity, bool longRequestLoggingEnabled) : this(metricEntity) => + _longRequestLoggingEnabled = longRequestLoggingEnabled; - if (string.IsNullOrEmpty(_metricEntity)) - { - _metricEntity = controllerActionDescriptor.ControllerName; - } + public MeasureAttribute(double longRequestTimeSeconds) => + _longRequestTime = TimeSpan.FromSeconds(longRequestTimeSeconds); - var metricsFactory = - ControllerNameMetricsFactories.GetOrAdd( - controllerName, - MetricsFactory.CreateControllerMetricsFactory(controllerName, "client_header_name")); + public MeasureAttribute(bool longRequestLoggingEnabled) + { + _longRequestLoggingEnabled = longRequestLoggingEnabled; + } - var serviceName = "Unknown"; - - if (context.HttpContext.Items.TryGetValue(CommonBehavior.ServiceNameItemKey, out var serviceNameValue)) - { - serviceName = serviceNameValue as string; - } + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var controllerActionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor; + var actionName = + $"{context.HttpContext.Request.Method}:{controllerActionDescriptor.AttributeRouteInfo.Template}"; + var controllerName = controllerActionDescriptor.ControllerName + "Controller"; - using (CreateTimer(context, metricsFactory, actionName, serviceName)) - { - await next.Invoke(); - } + if (string.IsNullOrEmpty(_metricEntity)) + { + _metricEntity = controllerActionDescriptor.ControllerName; } - private IDisposable CreateTimer(ActionExecutingContext context, MetricsFactory metricsFactory, - string actionName, string serviceName) + var metricsFactory = + ControllerNameMetricsFactories.GetOrAdd( + controllerName, + MetricsFactory.CreateControllerMetricsFactory(controllerName)); + + using (CreateTimer(context, metricsFactory, actionName)) { - return _longRequestLoggingEnabled - ? metricsFactory.CreateLoggingMetricsTimer(_metricEntity, actionName, context.ActionArguments, - _longRequestTime, serviceName) - : metricsFactory.CreateMetricsTimer(_metricEntity, actionName, serviceName); + await next.Invoke(); } } + + private IDisposable CreateTimer(ActionExecutingContext context, MetricsFactory metricsFactory, string actionName) + { + return _longRequestLoggingEnabled + ? metricsFactory.CreateLoggingMetricsTimer(_metricEntity, actionName, context.ActionArguments, _longRequestTime) + : metricsFactory.CreateMetricsTimer(_metricEntity, actionName); + } } \ No newline at end of file From f45a85e57071dbe5d24a3b35b3938eece510cc03 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Mon, 26 Feb 2024 17:12:51 +0600 Subject: [PATCH 12/17] Naming refactoring --- ATI.Services.Common/Metrics/MetricsExtensions.cs | 2 +- ATI.Services.Common/Metrics/MetricsFactory.cs | 6 +++--- ATI.Services.Common/Metrics/RabbitMetricsDirection.cs | 7 ------- ATI.Services.Common/Metrics/RabbitMetricsType.cs | 7 +++++++ 4 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 ATI.Services.Common/Metrics/RabbitMetricsDirection.cs create mode 100644 ATI.Services.Common/Metrics/RabbitMetricsType.cs diff --git a/ATI.Services.Common/Metrics/MetricsExtensions.cs b/ATI.Services.Common/Metrics/MetricsExtensions.cs index d23f03a..2c6acb2 100644 --- a/ATI.Services.Common/Metrics/MetricsExtensions.cs +++ b/ATI.Services.Common/Metrics/MetricsExtensions.cs @@ -16,7 +16,7 @@ public static class MetricsExtensions /// /// Alias to distinguish from Microsoft.Extensions.DependencyInjection.MetricsServiceExtensions.AddMetrics(IServiceCollection) /// - public static void AddAtiMetrics(this IServiceCollection services) => services.AddMetrics(); + public static void AddCommonMetrics(this IServiceCollection services) => services.AddMetrics(); public static void AddMetrics(this IServiceCollection services) { diff --git a/ATI.Services.Common/Metrics/MetricsFactory.cs b/ATI.Services.Common/Metrics/MetricsFactory.cs index 1bb668f..8f8c1fa 100644 --- a/ATI.Services.Common/Metrics/MetricsFactory.cs +++ b/ATI.Services.Common/Metrics/MetricsFactory.cs @@ -44,7 +44,7 @@ public static MetricsFactory CreateHttpClientMetricsFactory( params string[] additionalSummaryLabels) { var labels = ConcatLabelNames( - "route_template", + "route_template", "entity_name", "external_http_service_name", MetricsLabelsAndHeaders.UserLabels, @@ -158,7 +158,7 @@ public static MetricsFactory CreateRepositoryMetricsFactory( } public static MetricsFactory CreateRabbitMqMetricsFactory( - RabbitMetricsDirection direction, + RabbitMetricsType type, [NotNull] string className, TimeSpan? requestLongTime = null, params string[] additionalSummaryLabels) @@ -173,7 +173,7 @@ public static MetricsFactory CreateRabbitMqMetricsFactory( return new MetricsFactory( className, LogSource.RabbitMq, - $"{Prefix}_rabbitmq_{direction.ToString().ToLower()}", + $"{Prefix}_rabbitmq_{type.ToString().ToLower()}", requestLongTime ?? _defaultLongRequestTime, labels); } diff --git a/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs b/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs deleted file mode 100644 index dc1cd6b..0000000 --- a/ATI.Services.Common/Metrics/RabbitMetricsDirection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ATI.Services.Common.Metrics; - -public enum RabbitMetricsDirection -{ - In, - Out -} \ No newline at end of file diff --git a/ATI.Services.Common/Metrics/RabbitMetricsType.cs b/ATI.Services.Common/Metrics/RabbitMetricsType.cs new file mode 100644 index 0000000..7f60838 --- /dev/null +++ b/ATI.Services.Common/Metrics/RabbitMetricsType.cs @@ -0,0 +1,7 @@ +namespace ATI.Services.Common.Metrics; + +public enum RabbitMetricsType +{ + Subscribe, + Publish +} \ No newline at end of file From b9453345ccbf025405a9f5cd91bcb48d793730ab Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Mon, 26 Feb 2024 18:37:12 +0600 Subject: [PATCH 13/17] README update --- README.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 929ad46..aec8b12 100644 --- a/README.md +++ b/README.md @@ -109,15 +109,36 @@ --- ### Метрики -Добавляем метрики в `Startup.cs` : `services.AddMetrics();` -Он автоматически добавит: `MetricsOptions` + +#### Виды метрик +``` +common_metric_sql - collected by DapperDb and PostgressDapper +common_metric_http_client - for outgoing http requests, ConsulMetricsHttpClientWrapper uses it +common_metric_rabbitmq_in - incoming messages from rmq, used by ATI.Services.RabbitMQ and ChangeTracking +common_metric_rabbitmq_out - outgoing messages to rmq, used by ATI.Services.RabbitMQ and ChangeTracking +common_metric_repository - should be collected manually +common_metric_controller - incoming http requests, added by MeasureAttribute in controllers +common_metric_Exceptions - application exceptions +common_metric_HttpStatusCodeCounter - aspnet response codes +common_metric_redis - collected by RedisCache +common_metric_mongo - should be collected manually +common_metric_{something} - this one reserved for custom metric, if you really need it, try to keep number of unique metrics as low as possible +``` +#### Добавление в проект Так как Prometheus собирает метрики через консул, добавляем тег в конфиг консула `metrics-port-*портприложения*`. -Добавляем [endpoint](http://stash.ri.domain:7990/projects/AS/repos/ati.firmservicecore/browse/ATI.FirmService.Core.Web.Api/Controllers/MetricsController.cs) для сбора метрик. -Добавляем мидлвару ```csharp - app.UseMetrics(); +services.AddMetrics(); //или services.AddCommonMetrics(); +//... +app.UseEndpoints(endpoints => + { + //... + endpoints.MapMetricsCollection(); //Добавляем эндпоинт для сбора метрик + //... + }); + +app.UseMetrics(); //Добавляем мидлвару ``` Для использования кастомных метрик в `appsettings.json` нужно определить следующую модель: @@ -126,15 +147,10 @@ "LabelsAndHeaders": { "Лейбл метрики" : "Header HTTP-запроса" }, - "MetricsServiceName": "notifications" //переопределяем название сервиса для метрик }, ``` Ключ словаря - лейбл метрики, значение - Header HTTP-запроса. - - -Просим девопсов добавить сервис в Prometheus. - Собственно сбор: На метод котроллера вешаем `MeasureAttribute`, в который передаем название сущности, с которой работает метод. В остальных файлах создаем нужный экземпляр `MetricsFactory` оборачиваем методы в using c `CreateMetricsTimer`: From 7f8cb496bd86a7870bfc3aeb9ea1e683e856dd7e Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Tue, 27 Feb 2024 15:21:37 +0600 Subject: [PATCH 14/17] Fix .net8 incompatible Microsoft.Data.SqlClient version --- ATI.Services.Common/ATI.Services.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Common/ATI.Services.Common.csproj b/ATI.Services.Common/ATI.Services.Common.csproj index e3d1e3c..03c1053 100644 --- a/ATI.Services.Common/ATI.Services.Common.csproj +++ b/ATI.Services.Common/ATI.Services.Common.csproj @@ -17,7 +17,7 @@ - + From 6e4055cdc1bccc9d51ea939ed98ff40be979194b Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Wed, 28 Feb 2024 17:00:29 +0600 Subject: [PATCH 15/17] Update prometheus-net due to critical bug in v8.0.0 to v8.1.1 --- ATI.Services.Common/ATI.Services.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Common/ATI.Services.Common.csproj b/ATI.Services.Common/ATI.Services.Common.csproj index 03c1053..9a6675e 100644 --- a/ATI.Services.Common/ATI.Services.Common.csproj +++ b/ATI.Services.Common/ATI.Services.Common.csproj @@ -25,7 +25,7 @@ - + From d2d394cc23fe70b5621dd79501cf9aff0a6fd182 Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Wed, 28 Feb 2024 17:10:16 +0600 Subject: [PATCH 16/17] Value of TrustServerCertificate from connectionString is ignored, true by default Can be overriden through DataBaseOptions --- ATI.Services.Common/Sql/DapperDb.cs | 2 ++ ATI.Services.Common/Sql/DataBaseOptions.cs | 36 +++++++++++----------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/ATI.Services.Common/Sql/DapperDb.cs b/ATI.Services.Common/Sql/DapperDb.cs index 100a97c..c265a80 100644 --- a/ATI.Services.Common/Sql/DapperDb.cs +++ b/ATI.Services.Common/Sql/DapperDb.cs @@ -1019,6 +1019,8 @@ private string BuildConnectionString() builder.ConnectRetryInterval = _options.ConnectRetryInterval.Value; } + builder.TrustServerCertificate = _options.TrustServerCertificate ?? true; + return builder.ToString(); } diff --git a/ATI.Services.Common/Sql/DataBaseOptions.cs b/ATI.Services.Common/Sql/DataBaseOptions.cs index c5ba327..43914ca 100644 --- a/ATI.Services.Common/Sql/DataBaseOptions.cs +++ b/ATI.Services.Common/Sql/DataBaseOptions.cs @@ -1,23 +1,23 @@ using System; using System.Collections.Generic; -namespace ATI.Services.Common.Sql +namespace ATI.Services.Common.Sql; + +public class DataBaseOptions { - public class DataBaseOptions - { - public string ConnectionString { get; set; } - public TimeSpan Timeout { get; set; } - public IDictionary TimeoutDictionary { get; set; } = new Dictionary(); - public TimeSpan? LongTimeRequest { get; set; } + public string ConnectionString { get; set; } + public TimeSpan Timeout { get; set; } + public IDictionary TimeoutDictionary { get; set; } = new Dictionary(); + public TimeSpan? LongTimeRequest { get; set; } - public string Server { get; set; } - public string Database { get; set; } - public string UserName { get; set; } - public string Password { get; set; } - public int? MinPoolSize { get; set; } - public int? MaxPoolSize { get; set; } - public int? ConnectTimeout { get; set; } - public int? ConnectRetryCount { get; set; } - public int? ConnectRetryInterval { get; set; } - } -} + public string Server { get; set; } + public string Database { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + public int? MinPoolSize { get; set; } + public int? MaxPoolSize { get; set; } + public int? ConnectTimeout { get; set; } + public int? ConnectRetryCount { get; set; } + public int? ConnectRetryInterval { get; set; } + public bool? TrustServerCertificate { get; set; } +} \ No newline at end of file From 2321b24cd6bd1f8b665f303f826a5c117c2c98ce Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Thu, 29 Feb 2024 10:59:27 +0600 Subject: [PATCH 17/17] Microsoft.Data.SqlClient bug fix version --- ATI.Services.Common/ATI.Services.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ATI.Services.Common/ATI.Services.Common.csproj b/ATI.Services.Common/ATI.Services.Common.csproj index 9a6675e..aeb5a12 100644 --- a/ATI.Services.Common/ATI.Services.Common.csproj +++ b/ATI.Services.Common/ATI.Services.Common.csproj @@ -17,7 +17,7 @@ - +