Files
natsdotnet/Documentation/Configuration/Overview.md
Joseph Doherty 539b2b7588 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
2026-02-22 21:05:53 -05:00

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 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.
// 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();
}