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