add LoopbackInsinTelemetry with rate-limited warn logging

This commit is contained in:
EugeneTes
2026-07-23 07:51:03 +00:00
parent 37318f9653
commit 465d9b4b4d
2 changed files with 130 additions and 0 deletions

View File

@@ -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();
}
}

View File

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