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:
193
Documentation/Operations/Overview.md
Normal file
193
Documentation/Operations/Overview.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Operations Overview
|
||||
|
||||
This document covers running the server, graceful shutdown, connecting clients, and the test suite.
|
||||
|
||||
---
|
||||
|
||||
## Running the Server
|
||||
|
||||
The host application is `src/NATS.Server.Host`. Run it with `dotnet run`:
|
||||
|
||||
```bash
|
||||
# Default (port 4222, bind 0.0.0.0)
|
||||
dotnet run --project src/NATS.Server.Host
|
||||
|
||||
# Custom port
|
||||
dotnet run --project src/NATS.Server.Host -- -p 14222
|
||||
|
||||
# Custom bind address and name
|
||||
dotnet run --project src/NATS.Server.Host -- -a 127.0.0.1 -n my-server
|
||||
```
|
||||
|
||||
Arguments after `--` are passed to the program. The three supported flags are `-p`/`--port`, `-a`/`--addr`, and `-n`/`--name`. See [Configuration Overview](../Configuration/Overview.md) for the full CLI reference.
|
||||
|
||||
On startup, the server logs the address it is listening on:
|
||||
|
||||
```
|
||||
[14:32:01 INF] Listening on 0.0.0.0:4222
|
||||
```
|
||||
|
||||
### Full host setup
|
||||
|
||||
`Program.cs` initializes Serilog, parses CLI arguments, starts the server, and handles graceful shutdown:
|
||||
|
||||
```csharp
|
||||
using NATS.Server;
|
||||
using Serilog;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger();
|
||||
|
||||
var options = new NatsOptions();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "-p" or "--port" when i + 1 < args.Length:
|
||||
options.Port = int.Parse(args[++i]);
|
||||
break;
|
||||
case "-a" or "--addr" when i + 1 < args.Length:
|
||||
options.Host = args[++i];
|
||||
break;
|
||||
case "-n" or "--name" when i + 1 < args.Length:
|
||||
options.ServerName = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
|
||||
var server = new NatsServer(options, loggerFactory);
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await server.StartAsync(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Pressing Ctrl+C triggers `Console.CancelKeyPress`. The handler sets `e.Cancel = true` — this prevents the process from terminating immediately — and calls `cts.Cancel()` to signal the `CancellationToken` passed to `server.StartAsync`.
|
||||
|
||||
`NatsServer.StartAsync` exits its accept loop on cancellation. In-flight client connections are left to drain naturally. After `StartAsync` returns (via `OperationCanceledException` which is caught), the `finally` block runs `Log.CloseAndFlush()` to ensure all buffered log output is written before the process exits.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Run the full test suite with:
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
|
||||
The test project is at `tests/NATS.Server.Tests/`. It uses xUnit with Shouldly for assertions.
|
||||
|
||||
### Test summary
|
||||
|
||||
69 tests across 6 test files:
|
||||
|
||||
| File | Tests | Coverage |
|
||||
|------|-------|----------|
|
||||
| `SubjectMatchTests.cs` | 33 | Subject validation and wildcard matching |
|
||||
| `SubListTests.cs` | 12 | Trie insert, remove, match, queue groups, cache |
|
||||
| `ParserTests.cs` | 14 | All command types, split packets, case insensitivity |
|
||||
| `ClientTests.cs` | 2 | Socket-level INFO on connect, PING/PONG |
|
||||
| `ServerTests.cs` | 3 | End-to-end accept, pub/sub, wildcard delivery |
|
||||
| `IntegrationTests.cs` | 5 | NATS.Client.Core protocol compatibility |
|
||||
|
||||
### Test categories
|
||||
|
||||
**SubjectMatchTests** — 33 `[Theory]` cases verifying `SubjectMatch.IsValidSubject` (16 cases), `SubjectMatch.IsValidPublishSubject` (6 cases), and `SubjectMatch.MatchLiteral` (11 cases). Covers empty strings, leading/trailing dots, embedded spaces, `>` in non-terminal position, and all wildcard combinations.
|
||||
|
||||
**SubListTests** — 12 `[Fact]` tests exercising the `SubList` trie directly: literal insert and match, empty result, `*` wildcard at various token levels, `>` wildcard, root `>`, multiple overlapping subscriptions, remove, queue group grouping, `Count` tracking, and cache invalidation after a wildcard insert.
|
||||
|
||||
**ParserTests** — 14 `async [Fact]` tests that write protocol bytes into a `Pipe` and assert on the resulting `ParsedCommand` list. Covers `PING`, `PONG`, `CONNECT`, `SUB` (with and without queue group), `UNSUB` (with and without `max-messages`), `PUB` (with payload, with reply-to, zero payload), `HPUB` (with header), `INFO`, multiple commands in a single buffer, and case-insensitive parsing.
|
||||
|
||||
**ClientTests** — 2 `async [Fact]` tests using a real loopback socket pair. Verifies that `NatsClient` sends an `INFO` frame immediately on connection, and that it responds `PONG` to a `PING` after `CONNECT`.
|
||||
|
||||
**ServerTests** — 3 `async [Fact]` tests that start `NatsServer` on a random port. Verifies `INFO` on connect, basic pub/sub delivery (`MSG` format), and wildcard subscription matching.
|
||||
|
||||
**IntegrationTests** — 5 `async [Fact]` tests using the official `NATS.Client.Core` v2.7.2 NuGet package. Verifies end-to-end protocol compatibility with a real NATS client library: basic pub/sub, `*` wildcard delivery, `>` wildcard delivery, fan-out to two subscribers, and `PingAsync`.
|
||||
|
||||
Integration tests use `NullLoggerFactory.Instance` for the server so test output is not cluttered with server logs.
|
||||
|
||||
---
|
||||
|
||||
## Connecting with Standard NATS Clients
|
||||
|
||||
The server speaks the standard NATS wire protocol. Any NATS client library can connect to it.
|
||||
|
||||
```bash
|
||||
# Using the nats CLI tool
|
||||
nats sub "test.>" --server nats://127.0.0.1:4222
|
||||
nats pub test.hello "world" --server nats://127.0.0.1:4222
|
||||
```
|
||||
|
||||
From .NET, using `NATS.Client.Core`:
|
||||
|
||||
```csharp
|
||||
var opts = new NatsOpts { Url = "nats://127.0.0.1:4222" };
|
||||
await using var conn = new NatsConnection(opts);
|
||||
await conn.ConnectAsync();
|
||||
await conn.PublishAsync("test.hello", "world");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Go Reference Server
|
||||
|
||||
The Go reference implementation can be built from `golang/nats-server/` for comparison testing:
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cd golang/nats-server && go build
|
||||
|
||||
# Run on default port
|
||||
./nats-server
|
||||
|
||||
# Run on custom port
|
||||
./nats-server -p 14222
|
||||
```
|
||||
|
||||
The Go server is useful for verifying that the .NET port produces identical protocol output and behaves the same way under edge-case client interactions.
|
||||
|
||||
---
|
||||
|
||||
## Current Limitations
|
||||
|
||||
The following features present in the Go reference server are not yet ported:
|
||||
|
||||
- Authentication — no username/password, token, NKey, or JWT support
|
||||
- Clustering — no routes, gateways, or leaf nodes
|
||||
- JetStream — no persistent streaming, streams, consumers, or RAFT
|
||||
- Monitoring — no HTTP endpoints (`/varz`, `/connz`, `/healthz`, etc.)
|
||||
- TLS — all connections are plaintext
|
||||
- WebSocket — no WebSocket transport
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Configuration Overview](../Configuration/Overview.md)
|
||||
- [Server Overview](../Server/Overview.md)
|
||||
- [Protocol Overview](../Protocol/Overview.md)
|
||||
|
||||
<!-- Last verified against codebase: 2026-02-22 -->
|
||||
Reference in New Issue
Block a user