fix(driver-twincat): resolve Medium code-review finding (Driver.TwinCAT-005)
Inject optional ILogger<TwinCATDriver> (NullLogger default) and log connect success/failure, ADS read errors, symbol-browse fallback, native-notification registration failures, and host-state transitions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -134,7 +134,7 @@ date/time semantics are intended to be exposed properly, track a follow-up to de
|
|||||||
| Severity | Medium |
|
| Severity | Medium |
|
||||||
| Category | OtOpcUa conventions |
|
| Category | OtOpcUa conventions |
|
||||||
| Location | `TwinCATDriver.cs` (whole file), `AdsTwinCATClient.cs` (whole file) |
|
| Location | `TwinCATDriver.cs` (whole file), `AdsTwinCATClient.cs` (whole file) |
|
||||||
| Status | Open |
|
| Status | Resolved |
|
||||||
|
|
||||||
**Description:** The driver performs no logging. `CLAUDE.md` Library Preferences mandate
|
**Description:** The driver performs no logging. `CLAUDE.md` Library Preferences mandate
|
||||||
Serilog with a rolling daily file sink. Connect failures, ADS error codes, symbol-browse
|
Serilog with a rolling daily file sink. Connect failures, ADS error codes, symbol-browse
|
||||||
@@ -147,7 +147,7 @@ success/failure per device, ADS errors with code, symbol-browse fallback (the `D
|
|||||||
catch), native-notification registration failures, and host state transitions
|
catch), native-notification registration failures, and host state transitions
|
||||||
(`TransitionDeviceState`).
|
(`TransitionDeviceState`).
|
||||||
|
|
||||||
**Resolution:** _(open)_
|
**Resolution:** Resolved 2026-05-22 — added optional `ILogger<TwinCATDriver>` constructor parameter (defaults to `NullLogger`); logs connect success/failure in `EnsureConnectedAsync`, ADS read errors in `ReadAsync`, symbol-browse fallback in `DiscoverAsync`, notification-registration failures in `SubscribeAsync`, and host-state transitions in `TransitionDeviceState`.
|
||||||
|
|
||||||
### Driver.TwinCAT-006
|
### Driver.TwinCAT-006
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||||
@@ -16,6 +18,7 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
private TwinCATDriverOptions _options;
|
private TwinCATDriverOptions _options;
|
||||||
private readonly string _driverInstanceId;
|
private readonly string _driverInstanceId;
|
||||||
private readonly ITwinCATClientFactory _clientFactory;
|
private readonly ITwinCATClientFactory _clientFactory;
|
||||||
|
private readonly ILogger<TwinCATDriver> _logger;
|
||||||
private readonly PollGroupEngine _poll;
|
private readonly PollGroupEngine _poll;
|
||||||
private readonly Dictionary<string, DeviceState> _devices = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, DeviceState> _devices = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Dictionary<string, TwinCATTagDefinition> _tagsByName = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, TwinCATTagDefinition> _tagsByName = new(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -26,12 +29,14 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
public event EventHandler<RediscoveryEventArgs>? OnRediscoveryNeeded;
|
public event EventHandler<RediscoveryEventArgs>? OnRediscoveryNeeded;
|
||||||
|
|
||||||
public TwinCATDriver(TwinCATDriverOptions options, string driverInstanceId,
|
public TwinCATDriver(TwinCATDriverOptions options, string driverInstanceId,
|
||||||
ITwinCATClientFactory? clientFactory = null)
|
ITwinCATClientFactory? clientFactory = null,
|
||||||
|
ILogger<TwinCATDriver>? logger = null)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(options);
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
_options = options;
|
_options = options;
|
||||||
_driverInstanceId = driverInstanceId;
|
_driverInstanceId = driverInstanceId;
|
||||||
_clientFactory = clientFactory ?? new AdsTwinCATClientFactory();
|
_clientFactory = clientFactory ?? new AdsTwinCATClientFactory();
|
||||||
|
_logger = logger ?? NullLogger<TwinCATDriver>.Instance;
|
||||||
_poll = new PollGroupEngine(
|
_poll = new PollGroupEngine(
|
||||||
reader: ReadAsync,
|
reader: ReadAsync,
|
||||||
onChange: (handle, tagRef, snapshot) =>
|
onChange: (handle, tagRef, snapshot) =>
|
||||||
@@ -155,8 +160,13 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
if (status == TwinCATStatusMapper.Good)
|
if (status == TwinCATStatusMapper.Good)
|
||||||
_health = new DriverHealth(DriverState.Healthy, now, null);
|
_health = new DriverHealth(DriverState.Healthy, now, null);
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' ADS read error 0x{Status:X8} for '{Reference}'",
|
||||||
|
_driverInstanceId, status, reference);
|
||||||
_health = new DriverHealth(DriverState.Degraded, _health.LastSuccessfulRead,
|
_health = new DriverHealth(DriverState.Degraded, _health.LastSuccessfulRead,
|
||||||
$"ADS status {status:X8} reading {reference}");
|
$"ADS status {status:X8} reading {reference}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { throw; }
|
catch (OperationCanceledException) { throw; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -286,10 +296,14 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { throw; }
|
catch (OperationCanceledException) { throw; }
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Symbol-loader failure is non-fatal to discovery — pre-declared tags already
|
// Symbol-loader failure is non-fatal to discovery — pre-declared tags already
|
||||||
// shipped + operators see the failure in driver health on next read.
|
// shipped. Log so operators can correlate the partial discovery.
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' symbol browse failed for device " +
|
||||||
|
"'{HostAddress}'; falling back to pre-declared tags only",
|
||||||
|
_driverInstanceId, device.HostAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,11 +353,15 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
registrations.Add(reg);
|
registrations.Add(reg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// On any registration failure, tear down everything we got so far + rethrow. Leaves
|
// On any registration failure, tear down everything we got so far + rethrow. Leaves
|
||||||
// the subscription in a clean "never existed" state rather than a half-registered
|
// the subscription in a clean "never existed" state rather than a half-registered
|
||||||
// state the caller has to clean up.
|
// state the caller has to clean up.
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' native-notification registration failed; " +
|
||||||
|
"tearing down {Count} partial registrations",
|
||||||
|
_driverInstanceId, registrations.Count);
|
||||||
foreach (var r in registrations) { try { r.Dispose(); } catch { } }
|
foreach (var r in registrations) { try { r.Dispose(); } catch { } }
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -411,6 +429,9 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
state.HostState = newState;
|
state.HostState = newState;
|
||||||
state.HostStateChangedUtc = DateTime.UtcNow;
|
state.HostStateChangedUtc = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
_logger.LogInformation(
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' device '{HostAddress}' state: {OldState} → {NewState}",
|
||||||
|
_driverInstanceId, state.Options.HostAddress, old, newState);
|
||||||
OnHostStatusChanged?.Invoke(this,
|
OnHostStatusChanged?.Invoke(this,
|
||||||
new HostStatusChangedEventArgs(state.Options.HostAddress, old, newState));
|
new HostStatusChangedEventArgs(state.Options.HostAddress, old, newState));
|
||||||
}
|
}
|
||||||
@@ -456,9 +477,15 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
|
|||||||
{
|
{
|
||||||
await client.ConnectAsync(device.ParsedAddress, _options.Timeout, ct)
|
await client.ConnectAsync(device.ParsedAddress, _options.Timeout, ct)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
_logger.LogInformation(
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' connected to {HostAddress}",
|
||||||
|
_driverInstanceId, device.Options.HostAddress);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"TwinCAT driver '{DriverInstanceId}' failed to connect to {HostAddress}",
|
||||||
|
_driverInstanceId, device.Options.HostAddress);
|
||||||
client.OnSymbolVersionChanged -= HandleSymbolVersionChanged;
|
client.OnSymbolVersionChanged -= HandleSymbolVersionChanged;
|
||||||
client.Dispose();
|
client.Dispose();
|
||||||
throw;
|
throw;
|
||||||
|
|||||||
Reference in New Issue
Block a user