New files: - Documentation/Authentication/Overview.md — all 7 auth mechanisms with real source snippets (NKey/JWT/username-password/token/TLS mapping), nonce generation, account system, permissions, JWT permission templates - Documentation/Clustering/Overview.md — route TCP handshake, in-process subscription propagation, gateway/leaf node stubs, honest gaps list - Documentation/JetStream/Overview.md — API surface (4 handled subjects), streams, consumers, storage (MemStore/FileStore), in-process RAFT, mirror/source, gaps list - Documentation/Monitoring/Overview.md — all 12 endpoints with real field tables, Go compatibility notes Updated files: - GettingStarted/Architecture.md — 14-subdirectory tree, real NatsClient/NatsServer field snippets, 9 new Go reference rows, Channel write queue design choice - GettingStarted/Setup.md — xUnit 3, 100 test files grouped by area - Operations/Overview.md — 99 test files, accurate Program.cs snippet, limitations section renamed to "Known Gaps vs Go Reference" with 7 real gaps - Server/Overview.md — grouped fields, TLS/WS accept path, lame-duck mode, POSIX signals - Configuration/Overview.md — 14 subsystem option tables, 24-row CLI table, LogOverrides - Server/Client.md — Channel write queue, 4-task RunAsync, CommandMatrix, real fields All docs verified against codebase 2026-02-23; 713 tests pass.
383 lines
21 KiB
Markdown
383 lines
21 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;
|
|
}
|
|
```
|
|
|
|
// NatsOptions contains 150+ fields organized into subsystem groups; the snippet shows the core network options.
|
|
|
|
### Option reference
|
|
|
|
The table below covers the core network options documented in the snippet above. For the full set of option groups, see the subsystem tables that follow.
|
|
|
|
| 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. |
|
|
|
|
### Subscription limits
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `MaxSubs` | `int` | `0` (unlimited) | Maximum subscriptions allowed per client connection. `0` disables the limit. |
|
|
| `MaxSubTokens` | `int` | `0` (unlimited) | Maximum number of tokens (dot-separated segments) allowed in a subject. `0` disables the limit. |
|
|
|
|
### Monitoring
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `MonitorPort` | `int` | `0` (disabled) | HTTP monitoring port. Set to `8222` for the standard NATS monitoring port. |
|
|
| `MonitorHost` | `string` | `"0.0.0.0"` | Bind address for the HTTP monitoring listener. |
|
|
| `MonitorBasePath` | `string?` | `null` | Optional URL path prefix for all monitoring endpoints (e.g., `"/nats"`). |
|
|
| `MonitorHttpsPort` | `int` | `0` (disabled) | HTTPS monitoring port. Requires TLS configuration to be set. |
|
|
|
|
### Lifecycle
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `MaxConnections` | `int` | `65536` | Maximum concurrent client connections. |
|
|
| `MaxPayload` | `int` | `1048576` | Maximum message payload in bytes. |
|
|
| `MaxPending` | `long` | `67108864` (64 MB) | Maximum bytes buffered per client before the server applies back-pressure. Matches Go `MAX_PENDING_SIZE`. |
|
|
| `WriteDeadline` | `TimeSpan` | `10 seconds` | Deadline for a single write operation to a client socket. Slow clients that cannot consume within this window are disconnected. |
|
|
| `LameDuckDuration` | `TimeSpan` | `2 minutes` | How long the server remains in lame-duck mode, draining existing clients before shutting down. |
|
|
| `LameDuckGracePeriod` | `TimeSpan` | `10 seconds` | Grace period at the start of lame-duck mode before the server begins rejecting new connections. |
|
|
|
|
### File paths
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `ConfigFile` | `string?` | `null` | Path to the NATS config file loaded at startup via `-c`. |
|
|
| `PidFile` | `string?` | `null` | Path where the server writes its process ID. |
|
|
| `PortsFileDir` | `string?` | `null` | Directory where the server writes a JSON file listing its bound ports. |
|
|
|
|
### Logging
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `Debug` | `bool` | `false` | Enables debug-level log output. Sets Serilog minimum level to `Debug`. |
|
|
| `Trace` | `bool` | `false` | Enables trace-level (verbose) log output. Sets Serilog minimum level to `Verbose`, overriding `Debug`. |
|
|
| `TraceVerbose` | `bool` | `false` | Enables verbose protocol tracing including message payload content. |
|
|
| `Logtime` | `bool` | `true` | Includes timestamps in log output. |
|
|
| `LogtimeUTC` | `bool` | `false` | Uses UTC timestamps instead of local time when `Logtime` is `true`. |
|
|
| `LogFile` | `string?` | `null` | Path to a log file. When set, the Serilog file sink is activated alongside the console sink. |
|
|
| `LogSizeLimit` | `long` | `0` (unlimited) | Maximum log file size in bytes before rotation. `0` disables size-based rotation. |
|
|
| `LogMaxFiles` | `int` | `0` (unlimited) | Number of rotated log files to retain. `0` retains all files. |
|
|
| `Syslog` | `bool` | `false` | Writes logs to the local syslog daemon. |
|
|
| `RemoteSyslog` | `string?` | `null` | UDP endpoint for remote syslog (e.g., `"udp://logs.example.com:514"`). Activates the UDP syslog sink. |
|
|
| `LogOverrides` | `Dictionary<string, string>?` | `null` | Per-namespace minimum level overrides applied to Serilog (e.g., `"NATS.Server.NatsClient" -> "Warning"`). |
|
|
|
|
### Authentication
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `Username` | `string?` | `null` | Single-user password auth: username. |
|
|
| `Password` | `string?` | `null` | Single-user password auth: password. |
|
|
| `Authorization` | `string?` | `null` | Single shared token auth. Equivalent to `token` in the Go config. |
|
|
| `Users` | `IReadOnlyList<User>?` | `null` | Multi-user list with per-user passwords and permissions. |
|
|
| `NKeys` | `IReadOnlyList<NKeyUser>?` | `null` | NKey-based user list. Each entry carries a public NKey and optional permissions. |
|
|
| `NoAuthUser` | `string?` | `null` | Username of the user to authenticate unauthenticated connections as. Must exist in `Users`. |
|
|
| `AuthTimeout` | `TimeSpan` | `2 seconds` | Time allowed for a client to complete the auth handshake. |
|
|
|
|
### JWT / Operator mode
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `TrustedKeys` | `string[]?` | `null` | Operator public NKeys that are permitted to sign account JWTs. |
|
|
| `AccountResolver` | `IAccountResolver?` | `null` | Pluggable resolver used to look up account JWTs by account public key. |
|
|
|
|
### TLS
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `TlsCert` | `string?` | `null` | Path to the server TLS certificate file (PEM). |
|
|
| `TlsKey` | `string?` | `null` | Path to the server TLS private key file (PEM). |
|
|
| `TlsCaCert` | `string?` | `null` | Path to the CA certificate file used to verify client certificates. |
|
|
| `TlsVerify` | `bool` | `false` | Requires clients to present a valid certificate signed by the CA. |
|
|
| `TlsMap` | `bool` | `false` | Maps the TLS client certificate subject to a NATS username for auth. |
|
|
| `TlsTimeout` | `TimeSpan` | `2 seconds` | Deadline for completing the TLS handshake. |
|
|
| `TlsHandshakeFirst` | `bool` | `false` | Performs the TLS handshake before the NATS `INFO`/`CONNECT` exchange. |
|
|
| `TlsHandshakeFirstFallback` | `TimeSpan` | `50 ms` | Time to wait for a TLS client hello before falling back to plain-text when `TlsHandshakeFirst` is `true`. |
|
|
| `AllowNonTls` | `bool` | `false` | Accepts non-TLS connections alongside TLS connections. |
|
|
| `TlsRateLimit` | `long` | `0` (unlimited) | Maximum new TLS handshakes per second. `0` disables rate limiting. |
|
|
| `TlsPinnedCerts` | `HashSet<string>?` | `null` | Set of SHA-256 certificate fingerprints that are permitted. Connections presenting other certs are rejected. |
|
|
| `TlsMinVersion` | `SslProtocols` | `Tls12` | Minimum TLS protocol version accepted. |
|
|
|
|
### OCSP stapling
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `OcspConfig` | `OcspConfig?` | `null` | OCSP stapling settings. When `null`, stapling is disabled. The `OcspConfig` type exposes `Mode` (`Auto`, `Always`, `Must`, `Never`) and `OverrideUrls`. |
|
|
| `OcspPeerVerify` | `bool` | `false` | Requires OCSP staples from connecting clients when mutual TLS is enabled. |
|
|
|
|
### Clustering
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `Cluster` | `ClusterOptions?` | `null` | Cluster listener and route configuration. When `null`, clustering is disabled. `ClusterOptions` exposes `Name`, `Host` (`"0.0.0.0"`), `Port` (`6222`), and `Routes` (list of seed URLs). |
|
|
| `Gateway` | `GatewayOptions?` | `null` | Gateway bridge to other clusters. `GatewayOptions` exposes `Name`, `Host`, and `Port`. |
|
|
| `LeafNode` | `LeafNodeOptions?` | `null` | Leaf node listener. `LeafNodeOptions` exposes `Host` and `Port`. |
|
|
|
|
### JetStream
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `JetStream` | `JetStreamOptions?` | `null` | Enables and configures JetStream persistence. When `null`, JetStream is disabled. `JetStreamOptions` exposes `StoreDir` (base directory for file-backed streams), `MaxMemoryStore` (bytes, `0` = unlimited), and `MaxFileStore` (bytes, `0` = unlimited). |
|
|
|
|
### MQTT
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `Mqtt` | `MqttOptions?` | `null` | MQTT protocol configuration. Config is parsed and stored but no MQTT listener is started yet. `MqttOptions` exposes network (`Host`, `Port`), auth (`Username`, `Password`, `Token`, `NoAuthUser`), TLS, and JetStream integration fields (`JsDomain`, `StreamReplicas`, `AckWait`). |
|
|
|
|
### WebSocket
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `WebSocket` | `WebSocketOptions` | `new()` | WebSocket transport configuration. Always present; the listener is inactive when `Port` is `-1` (the default). `WebSocketOptions` exposes `Host`, `Port`, `NoTls`, `SameOrigin`, `AllowedOrigins`, `Compression`, `HandshakeTimeout`, per-connection auth fields, and TLS cert paths. |
|
|
|
|
### Advanced
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `NoHeaderSupport` | `bool` | `false` | Disables NATS header support. Clients are informed via the `INFO` message; `HPUB`/`HMSG` commands are rejected. |
|
|
| `DisableSublistCache` | `bool` | `false` | Disables the `SubList` match cache. Useful in benchmarks to isolate raw matching cost. |
|
|
| `NoSystemAccount` | `bool` | `false` | Suppresses creation of the built-in `$SYS` account used for system events. |
|
|
| `SystemAccount` | `string?` | `null` | Name of the account to use as the system account instead of the built-in default. |
|
|
| `MaxClosedClients` | `int` | `10000` | Number of recently closed client records retained for monitoring (`/connz?closed=true`). |
|
|
| `ConnectErrorReports` | `int` | `3600` | How often (in attempts) connection errors to routes/gateways are logged. |
|
|
| `ReconnectErrorReports` | `int` | `1` | How often reconnect errors are logged. `1` logs every attempt. |
|
|
| `MaxTracedMsgLen` | `int` | `0` (unlimited) | Truncation length for message payloads in trace-level logs. `0` logs the full payload. |
|
|
| `Tags` | `Dictionary<string, string>?` | `null` | Arbitrary key-value tags exposed via the `/varz` monitoring endpoint. |
|
|
| `ClientAdvertise` | `string?` | `null` | Alternative `host:port` advertised to cluster peers for client connections (NAT traversal). |
|
|
| `SubjectMappings` | `Dictionary<string, string>?` | `null` | Subject transform rules mapping source patterns to destination templates. |
|
|
| `InCmdLine` | `HashSet<string>` | `[]` | Tracks which property names were set via CLI flags. Used during config reload to prevent file-based values from overwriting CLI-supplied ones. Not a user-settable option. |
|
|
|
|
### 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 in two passes before creating `NatsServer`. The first pass scans for `-c` to load a config file as the base `NatsOptions`. The second pass applies all remaining flags on top of the loaded options. Every flag that is processed is recorded in `options.InCmdLine` so that config-file reloads cannot overwrite values that were explicitly supplied on the command line.
|
|
|
|
| Flag | Alias | Field | Example |
|
|
|------|-------|-------|---------|
|
|
| `-c` | — | `ConfigFile` (load only) | `-c /etc/nats/server.conf` |
|
|
| `-p` | `--port` | `Port` | `-p 14222` |
|
|
| `-a` | `--addr` | `Host` | `-a 127.0.0.1` |
|
|
| `-n` | `--name` | `ServerName` | `-n my-server` |
|
|
| `-m` | `--http_port` | `MonitorPort` | `-m 8222` |
|
|
| — | `--http_base_path` | `MonitorBasePath` | `--http_base_path /nats` |
|
|
| — | `--https_port` | `MonitorHttpsPort` | `--https_port 8443` |
|
|
| — | `--pid` | `PidFile` | `--pid /var/run/nats.pid` |
|
|
| — | `--ports_file_dir` | `PortsFileDir` | `--ports_file_dir /tmp` |
|
|
| — | `--tlscert` | `TlsCert` | `--tlscert server.pem` |
|
|
| — | `--tlskey` | `TlsKey` | `--tlskey server-key.pem` |
|
|
| — | `--tlscacert` | `TlsCaCert` | `--tlscacert ca.pem` |
|
|
| — | `--tlsverify` | `TlsVerify` | `--tlsverify` |
|
|
| `-D` | `--debug` | `Debug` | `-D` |
|
|
| `-V` / `-T` | `--trace` | `Trace` | `-V` |
|
|
| `-DV` | — | `Debug` + `Trace` | `-DV` |
|
|
| `-l` | `--log` / `--log_file` | `LogFile` | `-l /var/log/nats.log` |
|
|
| — | `--log_size_limit` | `LogSizeLimit` | `--log_size_limit 104857600` |
|
|
| — | `--log_max_files` | `LogMaxFiles` | `--log_max_files 5` |
|
|
| — | `--logtime` | `Logtime` | `--logtime false` |
|
|
| — | `--logtime_utc` | `LogtimeUTC` | `--logtime_utc` |
|
|
| — | `--syslog` | `Syslog` | `--syslog` |
|
|
| — | `--remote_syslog` | `RemoteSyslog` | `--remote_syslog udp://logs.example.com:514` |
|
|
| — | `--log_level_override` | `LogOverrides` | `--log_level_override NATS.Server.NatsClient=Warning` |
|
|
| — | `--service` | Windows Service mode | `--service` |
|
|
|
|
The `-c` flag is consumed in the first pass and silently skipped in the second pass. Unrecognized flags are silently ignored. There is no `--help` output.
|
|
|
|
The `InCmdLine` set is used after startup to establish reload precedence. When a config-file reload is triggered (e.g., via `SIGHUP`), `ConfigReloader.MergeCliOverrides` copies the CLI-supplied field values back over the reloaded options, ensuring flags like `-p` or `-D` cannot be reverted by a config change.
|
|
|
|
```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]);
|
|
options.InCmdLine.Add("Port");
|
|
break;
|
|
case "-a" or "--addr" when i + 1 < args.Length:
|
|
options.Host = args[++i];
|
|
options.InCmdLine.Add("Host");
|
|
break;
|
|
case "-n" or "--name" when i + 1 < args.Length:
|
|
options.ServerName = args[++i];
|
|
options.InCmdLine.Add("ServerName");
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
### Debug and Trace flags
|
|
|
|
`NatsOptions` exposes two boolean flags that control the Serilog minimum log level. `Debug` sets the minimum level to `Debug`; `Trace` sets it to `Verbose` (Serilog's finest level, matching NATS protocol tracing). When both are present, `Trace` wins because `Verbose` is finer than `Debug`. Neither flag changes log output format — only the minimum severity threshold.
|
|
|
|
`TraceVerbose` is a separate flag that enables payload content in protocol traces. It is not wired to a Serilog level; components that check it emit additional `Verbose`-level log entries that include message body bytes.
|
|
|
|
### LogOverrides dictionary
|
|
|
|
`LogOverrides` is a `Dictionary<string, string>?` on `NatsOptions` that maps .NET logger category name prefixes to Serilog level names (`Verbose`, `Debug`, `Information`, `Warning`, `Error`, `Fatal`). Each entry becomes a `MinimumLevel.Override(ns, level)` call in the Serilog configuration:
|
|
|
|
```csharp
|
|
if (options.LogOverrides is not null)
|
|
{
|
|
foreach (var (ns, level) in options.LogOverrides)
|
|
{
|
|
if (Enum.TryParse<Serilog.Events.LogEventLevel>(level, true, out var serilogLevel))
|
|
logConfig.MinimumLevel.Override(ns, serilogLevel);
|
|
}
|
|
}
|
|
```
|
|
|
|
This maps directly to Serilog's per-category filtering, which is applied before the global minimum level check. A useful override pattern is silencing the high-volume per-client category while keeping server-level events visible:
|
|
|
|
```
|
|
--log_level_override NATS.Server.NatsClient=Warning
|
|
```
|
|
|
|
The `--log_level_override` CLI flag sets a single entry in `LogOverrides` using `key=value` format. Multiple flags may be supplied to add multiple overrides.
|
|
|
|
### 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-23 -->
|