Compare commits

...

4 Commits

Author SHA1 Message Date
Loretta 41bd9f0eb5 Clarify SignalR log writer docs and comments
Updated docs and inline comments to clarify that the generic `AcSignaRClientLogItemWriter<TLogItem>` expects the consumer to provide the log item type, typically with `[AcBinarySerializable]`. Improved Hungarian explanations for the diagnostic logger and filtering logic. Refined descriptions in `LOGGING_TODO.md`, `SIGNALR_BINARY_PROTOCOL_TODO.md`, and `README.md` for accuracy and reduced ambiguity. No functional code changes.
2026-07-13 08:56:51 +02:00
Loretta a6bd024d17 Generic SignalR logger hub and writer, doc updates
Introduced generic `AcLoggerSignalRHub<TLogger, TLogItem>` and `AcSignaRClientLogItemWriter<TLogItem>` 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.
2026-07-13 08:38:17 +02:00
Loretta 2bb9e3a77e Improve debug logging with caller context in AcBinaryHubProtocol
Diagnostic logging methods now use [CallerMemberName] to pass the actual caller's name to the logger via EventId.Name, ensuring logs reflect the true call site. Log message formats were simplified by removing explicit class prefixes. Comments clarify the rationale, and all internal calls were updated to the new signatures.
2026-07-13 07:01:19 +02:00
Loretta f2c636bb1c Improve logger caller resolution and SignalR log reporting
Added ResolveBridgeCallerNames to AcLoggerBase for accurate caller detection in ILogger bridge logs using stack trace analysis. Updated debug logging to include caller info via EventId. Enhanced AcLoggerSignalRHub and AcSignaRClientLogItemWriter to log connection lifecycle events and improve connection state reporting. Updated documentation to reflect these changes.
2026-07-12 19:41:11 +02:00
9 changed files with 199 additions and 45 deletions

View File

@ -27,6 +27,13 @@ public abstract class AcLoggerBase : IAcLoggerBase
/// </summary> /// </summary>
public bool ShortenCategoryNames { get; set; } = true; public bool ShortenCategoryNames { get; set; } = true;
/// <summary>
/// If true, ILogger bridge calls without an EventId name resolve the real caller from the stack trace
/// (async state machines unwrapped) instead of showing "Log". Costs a few µs per enabled bridge log;
/// named AC methods (Debug/Info/...) are unaffected. Set false to restore the plain "Log" fallback.
/// </summary>
public bool ResolveBridgeCallerNames { get; set; } = true;
protected AcLoggerBase() : this(null) protected AcLoggerBase() : this(null)
{ {
} }
@ -207,8 +214,11 @@ public abstract class AcLoggerBase : IAcLoggerBase
var fullMessage = message; var fullMessage = message;
var category = ShortenCategoryNames ? GetShortCategoryName(CategoryName) : CategoryName; var category = ShortenCategoryNames ? GetShortCategoryName(CategoryName) : CategoryName;
// Use eventId.Name as memberName if available, otherwise null (will show as empty, not "Log") // Explicit EventId name wins; otherwise resolve the real caller from the stack (opt-out: ResolveBridgeCallerNames).
var memberName = !string.IsNullOrEmpty(eventId.Name) ? $"{eventId.Name}:{eventId.Id}" : "Log"; // EventId.Id 0 means "no id" — show the bare name without the ":0" suffix.
var memberName = !string.IsNullOrEmpty(eventId.Name)
? eventId.Id != 0 ? $"{eventId.Name}:{eventId.Id}" : eventId.Name
: ResolveBridgeCallerNames ? ResolveCallerFromStackTrace() : "Log";
switch (logLevel) switch (logLevel)
{ {
@ -249,6 +259,48 @@ public abstract class AcLoggerBase : IAcLoggerBase
return lastDot >= 0 ? categoryName[(lastDot + 1)..] : categoryName; return lastDot >= 0 ? categoryName[(lastDot + 1)..] : categoryName;
} }
/// <summary>
/// Finds the first stack frame outside the logging infrastructure so ILogger bridge calls
/// (LogDebug/LogInformation/... extensions — these cannot carry CallerMemberName) still show
/// the real caller. Async state machines and lambdas are unwrapped (&lt;Method&gt;d__N.MoveNext -> Method).
/// Only runs when the level is enabled and no EventId name was provided.
/// </summary>
private static string ResolveCallerFromStackTrace()
{
var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: false);
var frameCount = Math.Min(stackTrace.FrameCount, 15);
for (var i = 0; i < frameCount; i++)
{
var method = stackTrace.GetFrame(i)?.GetMethod();
var declaringType = method?.DeclaringType;
if (method == null || declaringType == null) continue;
if (declaringType.Namespace?.StartsWith("Microsoft.Extensions.Logging", StringComparison.Ordinal) == true) continue;
if (typeof(AcLoggerBase).IsAssignableFrom(declaringType)) continue;
// Async state machine: compiler-generated "<Method>d__N" type, method is MoveNext.
var typeName = declaringType.Name;
if (typeName.Length > 2 && typeName[0] == '<')
{
var end = typeName.IndexOf('>');
if (end > 1) return typeName[1..end];
}
// Lambda / local function: "<Caller>b__0"-style method name — unwrap to the containing method.
var methodName = method.Name;
if (methodName.Length > 2 && methodName[0] == '<')
{
var end = methodName.IndexOf('>');
if (end > 1) return methodName[1..end];
}
return methodName;
}
return "Log";
}
/// <summary> /// <summary>
/// Maps Microsoft.Extensions.Logging.LogLevel to AyCode.Core.Loggers.LogLevel. /// Maps Microsoft.Extensions.Logging.LogLevel to AyCode.Core.Loggers.LogLevel.
/// </summary> /// </summary>

