From a6bd024d17450f9c1d373de50e84da5a4c2d37de Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 13 Jul 2026 08:38:17 +0200 Subject: [PATCH] Generic SignalR logger hub and writer, doc updates Introduced generic `AcLoggerSignalRHub` and `AcSignaRClientLogItemWriter` for flexible log item wire types (AcBinary/JSON). Updated writer to accept protocol options and enforce feedback-loop prevention. Improved documentation to clarify generic usage, protocol selection, and migration. Added TODOs for protocol-diagnostic logger and log noise reduction. Refactored exception handling and signatures for generics; updated issue/TODO tracking and enhanced code comments. --- AyCode.Core/docs/LOGGING/LOGGING_TODO.md | 15 +++++++ .../SignalRs/AcLoggerSignalRHub.cs | 27 +++++++++--- .../Loggers/AcSignaRClientLogItemWriter.cs | 42 +++++++++++++++---- AyCode.Services/docs/LOGGING/README.md | 10 ++++- .../docs/SIGNALR/SIGNALR_ISSUES.md | 2 +- .../SIGNALR_BINARY_PROTOCOL_TODO.md | 11 +++++ 6 files changed, 91 insertions(+), 16 deletions(-) diff --git a/AyCode.Core/docs/LOGGING/LOGGING_TODO.md b/AyCode.Core/docs/LOGGING/LOGGING_TODO.md index c588283..44eab79 100644 --- a/AyCode.Core/docs/LOGGING/LOGGING_TODO.md +++ b/AyCode.Core/docs/LOGGING/LOGGING_TODO.md @@ -121,3 +121,18 @@ services.AddAcLoggerFactory(); ### Why this is a separate TODO from ACCORE-LOG-T-R7L3 ACCORE-LOG-T-R7L3 is about the **framework-level decision** (keep both patterns vs deprecate legacy). ACCORE-LOG-T-W4H9 is the **consumer-level execution** of that decision for the server plugin — independent of whether ACCORE-LOG-T-R7L3 ultimately deprecates the legacy path or keeps it alive. Migrating the server plugin removes ACCORE-LOG-I-K7M2 noise regardless of ACCORE-LOG-T-R7L3's outcome. + +## ACCORE-LOG-T-D7S3: Protokoll-diagnosztikai logger a log-feltöltő csatornának (szűrt writer-lista) — ⚠️ ÁTGONDOLANDÓ +**Priority:** P3 · **Type:** Feature (döntés függőben) · **Related:** `../../../AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md#accore-sbp-t-t5r2` + +**Státusz: ÁTGONDOLANDÓ — még kérdéses, kell-e egyáltalán. A jelenlegi megoldás (`NullLogger.Instance` a loggerHub AcBinary-protokollján, ld. FruitBank `SignaRClientLogItemWriter.CreateProtocolOptions`) alapvetően jó:** a hiba-út már látható (a writer `ReportFailureOnce` Console.Error-jelentése a `SendAsync`-kivételeknél a serializer-hibákat is elkapja), csak a Debug/Trace-szintű protokoll-trace néma. + +**Cél (ha bevezetjük):** a log-feltöltő csatorna (`AcSignaRClientLogItemWriter`) protokollja süket logger helyett diagnosztikai loggert kapjon, amiből a feltöltő writer **strukturálisan ki van szűrve** — a visszacsatolási hurok (log → sorosítás → protokoll-log → feltöltés → …) bekötése így lehetetlen marad, de a protokoll-diagnosztika a host TÖBBI writerére (konzol, fájl, DB — bármi, amit a host regisztrál) kimegy, és automatikusan követi a jövőbeli writer-regisztrációkat. + +**Megbeszélt terv (2026-07):** +1. `IAcSignalRLogWriter` marker-interfész az `AcSignaRClientLogItemWriter`-re — a szűrés típus-alapú: `writers.Where(w => w is not IAcSignalRLogWriter).ToList()` (saját, materializált lista az új loggernek). +2. A diagnosztikai logger **üresen** születik — a szűrt lista a feltöltő ctorába NEM injektálható (körkörös DI: a feltöltő maga is eleme az `IEnumerable`-nek). A host `Build()` után tölti fel: `host.Services.GetServices().Where(…).ToList()` → `SetWriters(…)`. +3. Keret-kiegészítés kell: `AcLoggerBase.SetWriters`/`AddWriters` (a `LogWriters` jelenleg protected). +4. Default szint: Debug — a Trace-re átsorolt per-üzenet protokoll-sorokkal (ACCORE-SBP-T-T5R2) normál üzemben csend; debugkor Detail/Trace-re engedve jön a teljes trace. + +**Ismert apró rés:** az első pár protokoll-sor (handshake, induló `StartAsync`) még az üres loggert látja — a Trace-átsorolás után gyakorlatilag irreleváns. diff --git a/AyCode.Services.Server/SignalRs/AcLoggerSignalRHub.cs b/AyCode.Services.Server/SignalRs/AcLoggerSignalRHub.cs index f02af41..a60a0ff 100644 --- a/AyCode.Services.Server/SignalRs/AcLoggerSignalRHub.cs +++ b/AyCode.Services.Server/SignalRs/AcLoggerSignalRHub.cs @@ -1,13 +1,27 @@ -using AyCode.Core.Consts; +using AyCode.Core.Consts; using AyCode.Core.Loggers; using AyCode.Entities.Server.LogItems; -using Castle.Core.Logging; using Microsoft.AspNetCore.SignalR; namespace AyCode.Services.Server.SignalRs; -public class AcLoggerSignalRHub(TLogger logger) : Hub where TLogger : IAcLoggerBase +/// +/// Back-compat: az eredeti egy-generikusos alak a szerver-oldali wire-típussal — +/// a nem-generikus (JSON-os) AcSignaRClientLogItemWriter kliensek párja. +/// +public class AcLoggerSignalRHub(TLogger logger) : AcLoggerSignalRHub(logger) + where TLogger : IAcLoggerBase; + + +/// +/// Kliens-log fogadó hub, generikus wire-típussal. A -nek meg kell egyeznie +/// a kliens-oldali AcSignaRClientLogItemWriter<TLogItem> típusparaméterével — a SignalR erre a +/// paraméter-típusra deserializál (AcBinary-nál a generált serializere fut, polymorph-dispatch nélkül). +/// +public class AcLoggerSignalRHub(TLogger logger) : Hub + where TLogger : IAcLoggerBase + where TLogItem : class, IAcLogItemClient { private TLogger _logger = logger; @@ -25,7 +39,7 @@ public class AcLoggerSignalRHub(TLogger logger) : Hub where TLogger : I await base.OnDisconnectedAsync(exception); } - public void AddLogItem(AcLogItem? logItem) + public void AddLogItem(TLogItem? logItem) { try { @@ -47,7 +61,8 @@ public class AcLoggerSignalRHub(TLogger logger) : Hub where TLogger : I } catch (Exception ex) { - Console.WriteLine($@"ERROR!!! {nameof(AcLoggerSignalRHub)}->AddLogItem; ex: {ex.Message}{AcEnv.NL}{AcEnv.NL}{ex}"); + Console.WriteLine($@"ERROR!!! {nameof(AcLoggerSignalRHub)}->AddLogItem; ex: {ex.Message}{AcEnv.NL}{AcEnv.NL}{ex}"); } } -} \ No newline at end of file +} + diff --git a/AyCode.Services/Loggers/AcSignaRClientLogItemWriter.cs b/AyCode.Services/Loggers/AcSignaRClientLogItemWriter.cs index 0c71a1e..e61de88 100644 --- a/AyCode.Services/Loggers/AcSignaRClientLogItemWriter.cs +++ b/AyCode.Services/Loggers/AcSignaRClientLogItemWriter.cs @@ -1,13 +1,33 @@ -using AyCode.Core.Enums; +using AyCode.Core.Enums; using AyCode.Core.Helpers; using AyCode.Core.Loggers; using AyCode.Entities; using AyCode.Entities.LogItems; +using AyCode.Services.SignalRs; using Microsoft.AspNetCore.SignalR.Client; namespace AyCode.Services.Loggers { - public class AcSignaRClientLogItemWriter : AcLogItemWriterBase, IAcLogWriterClientBase + /// + /// Nem-generikus, JSON-protokollos alak — back-compat a meglévő fogyasztóknak + /// (wire-típus: AcLogItemClient; a szerver-oldali párja az AcLoggerSignalRHub<TLogger> shim). + /// + public class AcSignaRClientLogItemWriter : AcSignaRClientLogItemWriter + { + public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null) + : base(fullHubName, appType, logLevel, categoryName) + { } + } + + /// + /// SignalR log-item feltöltő writer, generikus wire-típussal. A -et az + /// alkalmazás adja (pl. FruitBank: FbLogItem [AcBinarySerializable] — így a csatorna AcBinary-protokollal mehet; + /// a generált serializer az app rétegében él, az AyCode.Entities generator-mentes marad). + /// Az opcionális acBinaryProtocolOptions-szel a kapcsolat az AcBinary protokollt használja; + /// enélkül a SignalR default JSON-ja megy (back-compat). + /// + public class AcSignaRClientLogItemWriter : AcLogItemWriterBase, IAcLogWriterClientBase + where TLogItem : class, IAcLogItemClient { private readonly HubConnection _hubConnection; private readonly string _fullHubName; @@ -19,14 +39,20 @@ namespace AyCode.Services.Loggers private bool _connectedReported; private long _droppedCount; - public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null) : base(appType, logLevel, categoryName) + public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null, AcBinaryHubProtocolOptions? acBinaryProtocolOptions = null) : base(appType, logLevel, categoryName) { _fullHubName = fullHubName; - _hubConnection = new HubConnectionBuilder() - .WithUrl(fullHubName) + // VISSZACSATOLÁS-VÉDELEM: erre a builderre TILOS app-loggert kötni (AddAcDefaults / ConfigureLogging) — + // a protokoll saját logja a feltöltőn át újra ide érkezne (log → sorosítás → log → … robbanó hurok). + // AcBinary-nál ezért a hívó az opciókban EXPLICIT süket loggert adjon (pl. NullLogger.Instance), + // hogy az AddAcBinaryProtocol ??= DI-fallbackje se tölthesse fel. + var hubBuilder = new HubConnectionBuilder().WithUrl(fullHubName); + + if (acBinaryProtocolOptions != null) hubBuilder.AddAcBinaryProtocol(acBinaryProtocolOptions); + + _hubConnection = hubBuilder //.WithAutomaticReconnect() - //.AddMessagePackProtocol(options => options.SerializerOptions = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData)) .Build(); StartConnection().Forget(); @@ -67,7 +93,7 @@ namespace AyCode.Services.Loggers public override void Write(AppType appType, LogLevel logLevel, string? logText, string? callerMemberName, string? categoryName, string? errorType, string? exMessage) => WriteLogItem(CreateLogItem(DateTime.UtcNow, appType, Environment.CurrentManagedThreadId, logLevel, logText, callerMemberName, categoryName, errorType, exMessage)); - protected override async void WriteLogItemCallback(AcLogItemClient logItem) + protected override async void WriteLogItemCallback(TLogItem logItem) { // Teljes try/catch: async void — egy StartAsync/SendAsync-kivétel különben kezeletlenül szökne el. try @@ -117,4 +143,6 @@ namespace AyCode.Services.Loggers _droppedCount = 0; } } + + } diff --git a/AyCode.Services/docs/LOGGING/README.md b/AyCode.Services/docs/LOGGING/README.md index ddecf62..5fa0b90 100644 --- a/AyCode.Services/docs/LOGGING/README.md +++ b/AyCode.Services/docs/LOGGING/README.md @@ -26,12 +26,16 @@ Note: `Forget()` — fire-and-forget, no await on the HTTP response. HTTP/2 by d ## AcSignaRClientLogItemWriter -SignalR log transport writer. Extends `AcLogItemWriterBase`. Sends items to a dedicated logger hub. Manages its own `HubConnection`: +SignalR log transport writer, generic in the wire type (2026-07): `AcSignaRClientLogItemWriter : AcLogItemWriterBase` — the app supplies its own `TLogItem` (e.g. FruitBank's `LogItemClient : AcLogItemClient` with `[AcBinarySerializable]`, so the generated serializer lives in the app layer and AyCode.Entities stays generator-free). The non-generic `AcSignaRClientLogItemWriter` shim (= ``, JSON) keeps existing consumers working. Sends items to a dedicated logger hub. Manages its own `HubConnection`: ```csharp -_hubConnection.SendAsync("AddLogItem", logItem).Forget(); +await _hubConnection.SendAsync("AddLogItem", logItem); ``` +**Protocol:** default JSON; pass `AcBinaryHubProtocolOptions` in the ctor to switch the connection to AcBinary. ⚠️ **Feedback-loop guard:** the protocol's `Logger` MUST be deaf (`NullLogger.Instance`) and the writer's builder must never get `AddAcDefaults`/`ConfigureLogging` — the protocol's own logs would re-enter this uploader (log → serialize → log → … runaway loop). The server counterpart's `TLogItem` (see `AcLoggerSignalRHub`) must match the client's wire type. + +**Failure reporting:** never silent — connect failures, dropped items and recovery are reported once per failure state via `Console.Error` (a log channel cannot log its own errors through itself); a `connected; url: …` marker prints once per established connection. + **UTC conversion note:** SignalR doesn't transmit `DateTime.Kind`. The writer calls `logItem.TimeStampUtc.ToUniversalTime()` before sending to ensure the server receives actual UTC. Connection lifecycle: `StartConnection()` waits up to 10s for `Connected` state. `StopConnection()` stops and disposes the hub. @@ -57,6 +61,8 @@ AcSignaRClientLogItemWriter AcLoggerSignalRHub Note: `AcLoggerSignalRHub` overrides client's `TimeStampUtc` with `DateTime.UtcNow` server-side for authoritative timestamps. The hub is a simple `Hub` (not `AcWebSignalRHubBase`) — does NOT use tag-based dispatch. +`AcLoggerSignalRHub` (2026-07): the wire type is generic — SignalR deserializes into `TLogItem`, so it must equal the client writer's `TLogItem` (with `[AcBinarySerializable(false)]` there is no polymorph dispatch). The single-generic `AcLoggerSignalRHub` shim (= ``) remains for JSON clients. The hub also logs connection lifecycle (`Logger client connected/disconnected; ConnectionId: …`) so a dead upload channel is visible on the server console. + ## Key Source Files | Component | Path | diff --git a/AyCode.Services/docs/SIGNALR/SIGNALR_ISSUES.md b/AyCode.Services/docs/SIGNALR/SIGNALR_ISSUES.md index f0b3eb4..0c3efab 100644 --- a/AyCode.Services/docs/SIGNALR/SIGNALR_ISSUES.md +++ b/AyCode.Services/docs/SIGNALR/SIGNALR_ISSUES.md @@ -89,7 +89,7 @@ See `SIGNALR_TODO.md#accore-sig-t-m5l6`. ### Related - `../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#accore-log-i-m4c9` (sibling gap — same plugin, logger setup) - `ACCORE-SIG-I-K6F1` (client-side equivalent — `HubConnectionBuilder.Services` inner DI isolation forces a different workaround) -- Plugin doc drift: `Nop.Plugin.Misc.AIPlugin/docs/SIGNALR/README.md:22` documents `services.AddSingleton(new AcBinaryHubProtocol())` — the actual code uses `.AddAcBinaryProtocol(opts => {...})`. Doc needs a rewrite. +- Plugin doc drift: `Nop.Plugin.Misc.AIPlugin/docs/SIGNALR/README.md:22` documents `services.AddSingleton(new AcBinaryHubProtocol())` — the actual code uses `.AddAcBinaryProtocol(opts => {...})`. **Resolved 2026-07-13** — plugin-README rewritten (AddAcBinaryProtocol; json+acbinary side-by-side registration; hub list corrected). ## Client-side Setup & DI diff --git a/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md b/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md index 43415f1..20db2f8 100644 --- a/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md +++ b/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL/SIGNALR_BINARY_PROTOCOL_TODO.md @@ -8,6 +8,17 @@ --- +## ACCORE-SBP-T-T5R2: Per-üzenet diagnosztikai sorok átsorolása Trace szintre +**Priority:** P2 · **Type:** Behaviour / zajcsökkentés · **Related:** `../../../AyCode.Core/docs/LOGGING/LOGGING_TODO.md#accore-log-t-d7s3` + +**Probléma:** a protokoll soronkénti diagnosztikája túl magas szinten logol — `Serialize/Deserialize start/end` = **Info**, `WriteArgument`/`ReadSingleArgument`/`ReadArguments`/`ParseInvocation`/`TryParseMessage` = **Debug/Trace**. Következmény: a szerver-konzolt elárasztja (a loggerHub-on MINDEN felküldött log-item ~8 protokoll-sort termel; az fbHub-forgalom ugyanígy), és egy leendő kliens-oldali protokoll-diagnosztika (ACCORE-LOG-T-D7S3) default szinten spammelne. + +**Megoldás:** a per-üzenet sorok `LogTrace`-re; hiba/anomália marad Warning/Error. A `Serialize end totalSentSize=…` jellegű összegző sorok szintje mérlegelendő (Debug-on hagyva olcsó forgalmi áttekintést adnak). + +**Ellenőrzendő:** az MS→AyCode szint-megfeleltetés az `AddAcLogger` hídban — a konzol-kimenet alapján MS `Trace` jelenleg Ac `Detail(0)`-ra képződik (a TryParseMessage-sorok `[Detail]`-lel jelennek meg), az Ac-enumban pedig `Detail(0) < Trace(5)`, tehát egy Detail-szintű writer a Trace-t IS átengedi. A „csend default szinten" célhoz a fogyasztó loggerek Debug(10)+ szintje és/vagy a mapping tisztázása kell. + +**Érintett:** `AcBinaryHubProtocol` `Log*` segédmetódusai + `DebugLogArgument`. + ## ACCORE-SBP-T-Y2R8: Per-message protocol mode selection **Priority:** P2 · **Type:** Feature