# GitConverter.Lib.Logging This module provides a small, testable logging abstraction and adapters used by the library and ConsoleApp. It is intentionally lightweight and designed to be safe for unit tests, CI, and production use. Contents - Purpose and design principles - Architecture and key types - Usage patterns (library and CLI) - File logging configuration (programmatic) - Test support and TestLogger pattern - Troubleshooting and common scenarios - Best practices and guidance Purpose and design principles - Offer a minimal facade (`Log`) and a small interface (`IAppLogger`) so library code can emit diagnostics without depending on a concrete logging framework. - Default to a no-op `NullLogger` so library logging is zero-cost until a host configures a logger. - Keep logging non-intrusive: logging must be best-effort and must not throw or change application control flow. - Make tests deterministic by allowing tests to swap the active logger for a `TestLogger` that captures messages in memory. Architecture overview - `IAppLogger` — abstraction for logging (Debug/Info/Warn/Error). Implementations must be non-throwing and lightweight. - `Log` — static facade used by library components; holds the mutable `Current` logger and exposes convenience forwarding methods. - `NullLogger` — default no-op implementation. - `Log4NetAdapter` — adapter that forwards `IAppLogger` calls to log4net `ILog`. - `LogHelper` — optional formatting utilities to emit separators, boxed messages and underlined headers. Usage patterns Library components (recommended) - Use the `Log` façade in library code. Do not depend on log4net-specific types. Example ```csharp Log.Info("Starting conversion"); try { // conversion logic Log.Debug($"Processed {count} features"); } catch (Exception ex) { Log.Error("Conversion failed", ex); } ``` CLI / host startup - Configure logging once at application startup; prefer programmatic call to `Log.SetFile(...)` or rely on host-provided log4net configuration and call `Log.Enable()`. - Wrap configuration in a try/catch and fall back to `Log.Disable()` when configuration fails to avoid terminating the host. File logging (programmatic) - Call `Log.SetFile(logFilePath, logLevel)` to configure a rolling file appender (size-based, UTF-8, minimal lock). - `logFilePath` is resolved to a full path; containing directories are created if necessary. - The helper probes write access and throws `IOException` when the path is not writable — callers should handle this during startup. - `logLevel` accepts names like `All`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`, `Off` (case-insensitive). - After calling `SetFile`, call `Log.Enable()` to set `Log.Current` to the `Log4NetAdapter`; `SetFile` also sets the adapter automatically. Example ```csharp try { Log.SetFile("logs/conversion.log", "Info"); Log.Enable(); } catch (Exception ex) { Console.Error.WriteLine($"Logging disabled: {ex.Message}"); Log.Disable(); } ``` Test support - For unit tests prefer setting `Log.Current` to a `TestLogger` or inject a test logger into components that accept `IAppLogger`. - `TestLogger` should record messages in simple collections so assertions can be made on message contents and levels. Example TestLogger pattern ```csharp public class TestLogger : IAppLogger { public List DebugMessages { get; } = new(); public List InfoMessages { get; } = new(); public List WarnMessages { get; } = new(); public List<(string Message, Exception Ex)> ErrorMessages { get; } = new(); public void Debug(string message) => DebugMessages.Add(message); public void Info(string message) => InfoMessages.Add(message); public void Warn(string message) => WarnMessages.Add(message); public void Error(string message, Exception ex = null) => ErrorMessages.Add((message, ex)); } ``` Troubleshooting - No log file created - Ensure `Log.SetFile` received a writable path. The method throws `IOException` when write access fails. - Check that `Log.Enable()` was called (or that the host configured log4net externally). - Log file exists but empty - Verify the active logger is not `NullLogger` and that the configured level includes the messages you expect (e.g., `Debug` vs `Info`). - Confirm `Log.Current` was replaced with `Log4NetAdapter`. - Unicode replacement characters - Ensure log encoding is UTF-8 (the rolling appender is configured with UTF-8 by default). Best practices - Configure logging once during host startup; do not repeatedly call `SetFile`. - Use `Log.Disable()` in tests when you want to suppress file/console logging side-effects. - Prefer substring checks on logged messages in tests rather than exact equality to resist wording changes. - Do not depend on logging for control flow or feature toggles. References - `GitConverter.Lib.Logging.Log` — static facade - `GitConverter.Lib.Logging.IAppLogger` — logger abstraction - `GitConverter.Lib.Logging.Log4NetAdapter` — adapter to log4net - `GitConverter.Lib.Logging.NullLogger` and `TestLogger` — no-op and test implementations --- If you want a condensed quickstart for configuring logging in the ConsoleApp I can add a `Quickstart.md` snippet showing best-practice wiring and example command-line arguments for log paths.