- 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)
188 lines
6.6 KiB
C#
188 lines
6.6 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// NATS server Logger interface.
|
|
/// Mirrors the Go <c>Logger</c> interface in log.go.
|
|
/// In .NET we bridge to <see cref="ILogger"/> from Microsoft.Extensions.Logging.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server logging state. Encapsulates the logger, debug/trace flags, and rate-limiting.
|
|
/// Mirrors the logging fields of Go's <c>Server</c> struct (logging struct + rateLimitLogging sync.Map).
|
|
/// </summary>
|
|
public sealed class ServerLogging
|
|
{
|
|
private readonly object _lock = new();
|
|
private INatsLogger? _logger;
|
|
private int _debug;
|
|
private int _trace;
|
|
private int _traceSysAcc;
|
|
private readonly ConcurrentDictionary<string, DateTime> _rateLimitMap = new();
|
|
|
|
/// <summary>Gets the current logger (thread-safe).</summary>
|
|
public INatsLogger? GetLogger()
|
|
{
|
|
lock (_lock) return _logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the logger with debug/trace flags.
|
|
/// Mirrors <c>Server.SetLoggerV2</c>.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the logger. Mirrors <c>Server.SetLogger</c>.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>Executes a log call under the read lock.</summary>
|
|
public void ExecuteLogCall(Action<INatsLogger> 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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rate-limited warning log. Only the first occurrence of each formatted statement is logged.
|
|
/// Mirrors <c>Server.RateLimitWarnf</c>.
|
|
/// </summary>
|
|
public void RateLimitWarnf(string format, params object[] args)
|
|
{
|
|
var statement = string.Format(format, args);
|
|
if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return;
|
|
Warnf("{0}", statement);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rate-limited debug log. Only the first occurrence of each formatted statement is logged.
|
|
/// Mirrors <c>Server.RateLimitDebugf</c>.
|
|
/// </summary>
|
|
public void RateLimitDebugf(string format, params object[] args)
|
|
{
|
|
var statement = string.Format(format, args);
|
|
if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return;
|
|
Debugf("{0}", statement);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rate-limited format warning. Only the first occurrence of each format string is logged.
|
|
/// Mirrors <c>Server.rateLimitFormatWarnf</c>.
|
|
/// </summary>
|
|
internal void RateLimitFormatWarnf(string format, params object[] args)
|
|
{
|
|
if (!_rateLimitMap.TryAdd(format, DateTime.UtcNow)) return;
|
|
var statement = string.Format(format, args);
|
|
Warnf("{0}", statement);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adapter that bridges <see cref="INatsLogger"/> to <see cref="ILogger"/>.
|
|
/// </summary>
|
|
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);
|
|
}
|