diff --git a/libgwmapi/GwmApiClient.cs b/libgwmapi/GwmApiClient.cs index 1e124d6..f6f0a89 100644 --- a/libgwmapi/GwmApiClient.cs +++ b/libgwmapi/GwmApiClient.cs @@ -1,6 +1,7 @@ using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; namespace libgwmapi; @@ -10,14 +11,16 @@ public partial class GwmApiClient public static readonly string AppHttpClientName = "eu-app-gateway"; private readonly HttpClient _h5Client; private readonly HttpClient _appClient; + private readonly ILogger _logger; - public GwmApiClient(IHttpClientFactory factory) - : this(factory.CreateClient(H5HttpClientName), factory.CreateClient(AppHttpClientName)) + public GwmApiClient(IHttpClientFactory factory, ILoggerFactory loggerFactory) + : this(factory.CreateClient(H5HttpClientName), factory.CreateClient(AppHttpClientName), loggerFactory) { } - public GwmApiClient(HttpClient h5Client, HttpClient appClient) + public GwmApiClient(HttpClient h5Client, HttpClient appClient, ILoggerFactory loggerFactory) { + _logger = loggerFactory.CreateLogger(); _h5Client = h5Client; _h5Client.DefaultRequestHeaders.Add("rs", "2"); _h5Client.DefaultRequestHeaders.Add("terminal", "GW_APP_ORA"); @@ -107,6 +110,11 @@ private async Task GetAppAsync(string url, CancellationToken cancellationT private async Task CheckResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) { + if (_logger.IsEnabled(LogLevel.Trace)) + { + await response.Content.LoadIntoBufferAsync(); + _logger.LogTrace(await response.Content.ReadAsStringAsync(cancellationToken)); + } response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); CheckResponse(result); @@ -114,6 +122,11 @@ private async Task CheckResponseAsync(HttpResponseMessage response, Cancellation private async Task GetResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) { + if (_logger.IsEnabled(LogLevel.Trace)) + { + await response.Content.LoadIntoBufferAsync(); + _logger.LogTrace(await response.Content.ReadAsStringAsync(cancellationToken)); + } response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>(cancellationToken: cancellationToken); CheckResponse(result); diff --git a/ora2mqtt/BaseCommand.cs b/ora2mqtt/BaseCommand.cs index 3ecf60c..c38a977 100644 --- a/ora2mqtt/BaseCommand.cs +++ b/ora2mqtt/BaseCommand.cs @@ -2,15 +2,28 @@ using libgwmapi; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Http.Logging; +using Microsoft.Extensions.Logging; using YamlDotNet.Serialization; namespace ora2mqtt; public abstract class BaseCommand { + [Option('d', "debug", Default = false, HelpText = "enable debug logging")] + public bool Debug { get; set; } + [Option('c', "config", Default = "ora2mqtt.yml", HelpText = "path to yaml config file")] public string ConfigFile { get; set; } + protected ILoggerFactory LoggerFactory { get; private set; } + + protected void Setup() + { + LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(x => x.SetMinimumLevel(Debug ? LogLevel.Trace : LogLevel.Error).AddConsole()); + } + protected GwmApiClient ConfigureApiClient(Ora2MqttOptions options) { var certHandler = new CertificateHandler(); @@ -39,7 +52,20 @@ protected GwmApiClient ConfigureApiClient(Ora2MqttOptions options) } } - return new GwmApiClient(new HttpClient(), new HttpClient(httpHandler)) + var httpLogger = LoggerFactory.CreateLogger(); + var httpOptions = new HttpClientFactoryOptions + { + ShouldRedactHeaderValue = x => "accessToken".Equals(x, StringComparison.InvariantCultureIgnoreCase) + }; + var h5Client = new HttpClient(new LoggingHttpMessageHandler(httpLogger, httpOptions) + { + InnerHandler = new HttpClientHandler() + }); + var appClient = new HttpClient(new LoggingHttpMessageHandler(httpLogger) + { + InnerHandler = httpHandler + }); + return new GwmApiClient(h5Client, appClient, LoggerFactory) { Country = options.Country }; diff --git a/ora2mqtt/ConfigureCommand.cs b/ora2mqtt/ConfigureCommand.cs index 83a492c..4bf2752 100644 --- a/ora2mqtt/ConfigureCommand.cs +++ b/ora2mqtt/ConfigureCommand.cs @@ -7,14 +7,20 @@ using Sharprompt; using Sharprompt.Fluent; using YamlDotNet.Serialization; +using ora2mqtt.Logging; +using Microsoft.Extensions.Logging; namespace ora2mqtt { [Verb("configure", HelpText = "run config file wizard")] public class ConfigureCommand:BaseCommand { + private ILogger _logger; + public async Task Run(CancellationToken cancellationToken) { + Setup(); + _logger = LoggerFactory.CreateLogger(); Ora2MqttOptions config; if (!File.Exists(ConfigFile)) { @@ -57,7 +63,7 @@ private void SelectCountry(Ora2MqttOptions options) { options.Country = Prompt.Select(o => o .WithMessage("Please choose your country") - .WithItems(new[] { "DE", "GB" }) + .WithItems(new[] { "DE", "GB", "EE" }) ); } } @@ -74,7 +80,7 @@ private async Task LoginAsync(GwmApiClient client, Ora2MqttOptions options, Canc } catch (GwmApiException e) { - await Console.Error.WriteLineAsync($"Access token expired ({e.Message}). Trying to refresh token..."); + _logger.LogError($"Access token expired ({e.Message}). Trying to refresh token..."); } var refresh = new RefreshTokenRequest { @@ -92,7 +98,7 @@ private async Task LoginAsync(GwmApiClient client, Ora2MqttOptions options, Canc } catch (GwmApiException e) { - await Console.Error.WriteLineAsync($"Token refresh failed: {e.Message}"); + _logger.LogError($"Token refresh failed: {e.Message}"); } } var request = new LoginAccountRequest @@ -160,7 +166,7 @@ private async Task TestMqttAsync(Ora2MqttOptions oraOptions, CancellationT try { - var factory = new MqttFactory(); + var factory = new MqttFactory(new MqttLogger(LoggerFactory)); using var client = factory.CreateMqttClient(); var builder = new MqttClientOptionsBuilder() .WithTcpServer(options.Host) @@ -175,7 +181,7 @@ private async Task TestMqttAsync(Ora2MqttOptions oraOptions, CancellationT } catch (MqttCommunicationException ex) { - await Console.Error.WriteLineAsync($"Mqtt connection failed: {ex.Message}"); + _logger.LogError($"Mqtt connection failed: {ex.Message}"); return false; } return true; diff --git a/ora2mqtt/Logging/MqttLogger.cs b/ora2mqtt/Logging/MqttLogger.cs new file mode 100644 index 0000000..13a9f2b --- /dev/null +++ b/ora2mqtt/Logging/MqttLogger.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Logging; +using MQTTnet.Diagnostics; + +namespace ora2mqtt.Logging +{ + internal class MqttLogger: IMqttNetLogger + { + private readonly ILogger _logger; + + public MqttLogger(ILoggerFactory factory) + { + _logger = factory.CreateLogger(); + } + + public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) + { + var level = logLevel switch + { + MqttNetLogLevel.Verbose => LogLevel.Debug, + MqttNetLogLevel.Info => LogLevel.Information, + MqttNetLogLevel.Warning => LogLevel.Warning, + MqttNetLogLevel.Error => LogLevel.Error, + _ => throw new ArgumentOutOfRangeException(nameof(logLevel)) + }; + _logger.Log(level, exception, message, parameters); + } + + public bool IsEnabled => true; + } +} diff --git a/ora2mqtt/Program.cs b/ora2mqtt/Program.cs index 69319ee..635535e 100644 --- a/ora2mqtt/Program.cs +++ b/ora2mqtt/Program.cs @@ -1,18 +1,8 @@ -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using CommandLine; -using libgwmapi; -using libgwmapi.DTO.UserAuth; -using MQTTnet; -using MQTTnet.Client; -using MQTTnet.Exceptions; +using CommandLine; using ora2mqtt; -using Sharprompt; -using Sharprompt.Fluent; -using YamlDotNet.Serialization; using var cts = new CancellationTokenSource(); -Console.CancelKeyPress += (s, e) => +Console.CancelKeyPress += (_, e) => { cts.Cancel(); e.Cancel = true; diff --git a/ora2mqtt/RunCommand.cs b/ora2mqtt/RunCommand.cs index e661be5..d462059 100644 --- a/ora2mqtt/RunCommand.cs +++ b/ora2mqtt/RunCommand.cs @@ -4,22 +4,27 @@ using MQTTnet.Client; using MQTTnet; using YamlDotNet.Serialization; -using MQTTnet.Server; using libgwmapi.DTO.UserAuth; +using Microsoft.Extensions.Logging; +using ora2mqtt.Logging; namespace ora2mqtt; [Verb("run", true, HelpText = "default")] public class RunCommand:BaseCommand { + private ILogger _logger; + [Option('i', "interval", Default = 10, HelpText = "GWM API polling interval")] public int Intervall { get; set; } public async Task Run(CancellationToken cancellationToken) { + Setup(); + _logger = LoggerFactory.CreateLogger(); if (!File.Exists(ConfigFile)) { - await Console.Error.WriteLineAsync($"config file ({ConfigFile}) missing"); + _logger.LogError($"config file ({ConfigFile}) missing"); return 1; } Ora2MqttOptions config; @@ -52,7 +57,7 @@ public async Task Run(CancellationToken cancellationToken) private async Task ConnectMqttAsync(Ora2MqttMqttOptions options,CancellationToken cancellationToken) { - var factory = new MqttFactory(); + var factory = new MqttFactory(new MqttLogger(LoggerFactory)); var client = factory.CreateMqttClient(); var builder = new MqttClientOptionsBuilder() .WithTcpServer(options.Host) @@ -91,7 +96,7 @@ private async Task RefreshTokenAsync(GwmApiClient client, Ora2MqttOptions option } catch (GwmApiException e) { - await Console.Error.WriteLineAsync($"Access token expired ({e.Message}). Trying to refresh token..."); + _logger.LogError($"Access token expired ({e.Message}). Trying to refresh token..."); } var refresh = new RefreshTokenRequest diff --git a/ora2mqtt/ora2mqtt.csproj b/ora2mqtt/ora2mqtt.csproj index 903baf9..f31d271 100644 --- a/ora2mqtt/ora2mqtt.csproj +++ b/ora2mqtt/ora2mqtt.csproj @@ -9,6 +9,7 @@ +