feat: add structured logging, Shouldly assertions, CPM, and project documentation

- Add Microsoft.Extensions.Logging + Serilog to NatsServer and NatsClient
- Convert all test assertions from xUnit Assert to Shouldly
- Add NSubstitute package for future mocking needs
- Introduce Central Package Management via Directory.Packages.props
- Add documentation_rules.md with style guide, generation/update rules, component map
- Generate 10 documentation files across 5 component folders (GettingStarted, Protocol, Subscriptions, Server, Configuration/Operations)
- Update CLAUDE.md with logging, testing, porting, agent model, CPM, and documentation guidance
This commit is contained in:
Joseph Doherty
2026-02-22 21:05:53 -05:00
parent b9f4dec523
commit 539b2b7588
25 changed files with 2734 additions and 110 deletions

View File

@@ -3,6 +3,7 @@ using System.IO.Pipelines;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using NATS.Server.Protocol;
using NATS.Server.Subscriptions;
@@ -29,6 +30,7 @@ public sealed class NatsClient : IDisposable
private readonly NatsParser _parser;
private readonly SemaphoreSlim _writeLock = new(1, 1);
private readonly Dictionary<string, Subscription> _subs = new();
private readonly ILogger _logger;
public ulong Id { get; }
public ClientOptions? ClientOpts { get; private set; }
@@ -43,13 +45,14 @@ public sealed class NatsClient : IDisposable
public IReadOnlyDictionary<string, Subscription> Subscriptions => _subs;
public NatsClient(ulong id, Socket socket, NatsOptions options, ServerInfo serverInfo)
public NatsClient(ulong id, Socket socket, NatsOptions options, ServerInfo serverInfo, ILogger logger)
{
Id = id;
_socket = socket;
_stream = new NetworkStream(socket, ownsSocket: false);
_options = options;
_serverInfo = serverInfo;
_logger = logger;
_parser = new NatsParser(options.MaxPayload);
}
@@ -68,7 +71,10 @@ public sealed class NatsClient : IDisposable
await Task.WhenAny(fillTask, processTask);
}
catch (OperationCanceledException) { }
catch (Exception) { /* connection error -- clean up */ }
catch (Exception ex)
{
_logger.LogDebug(ex, "Client {ClientId} connection error", Id);
}
finally
{
Router?.RemoveClient(this);
@@ -160,6 +166,7 @@ public sealed class NatsClient : IDisposable
ClientOpts = JsonSerializer.Deserialize<ClientOptions>(cmd.Payload.Span)
?? new ClientOptions();
ConnectReceived = true;
_logger.LogDebug("CONNECT received from client {ClientId}, name={ClientName}", Id, ClientOpts?.Name);
}
private void ProcessSub(ParsedCommand cmd)
@@ -174,12 +181,16 @@ public sealed class NatsClient : IDisposable
_subs[cmd.Sid!] = sub;
sub.Client = this;
_logger.LogDebug("SUB {Subject} {Sid} from client {ClientId}", cmd.Subject, cmd.Sid, Id);
if (Router is ISubListAccess sl)
sl.SubList.Insert(sub);
}
private void ProcessUnsub(ParsedCommand cmd)
{
_logger.LogDebug("UNSUB {Sid} from client {ClientId}", cmd.Sid, Id);
if (!_subs.TryGetValue(cmd.Sid!, out var sub))
return;