feat(batch4-task3): implement error and rate-limit logging helpers
This commit is contained in:
@@ -171,6 +171,83 @@ public sealed partial class NatsServer
|
||||
action(logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error with a scope.
|
||||
/// Mirrors Go <c>Server.Errors()</c>.
|
||||
/// </summary>
|
||||
public void Errors(object scope, Exception e)
|
||||
{
|
||||
ExecuteLogCall(l => l.Errorf("{0} - {1}", scope, ErrorContextHelper.UnpackIfErrorCtx(e)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error with context.
|
||||
/// Mirrors Go <c>Server.Errorc()</c>.
|
||||
/// </summary>
|
||||
public void Errorc(string ctx, Exception e)
|
||||
{
|
||||
ExecuteLogCall(l => l.Errorf("{0}: {1}", ctx, ErrorContextHelper.UnpackIfErrorCtx(e)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error with scope and context.
|
||||
/// Mirrors Go <c>Server.Errorsc()</c>.
|
||||
/// </summary>
|
||||
public void Errorsc(object scope, string ctx, Exception e)
|
||||
{
|
||||
ExecuteLogCall(l => l.Errorf("{0} - {1}: {2}", scope, ctx, ErrorContextHelper.UnpackIfErrorCtx(e)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rate-limited warning based on the raw format string.
|
||||
/// Mirrors Go <c>Server.rateLimitFormatWarnf()</c>.
|
||||
/// </summary>
|
||||
internal void RateLimitFormatWarnf(string format, params object[] args)
|
||||
{
|
||||
if (!_rateLimitLogging.TryAdd(format, DateTime.UtcNow))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var statement = string.Format(format, args);
|
||||
ExecuteLogCall(l => l.Warnf("{0}", statement));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rate-limited warning based on rendered statement.
|
||||
/// Mirrors Go <c>Server.RateLimitWarnf()</c>.
|
||||
/// </summary>
|
||||
public void RateLimitWarnf(string format, params object[] args)
|
||||
{
|
||||
var statement = string.Format(format, args);
|
||||
if (!_rateLimitLogging.TryAdd(statement, DateTime.UtcNow))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteLogCall(l => l.Warnf("{0}", statement));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rate-limited debug logging based on rendered statement.
|
||||
/// Mirrors Go <c>Server.RateLimitDebugf()</c>.
|
||||
/// </summary>
|
||||
public void RateLimitDebugf(string format, params object[] args)
|
||||
{
|
||||
var statement = string.Format(format, args);
|
||||
if (!_rateLimitLogging.TryAdd(statement, DateTime.UtcNow))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref _debugEnabled, 0, 0) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteLogCall(l => l.Debugf("{0}", statement));
|
||||
}
|
||||
|
||||
private static ILogger ToMicrosoftLogger(INatsLogger? logger)
|
||||
{
|
||||
return logger switch
|
||||
|
||||
@@ -220,6 +220,76 @@ public class ServerLoggerTests
|
||||
content.ShouldContain("message-after-reopen");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ErrorsVariants_ShouldUseExpectedFormatting()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var setLogger = GetRequiredServerMethod("SetLogger", typeof(INatsLogger), typeof(bool), typeof(bool));
|
||||
var errors = GetRequiredServerMethod("Errors", typeof(object), typeof(Exception));
|
||||
var errorc = GetRequiredServerMethod("Errorc", typeof(string), typeof(Exception));
|
||||
var errorsc = GetRequiredServerMethod("Errorsc", typeof(object), typeof(string), typeof(Exception));
|
||||
|
||||
var logger = new CapturingLogger();
|
||||
setLogger.Invoke(server, [logger, false, false]);
|
||||
|
||||
var wrapped = ErrorContextHelper.NewErrorCtx(new Exception("connection reset"), "leaf reconnect");
|
||||
errors.Invoke(server, ["client", wrapped]);
|
||||
errorc.Invoke(server, ["tls", wrapped]);
|
||||
errorsc.Invoke(server, ["route", "cluster", wrapped]);
|
||||
|
||||
logger.Messages.Count.ShouldBe(3);
|
||||
logger.Messages[0].ShouldContain("client - connection reset: leaf reconnect");
|
||||
logger.Messages[1].ShouldContain("tls: connection reset: leaf reconnect");
|
||||
logger.Messages[2].ShouldContain("route - cluster: connection reset: leaf reconnect");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RateLimitHelpers_ShouldRespectDedupeSemanticsAndDebugFlag()
|
||||
{
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.ShouldNotBeNull();
|
||||
|
||||
var setLogger = GetRequiredServerMethod("SetLogger", typeof(INatsLogger), typeof(bool), typeof(bool));
|
||||
var rateLimitFormatWarnf = GetRequiredServerMethod("RateLimitFormatWarnf", typeof(string), typeof(object[]));
|
||||
var rateLimitWarnf = GetRequiredServerMethod("RateLimitWarnf", typeof(string), typeof(object[]));
|
||||
var rateLimitDebugf = GetRequiredServerMethod("RateLimitDebugf", typeof(string), typeof(object[]));
|
||||
|
||||
var logger = new CapturingLogger();
|
||||
setLogger.Invoke(server, [logger, false, false]);
|
||||
|
||||
// Dedupe by format string (same format, different args => single warning).
|
||||
rateLimitFormatWarnf.Invoke(server, ["format {0}", new object[] { "one" }]);
|
||||
rateLimitFormatWarnf.Invoke(server, ["format {0}", new object[] { "two" }]);
|
||||
rateLimitFormatWarnf.Invoke(server, ["other {0}", new object[] { "three" }]);
|
||||
logger.Messages.Count.ShouldBe(2);
|
||||
logger.Messages.ShouldContain("format one");
|
||||
logger.Messages.ShouldContain("other three");
|
||||
|
||||
// Dedupe by rendered statement.
|
||||
logger.Messages.Clear();
|
||||
rateLimitWarnf.Invoke(server, ["warn {0}", new object[] { "same" }]);
|
||||
rateLimitWarnf.Invoke(server, ["warn {0}", new object[] { "same" }]);
|
||||
rateLimitWarnf.Invoke(server, ["warn {0}", new object[] { "other" }]);
|
||||
logger.Messages.Count.ShouldBe(2);
|
||||
logger.Messages.ShouldContain("warn same");
|
||||
logger.Messages.ShouldContain("warn other");
|
||||
|
||||
// Debug dedupe + debug-flag gating.
|
||||
logger.Messages.Clear();
|
||||
rateLimitDebugf.Invoke(server, ["debug {0}", new object[] { "suppressed" }]);
|
||||
logger.Messages.ShouldBeEmpty();
|
||||
|
||||
setLogger.Invoke(server, [logger, true, false]);
|
||||
rateLimitDebugf.Invoke(server, ["debug {0}", new object[] { "visible" }]);
|
||||
rateLimitDebugf.Invoke(server, ["debug {0}", new object[] { "visible" }]);
|
||||
logger.Messages.Count.ShouldBe(1);
|
||||
logger.Messages[0].ShouldContain("debug visible");
|
||||
}
|
||||
|
||||
private static int GetPrivateIntField(object target, string fieldName)
|
||||
{
|
||||
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
Reference in New Issue
Block a user