feat: add per-client trace delivery and echo control (Gap 5.7)
Implements ClientTraceInfo with TraceMsgDelivery recording and per-client echo suppression; fixes AccountGoParityTests namespace ambiguity caused by the new NATS.Server.Tests.Subscriptions test namespace.
This commit is contained in:
83
src/NATS.Server/ClientTraceInfo.cs
Normal file
83
src/NATS.Server/ClientTraceInfo.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
namespace NATS.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-client trace configuration and echo control.
|
||||||
|
/// Go reference: server/client.go — c.trace, c.echo fields.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ClientTraceInfo
|
||||||
|
{
|
||||||
|
private bool _traceEnabled;
|
||||||
|
private bool _echoEnabled = true; // default: echo is enabled
|
||||||
|
private readonly List<TraceRecord> _traceLog = new();
|
||||||
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
|
/// <summary>Whether message delivery tracing is enabled for this client.</summary>
|
||||||
|
public bool TraceEnabled
|
||||||
|
{
|
||||||
|
get => Volatile.Read(ref _traceEnabled);
|
||||||
|
set => Volatile.Write(ref _traceEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Whether this client echoes its own published messages back.</summary>
|
||||||
|
public bool EchoEnabled
|
||||||
|
{
|
||||||
|
get => Volatile.Read(ref _echoEnabled);
|
||||||
|
set => Volatile.Write(ref _echoEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records a message delivery trace if tracing is enabled.
|
||||||
|
/// Go reference: server/client.go — traceMsg / TraceMsgDelivery.
|
||||||
|
/// </summary>
|
||||||
|
public void TraceMsgDelivery(string subject, string destination, int payloadSize)
|
||||||
|
{
|
||||||
|
if (!TraceEnabled) return;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_traceLog.Add(new TraceRecord
|
||||||
|
{
|
||||||
|
Subject = subject,
|
||||||
|
Destination = destination,
|
||||||
|
PayloadSize = payloadSize,
|
||||||
|
TimestampUtc = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether a message from this client should be delivered back to it.
|
||||||
|
/// When echo is disabled, messages published by this client are not delivered to
|
||||||
|
/// subscriptions on the same client.
|
||||||
|
/// Go reference: server/client.go — c.echo check in deliverMsg.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShouldEcho(string publisherClientId, string subscriberClientId)
|
||||||
|
{
|
||||||
|
if (EchoEnabled) return true;
|
||||||
|
return !string.Equals(publisherClientId, subscriberClientId, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns all trace records and clears the log.</summary>
|
||||||
|
public IReadOnlyList<TraceRecord> DrainTraceLog()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var copy = _traceLog.ToList();
|
||||||
|
_traceLog.Clear();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Current trace log count.</summary>
|
||||||
|
public int TraceLogCount
|
||||||
|
{
|
||||||
|
get { lock (_lock) return _traceLog.Count; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record TraceRecord
|
||||||
|
{
|
||||||
|
public string Subject { get; init; } = string.Empty;
|
||||||
|
public string Destination { get; init; } = string.Empty;
|
||||||
|
public int PayloadSize { get; init; }
|
||||||
|
public DateTime TimestampUtc { get; init; }
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using NATS.Server.Auth;
|
using NATS.Server.Auth;
|
||||||
using NATS.Server.Imports;
|
using NATS.Server.Imports;
|
||||||
using NATS.Server.Subscriptions;
|
using ServerSubscriptions = NATS.Server.Subscriptions;
|
||||||
|
|
||||||
namespace NATS.Server.Tests.Auth;
|
namespace NATS.Server.Tests.Auth;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public class AccountGoParityTests
|
|||||||
using var accB = new Account("B");
|
using var accB = new Account("B");
|
||||||
|
|
||||||
// Add subscriptions to account A's SubList
|
// Add subscriptions to account A's SubList
|
||||||
var subA = new Subscription { Subject = "foo", Sid = "1" };
|
var subA = new ServerSubscriptions.Subscription { Subject = "foo", Sid = "1" };
|
||||||
accA.SubList.Insert(subA);
|
accA.SubList.Insert(subA);
|
||||||
|
|
||||||
// Account B should not see account A's subscriptions
|
// Account B should not see account A's subscriptions
|
||||||
@@ -52,8 +52,8 @@ public class AccountGoParityTests
|
|||||||
// Go: TestAccountWildcardRouteMapping — wildcards work per-account.
|
// Go: TestAccountWildcardRouteMapping — wildcards work per-account.
|
||||||
using var acc = new Account("TEST");
|
using var acc = new Account("TEST");
|
||||||
|
|
||||||
var sub1 = new Subscription { Subject = "orders.*", Sid = "1" };
|
var sub1 = new ServerSubscriptions.Subscription { Subject = "orders.*", Sid = "1" };
|
||||||
var sub2 = new Subscription { Subject = "orders.>", Sid = "2" };
|
var sub2 = new ServerSubscriptions.Subscription { Subject = "orders.>", Sid = "2" };
|
||||||
acc.SubList.Insert(sub1);
|
acc.SubList.Insert(sub1);
|
||||||
acc.SubList.Insert(sub2);
|
acc.SubList.Insert(sub2);
|
||||||
|
|
||||||
|
|||||||
136
tests/NATS.Server.Tests/ClientTraceTests.cs
Normal file
136
tests/NATS.Server.Tests/ClientTraceTests.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using NATS.Server;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace NATS.Server.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for per-client trace delivery and echo control.
|
||||||
|
/// Go reference: server/client.go — c.trace, c.echo fields and TraceMsgDelivery / deliverMsg logic.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientTraceTests
|
||||||
|
{
|
||||||
|
// 1. TraceEnabled defaults to false
|
||||||
|
[Fact]
|
||||||
|
public void TraceEnabled_defaults_to_false()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.TraceEnabled.ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. EchoEnabled defaults to true
|
||||||
|
[Fact]
|
||||||
|
public void EchoEnabled_defaults_to_true()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.EchoEnabled.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. TraceMsgDelivery records when enabled
|
||||||
|
[Fact]
|
||||||
|
public void TraceMsgDelivery_records_when_enabled()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.TraceEnabled = true;
|
||||||
|
|
||||||
|
info.TraceMsgDelivery("foo.bar", "client-1", 42);
|
||||||
|
|
||||||
|
info.TraceLogCount.ShouldBe(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. TraceMsgDelivery skips when disabled
|
||||||
|
[Fact]
|
||||||
|
public void TraceMsgDelivery_skips_when_disabled()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
// TraceEnabled is false by default
|
||||||
|
|
||||||
|
info.TraceMsgDelivery("foo.bar", "client-1", 42);
|
||||||
|
|
||||||
|
info.TraceLogCount.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. TraceMsgDelivery captures subject, destination and payload size correctly
|
||||||
|
[Fact]
|
||||||
|
public void TraceMsgDelivery_captures_subject_destination_size()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.TraceEnabled = true;
|
||||||
|
var before = DateTime.UtcNow;
|
||||||
|
|
||||||
|
info.TraceMsgDelivery("orders.new", "client-42", 128);
|
||||||
|
|
||||||
|
var records = info.DrainTraceLog();
|
||||||
|
records.Count.ShouldBe(1);
|
||||||
|
var rec = records[0];
|
||||||
|
rec.Subject.ShouldBe("orders.new");
|
||||||
|
rec.Destination.ShouldBe("client-42");
|
||||||
|
rec.PayloadSize.ShouldBe(128);
|
||||||
|
rec.TimestampUtc.ShouldBeGreaterThanOrEqualTo(before);
|
||||||
|
rec.TimestampUtc.ShouldBeLessThanOrEqualTo(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. ShouldEcho returns true when echo is enabled (same client)
|
||||||
|
[Fact]
|
||||||
|
public void ShouldEcho_true_when_echo_enabled()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.EchoEnabled = true;
|
||||||
|
|
||||||
|
info.ShouldEcho("client-1", "client-1").ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. ShouldEcho returns false when echo is disabled and same client
|
||||||
|
[Fact]
|
||||||
|
public void ShouldEcho_false_when_echo_disabled_same_client()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.EchoEnabled = false;
|
||||||
|
|
||||||
|
info.ShouldEcho("client-1", "client-1").ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. ShouldEcho returns true when echo is disabled but different client
|
||||||
|
[Fact]
|
||||||
|
public void ShouldEcho_true_when_echo_disabled_different_client()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.EchoEnabled = false;
|
||||||
|
|
||||||
|
info.ShouldEcho("client-1", "client-2").ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. DrainTraceLog returns records and clears the log
|
||||||
|
[Fact]
|
||||||
|
public void DrainTraceLog_returns_and_clears()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.TraceEnabled = true;
|
||||||
|
info.TraceMsgDelivery("a", "dest-a", 10);
|
||||||
|
info.TraceMsgDelivery("b", "dest-b", 20);
|
||||||
|
|
||||||
|
var drained = info.DrainTraceLog();
|
||||||
|
|
||||||
|
drained.Count.ShouldBe(2);
|
||||||
|
info.TraceLogCount.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. TraceLogCount reflects current entries before and after drain
|
||||||
|
[Fact]
|
||||||
|
public void TraceLogCount_reflects_current_entries()
|
||||||
|
{
|
||||||
|
var info = new ClientTraceInfo();
|
||||||
|
info.TraceEnabled = true;
|
||||||
|
|
||||||
|
info.TraceLogCount.ShouldBe(0);
|
||||||
|
|
||||||
|
info.TraceMsgDelivery("x", "dest-x", 5);
|
||||||
|
info.TraceMsgDelivery("y", "dest-y", 15);
|
||||||
|
info.TraceMsgDelivery("z", "dest-z", 25);
|
||||||
|
|
||||||
|
info.TraceLogCount.ShouldBe(3);
|
||||||
|
|
||||||
|
info.DrainTraceLog();
|
||||||
|
|
||||||
|
info.TraceLogCount.ShouldBe(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user