nc_program_manager/.paul/phases/15-logging/15-01-PLAN.md
dtrentin 60ce1c9d57 feat(15-logging): NLog structured logging — all output through NLog (v0.5 complete)
Phase 15 complete (2 plans):
- 15-01: NLog 5.2.8 + nlog.config (rolling file daily/7d + console); logger fields in
  FanucMachine, HeidenhainMachine, SiemensMachine, MitsubishiMachine, Lsv2Client, Program.cs;
  SetState event handler catches → _log.Warn; cleanup catches annotated; ISS-003/ISS-004 closed
- 15-02: All Console.Write/WriteLine in Program.cs replaced with _log.Info (103 calls);
  nlog.config console target layout=${message}, levels=Info only — Warn/Error file-only;
  operator console output identical; every interaction now timestamped in log file

Milestone v0.5 Operational Quality complete.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 23:57:27 +02:00

317 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
---
<objective>
## 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`
</objective>
<context>
@.paul/PROJECT.md
@.paul/ISSUES.md
@Cnc/Siemens/SiemensMachine.cs
@Cnc/Heidenhain/Lsv2Client.cs
</context>
<acceptance_criteria>
## 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
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Add NLog package and nlog.config</name>
<files>
packages.config,
NcProgramManager.csproj,
nlog.config
</files>
<action>
1. In `packages.config`, add inside `<packages>`:
```xml
<package id="NLog" version="5.2.8" targetFramework="net472" />
```
2. In `NcProgramManager.csproj`, inside the existing `<ItemGroup>` that contains `<Reference>` entries, add:
```xml
<Reference Include="NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>packages\NLog.5.2.8\lib\net46\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
```
Also in `NcProgramManager.csproj`, inside the `<ItemGroup>` that contains `<Compile>` entries (or a new `<ItemGroup>`), add:
```xml
<Content Include="nlog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
```
3. Create `nlog.config` in the project root:
```xml
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
throwConfigExceptions="true">
<targets>
<target xsi:type="File"
name="logfile"
fileName="${basedir}/logs/nc_program_manager.log"
archiveFileName="${basedir}/logs/nc_program_manager.{#}.log"
archiveEvery="Day"
archiveNumbering="Date"
maxArchiveFiles="7"
layout="${longdate} ${level:uppercase=true:padding=5} [${logger:shortName=true}] ${message}${onexception:${newline}${exception:format=tostring}}" />
<target xsi:type="Console"
name="console"
layout="${level:uppercase=true:padding=5} [${logger:shortName=true}] ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
<logger name="*" minlevel="Warn" writeTo="console" />
</rules>
</nlog>
```
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
</action>
<verify>
- 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
</verify>
<done>AC-1 satisfied: NLog wired into build; nlog.config copied to output directory on build</done>
</task>
<task type="auto">
<name>Task 2: Wire logger into machine classes and fix event handler catches</name>
<files>
Cnc/Fanuc/FanucMachine.cs,
Cnc/Heidenhain/HeidenhainMachine.cs,
Cnc/Heidenhain/Lsv2Client.cs,
Cnc/Siemens/SiemensMachine.cs,
Cnc/Mitsubishi/MitsubishiMachine.cs
</files>
<action>
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)
</action>
<verify>
- 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
</verify>
<done>AC-2 and AC-3 satisfied: event handler exceptions logged at WARN; cleanup catches annotated as intentional</done>
</task>
<task type="auto">
<name>Task 3: Add file logging to Program.cs error catch blocks</name>
<files>
Program.cs
</files>
<action>
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
</action>
<verify>
- 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)
</verify>
<done>AC-4 satisfied: all 3 Program.cs error paths log to file before Console.WriteLine; no Console.WriteLine removed</done>
</task>
</tasks>
<boundaries>
## 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
</boundaries>
<verification>
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
</verification>
<success_criteria>
- 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)
</success_criteria>
<output>
After completion, create `.paul/phases/15-logging/15-01-SUMMARY.md`
</output>