From 13b324fc39e182baeed9a424f9e38415c4367408 Mon Sep 17 00:00:00 2001 From: XtremeOwnage <5262735+XtremeOwnageDotCom@users.noreply.github.com> Date: Sun, 1 Sep 2024 14:56:28 -0500 Subject: [PATCH] V0.2.1 - Bug Fixes, and Better Logging (#35) * Added "Debug" Configuration (#32) * Added Configuration options to support #31 * ALlow cfg to disable publishing discovery messages, for #31 * #31 - Only print discovery messages when specified by debug config. * If publshing messages is disabled- also don't spam the console the broker is not connected. * Correct NullReferenceExceptions (#33) * Remove uneeded braces. * This fixes #27, caused because the default value was null. * throw error, if for unexpected reasons, we do get a null http client. * Change default topic to "rPDU2MQTT". * Better Logging (#34) * Log a message indicating we found the config file. * Added starting logging configuration, for #25 * Renamed these files to be more concise. * Use serilog for logging. * Updating application to use Serilog Static Logging. Don't see value in using Microsoft.Extensions.Logging. Just- extra dependancies to move around. * Removed appsettings.json completely. * Static Logger, used for configuring dependancies, initial setup, etc. * Global using for Serilog. * Added serilog packages. * Removed static global using. "Information" does not have the needed context. * Log message showing that we did successfully load a configuration file. * Sorting usings * Added Configuration for Logging. * Added extension to configure logging. * Use serilog * Remove appsettings from .gitignore. * Use serilog here. * Added Path to default/example config. * Moved file-based options to dedicated class. Added file rollover support, and retention support. * Set minimum log level during startup to verbose. * Added FileRollover / FileRetention to Config.yaml --- .gitignore | 1 - rPDU2MQTT/Classes/PDU.cs | 15 ++--- .../Extensions/EntityWithName_Overrides.cs | 6 +- rPDU2MQTT/Helpers/StringHelper.cs | 4 +- rPDU2MQTT/Helpers/ThrowError.cs | 8 +-- rPDU2MQTT/Models/Config/Config.cs | 6 ++ rPDU2MQTT/Models/Config/DebugConfig.cs | 28 +++++++++ rPDU2MQTT/Models/Config/LoggingConfig.cs | 29 +++++++++ rPDU2MQTT/Models/Config/MQTTConfig.cs | 4 +- rPDU2MQTT/Models/Config/Overrides.cs | 11 ++-- .../Schemas/FileLoggingTargetConfiguration.cs | 19 ++++++ .../Schemas/LoggingTargetConfiguration.cs | 13 ++++ rPDU2MQTT/Program.cs | 16 +++-- .../Services/HomeAssistantDiscoveryService.cs | 8 +-- rPDU2MQTT/Services/MQTTPublishingService.cs | 7 +-- .../baseTypes/baseDiscoveryService.cs | 7 ++- .../Services/baseTypes/baseMQTTTService.cs | 27 ++++---- .../baseTypes/basePublishingService.cs | 4 +- rPDU2MQTT/Startup/ConfigLoader.cs | 28 --------- rPDU2MQTT/Startup/ConfigureLogging.cs | 43 +++++++++++++ rPDU2MQTT/Startup/ServiceConfiguration.cs | 7 ++- ...{FindYamlConfig.cs => YamlConfigLoader.cs} | 20 +++--- rPDU2MQTT/appsettings.json | 9 --- rPDU2MQTT/config.defaults.yaml | 61 +++++++++++++++++++ rPDU2MQTT/globalusings.cs | 3 +- rPDU2MQTT/rPDU2MQTT.csproj | 55 +++++++++-------- 26 files changed, 303 insertions(+), 136 deletions(-) create mode 100644 rPDU2MQTT/Models/Config/DebugConfig.cs create mode 100644 rPDU2MQTT/Models/Config/LoggingConfig.cs create mode 100644 rPDU2MQTT/Models/Config/Schemas/FileLoggingTargetConfiguration.cs create mode 100644 rPDU2MQTT/Models/Config/Schemas/LoggingTargetConfiguration.cs delete mode 100644 rPDU2MQTT/Startup/ConfigLoader.cs create mode 100644 rPDU2MQTT/Startup/ConfigureLogging.cs rename rPDU2MQTT/Startup/{FindYamlConfig.cs => YamlConfigLoader.cs} (83%) delete mode 100644 rPDU2MQTT/appsettings.json diff --git a/.gitignore b/.gitignore index a52350b..92e67ad 100644 --- a/.gitignore +++ b/.gitignore @@ -362,5 +362,4 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -/rPDU2MQTT/appsettings.Development.json /rPDU2MQTT/config.yaml diff --git a/rPDU2MQTT/Classes/PDU.cs b/rPDU2MQTT/Classes/PDU.cs index 361d032..3e649c1 100644 --- a/rPDU2MQTT/Classes/PDU.cs +++ b/rPDU2MQTT/Classes/PDU.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using rPDU2MQTT.Extensions; +using rPDU2MQTT.Extensions; using rPDU2MQTT.Helpers; using rPDU2MQTT.Models.PDU; using rPDU2MQTT.Models.PDU.DummyDevices; @@ -13,13 +12,11 @@ public partial class PDU { private readonly Config config; private readonly HttpClient http; - private readonly ILogger<PDU> log; - public PDU(Config config, HttpClient http, ILogger<PDU> log) + public PDU(Config config, [DisallowNull, NotNull] HttpClient http) { this.config = config; - this.http = http; - this.log = log; + this.http = http ?? throw new NullReferenceException("HttpClient in constructor was null"); } /// <summary> @@ -29,9 +26,9 @@ public PDU(Config config, HttpClient http, ILogger<PDU> log) /// <returns></returns> public async Task<RootData> GetRootData_Public(CancellationToken cancellationToken) { - log.LogDebug("Querying /api"); + Log.Debug("Querying /api"); var model = await http.GetFromJsonAsync<GetResponse<RootData>>("/api", options: Models.PDU.Converter.Settings, cancellationToken); - log.LogDebug($"Query response {model.RetCode}"); + Log.Debug($"Query response {model.RetCode}"); //Process device data. processData(model.Data, cancellationToken); @@ -44,7 +41,7 @@ public async void processData(RootData data, CancellationToken cancellationToken //Set basic details. data.Record_Parent = null; data.Record_Key = config.MQTT.ParentTopic; - data.URL = http.BaseAddress.ToString(); + data.URL = http.BaseAddress!.ToString(); data.Entity_Identifier = Coalesce(config.Overrides?.PDU?.ID, "rPDU2MQTT")!; data.Entity_Name = data.Entity_DisplayName = Coalesce(config.Overrides?.PDU?.Name, data.Sys.Label, data.Sys.Name, "rPDU2MQTT")!; diff --git a/rPDU2MQTT/Extensions/EntityWithName_Overrides.cs b/rPDU2MQTT/Extensions/EntityWithName_Overrides.cs index b937b1f..4a1036e 100644 --- a/rPDU2MQTT/Extensions/EntityWithName_Overrides.cs +++ b/rPDU2MQTT/Extensions/EntityWithName_Overrides.cs @@ -38,9 +38,9 @@ public static void SetEntityNameAndEnabled<TKey, TEntity>([DisallowNull] this Di { // We are adding + 1 to the outlet's key- because the PDU gives data 0-based. However, when the entities are viewed through // its UI, they are 1-based. This corrects that. - (int k, Outlet o) => overrides.Outlets!.TryGetValue(k + 1, out var outletOverride) ? outletOverride : null, - (string k, Device o) => overrides.Devices!.TryGetValue(k, out var outletOverride) ? outletOverride : null, - (string k, Measurement o) => overrides.Measurements!.TryGetValue(o.Type, out var outletOverride) ? outletOverride : null, + (int k, Outlet o) => overrides.Outlets.TryGetValue(k + 1, out var outletOverride) ? outletOverride : null, + (string k, Device o) => overrides.Devices.TryGetValue(k, out var outletOverride) ? outletOverride : null, + (string k, Measurement o) => overrides.Measurements.TryGetValue(o.Type, out var outletOverride) ? outletOverride : null, _ => null }; diff --git a/rPDU2MQTT/Helpers/StringHelper.cs b/rPDU2MQTT/Helpers/StringHelper.cs index 4f612fc..463eded 100644 --- a/rPDU2MQTT/Helpers/StringHelper.cs +++ b/rPDU2MQTT/Helpers/StringHelper.cs @@ -10,10 +10,8 @@ public static class StringHelper public static string? Coalesce(params string?[] values) { foreach (var value in values) - { if (!string.IsNullOrWhiteSpace(value)) - return value; - } + return value; return null; } diff --git a/rPDU2MQTT/Helpers/ThrowError.cs b/rPDU2MQTT/Helpers/ThrowError.cs index be0a7d5..293bfcd 100644 --- a/rPDU2MQTT/Helpers/ThrowError.cs +++ b/rPDU2MQTT/Helpers/ThrowError.cs @@ -7,9 +7,9 @@ public static class ThrowError [DoesNotReturn] public static T ConfigurationMissing<T>(string ConfigurationPath) { - Console.WriteLine("Please validate configuration.yaml"); + Log.Fatal("Please validate configuration.yaml"); string msg = $"Missing required configuration of type {typeof(T).Name}. Path: " + ConfigurationPath; - Console.WriteLine(msg); + Log.Fatal(msg); throw new Exception(msg); } @@ -18,9 +18,9 @@ public static void TestRequiredConfigurationSection([AllowNull, NotNull] object { if (section is null) { - Console.WriteLine("Please validate configuration.yaml"); + Log.Fatal("Please validate configuration.yaml"); string msg = $"Missing required configuration. Path: " + ConfigurationPath; - Console.WriteLine(msg); + Log.Fatal(msg); throw new Exception(msg); } diff --git a/rPDU2MQTT/Models/Config/Config.cs b/rPDU2MQTT/Models/Config/Config.cs index c3852a4..7b3f732 100644 --- a/rPDU2MQTT/Models/Config/Config.cs +++ b/rPDU2MQTT/Models/Config/Config.cs @@ -19,4 +19,10 @@ public class Config [YamlMember(Alias = "Overrides", DefaultValuesHandling = DefaultValuesHandling.OmitDefaults, Description = "Overrides")] public Overrides Overrides { get; set; } = new Overrides(); + + [YamlMember(Alias = "Debug", DefaultValuesHandling = DefaultValuesHandling.OmitDefaults, Description = "Settings for debugging and diagnostics.")] + public DebugConfig Debug { get; set; } = new DebugConfig(); + + [YamlMember(Alias = "Logging")] + public LoggingConfig Logging { get; set; } = new LoggingConfig(); } diff --git a/rPDU2MQTT/Models/Config/DebugConfig.cs b/rPDU2MQTT/Models/Config/DebugConfig.cs new file mode 100644 index 0000000..b771d44 --- /dev/null +++ b/rPDU2MQTT/Models/Config/DebugConfig.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; +using YamlDotNet.Serialization; + +namespace rPDU2MQTT.Models.Config; + +/// <summary> +/// Settings for debugging and diagnostics. +/// </summary> +public class DebugConfig +{ + /// <summary> + /// This- determines if messages are actually PUSHED to the MQTT broker. + /// </summary> + /// <remarks> + /// Intended usage, is to allow the entire process to be tested, and debugged, without actually publishing the mwssages. + /// </remarks> + [YamlMember(Alias = "PublishMessages")] + [DefaultValue(true)] + public bool PublishMessages { get; set; } = true; + + + /// <summary> + /// When enabled, this will print the MQTT discovery messages to the console. + /// </summary> + [YamlMember(Alias = "PrintDiscovery")] + [DefaultValue(false)] + public bool PrintDiscovery { get; set; } = false; +} \ No newline at end of file diff --git a/rPDU2MQTT/Models/Config/LoggingConfig.cs b/rPDU2MQTT/Models/Config/LoggingConfig.cs new file mode 100644 index 0000000..1409388 --- /dev/null +++ b/rPDU2MQTT/Models/Config/LoggingConfig.cs @@ -0,0 +1,29 @@ +using rPDU2MQTT.Models.Config.Schemas; +using Serilog.Events; +using YamlDotNet.Serialization; + +namespace rPDU2MQTT.Models.Config; + +/// <summary> +/// Configuration for logging. +/// </summary> +public class LoggingConfig +{ + public string? LogFilePath { get; set; } = null; + + [YamlMember(Alias = "Console")] + public LoggingTargetConfiguration Console { get; set; } = new LoggingTargetConfiguration() + { + Enabled = true, + Severity = LogEventLevel.Information, + }; + + [YamlMember(Alias = "File")] + public FileLoggingTargetConfiguration File { get; set; } = new FileLoggingTargetConfiguration() + { + Enabled = false, + Severity = LogEventLevel.Debug, + FileRetention = 30, + FileRollover = RollingInterval.Day, + }; +} \ No newline at end of file diff --git a/rPDU2MQTT/Models/Config/MQTTConfig.cs b/rPDU2MQTT/Models/Config/MQTTConfig.cs index 56fa26f..4ef2a0f 100644 --- a/rPDU2MQTT/Models/Config/MQTTConfig.cs +++ b/rPDU2MQTT/Models/Config/MQTTConfig.cs @@ -1,4 +1,5 @@ using rPDU2MQTT.Models.Config.Schemas; +using System.ComponentModel; using YamlDotNet.Serialization; namespace rPDU2MQTT.Models.Config; @@ -23,7 +24,8 @@ public class MQTTConfig /// </summary> [Required(ErrorMessage = "ParentTopic is required.")] [Display(Description = "The parent topic for MQTT messages.")] - public string ParentTopic { get; set; } = "Rack_PDU"; + [DefaultValue("rPDU2MQTT")] + public string ParentTopic { get; set; } = "rPDU2MQTT"; /// <summary> /// Gets or sets the connection details for MQTT Broker. diff --git a/rPDU2MQTT/Models/Config/Overrides.cs b/rPDU2MQTT/Models/Config/Overrides.cs index 33aff47..5b637bd 100644 --- a/rPDU2MQTT/Models/Config/Overrides.cs +++ b/rPDU2MQTT/Models/Config/Overrides.cs @@ -7,14 +7,17 @@ namespace rPDU2MQTT.Models.Config; public class Overrides { [YamlMember(Alias = "PDU", DefaultValuesHandling = DefaultValuesHandling.OmitNull, Description = "Allows overriding values for the PDU itself.")] - public EntityOverride? PDU { get; set; } + public EntityOverride PDU { get; set; } = new(); + [YamlMember(Alias = "Devices", DefaultValuesHandling = DefaultValuesHandling.OmitNull, Description = "Allows overriding configuration for individual devices.")] - public Dictionary<string, EntityOverride?>? Devices { get; set; } + public Dictionary<string, EntityOverride?> Devices { get; set; } = new(); + [YamlMember(Alias = "Outlets", DefaultValuesHandling = DefaultValuesHandling.OmitNull, Description = "Allows overriding values for individual outlets.")] - public Dictionary<int, EntityOverride?>? Outlets { get; set; } + public Dictionary<int, EntityOverride?> Outlets { get; set; } = new(); + [YamlMember(Alias = "Measurements", DefaultValuesHandling = DefaultValuesHandling.OmitNull, Description = "Allows overriding individual measurements")] - public Dictionary<string, EntityOverride?>? Measurements { get; set; } + public Dictionary<string, EntityOverride?> Measurements { get; set; } = new(); } diff --git a/rPDU2MQTT/Models/Config/Schemas/FileLoggingTargetConfiguration.cs b/rPDU2MQTT/Models/Config/Schemas/FileLoggingTargetConfiguration.cs new file mode 100644 index 0000000..20146b0 --- /dev/null +++ b/rPDU2MQTT/Models/Config/Schemas/FileLoggingTargetConfiguration.cs @@ -0,0 +1,19 @@ +namespace rPDU2MQTT.Models.Config.Schemas; + +public class FileLoggingTargetConfiguration : LoggingTargetConfiguration +{ + /// <summary> + /// Path to log file which will be used. + /// </summary> + public string? Path { get; set; } = null; + + /// <summary> + /// This determines when files are rolled over. + /// </summary> + public RollingInterval FileRollover { get; set; } = RollingInterval.Day; + + /// <summary> + /// This determines how many rolled over files to retain. + /// </summary> + public int FileRetention { get; set; } = 30; +} diff --git a/rPDU2MQTT/Models/Config/Schemas/LoggingTargetConfiguration.cs b/rPDU2MQTT/Models/Config/Schemas/LoggingTargetConfiguration.cs new file mode 100644 index 0000000..acdb6e2 --- /dev/null +++ b/rPDU2MQTT/Models/Config/Schemas/LoggingTargetConfiguration.cs @@ -0,0 +1,13 @@ +using Serilog.Events; + +namespace rPDU2MQTT.Models.Config.Schemas; + +public class LoggingTargetConfiguration +{ + public LogEventLevel Severity { get; set; } = LogEventLevel.Information; + + public string Format { get; set; } = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"; + + public bool Enabled { get; set; } = false; + +} diff --git a/rPDU2MQTT/Program.cs b/rPDU2MQTT/Program.cs index bfd0fba..58d9eb4 100644 --- a/rPDU2MQTT/Program.cs +++ b/rPDU2MQTT/Program.cs @@ -1,17 +1,23 @@ using HiveMQtt.Client; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using rPDU2MQTT.Startup; -using System.Runtime.InteropServices; + +Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Console() + .MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose) + .CreateLogger(); var host = Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration(ConfigLoader.Configure) + .ConfigureAppConfiguration(o => { o.AddEnvironmentVariables(); }) .ConfigureServices(ServiceConfiguration.Configure) .ConfigureLogging(logging => { logging.ClearProviders(); -logging.AddConsole(); + logging.AddConsole(); }) .Build(); @@ -19,10 +25,10 @@ var client = host.Services.GetRequiredService<IHiveMQClient>(); var logger = host.Services.GetRequiredService<ILogger<IHiveMQClient>>(); -logger.LogInformation($"Connecting to MQTT Broker at {client.Options.Host}:{client.Options.Port}"); +Log.Information($"Connecting to MQTT Broker at {client.Options.Host}:{client.Options.Port}"); await client.ConnectAsync(); -logger.LogInformation("Successfully connected to broker!"); +Log.Information("Successfully connected to broker!"); host.Run(); \ No newline at end of file diff --git a/rPDU2MQTT/Services/HomeAssistantDiscoveryService.cs b/rPDU2MQTT/Services/HomeAssistantDiscoveryService.cs index 7ae3417..4285e15 100644 --- a/rPDU2MQTT/Services/HomeAssistantDiscoveryService.cs +++ b/rPDU2MQTT/Services/HomeAssistantDiscoveryService.cs @@ -14,19 +14,19 @@ namespace rPDU2MQTT.Services; /// </summary> public class HomeAssistantDiscoveryService : baseDiscoveryService { - public HomeAssistantDiscoveryService(ILogger<HomeAssistantDiscoveryService> log, MQTTServiceDependancies deps) : base(deps, log) { } + public HomeAssistantDiscoveryService(MQTTServiceDependancies deps) : base(deps) { } protected override async Task Execute(CancellationToken cancellationToken) { var data = await pdu.GetRootData_Public(cancellationToken); var pduDevice = data.GetDiscoveryDevice(); - log.LogDebug("Starting discovery job."); + Log.Debug("Starting discovery job."); // Recursively discover everything. await recursiveDiscovery(data.Devices, pduDevice, cancellationToken); - log.LogInformation("Discovery information published."); + Log.Information("Discovery information published."); } @@ -81,7 +81,7 @@ protected async Task recursiveDiscovery<TEntity>([AllowNull] TEntity entity, Dis // Discover measurements await recursiveDiscovery(pduEntity.Measurements, parent, cancellationToken); } - else if(entity is Measurement measurement) + else if (entity is Measurement measurement) { await DiscoverMeasurementAsync(measurement, parent, cancellationToken); } diff --git a/rPDU2MQTT/Services/MQTTPublishingService.cs b/rPDU2MQTT/Services/MQTTPublishingService.cs index 3aaa200..5e4a62c 100644 --- a/rPDU2MQTT/Services/MQTTPublishingService.cs +++ b/rPDU2MQTT/Services/MQTTPublishingService.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using rPDU2MQTT.Classes; +using rPDU2MQTT.Classes; using rPDU2MQTT.Services.baseTypes; namespace rPDU2MQTT.Services; @@ -9,7 +8,7 @@ namespace rPDU2MQTT.Services; /// </summary> public class MQTTPublishingService : basePublishingService { - public MQTTPublishingService(ILogger<MQTTPublishingService> log, MQTTServiceDependancies deps) : base(log, deps) { } + public MQTTPublishingService(MQTTServiceDependancies deps) : base(deps) { } protected override async Task Execute(CancellationToken cancellationToken) { @@ -17,7 +16,7 @@ protected override async Task Execute(CancellationToken cancellationToken) foreach (var device in rootData.Devices.Values) { await PublishState(device, cancellationToken); - + foreach (var entity in device.Entity.Values) { await PublishName(entity, cancellationToken); diff --git a/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs b/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs index d388a71..4e75881 100644 --- a/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs +++ b/rPDU2MQTT/Services/baseTypes/baseDiscoveryService.cs @@ -14,7 +14,7 @@ namespace rPDU2MQTT.Services.baseTypes; public abstract class baseDiscoveryService : baseMQTTTService { - public baseDiscoveryService(MQTTServiceDependancies deps, ILogger log) : base(deps, log, deps.Cfg.HASS.DiscoveryInterval) { } + public baseDiscoveryService(MQTTServiceDependancies deps) : base(deps, deps.Cfg.HASS.DiscoveryInterval) { } /// <summary> /// Publish a discovery message for the specified <paramref name="measurement"/>, for device <paramref name="Parent"/> @@ -105,7 +105,7 @@ protected Task PushDiscoveryMessage<T>(T sensor, CancellationToken cancellationT { var topic = $"{cfg.HASS.DiscoveryTopic}/{sensor.EntityType.ToJsonString()}/{sensor.ID}/config"; - log.LogDebug($"Publishing Discovery of type {sensor.EntityType.ToJsonString()} for {sensor.ID} to {topic}"); + Log.Debug($"Publishing Discovery of type {sensor.EntityType.ToJsonString()} for {sensor.ID} to {topic}"); var msg = new MQTT5PublishMessage(topic, QualityOfService.AtLeastOnceDelivery) { @@ -113,7 +113,8 @@ protected Task PushDiscoveryMessage<T>(T sensor, CancellationToken cancellationT PayloadAsString = System.Text.Json.JsonSerializer.Serialize<T>(sensor, this.jsonOptions) }; - Console.WriteLine(msg.PayloadAsString); + if (cfg.Debug.PrintDiscovery) + Log.Debug(msg.PayloadAsString); return this.Publish(msg, cancellationToken); } diff --git a/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs b/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs index 54f766c..da420ab 100644 --- a/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs +++ b/rPDU2MQTT/Services/baseTypes/baseMQTTTService.cs @@ -1,15 +1,9 @@ using HiveMQtt.Client; using HiveMQtt.MQTT5.Types; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using rPDU2MQTT.Classes; -using rPDU2MQTT.Extensions; using rPDU2MQTT.Models.Converters; -using rPDU2MQTT.Models.HomeAssistant; -using rPDU2MQTT.Models.HomeAssistant.Enums; -using rPDU2MQTT.Models.PDU; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; namespace rPDU2MQTT.Services.baseTypes; @@ -19,8 +13,7 @@ namespace rPDU2MQTT.Services.baseTypes; public abstract class baseMQTTTService : IHostedService, IDisposable { private readonly int interval; - protected ILogger log { get; init; } - protected IHiveMQClient mqtt { get; init; } + private IHiveMQClient mqtt { get; init; } private PeriodicTimer timer; private Task timerTask = Task.CompletedTask; protected Config cfg { get; } @@ -28,11 +21,10 @@ public abstract class baseMQTTTService : IHostedService, IDisposable protected System.Text.Json.JsonSerializerOptions jsonOptions { get; init; } - protected baseMQTTTService(MQTTServiceDependancies dependancies, ILogger log) : this(dependancies, log, dependancies.Cfg.PDU.PollInterval) { } - protected baseMQTTTService(MQTTServiceDependancies dependancies, ILogger log, int Interval) + protected baseMQTTTService(MQTTServiceDependancies dependancies) : this(dependancies, dependancies.Cfg.PDU.PollInterval) { } + protected baseMQTTTService(MQTTServiceDependancies dependancies, int Interval) { interval = Interval; - this.log = log; mqtt = dependancies.Mqtt; cfg = dependancies.Cfg; pdu = dependancies.PDU; @@ -54,14 +46,14 @@ protected baseMQTTTService(MQTTServiceDependancies dependancies, ILogger log, in public async Task StartAsync(CancellationToken cancellationToken) { - log.LogInformation($"{GetType().Name} is starting."); + Log.Information($"{GetType().Name} is starting."); timerTask = Task.Run(() => timerTaskExecution(cancellationToken).Wait()); //Kick off the first one manually. await Execute(cancellationToken); - log.LogInformation($"{GetType().Name} is running."); + Log.Information($"{GetType().Name} is running."); } private async Task timerTaskExecution(CancellationToken cancellationToken) @@ -74,11 +66,11 @@ private async Task timerTaskExecution(CancellationToken cancellationToken) public Task StopAsync(CancellationToken stoppingToken) { - log.LogInformation($"{GetType().Name} is stopping."); + Log.Information($"{GetType().Name} is stopping."); //Do Something? - log.LogInformation($"{GetType().Name} has stopped."); + Log.Information($"{GetType().Name} has stopped."); return Task.CompletedTask; } @@ -110,8 +102,11 @@ protected Task PublishString(string Topic, string Message, CancellationToken can /// <returns></returns> protected Task Publish(MQTT5PublishMessage msg, CancellationToken cancellationToken) { + if (cfg.Debug.PublishMessages == false) + return Task.CompletedTask; + if (!mqtt.IsConnected()) - log.LogError("MQTT Broker is not connected!!!!!"); + Log.Error("MQTT Broker is not connected!!!!!"); return mqtt.PublishAsync(msg, cancellationToken); } diff --git a/rPDU2MQTT/Services/baseTypes/basePublishingService.cs b/rPDU2MQTT/Services/baseTypes/basePublishingService.cs index cea857e..d446d9f 100644 --- a/rPDU2MQTT/Services/baseTypes/basePublishingService.cs +++ b/rPDU2MQTT/Services/baseTypes/basePublishingService.cs @@ -9,8 +9,8 @@ namespace rPDU2MQTT.Services.baseTypes; public abstract class basePublishingService : baseMQTTTService { - protected basePublishingService(ILogger log, MQTTServiceDependancies dependancies) : base(dependancies, log, dependancies.Cfg.PDU.PollInterval) { } - protected basePublishingService(MQTTServiceDependancies dependancies, ILogger log, int Interval) : base(dependancies, log, Interval) { } + protected basePublishingService(MQTTServiceDependancies dependancies) : base(dependancies, dependancies.Cfg.PDU.PollInterval) { } + protected basePublishingService(MQTTServiceDependancies dependancies, int Interval) : base(dependancies, Interval) { } /// <summary> /// Publish a series of measurements under <paramref name="Topic"/> diff --git a/rPDU2MQTT/Startup/ConfigLoader.cs b/rPDU2MQTT/Startup/ConfigLoader.cs deleted file mode 100644 index 7308663..0000000 --- a/rPDU2MQTT/Startup/ConfigLoader.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; - -namespace rPDU2MQTT.Startup; - -public static class ConfigLoader -{ - /// <summary> - /// This loads configuration from appsettings.json, and appsettings.$ENV.json - /// </summary> - /// <param name="context"></param> - /// <param name="config"></param> - public static void Configure(HostBuilderContext context, IConfigurationBuilder config) - { - string baseConfig = "appsettings.json"; - string envSpecificConfig = $"appsettings.{context.HostingEnvironment.EnvironmentName}.json"; - - Console.WriteLine("Loading JSON Configuration"); - Console.WriteLine($"{baseConfig} exists: {File.Exists(baseConfig)}"); - Console.WriteLine($"{envSpecificConfig} exists: {File.Exists(envSpecificConfig)}"); - - //Check for configuration files in the current directory. - config - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile(envSpecificConfig, optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); - } -} diff --git a/rPDU2MQTT/Startup/ConfigureLogging.cs b/rPDU2MQTT/Startup/ConfigureLogging.cs new file mode 100644 index 0000000..720d356 --- /dev/null +++ b/rPDU2MQTT/Startup/ConfigureLogging.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; +using rPDU2MQTT.Classes; +using rPDU2MQTT.Helpers; + +namespace rPDU2MQTT.Startup; + +public static class ConfigureLoggingExtension +{ + public static IServiceCollection ConfigureLogging(this IServiceCollection services, Config cfg) + { + services.AddSerilog(o => + { + // HttpClient logging is extremely verbose.... Only show warnings. + o.MinimumLevel.Override("System.Net.Http.HttpClient", Serilog.Events.LogEventLevel.Warning); + o.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose); + o.Enrich.FromLogContext(); + if (cfg.Logging.Console.Enabled) + o.WriteTo.Console(cfg.Logging.Console.Severity, outputTemplate: cfg.Logging.Console.Format); + + // Configure logging to file. + if (cfg.Logging.File.Enabled) + { + if (string.IsNullOrEmpty(cfg.Logging.File.Path)) + { + Log.Fatal("Config.Logging.File.Enabled=true, but, Config.Logging.File.Path is not specified. Please either provide a path, or disable Logging to file."); + ThrowError.TestRequiredConfigurationSection(cfg.Logging.File.Path, "Config.Logging.File.Path"); + } + + o.WriteTo.File(path: cfg.Logging.File.Path + , restrictedToMinimumLevel: cfg.Logging.Console.Severity + , outputTemplate: cfg.Logging.Console.Format + , rollingInterval: cfg.Logging.File.FileRollover + , retainedFileCountLimit: cfg.Logging.File.FileRetention); + + Log.Debug("Will log to file at " + cfg.Logging.File.Path); + } + else + Log.Debug("Will not log to file."); + }); + + return services; + } +} diff --git a/rPDU2MQTT/Startup/ServiceConfiguration.cs b/rPDU2MQTT/Startup/ServiceConfiguration.cs index 3a40313..0646053 100644 --- a/rPDU2MQTT/Startup/ServiceConfiguration.cs +++ b/rPDU2MQTT/Startup/ServiceConfiguration.cs @@ -14,11 +14,14 @@ public static void Configure(HostBuilderContext context, IServiceCollection serv // While- we can request services when building dependancies- // Need the configuration DURING service collection initilization- // Because it determiens which hosted services we want to add. - Config cfg = FindYamlConfig.GetConfig() ?? throw new Exception("Unable to load configuration"); + Config cfg = YamlConfigLoader.GetConfig() ?? throw new Exception("Unable to load configuration"); // Bind Configuration services.AddSingleton(cfg); + // Configure Logging. + services.ConfigureLogging(cfg); + // Bind IHiveMQClient services.AddSingleton<IHiveMQClient, HiveMQClient>((sp) => { @@ -93,6 +96,6 @@ public static void Configure(HostBuilderContext context, IServiceCollection serv if (cfg.HASS.DiscoveryEnabled) services.AddHostedService<HomeAssistantDiscoveryService>(); else - Console.WriteLine($"Home Assistant Discovery Disabled."); + Log.Warning($"Home Assistant Discovery Disabled."); } } diff --git a/rPDU2MQTT/Startup/FindYamlConfig.cs b/rPDU2MQTT/Startup/YamlConfigLoader.cs similarity index 83% rename from rPDU2MQTT/Startup/FindYamlConfig.cs rename to rPDU2MQTT/Startup/YamlConfigLoader.cs index 569777b..3fced46 100644 --- a/rPDU2MQTT/Startup/FindYamlConfig.cs +++ b/rPDU2MQTT/Startup/YamlConfigLoader.cs @@ -1,17 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; -using rPDU2MQTT.Classes; +using rPDU2MQTT.Classes; using System.Runtime.InteropServices; using YamlDotNet.Serialization; - namespace rPDU2MQTT.Startup; /// <summary> /// This class, validates a YAML configuration exists, and returns the path. /// </summary> -internal class FindYamlConfig +internal class YamlConfigLoader { public static string Find() { - Console.WriteLine("Attempting to locate configuration file."); + Log.Information("Attempting to locate configuration file."); // Before starting- we need to validate a configuration file exists. string[] SearchPaths = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch @@ -32,18 +30,20 @@ from extension in YamlExtensions foreach (var file in combinations) { if (File.Exists(file)) + { + Log.Information($"Found config file at {file}"); return file; + } } /// At this point, we cannot find a configuration. /// Print an error to the console, and lets add a sleep / delay - /// before throwing the exception. - /// + /// before throwing the exception. - Console.WriteLine("Unable to locate config.yaml. Paths searched:"); + Log.Error("Unable to locate config.yaml. Paths searched:"); foreach (var file in combinations) - Console.WriteLine($"\t{file}"); - Console.WriteLine("Restarting in 15 seconds."); + Log.Error($"\t{file}"); + Log.Information("Restarting in 15 seconds."); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(15)); diff --git a/rPDU2MQTT/appsettings.json b/rPDU2MQTT/appsettings.json deleted file mode 100644 index d2267d3..0000000 --- a/rPDU2MQTT/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "System.Net.Http.HttpClient": "Warning", - "rPDU2MQTT.*": "Debug" - } - } -} diff --git a/rPDU2MQTT/config.defaults.yaml b/rPDU2MQTT/config.defaults.yaml index f3a4edf..f64c12d 100644 --- a/rPDU2MQTT/config.defaults.yaml +++ b/rPDU2MQTT/config.defaults.yaml @@ -149,3 +149,64 @@ HomeAssistant: DiscoveryInterval: 300 # Default expireAfter interval applied to all sensors. After this time- the sensor will be marked as unavailable. SensorExpireAfterSeconds: 300 + +# These settings are used when debugging, or when additional data or diagnostics is needed. +Debug: + # When enabled, discovery messages will be formatted, and printed to console. + # Default: false + PrintDiscovery: false + + # When set to false, this will prevent messages from being published to the MQTT Broker. + # This is used to test the entire program, WITHOUT sending messages. + # Default: true + PublishMessages: true + +# Optional settings used to configure logging. +Logging: + # Supported Severity Values: Verbose,Debug,Information,Warning,Error,Fatal + + # Customize messages printed to Console / stdout + Console: + # Should messages be printed to stdout/console? + # (Optional). Default: true + Enabled: true + + # Only messages with this severity or higher will be printed. + # (Optional). Default: Information + Severity: Information + + # Allows customizing the format of log messages. + # (Optional). Default: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + # For details: https://github.com/serilog/serilog/wiki/Formatting-Output + Format: "[{Timestamp:HH:mm:ss} {Level}] {Message:lj}{NewLine}{Exception}" + + # Customize logging to file. + File: + # Should messages be saved to a file? + # (Optional). Default: false + Enabled: false + + # Only messages with this severity or higher will be printed. + # (Optional). Default: Information + Severity: Debug + + # Allows customizing the format of log messages. + # (Optional). Default: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + # For details: https://github.com/serilog/serilog/wiki/Formatting-Output + Format: "[{Timestamp:HH:mm:ss} {Level}] {Message:lj}{NewLine}{Exception}" + + # Set to the path where you wish to output a log file. + # (Optional). Default: Not Specified + # Example: /config/mylog.log + Path: null + + # File Rollover + # Options: + # - Infinite = Files will never rollover. + # - Year, Month, Day, Hour, Minute = Files will rollover this often. + # (Optional.) Default: Day + FileRollover: Day + + # The number of rolled over logs which will be retained. + # Example, if RolloverMode=Day, and this is set to 30, you will retain ~30 days of logs. + FileRetention: 30 \ No newline at end of file diff --git a/rPDU2MQTT/globalusings.cs b/rPDU2MQTT/globalusings.cs index d5b8a12..7b08668 100644 --- a/rPDU2MQTT/globalusings.cs +++ b/rPDU2MQTT/globalusings.cs @@ -4,4 +4,5 @@ global using System.Text; global using System.Threading.Tasks; global using System.ComponentModel.DataAnnotations; -global using static rPDU2MQTT.Helpers.StringHelper; \ No newline at end of file +global using static rPDU2MQTT.Helpers.StringHelper; +global using Serilog; \ No newline at end of file diff --git a/rPDU2MQTT/rPDU2MQTT.csproj b/rPDU2MQTT/rPDU2MQTT.csproj index ae3004b..80ec2b0 100644 --- a/rPDU2MQTT/rPDU2MQTT.csproj +++ b/rPDU2MQTT/rPDU2MQTT.csproj @@ -1,33 +1,34 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - <PublishAot>False</PublishAot> - <InvariantGlobalization>true</InvariantGlobalization> - <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> - </PropertyGroup> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <PublishAot>False</PublishAot> + <InvariantGlobalization>true</InvariantGlobalization> + <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> + </PropertyGroup> - <ItemGroup> - <PackageReference Include="HiveMQtt" Version="0.22.1" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> - <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> - <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" /> - <PackageReference Include="System.Net.Http.Json" Version="8.0.0" /> - <PackageReference Include="System.Text.Json" Version="8.0.4" /> - <PackageReference Include="YamlDotNet" Version="16.0.0" /> - </ItemGroup> + <ItemGroup> + <PackageReference Include="HiveMQtt" Version="0.22.1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> + <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> + <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" /> + <PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" /> + <PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" /> + <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> + <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" /> + <PackageReference Include="System.Net.Http.Json" Version="8.0.0" /> + <PackageReference Include="System.Text.Json" Version="8.0.4" /> + <PackageReference Include="YamlDotNet" Version="16.0.0" /> + </ItemGroup> - <ItemGroup> - <None Update="appsettings.json"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </None> - <None Update="config.yaml"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </None> - </ItemGroup> + <ItemGroup> + <None Update="config.yaml"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + </ItemGroup> </Project>