View File

@ -121,3 +121,18 @@ services.AddAcLoggerFactory<Logger>();
### Why this is a separate TODO from ACCORE-LOG-T-R7L3 ### 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-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: A SignalR log-feltöltő writer diagnosztikai loggere (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 (a hívó süket loggert — `NullLogger.Instance` — ad a writer kapcsolat-protokolljának) 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 mély diagnosztikai trace néma.
**Cél (ha bevezetjük):** az `AcSignaRClientLogItemWriter<TLogItem>` kapcsolat-diagnosztikája süket logger helyett olyan loggert kapjon, amiből a feltöltő writer **strukturálisan ki van szűrve** — a visszacsatolási hurok (log → sorosítás → diagnosztika-log → feltöltés → …) bekötése így lehetetlen marad, a diagnosztika viszont a host TÖBBI writerére (konzol, fájl, DB — bármi regisztrált) 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<TLogItem>`-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<IAcLogWriterClientBase>`-nek). A host `Build()` után tölti fel: `GetServices<IAcLogWriterClientBase>().Where(…).ToList()``SetWriters(…)`.
3. Keret-kiegészítés kell: `AcLoggerBase.SetWriters`/`AddWriters` (a `LogWriters` jelenleg protected).
4. Default szint: Debug — a soronkénti trace zajszintje protokoll-oldali kérdés, ld. ACCORE-SBP-T-T5R2.
**Ismert apró rés:** a kapcsolat-felépítés első diagnosztika-sorai még az üres loggert látják — elhanyagolható.

File diff suppressed because one or more lines are too long

View File

