AyCode.Core/AyCode.Services/Loggers/AcSignaRClientLogItemWriter.cs

121 lines
5.6 KiB
C#

using AyCode.Core.Enums;
using AyCode.Core.Helpers;
using AyCode.Core.Loggers;
using AyCode.Entities;
using AyCode.Entities.LogItems;
using Microsoft.AspNetCore.SignalR.Client;
namespace AyCode.Services.Loggers
{
public class AcSignaRClientLogItemWriter : AcLogItemWriterBase<AcLogItemClient>, IAcLogWriterClientBase
{
private readonly HubConnection _hubConnection;
private readonly string _fullHubName;
// A log-csatorna a SAJÁT hibáit nem logolhatja önmagán át (rekurzió) → Console.Error.
// 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.)
private bool _failureReported;
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)
{
_fullHubName = fullHubName;
_hubConnection = new HubConnectionBuilder()
.WithUrl(fullHubName)
//.WithAutomaticReconnect()
//.AddMessagePackProtocol(options => options.SerializerOptions = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData))
.Build();
StartConnection().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()
{
try
{
if (_hubConnection.State == HubConnectionState.Disconnected)
await _hubConnection.StartAsync();
if (_hubConnection.State != HubConnectionState.Connected)
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()
{
await _hubConnection.StopAsync();
await _hubConnection.DisposeAsync();
}
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)
{
// Teljes try/catch: async void — egy StartAsync/SendAsync-kivétel különben kezeletlenül szökne el.
try
{
//Ez fontos, mert a signalR nem küldi a DateTime.Kind-ot! A szeró oldalon kap egy utc DateTime-ot, ami a kliens lokális DateTime-ját tartalmazza! - J.
logItem.TimeStampUtc = logItem.TimeStampUtc.ToUniversalTime();
await StartConnection();
if (_hubConnection.State != HubConnectionState.Connected)
{
_droppedCount++;
ReportFailureOnce($"connection not available ({_hubConnection.State}), log item DROPPED", null);
return;
}
//var refreshToken = Setting.UserBasicDetails?.RefreshToken ?? Guid.NewGuid().ToString("N");
await _hubConnection.SendAsync("AddLogItem", /*refreshToken,*/ logItem);
ReportRecoveryIfNeeded();
}
catch (Exception ex)
{
_droppedCount++;
ReportFailureOnce("send failed, log item DROPPED", ex);
}
}
/// <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)
{
_connectedReported = false; // a következő sikeres felépülés újra jelentkezhessen
if (_failureReported) return;
_failureReported = true;
Console.Error.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] {message}; url: {_fullHubName}; droppedCount: {_droppedCount}{(ex == null ? string.Empty : $"; ex: {ex.GetType().Name}: {ex.Message}")}");
}
/// <summary>Sikeres küldésnél: ha hiba-állapotból jöttünk vissza, jelentjük a kiesés alatt elveszett logok számát.</summary>
private void ReportRecoveryIfNeeded()
{
if (!_failureReported) return;
_failureReported = false;
Console.Error.WriteLine($"[{nameof(AcSignaRClientLogItemWriter)}] connection recovered; url: {_fullHubName}; DROPPED items while down: {_droppedCount}");
_droppedCount = 0;
}
}
}