d67634cc4f
The driver logged and surfaced the raw AgentUri (which may carry user:pass@) as HostName (the /hosts key), in ~20 log lines and in exception messages that reach DriverHealth.LastError, and the client's request-URI messages (timeouts, stream end, not-a-stream, framing) embedded it too - while the Test-Connect probe deliberately redacted it. New MTConnectUriRedaction (user-info, and for the configured string its query, removed; a clean URI is unchanged so host keys do not move); the driver computes its display form once per options document. Refs #743 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
85 lines
2.6 KiB
C#
85 lines
2.6 KiB
C#
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests;
|
|
|
|
/// <summary>
|
|
/// Captures what the driver logs, so that "and it says so" can be asserted rather than assumed.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Used where the log line <b>is</b> the behaviour: a browse scope that matches nothing, and a
|
|
/// subscription the driver silently refuses to serve. Both are cases whose only other symptom is
|
|
/// an operator staring at an empty panel, so a fix that emits nothing is not a fix.
|
|
/// </remarks>
|
|
internal sealed class RecordingDriverLogger : ILogger<MTConnectDriver>
|
|
{
|
|
/// <summary>Formatted <see cref="LogLevel.Warning"/> lines, in order.</summary>
|
|
public List<string> Warnings { get; } = [];
|
|
|
|
/// <summary>Formatted <see cref="LogLevel.Error"/> lines, in order.</summary>
|
|
public List<string> Errors { get; } = [];
|
|
|
|
/// <summary>Formatted <see cref="LogLevel.Information"/> lines, in order.</summary>
|
|
public List<string> Infos { get; } = [];
|
|
|
|
/// <summary>Every recorded line at Information and above, with its exception's message appended.</summary>
|
|
public IReadOnlyList<string> AllSnapshot()
|
|
{
|
|
var all = new List<string>();
|
|
foreach (var sink in new[] { Infos, Warnings, Errors })
|
|
{
|
|
lock (sink)
|
|
{
|
|
all.AddRange(sink);
|
|
}
|
|
}
|
|
|
|
return all;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IDisposable? BeginScope<TState>(TState state)
|
|
where TState : notnull => null;
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsEnabled(LogLevel logLevel) => true;
|
|
|
|
/// <inheritdoc/>
|
|
public void Log<TState>(
|
|
LogLevel logLevel,
|
|
EventId eventId,
|
|
TState state,
|
|
Exception? exception,
|
|
Func<TState, Exception?, string> formatter)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(formatter);
|
|
|
|
var sink = logLevel switch
|
|
{
|
|
LogLevel.Warning => Warnings,
|
|
LogLevel.Error => Errors,
|
|
LogLevel.Information => Infos,
|
|
_ => null,
|
|
};
|
|
|
|
// Locked: the /sample pump logs from its own task while the test thread reads.
|
|
if (sink is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (sink)
|
|
{
|
|
sink.Add(exception is null ? formatter(state, exception) : $"{formatter(state, exception)} | {exception.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>A snapshot copy of <see cref="Warnings"/>, safe to enumerate while the pump runs.</summary>
|
|
public IReadOnlyList<string> WarningsSnapshot()
|
|
{
|
|
lock (Warnings)
|
|
{
|
|
return [.. Warnings];
|
|
}
|
|
}
|
|
}
|