add LoopbackInsinTelemetry with rate-limited warn logging
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Inspectron.Epson.PrintServer.Telemetry;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Inspectron.Epson.Tests.Telemetry;
|
||||
|
||||
public class LoopbackInsinTelemetryTests
|
||||
{
|
||||
private const int TestPort = 47823;
|
||||
|
||||
[Fact]
|
||||
public async Task Emit_posts_json_payload_to_loopback()
|
||||
{
|
||||
using var listener = new HttpListener();
|
||||
listener.Prefixes.Add($"http://127.0.0.1:{TestPort}/");
|
||||
listener.Start();
|
||||
|
||||
using var http = new HttpClient();
|
||||
var telemetry = new LoopbackInsinTelemetry(http, NullLogger.Instance);
|
||||
telemetry.Emit("service.started", "version=1.0.14");
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
var ctxTask = listener.GetContextAsync();
|
||||
var completed = await Task.WhenAny(ctxTask, Task.Delay(Timeout.Infinite, cts.Token));
|
||||
Assert.Same(ctxTask, completed);
|
||||
var ctx = ctxTask.Result;
|
||||
|
||||
Assert.Equal("POST", ctx.Request.HttpMethod);
|
||||
Assert.Equal("/events", ctx.Request.Url!.AbsolutePath);
|
||||
using var sr = new StreamReader(ctx.Request.InputStream, Encoding.UTF8);
|
||||
var body = await sr.ReadToEndAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.Equal("service.started", doc.RootElement.GetProperty("kind").GetString());
|
||||
Assert.Equal("version=1.0.14", doc.RootElement.GetProperty("message").GetString());
|
||||
var at = doc.RootElement.GetProperty("at").GetString();
|
||||
Assert.False(string.IsNullOrEmpty(at));
|
||||
Assert.EndsWith("Z", at);
|
||||
|
||||
ctx.Response.StatusCode = 200;
|
||||
ctx.Response.Close();
|
||||
listener.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Emit_swallows_connection_refused_and_does_not_throw()
|
||||
{
|
||||
using var http = new HttpClient();
|
||||
var telemetry = new LoopbackInsinTelemetry(http, NullLogger.Instance);
|
||||
var ex = Record.Exception(() => telemetry.Emit("service.started", "version=1.0.14"));
|
||||
Assert.Null(ex);
|
||||
await Task.Delay(200);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Emit_swallows_500_response_and_does_not_throw()
|
||||
{
|
||||
using var listener = new HttpListener();
|
||||
listener.Prefixes.Add($"http://127.0.0.1:{TestPort}/");
|
||||
listener.Start();
|
||||
|
||||
using var http = new HttpClient();
|
||||
var telemetry = new LoopbackInsinTelemetry(http, NullLogger.Instance);
|
||||
telemetry.Emit("service.started", "version=1.0.14");
|
||||
|
||||
var ctx = await listener.GetContextAsync();
|
||||
ctx.Response.StatusCode = 500;
|
||||
ctx.Response.Close();
|
||||
|
||||
await Task.Delay(200);
|
||||
listener.Stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Inspectron.Epson.PrintServer.Telemetry;
|
||||
|
||||
public sealed class LoopbackInsinTelemetry : IInsinTelemetry
|
||||
{
|
||||
private const string Endpoint = "http://127.0.0.1:47823/events";
|
||||
private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(2);
|
||||
private static readonly long WarnIntervalTicks = TimeSpan.FromMinutes(5).Ticks / TimeSpan.TicksPerMillisecond;
|
||||
|
||||
private readonly HttpClient _http;
|
||||
private readonly ILogger _logger;
|
||||
private long _lastWarnMs = -WarnIntervalTicks;
|
||||
|
||||
public LoopbackInsinTelemetry(HttpClient http, ILogger logger)
|
||||
{
|
||||
_http = http;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Emit(string kind, string message)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
kind,
|
||||
message,
|
||||
at = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
};
|
||||
_ = Task.Run(() => SendAsync(payload));
|
||||
}
|
||||
|
||||
private async Task SendAsync(object payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(RequestTimeout);
|
||||
using var resp = await _http.PostAsJsonAsync(Endpoint, payload, cts.Token);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
MaybeWarn($"insin loopback returned {(int)resp.StatusCode}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MaybeWarn($"insin loopback unreachable at 127.0.0.1:47823 ({ex.GetType().Name}), dropping events");
|
||||
}
|
||||
}
|
||||
|
||||
private void MaybeWarn(string message)
|
||||
{
|
||||
var now = Environment.TickCount64;
|
||||
var last = Interlocked.Read(ref _lastWarnMs);
|
||||
if (now - last < WarnIntervalTicks) return;
|
||||
if (Interlocked.CompareExchange(ref _lastWarnMs, now, last) != last) return;
|
||||
_logger.LogWarning("{Message}", message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user