diff --git a/src/Services/EnphaseMetricsService.cs b/src/Services/EnphaseMetricsService.cs index 0e546e8..1cb53b2 100644 --- a/src/Services/EnphaseMetricsService.cs +++ b/src/Services/EnphaseMetricsService.cs @@ -15,8 +15,7 @@ namespace SolarGateway_PrometheusProxy.Services; /// public class EnphaseMetricsService( HttpClient httpClient, - ILogger logger, - ILoggerFactory loggerFactory) : MetricsServiceBase(httpClient, logger, loggerFactory) + ILogger logger) : MetricsServiceBase(httpClient, logger) { protected override string MetricCategory => "enphase"; diff --git a/src/Services/MetricsServiceBase.cs b/src/Services/MetricsServiceBase.cs index 4fb6e4a..366eaba 100644 --- a/src/Services/MetricsServiceBase.cs +++ b/src/Services/MetricsServiceBase.cs @@ -1,8 +1,6 @@ using System.Net; using System.Net.Http.Headers; using System.Text.Json; -using Polly; -using Polly.Retry; using Prometheus; using SolarGateway_PrometheusProxy.Exceptions; using SolarGateway_PrometheusProxy.Models; @@ -12,55 +10,22 @@ namespace SolarGateway_PrometheusProxy.Services; /// /// Base class that provides common functionality for brands that use a REST API to collect metrics. /// -public abstract class MetricsServiceBase : IMetricsService +public abstract class MetricsServiceBase(HttpClient client, ILogger logger) : IMetricsService { /// /// The HTTP client used to call the brand's API. /// - protected readonly HttpClient _client; + protected readonly HttpClient _client = client; /// /// The logger used to log messages. /// - protected readonly ILogger _logger; - - /// - /// The resilience pipeline used to call the brand's API. - /// - protected readonly ResiliencePipeline _resiliencePipeline; + protected readonly ILogger _logger = logger; private readonly SemaphoreSlim _authTokenRefreshLock = new(1, 1); private bool _authTokenRefreshed; private AuthenticationHeaderValue? _authenticationHeader; - public MetricsServiceBase(HttpClient client, ILogger logger, ILoggerFactory factory) - { - this._client = client; - this._logger = logger; - this._resiliencePipeline = new ResiliencePipelineBuilder() - .ConfigureTelemetry(factory) - .AddRetry( - new RetryStrategyOptions - { - ShouldHandle = new PredicateBuilder() - .HandleResult(result => this.UsesAuthToken && result.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden), - MaxRetryAttempts = 1, - Delay = TimeSpan.Zero, - OnRetry = args => this.RefreshTokenAsync(args.Context.CancellationToken) - }) - .AddRetry( - new RetryStrategyOptions - { - ShouldHandle = new PredicateBuilder() - .HandleResult(result => result.StatusCode is HttpStatusCode.TooManyRequests), - MaxRetryAttempts = 3, - BackoffType = DelayBackoffType.Exponential, - UseJitter = true - }) - .AddTimeout(TimeSpan.FromSeconds(5)) - .Build(); - } - /// /// Main method to collect metrics from the brand's device(s) and save them to the Prometheus . /// @@ -129,6 +94,7 @@ protected void SetRequestDurationMetric(CollectorRegistry registry, TimeSpan dur /// protected async Task CallMetricEndpointAsync(string path, CancellationToken cancellationToken) { + HttpResponseMessage? response = null; try { if (this.UsesAuthToken && this._authenticationHeader == null) @@ -136,13 +102,23 @@ protected void SetRequestDurationMetric(CollectorRegistry registry, TimeSpan dur await this.RefreshTokenAsync(cancellationToken); } - using var response = await _resiliencePipeline.ExecuteAsync(async token => + async Task SendRequestAsync() { using var request = new HttpRequestMessage(HttpMethod.Get, path); request.Headers.Authorization = this._authenticationHeader; - return await this._client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token); - }, cancellationToken); + return await this._client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + } + + response = await SendRequestAsync(); + + if (this.UsesAuthToken && response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) + { + // Try refresh token and issue again + await this.RefreshTokenAsync(cancellationToken); + response.Dispose(); + response = await SendRequestAsync(); + } if (!response.IsSuccessStatusCode) { @@ -157,12 +133,22 @@ protected void SetRequestDurationMetric(CollectorRegistry registry, TimeSpan dur } catch (Exception ex) { - throw new MetricRequestFailedException(ex.Message, ex); + this._logger.LogError(ex, "Failed to call '{Path}'", path); + return null; + } + finally + { + response?.Dispose(); } } private async ValueTask RefreshTokenAsync(CancellationToken cancellationToken) { + if (!this.UsesAuthToken || this._authTokenRefreshed) + { + return; + } + await _authTokenRefreshLock.WaitAsync(cancellationToken); try { diff --git a/src/Services/TeslaGatewayMetricsService.cs b/src/Services/TeslaGatewayMetricsService.cs index e905780..706f743 100644 --- a/src/Services/TeslaGatewayMetricsService.cs +++ b/src/Services/TeslaGatewayMetricsService.cs @@ -16,8 +16,7 @@ public partial class TeslaGatewayMetricsService( HttpClient httpClient, IOptions configuration, IOptions loginRequest, - ILogger logger, - ILoggerFactory loggerFactory) : MetricsServiceBase(httpClient, logger, loggerFactory) + ILogger logger) : MetricsServiceBase(httpClient, logger) { private readonly TeslaLoginRequest _loginRequest = loginRequest.Value; private readonly TimeSpan _loginCacheLength = TimeSpan.FromMinutes(configuration.Value.LoginCacheMinutes); @@ -68,12 +67,12 @@ public override async Task CollectMetricsAsync(CollectorRegistry collectorRegist this.PullSiteInfo(collectorRegistry, cancellationToken), this.PullStatus(collectorRegistry, cancellationToken), this.PullOperation(collectorRegistry, cancellationToken)); + + base.SetRequestDurationMetric(collectorRegistry, sw.Elapsed); if (!results.All(r => r)) { throw new MetricRequestFailedException($"Failed to pull {results.Count(r => !r)}/{results.Length} endpoints on Tesla gateway"); } - - base.SetRequestDurationMetric(collectorRegistry, sw.Elapsed); } private async Task PullMeterAggregates(CollectorRegistry registry, CancellationToken cancellationToken) diff --git a/src/SolarGateway_PrometheusProxy.csproj b/src/SolarGateway_PrometheusProxy.csproj index a1b5090..73ddc22 100644 --- a/src/SolarGateway_PrometheusProxy.csproj +++ b/src/SolarGateway_PrometheusProxy.csproj @@ -19,7 +19,6 @@ - @@ -31,7 +30,6 @@ -