Files
MoShiro/backend/tests/MoShiro.Backend.Tests/NodeRuntimeLogTests.cs
T
justme fb3a9c260f fleet: send log counters in heartbeats and pull the text on demand
Heartbeats carry per-level counts, the most frequent log types (a stable hash of the message) and the newest id, no text. The node keeps its last 2048 lines; moshiro.logs.fetch lets the backend pull them. The log page pulls when it is open, and the backend pulls warnings and errors once a minute after a node reports an error.
2026-10-22 05:28:29 +08:00

54 lines
2.4 KiB
C#

using System.Text.Json.Nodes;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace MoShiro.Backend.Tests;
public sealed class NodeRuntimeLogTests : IClassFixture<BackendFixture>
{
private readonly BackendFixture _backend;
public NodeRuntimeLogTests(BackendFixture backend)
{
_backend = backend;
}
private static LogEntryDto Entry(long id, string level, string message, Dictionary<string, string>? fields = null) =>
new() { Id = id, Type = (uint)id, AtUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(), Level = level, Message = message, Fields = fields ?? [] };
private async Task<JsonNode> LogsAsync(AgentSimulator agent) =>
await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/nodes/{agent.NodeId.Value}/logs?limit=50");
[Fact]
public async Task Logs_ArePulledFromTheNodeOnDemand_OnlyTheNewLinesEachTime_WithSecretsAndOversizedFieldsBounded()
{
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
Dictionary<string, string> fields = Enumerable.Range(0, 18).ToDictionary(i => "field_" + i, _ => "value");
fields.Add("api_token", "must-not-survive");
fields.Add("detail", new string('x', 500));
fields.Add("overflow", "discard");
agent.Logs.Add(Entry(1, "info", "link up"));
agent.Logs.Add(Entry(2, "warn", "handshake failed", fields));
JsonNode first = await LogsAsync(agent);
Assert.Equal(["handshake failed", "link up"], first["items"]!.AsArray().Select(i => i!["message"]!.GetValue<string>())); // 新的在前
JsonNode warn = first["items"]![0]!;
Assert.Equal(20, warn["fields"]!.AsObject().Count);
Assert.Equal("[redacted]", warn["fields"]!["api_token"]!.GetValue<string>());
Assert.Equal(128, warn["fields"]!["detail"]!.GetValue<string>().Length);
Assert.Null(warn["fields"]!["overflow"]);
agent.Logs.Add(Entry(3, "error", "transport shutdown"));
JsonNode second = await LogsAsync(agent);
Assert.Equal(3, second["items"]!.AsArray().Count);
Assert.Equal(2, agent.LogFetches.Last().AfterId); // 第二次只拉编号 2 之后的
}
[Fact]
public async Task Logs_OfAnOfflineNode_AreWhatTheBackendKept()
{
JsonNode logs = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/nodes/agt_nobody/logs");
Assert.Empty(logs["items"]!.AsArray());
}
}