@ -1,17 +1,45 @@
using AyCode.Core.Consts; using AyCode.Core.Consts;
using AyCode.Core.Loggers; using AyCode.Core.Loggers;
using AyCode.Entities.Server.LogItems; using AyCode.Entities.Server.LogItems;
using Castle.Core.Logging;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
namespace AyCode.Services.Server.SignalRs; namespace AyCode.Services.Server.SignalRs;
public class AcLoggerSignalRHub<TLogger>(TLogger logger) : Hub where TLogger : IAcLoggerBase /// <summary>
/// Back-compat: az eredeti egy-generikusos alak a szerver-oldali <see cref="AcLogItem"/> wire-típussal —
/// a nem-generikus (JSON-os) <c>AcSignaRClientLogItemWriter</c> kliensek párja.
/// </summary>
public class AcLoggerSignalRHub<TLogger>(TLogger logger) : AcLoggerSignalRHub<TLogger, AcLogItem>(logger)
where TLogger : IAcLoggerBase;
/// <summary>
/// Kliens-log fogadó hub, generikus wire-típussal. A <typeparamref name="TLogItem"/>-nek meg kell egyeznie
/// a kliens-oldali <c>AcSignaRClientLogItemWriter&lt;TLogItem&gt;</c> 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).
/// </summary>
public class AcLoggerSignalRHub<TLogger, TLogItem>(TLogger logger) : Hub
where TLogger : IAcLoggerBase
where TLogItem : class, IAcLogItemClient
{ {
private TLogger _logger = logger; private TLogger _logger = logger;
public void AddLogItem(AcLogItem? logItem) // Kapcsolat-életciklus markerek: enélkül a szerver-konzolból nem látszik, hogy a kliens-log csatorna
// egyáltalán felépült-e (a néma loggerHub a "sosem csatlakozott" és a "csatlakozott, de nem küld" esetben ugyanúgy néz ki).
public override async Task OnConnectedAsync()
{
_logger.Info($"Logger client connected; ConnectionId: {Context.ConnectionId}");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_logger.Info($"Logger client disconnected; ConnectionId: {Context.ConnectionId}{(exception == null ? string.Empty : $"; ex: {exception.GetType().Name}: {exception.Message}")}");
await base.OnDisconnectedAsync(exception);
}
public void AddLogItem(TLogItem? logItem)
{ {
try try
{ {
@ -33,7 +61,8 @@ public class AcLoggerSignalRHub<TLogger>(TLogger logger) : Hub where TLogger : I
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($@"ERROR!!! {nameof(AcLoggerSignalRHub<TLogger>)}->AddLogItem; ex: {ex.Message}{AcEnv.NL}{AcEnv.NL}{ex}"); Console.WriteLine($@"ERROR!!! {nameof(AcLoggerSignalRHub<TLogger, TLogItem>)}->AddLogItem; ex: {ex.Message}{AcEnv.NL}{AcEnv.NL}{ex}");
} }
} }
} }

View File

