From b8f2f66d45ec9507a68a7b78d5790c2ae953772c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 26 Feb 2026 11:54:25 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20port=20session=2004=20=E2=80=94=20Loggi?= =?UTF-8?q?ng,=20Signals=20&=20Services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NatsLogger.cs: INatsLogger interface (Noticef/Warnf/Fatalf/Errorf/Debugf/Tracef), ServerLogging state class with atomic debug/trace flags, rate-limited logging (RateLimitWarnf/RateLimitDebugf), error variants (Errors/Errorc/Errorsc), MicrosoftLoggerAdapter bridging to ILogger - SignalHandler.cs: ProcessSignal (Unix kill via Process), CommandToUnixSignal mapping (Stop→SIGKILL, Quit→SIGINT, Reopen→SIGUSR1, Reload→SIGHUP), ResolvePids via pgrep, SetProcessName, Run/IsWindowsService stubs for non-Windows - 11 tests (6 logger, 5 signal/service) - WASM/Windows signal stubs already n/a - All 141 tests pass (140 unit + 1 integration) - DB: features 368/3673 complete, tests 155/3257 complete (9.6% overall) --- .../Internal/NatsLogger.cs | 187 ++++++++++++++++++ .../Internal/SignalHandler.cs | 145 ++++++++++++++ .../Internal/NatsLoggerTests.cs | 131 ++++++++++++ .../Internal/SignalHandlerTests.cs | 70 +++++++ porting.db | Bin 2469888 -> 2469888 bytes reports/current.md | 18 +- reports/report_f08fc5d.md | 39 ++++ 7 files changed, 581 insertions(+), 9 deletions(-) create mode 100644 dotnet/src/ZB.MOM.NatsNet.Server/Internal/NatsLogger.cs create mode 100644 dotnet/src/ZB.MOM.NatsNet.Server/Internal/SignalHandler.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/NatsLoggerTests.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/SignalHandlerTests.cs create mode 100644 reports/report_f08fc5d.md diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Internal/NatsLogger.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/NatsLogger.cs new file mode 100644 index 0000000..b547b2c --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/NatsLogger.cs @@ -0,0 +1,187 @@ +// Copyright 2012-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/log.go in the NATS server Go source. + +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace ZB.MOM.NatsNet.Server.Internal; + +/// +/// NATS server Logger interface. +/// Mirrors the Go Logger interface in log.go. +/// In .NET we bridge to from Microsoft.Extensions.Logging. +/// +public interface INatsLogger +{ + void Noticef(string format, params object[] args); + void Warnf(string format, params object[] args); + void Fatalf(string format, params object[] args); + void Errorf(string format, params object[] args); + void Debugf(string format, params object[] args); + void Tracef(string format, params object[] args); +} + +/// +/// Server logging state. Encapsulates the logger, debug/trace flags, and rate-limiting. +/// Mirrors the logging fields of Go's Server struct (logging struct + rateLimitLogging sync.Map). +/// +public sealed class ServerLogging +{ + private readonly object _lock = new(); + private INatsLogger? _logger; + private int _debug; + private int _trace; + private int _traceSysAcc; + private readonly ConcurrentDictionary _rateLimitMap = new(); + + /// Gets the current logger (thread-safe). + public INatsLogger? GetLogger() + { + lock (_lock) return _logger; + } + + /// + /// Sets the logger with debug/trace flags. + /// Mirrors Server.SetLoggerV2. + /// + public void SetLoggerV2(INatsLogger? logger, bool debugFlag, bool traceFlag, bool sysTrace) + { + Interlocked.Exchange(ref _debug, debugFlag ? 1 : 0); + Interlocked.Exchange(ref _trace, traceFlag ? 1 : 0); + Interlocked.Exchange(ref _traceSysAcc, sysTrace ? 1 : 0); + + lock (_lock) + { + if (_logger is IDisposable disposable) + disposable.Dispose(); + _logger = logger; + } + } + + /// + /// Sets the logger. Mirrors Server.SetLogger. + /// + public void SetLogger(INatsLogger? logger, bool debugFlag, bool traceFlag) => + SetLoggerV2(logger, debugFlag, traceFlag, false); + + public bool IsDebug => Interlocked.CompareExchange(ref _debug, 0, 0) != 0; + public bool IsTrace => Interlocked.CompareExchange(ref _trace, 0, 0) != 0; + public bool IsTraceSysAcc => Interlocked.CompareExchange(ref _traceSysAcc, 0, 0) != 0; + + /// Executes a log call under the read lock. + public void ExecuteLogCall(Action action) + { + INatsLogger? logger; + lock (_lock) logger = _logger; + if (logger == null) return; + action(logger); + } + + // ---- Convenience methods ---- + + public void Noticef(string format, params object[] args) => + ExecuteLogCall(l => l.Noticef(format, args)); + + public void Errorf(string format, params object[] args) => + ExecuteLogCall(l => l.Errorf(format, args)); + + public void Errors(object scope, Exception e) => + ExecuteLogCall(l => l.Errorf("{0} - {1}", scope, e.Message)); + + public void Errorc(string ctx, Exception e) => + ExecuteLogCall(l => l.Errorf("{0}: {1}", ctx, e.Message)); + + public void Errorsc(object scope, string ctx, Exception e) => + ExecuteLogCall(l => l.Errorf("{0} - {1}: {2}", scope, ctx, e.Message)); + + public void Warnf(string format, params object[] args) => + ExecuteLogCall(l => l.Warnf(format, args)); + + public void Fatalf(string format, params object[] args) => + ExecuteLogCall(l => l.Fatalf(format, args)); + + public void Debugf(string format, params object[] args) + { + if (!IsDebug) return; + ExecuteLogCall(l => l.Debugf(format, args)); + } + + public void Tracef(string format, params object[] args) + { + if (!IsTrace) return; + ExecuteLogCall(l => l.Tracef(format, args)); + } + + /// + /// Rate-limited warning log. Only the first occurrence of each formatted statement is logged. + /// Mirrors Server.RateLimitWarnf. + /// + public void RateLimitWarnf(string format, params object[] args) + { + var statement = string.Format(format, args); + if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return; + Warnf("{0}", statement); + } + + /// + /// Rate-limited debug log. Only the first occurrence of each formatted statement is logged. + /// Mirrors Server.RateLimitDebugf. + /// + public void RateLimitDebugf(string format, params object[] args) + { + var statement = string.Format(format, args); + if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return; + Debugf("{0}", statement); + } + + /// + /// Rate-limited format warning. Only the first occurrence of each format string is logged. + /// Mirrors Server.rateLimitFormatWarnf. + /// + internal void RateLimitFormatWarnf(string format, params object[] args) + { + if (!_rateLimitMap.TryAdd(format, DateTime.UtcNow)) return; + var statement = string.Format(format, args); + Warnf("{0}", statement); + } +} + +/// +/// Adapter that bridges to . +/// +public sealed class MicrosoftLoggerAdapter : INatsLogger +{ + private readonly ILogger _logger; + + public MicrosoftLoggerAdapter(ILogger logger) => _logger = logger; + + public void Noticef(string format, params object[] args) => + _logger.LogInformation(format, args); + + public void Warnf(string format, params object[] args) => + _logger.LogWarning(format, args); + + public void Fatalf(string format, params object[] args) => + _logger.LogCritical(format, args); + + public void Errorf(string format, params object[] args) => + _logger.LogError(format, args); + + public void Debugf(string format, params object[] args) => + _logger.LogDebug(format, args); + + public void Tracef(string format, params object[] args) => + _logger.LogTrace(format, args); +} diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/Internal/SignalHandler.cs b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/SignalHandler.cs new file mode 100644 index 0000000..ef55123 --- /dev/null +++ b/dotnet/src/ZB.MOM.NatsNet.Server/Internal/SignalHandler.cs @@ -0,0 +1,145 @@ +// Copyright 2012-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Adapted from server/signal.go and server/service.go in the NATS server Go source. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ZB.MOM.NatsNet.Server.Internal; + +/// +/// Maps to OS signal-like behavior. +/// Mirrors CommandToSignal and ProcessSignal from signal.go. +/// In .NET, signal sending is replaced by process-level signaling on Unix. +/// +public static class SignalHandler +{ + private static string _processName = "nats-server"; + + /// + /// Sets the process name used for resolving PIDs. + /// Mirrors SetProcessName in signal.go. + /// + public static void SetProcessName(string name) => _processName = name; + + /// + /// Sends a signal command to a running NATS server process. + /// On Unix, maps commands to kill signals. + /// On Windows, this is a no-op (service manager handles signals). + /// Mirrors ProcessSignal in signal.go. + /// + public static Exception? ProcessSignal(ServerCommand command, string pidExpr = "") + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return new PlatformNotSupportedException("Signal processing not supported on Windows; use service manager."); + + try + { + List pids; + if (string.IsNullOrEmpty(pidExpr)) + { + pids = ResolvePids(); + if (pids.Count == 0) + return new InvalidOperationException("no nats-server processes found"); + } + else + { + if (int.TryParse(pidExpr, out var pid)) + pids = [pid]; + else + return new InvalidOperationException($"invalid pid: {pidExpr}"); + } + + var signal = CommandToUnixSignal(command); + + foreach (var pid in pids) + Process.GetProcessById(pid).Kill(signal == UnixSignal.SigKill); + + return null; + } + catch (Exception ex) + { + return ex; + } + } + + /// + /// Resolves PIDs of running nats-server processes via pgrep. + /// Mirrors resolvePids in signal.go. + /// + public static List ResolvePids() + { + var pids = new List(); + try + { + var psi = new ProcessStartInfo("pgrep", _processName) + { + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + using var proc = Process.Start(psi); + if (proc == null) return pids; + + var output = proc.StandardOutput.ReadToEnd(); + proc.WaitForExit(); + + var currentPid = Environment.ProcessId; + foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries)) + { + if (int.TryParse(line.Trim(), out var pid) && pid != currentPid) + pids.Add(pid); + } + } + catch + { + // pgrep not available or failed + } + return pids; + } + + /// + /// Maps a server command to Unix signal. + /// Mirrors CommandToSignal in signal.go. + /// + public static UnixSignal CommandToUnixSignal(ServerCommand command) => command switch + { + ServerCommand.Stop => UnixSignal.SigKill, + ServerCommand.Quit => UnixSignal.SigInt, + ServerCommand.Reopen => UnixSignal.SigUsr1, + ServerCommand.Reload => UnixSignal.SigHup, + _ => throw new ArgumentOutOfRangeException(nameof(command), $"unknown command: {command}"), + }; + + /// + /// Runs the server (non-Windows). Mirrors Run in service.go. + /// + public static void Run(Action startServer) => startServer(); + + /// + /// Returns false on non-Windows. Mirrors isWindowsService. + /// + public static bool IsWindowsService() => false; +} + +/// Unix signal codes for NATS command mapping. +public enum UnixSignal +{ + SigInt = 2, + SigKill = 9, + SigUsr1 = 10, + SigHup = 1, + SigUsr2 = 12, + SigTerm = 15, +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/NatsLoggerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/NatsLoggerTests.cs new file mode 100644 index 0000000..f5466c7 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/NatsLoggerTests.cs @@ -0,0 +1,131 @@ +// Copyright 2012-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 + +using Shouldly; +using ZB.MOM.NatsNet.Server.Internal; + +namespace ZB.MOM.NatsNet.Server.Tests.Internal; + +/// +/// Tests for NatsLogger / ServerLogging — mirrors tests from server/log_test.go. +/// +public class NatsLoggerTests +{ + private sealed class TestLogger : INatsLogger + { + public List Messages { get; } = []; + + public void Noticef(string format, params object[] args) => Messages.Add($"[INF] {string.Format(format, args)}"); + public void Warnf(string format, params object[] args) => Messages.Add($"[WRN] {string.Format(format, args)}"); + public void Fatalf(string format, params object[] args) => Messages.Add($"[FTL] {string.Format(format, args)}"); + public void Errorf(string format, params object[] args) => Messages.Add($"[ERR] {string.Format(format, args)}"); + public void Debugf(string format, params object[] args) => Messages.Add($"[DBG] {string.Format(format, args)}"); + public void Tracef(string format, params object[] args) => Messages.Add($"[TRC] {string.Format(format, args)}"); + } + + /// + /// Mirrors TestSetLogger — verify logger assignment and atomic flags. + /// + [Fact] // T:2017 + public void SetLogger_ShouldSetLoggerAndFlags() + { + var logging = new ServerLogging(); + var testLog = new TestLogger(); + + logging.SetLoggerV2(testLog, true, true, false); + logging.IsDebug.ShouldBeTrue(); + logging.IsTrace.ShouldBeTrue(); + logging.IsTraceSysAcc.ShouldBeFalse(); + logging.GetLogger().ShouldBe(testLog); + } + + /// + /// Verify all log methods produce output when flags enabled. + /// + [Fact] // T:2017 (continuation) + public void AllLogMethods_ShouldProduceOutput() + { + var logging = new ServerLogging(); + var testLog = new TestLogger(); + logging.SetLoggerV2(testLog, true, true, false); + + logging.Noticef("notice {0}", "test"); + logging.Errorf("error {0}", "test"); + logging.Warnf("warn {0}", "test"); + logging.Fatalf("fatal {0}", "test"); + logging.Debugf("debug {0}", "test"); + logging.Tracef("trace {0}", "test"); + + testLog.Messages.Count.ShouldBe(6); + testLog.Messages[0].ShouldContain("[INF]"); + testLog.Messages[1].ShouldContain("[ERR]"); + testLog.Messages[4].ShouldContain("[DBG]"); + testLog.Messages[5].ShouldContain("[TRC]"); + } + + /// + /// Debug/Trace should not produce output when flags disabled. + /// + [Fact] // T:2017 (continuation) + public void DebugTrace_ShouldBeNoOpWhenDisabled() + { + var logging = new ServerLogging(); + var testLog = new TestLogger(); + logging.SetLoggerV2(testLog, false, false, false); + + logging.Debugf("debug"); + logging.Tracef("trace"); + + testLog.Messages.ShouldBeEmpty(); + } + + /// + /// Verify null logger does not throw. + /// + [Fact] + public void NullLogger_ShouldNotThrow() + { + var logging = new ServerLogging(); + Should.NotThrow(() => logging.Noticef("test")); + Should.NotThrow(() => logging.Errorf("test")); + Should.NotThrow(() => logging.Debugf("test")); + } + + /// + /// Verify rate-limited logging suppresses duplicate messages. + /// + [Fact] // T:2017 (RateLimitWarnf behavior) + public void RateLimitWarnf_ShouldSuppressDuplicates() + { + var logging = new ServerLogging(); + var testLog = new TestLogger(); + logging.SetLoggerV2(testLog, false, false, false); + + logging.RateLimitWarnf("duplicate message"); + logging.RateLimitWarnf("duplicate message"); + logging.RateLimitWarnf("different message"); + + // Should only log 2 unique messages, not 3. + testLog.Messages.Count.ShouldBe(2); + } + + /// + /// Verify Errors/Errorc/Errorsc convenience methods. + /// + [Fact] + public void ErrorVariants_ShouldFormatCorrectly() + { + var logging = new ServerLogging(); + var testLog = new TestLogger(); + logging.SetLoggerV2(testLog, false, false, false); + + logging.Errors("client", new Exception("conn reset")); + logging.Errorc("TLS", new Exception("cert expired")); + logging.Errorsc("route", "cluster", new Exception("timeout")); + + testLog.Messages.Count.ShouldBe(3); + testLog.Messages[0].ShouldContain("client - conn reset"); + testLog.Messages[1].ShouldContain("TLS: cert expired"); + testLog.Messages[2].ShouldContain("route - cluster: timeout"); + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/SignalHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/SignalHandlerTests.cs new file mode 100644 index 0000000..cfc3400 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/SignalHandlerTests.cs @@ -0,0 +1,70 @@ +// Copyright 2012-2025 The NATS Authors +// Licensed under the Apache License, Version 2.0 + +using System.Runtime.InteropServices; +using Shouldly; +using ZB.MOM.NatsNet.Server.Internal; + +namespace ZB.MOM.NatsNet.Server.Tests.Internal; + +/// +/// Tests for SignalHandler — mirrors tests from server/signal_test.go. +/// +public class SignalHandlerTests +{ + /// + /// Mirrors CommandToSignal mapping tests. + /// + [Fact] // T:3158 + public void CommandToUnixSignal_ShouldMapCorrectly() + { + SignalHandler.CommandToUnixSignal(ServerCommand.Stop).ShouldBe(UnixSignal.SigKill); + SignalHandler.CommandToUnixSignal(ServerCommand.Quit).ShouldBe(UnixSignal.SigInt); + SignalHandler.CommandToUnixSignal(ServerCommand.Reopen).ShouldBe(UnixSignal.SigUsr1); + SignalHandler.CommandToUnixSignal(ServerCommand.Reload).ShouldBe(UnixSignal.SigHup); + } + + /// + /// Mirrors SetProcessName test. + /// + [Fact] // T:3155 + public void SetProcessName_ShouldNotThrow() + { + Should.NotThrow(() => SignalHandler.SetProcessName("test-server")); + } + + /// + /// Verify IsWindowsService returns false on non-Windows. + /// + [Fact] // T:3149 + public void IsWindowsService_ShouldReturnFalse() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; // Skip on Windows + SignalHandler.IsWindowsService().ShouldBeFalse(); + } + + /// + /// Mirrors Run — service.go Run() simply invokes the start function. + /// + [Fact] // T:3148 + public void Run_ShouldInvokeStartAction() + { + var called = false; + SignalHandler.Run(() => called = true); + called.ShouldBeTrue(); + } + + /// + /// ProcessSignal with invalid PID expression should return error. + /// + [Fact] // T:3157 + public void ProcessSignal_InvalidPid_ShouldReturnError() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; // Skip on Windows + + var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "not-a-pid"); + err.ShouldNotBeNull(); + } +} diff --git a/porting.db b/porting.db index 3c7bf3a8699c4b359d02baa0237a426781d90b99..ea5a0b70e0891b39ecc45c428b573315da49689d 100644 GIT binary patch delta 4566 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 bRE(W;!~KYg%@1x>!%cU?&G delta 3389 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 p!)t0T$zU!Wm%K~*Z4_ diff --git a/reports/current.md b/reports/current.md index 9062e7b..9a95428 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-26 16:51:01 UTC +Generated: 2026-02-26 16:54:25 UTC ## Modules (12 total) @@ -13,19 +13,19 @@ Generated: 2026-02-26 16:51:01 UTC | Status | Count | |--------|-------| -| complete | 344 | +| complete | 368 | | n_a | 82 | -| not_started | 3180 | -| stub | 67 | +| not_started | 3155 | +| stub | 68 | ## Unit Tests (3257 total) | Status | Count | |--------|-------| -| complete | 148 | -| n_a | 49 | -| not_started | 2980 | -| stub | 80 | +| complete | 155 | +| n_a | 50 | +| not_started | 2953 | +| stub | 99 | ## Library Mappings (36 total) @@ -36,4 +36,4 @@ Generated: 2026-02-26 16:51:01 UTC ## Overall Progress -**634/6942 items complete (9.1%)** +**666/6942 items complete (9.6%)** diff --git a/reports/report_f08fc5d.md b/reports/report_f08fc5d.md new file mode 100644 index 0000000..9a95428 --- /dev/null +++ b/reports/report_f08fc5d.md @@ -0,0 +1,39 @@ +# NATS .NET Porting Status Report + +Generated: 2026-02-26 16:54:25 UTC + +## Modules (12 total) + +| Status | Count | +|--------|-------| +| complete | 11 | +| not_started | 1 | + +## Features (3673 total) + +| Status | Count | +|--------|-------| +| complete | 368 | +| n_a | 82 | +| not_started | 3155 | +| stub | 68 | + +## Unit Tests (3257 total) + +| Status | Count | +|--------|-------| +| complete | 155 | +| n_a | 50 | +| not_started | 2953 | +| stub | 99 | + +## Library Mappings (36 total) + +| Status | Count | +|--------|-------| +| mapped | 36 | + + +## Overall Progress + +**666/6942 items complete (9.6%)**