- 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
6.7 KiB
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.
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 PINGs 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}:
_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 |
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 |
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 with the console sink, configured in Program.cs before any other code runs:
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:
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 vialoggerFactory.CreateLogger<NatsServer>(), used for server-level events (listening, client connect/disconnect).ILoggerwith named category — obtained vialoggerFactory.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.
// 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:
try
{
await server.StartAsync(cts.Token);
}
catch (OperationCanceledException) { }
finally
{
Log.CloseAndFlush();
}