@ -1,13 +1,33 @@
using AyCode.Core.Enums; using AyCode.Core.Enums;
using AyCode.Core.Helpers; using AyCode.Core.Helpers;
using AyCode.Core.Loggers; using AyCode.Core.Loggers;
using AyCode.Entities; using AyCode.Entities;
using AyCode.Entities.LogItems; using AyCode.Entities.LogItems;
using AyCode.Services.SignalRs;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
namespace AyCode.Services.Loggers namespace AyCode.Services.Loggers
{ {
public class AcSignaRClientLogItemWriter : AcLogItemWriterBase<AcLogItemClient>, IAcLogWriterClientBase /// <summary>
/// Nem-generikus, JSON-protokollos alak — back-compat a meglévő fogyasztóknak
/// (wire-típus: AcLogItemClient; a szerver-oldali párja az AcLoggerSignalRHub&lt;TLogger&gt; shim).
/// </summary>
public class AcSignaRClientLogItemWriter : AcSignaRClientLogItemWriter<AcLogItemClient>
{
public AcSignaRClientLogItemWriter(string fullHubName, AppType appType = AppType.Web, LogLevel logLevel = LogLevel.Detail, string? categoryName = null)
: base(fullHubName, appType, logLevel, categoryName)
{ }
}
/// <summary>
/// SignalR log-item feltöltő writer, generikus wire-típussal. A <typeparamref name="TLogItem"/>-et a fogyasztó
/// adja (tipikusan egy [AcBinarySerializable]-lel jelölt AcLogItemClient-leszármazott — így a csatorna
/// AcBinary-protokollal mehet; a generált serializer a fogyasztó 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).
/// </summary>
public class AcSignaRClientLogItemWriter<TLogItem> : AcLogItemWriterBase<TLogItem>, IAcLogWriterClientBase
where TLogItem : class, IAcLogItemClient
{ {
private readonly HubConnection _hubConnection; private readonly HubConnection _hubConnection;
private readonly string _fullHubName; private readonly string _fullHubName;
@ -16,29 +36,52 @@ namespace AyCode.Services.Loggers
// Spam-védelem: hiba-állapotonként EGY jelentés; sikeres küldés után a kapu újraélesedik, és // Spam-védelem: hiba-állapotonként EGY jelentés; sikeres küldés után a kapu újraélesedik, és
// helyreálláskor kiírjuk, hány log-item veszett el. (Egyszerű mezők — a torz dupla-jelentés ártalmatlan.) // helyreálláskor kiírjuk, hány log-item veszett el. (Egyszerű mezők — a torz dupla-jelentés ártalmatlan.)
private bool _failureReported; private bool _failureReported;
private bool _connectedReported;
private long _droppedCount; 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; _fullHubName = fullHubName;
_hubConnection = new HubConnectionBuilder() // VISSZACSATOLÁS-VÉDELEM: erre a builderre TILOS app-loggert kötni (AddAcDefaults / ConfigureLogging) —
.WithUrl(fullHubName) // 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() //.WithAutomaticReconnect()
//.AddMessagePackProtocol(options => options.SerializerOptions = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData))
.Build(); .Build();
// A .Forget() logger nélkül elnyelné az induló kapcsolat hibáját → jelentő wrapper. StartConnection().Forget();
StartConnectionReportedAsync().Forget();
} }
/// <summary>NEM dob: a kapcsolódási hibát maga jelenti (ReportFailureOnce) — a hívók (ctor fire-and-forget,
/// WriteLogItemCallback) a State-ből látják az eredményt.</summary>
public async Task StartConnection() public async Task StartConnection()
{
try
{ {
if (_hubConnection.State == HubConnectionState.Disconnected) if (_hubConnection.State == HubConnectionState.Disconnected)
await _hubConnection.StartAsync(); await _hubConnection.StartAsync();
if (_hubConnection.State != HubConnectionState.Connected) if (_hubConnection.State != HubConnectionState.Connected)
await TaskHelper.WaitToAsync(() => _hubConnection.State == HubConnectionState.Connected, 10000, 10, 25); await TaskHelper.WaitToAsync(() => _hubConnection.State == HubConnectionState.Connected, 10000, 10, 25);
// Felépülés-marker (kapcsolatonként egyszer) — enélkül a „minden csendben OK" és a „soha el sem indult"
// megkülönböztethetetlen a konzolból.
if (_hubConnection.State == HubConnectionState.Connected && !_connectedReported)
{
_connectedReported = true;
Console.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] connected; url: {_fullHubName}");
}
}
catch (Exception ex)
{
ReportFailureOnce("connect failed", ex);
}
} }
public async Task StopConnection() public async Task StopConnection()
@ -50,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) 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)); => 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. // Teljes try/catch: async void — egy StartAsync/SendAsync-kivétel különben kezeletlenül szökne el.
try try
@ -79,15 +122,11 @@ namespace AyCode.Services.Loggers
} }
} }
private async Task StartConnectionReportedAsync()
{
try { await StartConnection(); }
catch (Exception ex) { ReportFailureOnce("initial connect failed", ex); }
}
/// <summary>Hiba-állapotonként EGYSZER ír a Console.Error-ra (nem spamel) — a következő sikeres küldés élesíti újra.</summary> /// <summary>Hiba-állapotonként EGYSZER ír a Console.Error-ra (nem spamel) — a következő sikeres küldés élesíti újra.</summary>
private void ReportFailureOnce(string message, Exception? ex) private void ReportFailureOnce(string message, Exception? ex)
{ {
_connectedReported = false; // a következő sikeres felépülés újra jelentkezhessen
if (_failureReported) return; if (_failureReported) return;
_failureReported = true; _failureReported = true;
@ -104,4 +143,6 @@ namespace AyCode.Services.Loggers
_droppedCount = 0; _droppedCount = 0;
} }
} }
} }

