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.
This commit is contained in:
justme committed 2026-10-22 05:28:29 +08:00
1 parent 4c31a4a79e
commit fb3a9c260f
17 files changed
+482 -110

No files matched your search

@@ -17,6 +17,11 @@ namespace MoShiro.TestSupport;
/// </summary>
public sealed class AgentSimulator : IAsyncDisposable
{
/// <summary>节点上保留的日志(<c>moshiro.logs.fetch</c> 按编号返回它们)和收到的拉取请求。</summary>
public System.Collections.Concurrent.ConcurrentBag<LogEntryDto> Logs { get; } = [];
public System.Collections.Concurrent.ConcurrentQueue<LogsFetchParams> LogFetches { get; } = new();
private readonly NodeIdentity _identity;
private readonly NodeIdentity _tlsKey;
private readonly ClientWebSocket _socket = new();
@@ -446,6 +451,13 @@ public sealed class AgentSimulator : IAsyncDisposable
await SendAsync(RpcMessage.Failure(message.Id, new FleetError(new ErrorCode(errorCode ?? "test_busy"), "refused")));
}
break;
case MoShiroMethods.LogsFetch:
LogsFetchParams fetch = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.LogsFetchParams)!;
LogFetches.Enqueue(fetch);
var page = new LogsFetchResult { LastId = Logs.IsEmpty ? 0 : Logs.Max(l => l.Id) };
page.Items.AddRange(Logs.Where(l => l.Id > fetch.AfterId).OrderBy(l => l.Id).Take(Math.Max(1, fetch.Limit)));
await SendAsync(RpcMessage.Success(message.Id!.Value, page, MoShiroJsonContext.Default.LogsFetchResult));
break;
case MoShiroMethods.TestCancel:
TestCancelParams cancel = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.TestCancelParams)!;