- 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
186 lines
6.7 KiB
Markdown
186 lines
6.7 KiB
Markdown
# Configuration Overview
|
|
|
|
`NatsOptions` is the single configuration object passed to `NatsServer` at construction time. The host application (`Program.cs`) also reads CLI arguments to populate it before server startup.
|
|
|
|
Go reference: `golang/nats-server/server/opts.go`
|
|
|
|
---
|
|
|
|
## NatsOptions Class
|
|
|
|
`NatsOptions` is a plain mutable class with no validation logic — values are consumed directly by `NatsServer` and `NatsClient`.
|
|
|
|
```csharp
|
|
namespace NATS.Server;
|
|
|
|
public sealed class NatsOptions
|
|
{
|
|
public string Host { get; set; } = "0.0.0.0";
|
|
public int Port { get; set; } = 4222;
|
|
public string? ServerName { get; set; }
|
|
public int MaxPayload { get; set; } = 1024 * 1024; // 1MB
|
|
public int MaxControlLine { get; set; } = 4096;
|
|
public int MaxConnections { get; set; } = 65536;
|
|
public TimeSpan PingInterval { get; set; } = TimeSpan.FromMinutes(2);
|
|
public int MaxPingsOut { get; set; } = 2;
|
|
}
|
|
```
|
|
|
|
### Option reference
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `Host` | `string` | `"0.0.0.0"` | Bind address for the TCP listener. Use `"127.0.0.1"` to restrict to loopback. |
|
|
| `Port` | `int` | `4222` | Client listen port. Standard NATS client port. |
|
|
| `ServerName` | `string?` | `null` (auto: `nats-dotnet-{MachineName}`) | Server name sent in the `INFO` message. When `null`, the server constructs a name from `Environment.MachineName`. |
|
|
| `MaxPayload` | `int` | `1048576` (1 MB) | Maximum allowed message payload in bytes. Clients that publish larger payloads are rejected. |
|
|
| `MaxControlLine` | `int` | `4096` | Maximum protocol control line size in bytes. Matches the Go server default. |
|
|
| `MaxConnections` | `int` | `65536` | Maximum concurrent client connections the server will accept. |
|
|
| `PingInterval` | `TimeSpan` | `2 minutes` | Interval between server-initiated `PING` messages to connected clients. |
|
|
| `MaxPingsOut` | `int` | `2` | Number of outstanding `PING`s without a `PONG` response before the server disconnects a client. |
|
|
|
|
### How ServerName is resolved
|
|
|
|
`NatsServer` constructs the `ServerInfo` sent to each client at connection time. If `ServerName` is `null`, it uses `nats-dotnet-{Environment.MachineName}`:
|
|
|
|
```csharp
|
|
_serverInfo = new ServerInfo
|
|
{
|
|
ServerId = Guid.NewGuid().ToString("N")[..20].ToUpperInvariant(),
|
|
ServerName = options.ServerName ?? $"nats-dotnet-{Environment.MachineName}",
|
|
Version = NatsProtocol.Version,
|
|
Host = options.Host,
|
|
Port = options.Port,
|
|
MaxPayload = options.MaxPayload,
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## CLI Arguments
|
|
|
|
`Program.cs` parses command-line arguments before creating `NatsServer`. The three supported flags map directly to `NatsOptions` fields:
|
|
|
|
| Flag | Alias | Field | Example |
|
|
|------|-------|-------|---------|
|
|
| `-p` | `--port` | `Port` | `-p 14222` |
|
|
| `-a` | `--addr` | `Host` | `-a 127.0.0.1` |
|
|
| `-n` | `--name` | `ServerName` | `-n my-server` |
|
|
|
|
```csharp
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
Unrecognized flags are silently ignored. There is no `--help` output.
|
|
|
|
---
|
|
|
|
## Protocol Constants
|
|
|
|
`NatsProtocol` defines wire-level constants that mirror the Go server's defaults. These values are used by the parser and the `INFO` response:
|
|
|
|
| Constant | Value | Description |
|
|
|----------|-------|-------------|
|
|
| `MaxControlLineSize` | `4096` | Maximum bytes in a protocol control line |
|
|
| `MaxPayloadSize` | `1048576` | Maximum message payload in bytes (1 MB) |
|
|
| `DefaultPort` | `4222` | Standard NATS client port |
|
|
| `Version` | `"0.1.0"` | Server version string sent in `INFO` |
|
|
| `ProtoVersion` | `1` | NATS protocol version number sent in `INFO` |
|
|
|
|
```csharp
|
|
public static class NatsProtocol
|
|
{
|
|
public const int MaxControlLineSize = 4096;
|
|
public const int MaxPayloadSize = 1024 * 1024; // 1MB
|
|
public const int DefaultPort = 4222;
|
|
public const string Version = "0.1.0";
|
|
public const int ProtoVersion = 1;
|
|
}
|
|
```
|
|
|
|
`MaxControlLine` in `NatsOptions` and `MaxControlLineSize` in `NatsProtocol` carry the same value. `NatsOptions.MaxPayload` is used as the per-server runtime limit passed into `NatsParser`; `NatsProtocol.MaxPayloadSize` is the compile-time default.
|
|
|
|
---
|
|
|
|
## Logging Configuration
|
|
|
|
### Serilog setup
|
|
|
|
Logging uses [Serilog](https://serilog.net/) with the console sink, configured in `Program.cs` before any other code runs:
|
|
|
|
```csharp
|
|
Log.Logger = new LoggerConfiguration()
|
|
.MinimumLevel.Debug()
|
|
.Enrich.FromLogContext()
|
|
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
|
.CreateLogger();
|
|
```
|
|
|
|
The output template produces lines like:
|
|
|
|
```
|
|
[14:32:01 INF] Listening on 0.0.0.0:4222
|
|
[14:32:01 DBG] Client 1 connected from 127.0.0.1:54321
|
|
```
|
|
|
|
### ILoggerFactory injection
|
|
|
|
`Program.cs` wraps the Serilog logger in a `SerilogLoggerFactory` and passes it to `NatsServer`:
|
|
|
|
```csharp
|
|
using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
|
|
var server = new NatsServer(options, loggerFactory);
|
|
```
|
|
|
|
`NatsServer` stores the factory and uses it to create two kinds of loggers:
|
|
|
|
- **`ILogger<NatsServer>`** — obtained via `loggerFactory.CreateLogger<NatsServer>()`, used for server-level events (listening, client connect/disconnect).
|
|
- **`ILogger` with named category** — obtained via `loggerFactory.CreateLogger($"NATS.Server.NatsClient[{clientId}]")`, created per connection. This gives each client a distinct category in log output so messages can be correlated by client ID without structured log properties.
|
|
|
|
```csharp
|
|
// In NatsServer.StartAsync — one logger per accepted connection
|
|
var clientLogger = _loggerFactory.CreateLogger($"NATS.Server.NatsClient[{clientId}]");
|
|
var client = new NatsClient(clientId, socket, _options, _serverInfo, clientLogger);
|
|
```
|
|
|
|
`NatsClient` takes `ILogger` (not `ILogger<NatsClient>`) so it can accept the pre-named logger instance rather than deriving its category from its own type.
|
|
|
|
### Log flushing
|
|
|
|
`Log.CloseAndFlush()` runs in the `finally` block of `Program.cs` after the server stops, ensuring buffered log entries are written before the process exits:
|
|
|
|
```csharp
|
|
try
|
|
{
|
|
await server.StartAsync(cts.Token);
|
|
}
|
|
catch (OperationCanceledException) { }
|
|
finally
|
|
{
|
|
Log.CloseAndFlush();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [Operations Overview](../Operations/Overview.md)
|
|
- [Server Overview](../Server/Overview.md)
|
|
|
|
<!-- Last verified against codebase: 2026-02-22 -->
|