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>