--- phase: 15-logging plan: 01 type: execute wave: 1 depends_on: [] files_modified: - packages.config - NcProgramManager.csproj - nlog.config - Program.cs - Cnc/Fanuc/FanucMachine.cs - Cnc/Heidenhain/HeidenhainMachine.cs - Cnc/Heidenhain/Lsv2Client.cs - Cnc/Siemens/SiemensMachine.cs - Cnc/Mitsubishi/MitsubishiMachine.cs autonomous: true --- ## Goal Add NLog file+console logging and surface silent exception swallows in event handler paths across all 4 machine classes. ## Purpose ISS-003: 18 silent `catch {}` blocks mean production failures are undiagnosable. ISS-004: all output goes to console only — operators cannot send a log file for support. Adding NLog with a rolling file target gives a persistent record for debugging. ## Output - `nlog.config` — rolling file log + console, next to EXE - NLog wired into 5 production classes via `LogManager.GetCurrentClassLogger()` - Event handler catch blocks changed from silent swallow to `_log.Warn` - Dispose/cleanup catch blocks annotated as intentionally silent (no logging — Dispose must not rethrow) - Program.cs error catch blocks emit `_log.Error` in addition to existing `Console.WriteLine` @.paul/PROJECT.md @.paul/ISSUES.md @Cnc/Siemens/SiemensMachine.cs @Cnc/Heidenhain/Lsv2Client.cs ## AC-1: NLog config present and wired ```gherkin Given nlog.config is placed next to NcProgramManager.exe When the application starts Then NLog initialises without throwing (no "NLog configuration file not found" in console) And a logs/ subdirectory is created containing nc_program_manager.log ``` ## AC-2: Event handler exceptions reach the log ```gherkin Given a StateChanged handler that throws When SetState() fires in any machine class Then the exception is caught and logged at WARN level (not silently swallowed) ``` ## AC-3: Dispose cleanup catch blocks are annotated ```gherkin Given all Dispose() and disconnect cleanup catch blocks When reviewed in code Then each has a // cleanup — intentionally swallowed comment (no log call — Dispose must not rethrow) ``` ## AC-4: Program.cs errors reach the log file ```gherkin Given any unhandled exception in Scarica, Invia, or arg parsing When the catch block runs Then _log.Error(ex, ...) is called BEFORE Console.WriteLine And all existing Console.WriteLine calls remain unchanged ``` Task 1: Add NLog package and nlog.config packages.config, NcProgramManager.csproj, nlog.config 1. In `packages.config`, add inside ``: ```xml ``` 2. In `NcProgramManager.csproj`, inside the existing `` that contains `` entries, add: ```xml packages\NLog.5.2.8\lib\net46\NLog.dll True ``` Also in `NcProgramManager.csproj`, inside the `` that contains `` entries (or a new ``), add: ```xml Always ``` 3. Create `nlog.config` in the project root: ```xml ``` Constraints: - NLog package version is 5.2.8 — supports .NET 4.6+ (project is 4.7.2) - lib/net46 is the highest .NET Framework target in the NLog 5.x package - Console rule minlevel=Warn: logger output must NOT duplicate user-facing Console.WriteLine in Program.cs - `throwConfigExceptions="true"` surfaces config errors at startup rather than silently failing - Read packages.config: NLog 5.2.8 entry present - Read NcProgramManager.csproj: Reference to NLog with HintPath present; nlog.config Content entry with CopyToOutputDirectory present - Read nlog.config: file target with archiving, console target, two rules present AC-1 satisfied: NLog wired into build; nlog.config copied to output directory on build Task 2: Wire logger into machine classes and fix event handler catches Cnc/Fanuc/FanucMachine.cs, Cnc/Heidenhain/HeidenhainMachine.cs, Cnc/Heidenhain/Lsv2Client.cs, Cnc/Siemens/SiemensMachine.cs, Cnc/Mitsubishi/MitsubishiMachine.cs For each of the 5 files: **A. Add using directive at top:** ```csharp using NLog; ``` **B. Add logger field** inside the class, after existing private fields: ```csharp private static readonly Logger _log = LogManager.GetCurrentClassLogger(); ``` **C. Fix event handler catch blocks** — there is one `SetState` method in each machine class containing: ```csharp try { h(this, next); } catch { } ``` Replace with: ```csharp try { h(this, next); } catch (Exception ex) { _log.Warn(ex, "StateChanged handler threw"); } ``` **D. Annotate Dispose() cleanup catches** — do NOT add logging (Dispose must not rethrow): ```csharp try { DisconnectAsync().GetAwaiter().GetResult(); } catch { /* cleanup — intentionally swallowed */ } try { _gate.Dispose(); } catch { /* cleanup — intentionally swallowed */ } ``` **For Lsv2Client specifically (no SetState, no _gate):** - Add `using NLog;` and `private static readonly Logger _log = LogManager.GetCurrentClassLogger();` - In `Connect()` timeout branch: `try { _tcp.Close(); } catch { /* cleanup — intentionally swallowed */ }` - In `Disconnect()`: annotate all 3 cleanup catches with `/* cleanup — intentionally swallowed */` - Add connect/disconnect info logging: ```csharp // At start of Connect(), before _tcp = new TcpClient(): _log.Info("Connecting to {0}:{1}", _config.IpAddress, _config.Port); // After successful Login(): _log.Info("Connected to {0}", _config.IpAddress); // At start of Disconnect(): _log.Info("Disconnecting from {0}", _config.IpAddress); ``` Constraints: - Do NOT add logging inside `Dispose()` or disposal catch blocks — Dispose must not throw and logging adds failure risk - Do NOT change `catch (Exception ex) { _errors.Add(...); }` blocks — already handled correctly - Do NOT change `catch (OperationCanceledException)` blocks — correct behavior - Logger is `static readonly` (one per class type, not per instance) — NLog standard pattern - Do NOT add `using NLog;` if already present (check first) - Grep for `LogManager.GetCurrentClassLogger()` in all 5 files — must appear exactly once per file - Grep for `try { h(this, next); } catch { }` — must return 0 results (all replaced) - Grep for `_log.Warn(ex` — must appear in FanucMachine, HeidenhainMachine, SiemensMachine, MitsubishiMachine - Read Lsv2Client.cs: verify info logging at connect/disconnect, cleanup catch annotations present AC-2 and AC-3 satisfied: event handler exceptions logged at WARN; cleanup catches annotated as intentional Task 3: Add file logging to Program.cs error catch blocks Program.cs 1. Add `using NLog;` to the using directives at top of Program.cs. 2. Add logger field inside the `Program` class (or at the start of Main — static field): ```csharp private static readonly Logger _log = LogManager.GetCurrentClassLogger(); ``` 3. In the arg parsing catch block (currently around line 19): ```csharp catch (Exception e) { _log.Error(e, "Arg parsing failed"); Console.Write("Gestore Programmi FANUC: "); Console.WriteLine(e.Message); // ... rest unchanged ``` 4. In the `Scarica` catch block (currently around line 157): ```csharp catch (Exception ex) { _log.Error(ex, "Scarica failed"); Console.WriteLine(ex.Message); // ... rest unchanged ``` 5. In the `Invia` catch block (currently around line 253): ```csharp catch (Exception ex) { _log.Error(ex, "Invia failed"); Console.WriteLine(ex.Message); // ... rest unchanged ``` Constraints: - `_log.Error(ex, ...)` MUST come BEFORE `Console.WriteLine(ex.Message)` — log first, then display - ALL existing Console.WriteLine calls remain exactly as-is — do not modify or remove any - Do NOT add logging for license failure (`Console.WriteLine("Licenza ERRATA")`) — that's a known condition, not an exception - Do NOT add a `NLog.LogManager.Shutdown()` call — not needed for a short-lived console EXE - Read Program.cs: `using NLog;` present - Read Program.cs: `_log` field present - Grep for `_log.Error` in Program.cs — must appear 3 times - Grep for `Console.WriteLine` in Program.cs — must still be 97 (unchanged count) AC-4 satisfied: all 3 Program.cs error paths log to file before Console.WriteLine; no Console.WriteLine removed ## DO NOT CHANGE - `Licensing/LicenseValidator.cs` — public key constant must not be modified - `Licensing/MachineFingerprint.cs` — fixed in Phase 13; not in scope - `NcProgramManager.Tests/` — no test changes in this phase - `Cnc/**/` catch blocks already handling exceptions via `_errors.Add(...)` — correct, leave alone - All existing `Console.WriteLine` calls — supplemented only, never replaced ## SCOPE LIMITS - No logging inside Dispose() or cleanup catch paths — comment only - No replacement of user-facing Console.WriteLine — file logging supplements via NLog - No logging for known control-flow conditions (license failure, validation failure) — only unexpected exceptions - No async/await changes — ISS-002 is a separate phase - No NuGet package other than NLog — no wrapper abstraction, no DI container Before declaring plan complete: - [ ] `packages.config` contains NLog 5.2.8 entry - [ ] `NcProgramManager.csproj` has NLog Reference with HintPath and nlog.config as Content/CopyAlways - [ ] `nlog.config` exists with file target (archiving) + console target (Warn+) + rules - [ ] All 5 machine/client files: `LogManager.GetCurrentClassLogger()` field present - [ ] `try { h(this, next); } catch { }` — zero occurrences remain in codebase - [ ] Dispose cleanup catches in all 4 machine classes: annotated with comment - [ ] Lsv2Client.Disconnect() catches: annotated with comment - [ ] Program.cs: 3× `_log.Error` added before Console.WriteLine; 97× Console.WriteLine count unchanged - All 3 tasks completed - All 4 ACs covered - ISS-003 addressed: event handler swallows surfaced; cleanup swallows documented - ISS-004 addressed: NLog file+console logging wired into all production classes - No existing tests broken (no test files modified) - No production behavior changed for operators (Console.WriteLine output identical) After completion, create `.paul/phases/15-logging/15-01-SUMMARY.md`