View File

@ -762,30 +762,32 @@ public class AcBinaryHubProtocol : IHubProtocol
[Obsolete("Use ILogger via constructor parameter instead. This property will be removed in a future version.")] [Obsolete("Use ILogger via constructor parameter instead. This property will be removed in a future version.")]
public static Action<string>? DiagnosticLogger { get; set; } public static Action<string>? DiagnosticLogger { get; set; }
// A caller-t EventId.Name-ként adjuk tovább (az AyCode logger onnan veszi a CallerName-et) — enélkül
// ez a wrapper saját magát bélyegzi be (pl. [->LogDiagnostic]) a valódi hívó helyett. Ld. DebugLogArgument.
[Conditional("DEBUG")] [Conditional("DEBUG")]
private void LogDiagnostic(string message) => _logger?.LogDebug(message); private void LogDiagnostic(string message, [CallerMemberName] string? caller = null) => _logger?.LogDebug(new EventId(0, caller), message);
[Conditional("DEBUG")] [Conditional("DEBUG")]
private void LogReadSingleArgument(ReadOnlySequence<byte> argSlice, int argLength, Type targetType) private void LogReadSingleArgument(ReadOnlySequence<byte> argSlice, int argLength, Type targetType, [CallerMemberName] string? caller = null)
{ {
if (_logger == null || !_logger.IsEnabled(LogLevel.Debug)) return; if (_logger == null || !_logger.IsEnabled(LogLevel.Debug)) return;
var segmentCount = 0; var segmentCount = 0;
foreach (var _ in argSlice) segmentCount++; foreach (var _ in argSlice) segmentCount++;
_logger.LogDebug("[AcBinaryHubProtocol] ReadSingleArgument: argLength={ArgLength}, isSingleSegment={IsSingleSegment}, segments={SegmentCount}, type={TypeName}", _logger.LogDebug(new EventId(0, caller), "ReadSingleArgument: argLength={ArgLength}, isSingleSegment={IsSingleSegment}, segments={SegmentCount}, type={TypeName}",
argLength, argSlice.IsSingleSegment, segmentCount, targetType.Name); argLength, argSlice.IsSingleSegment, segmentCount, targetType.Name);
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
private void LogParseInvocation(string target, IReadOnlyList<Type> paramTypes, long remaining) private void LogParseInvocation(string target, IReadOnlyList<Type> paramTypes, long remaining, [CallerMemberName] string? caller = null)
{ {
if (_logger == null || !_logger.IsEnabled(LogLevel.Debug)) return; if (_logger == null || !_logger.IsEnabled(LogLevel.Debug)) return;
var typeNames = new string[paramTypes.Count]; var typeNames = new string[paramTypes.Count];
for (var i = 0; i < paramTypes.Count; i++) typeNames[i] = paramTypes[i].Name; for (var i = 0; i < paramTypes.Count; i++) typeNames[i] = paramTypes[i].Name;
_logger.LogDebug("[AcBinaryHubProtocol] ParseInvocation target='{Target}'; paramTypes.Count={ParamCount}; types=[{Types}]; remaining={Remaining}", _logger.LogDebug(new EventId(0, caller), "ParseInvocation target='{Target}'; paramTypes.Count={ParamCount}; types=[{Types}]; remaining={Remaining}",
target, paramTypes.Count, string.Join(", ", typeNames), remaining); target, paramTypes.Count, string.Join(", ", typeNames), remaining);
} }
@ -1383,7 +1385,7 @@ public class AcBinaryHubProtocol : IHubProtocol
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
protected void DebugLogArgument(Type runtimeType, int argBytes, object? value) protected void DebugLogArgument(Type runtimeType, int argBytes, object? value, [CallerMemberName] string? caller = null)
{ {
var kind = value switch var kind = value switch
{ {
@ -1393,22 +1395,20 @@ public class AcBinaryHubProtocol : IHubProtocol
_ => "scalar" _ => "scalar"
}; };
_logger?.LogDebug("WriteArgument runtimeType={RuntimeType} argBytes={ArgBytes} valueIsNull={ValueIsNull} valueTypeKind={Kind}", runtimeType.FullName, argBytes, value == null, kind); _logger?.LogDebug(new EventId(0, caller), "WriteArgument runtimeType={RuntimeType} argBytes={ArgBytes} valueIsNull={ValueIsNull} valueTypeKind={Kind}", runtimeType.FullName, argBytes, value == null, kind);
Console.WriteLine($"[DEBUG] WriteArgument runtimeType={runtimeType.FullName} argBytes={argBytes} valueIsNull={value == null} kind={kind}");
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
protected void DebugLogArgument(Type targetType, int argLength, long remaining) protected void DebugLogArgument(Type targetType, int argLength, long remaining, [CallerMemberName] string? caller = null)
{ {
_logger?.LogDebug("ReadSingleArgument targetType={TargetType} argLength={ArgLength} remaining={Remaining}", targetType.FullName, argLength, remaining); _logger?.LogDebug(new EventId(0, caller), "ReadSingleArgument targetType={TargetType} argLength={ArgLength} remaining={Remaining}", targetType.FullName, argLength, remaining);
Console.WriteLine($"[DEBUG] ReadSingleArgument targetType={targetType.FullName} argLength={argLength} remaining={remaining}");
} }
private object?[] ReadArguments(ref SequenceReader<byte> r, IReadOnlyList<Type> paramTypes, object? headerContext) private object?[] ReadArguments(ref SequenceReader<byte> r, IReadOnlyList<Type> paramTypes, object? headerContext)
{ {
var count = (int)ReadVarUInt(ref r); var count = (int)ReadVarUInt(ref r);
LogDiagnostic($"[AcBinaryHubProtocol] ReadArguments count={count}; remaining={r.Remaining}"); LogDiagnostic($"ReadArguments count={count}; remaining={r.Remaining}");
var args = new object?[count]; var args = new object?[count];
@ -1416,7 +1416,7 @@ public class AcBinaryHubProtocol : IHubProtocol
{ {
var targetType = i < paramTypes.Count ? paramTypes[i] : typeof(object); var targetType = i < paramTypes.Count ? paramTypes[i] : typeof(object);
LogDiagnostic($"[AcBinaryHubProtocol] arg[{i}] targetType={targetType.Name}; remaining={r.Remaining}"); LogDiagnostic($"arg[{i}] targetType={targetType.Name}; remaining={r.Remaining}");
args[i] = ReadSingleArgument(ref r, targetType, headerContext); args[i] = ReadSingleArgument(ref r, targetType, headerContext);
OnArgumentRead(args[i], i); OnArgumentRead(args[i], i);

File diff suppressed because one or more lines are too long

View File

@ -89,7 +89,7 @@ See `SIGNALR_TODO.md#accore-sig-t-m5l6`.
### Related ### Related
- `../../../AyCode.Core/docs/LOGGING/LOGGING_ISSUES.md#accore-log-i-m4c9` (sibling gap — same plugin, logger setup) - `../../../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) - `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<IHubProtocol>(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<IHubProtocol>(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 ## Client-side Setup & DI

View File

@ -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: minden hub-üzenet ~48 diagnosztika-sort termel a protokoll-loggerrel rendelkező oldalon — nagy forgalomnál konzol-áradat, és egy mindig-bekötött diagnosztikai logger (ld. 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 ## ACCORE-SBP-T-Y2R8: Per-message protocol mode selection
**Priority:** P2 · **Type:** Feature **Priority:** P2 · **Type:** Feature