This commit is contained in:
2026-01-12 22:13:42 +01:00
parent 576a590104
commit 574bbe0632
2 changed files with 36 additions and 32 deletions

View File

@@ -23,7 +23,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<EVRevisionFormat>1.5.3+{chash:10}.{c:ymd}</EVRevisionFormat> <EVRevisionFormat>1.5.4+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault> <EVDefault>true</EVDefault>
<EVInfo>true</EVInfo> <EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch> <EVTagMatch>v[0-9]*</EVTagMatch>

View File

@@ -8,9 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
namespace EonaCat.Logger.EonaCatCoreLogger namespace EonaCat.Logger.EonaCatCoreLogger
{ {
public class DiscordLogger : ILogger, IDisposable public class DiscordLogger : ILogger, IDisposable
@@ -20,9 +17,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger
private readonly LoggerScopedContext _context = new LoggerScopedContext(); private readonly LoggerScopedContext _context = new LoggerScopedContext();
// Static shared resources // Static shared resources
private static readonly ConcurrentQueue<string> _messageQueue = new ConcurrentQueue<string>();
private static readonly HttpClient _httpClient = new HttpClient(); private static readonly HttpClient _httpClient = new HttpClient();
private static readonly SemaphoreSlim _flushLock = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim _flushLock = new SemaphoreSlim(1, 1);
private static readonly ConcurrentQueue<string> _messageQueue = new ConcurrentQueue<string>();
private static bool _flushLoopStarted = false; private static bool _flushLoopStarted = false;
private static CancellationTokenSource _cts; private static CancellationTokenSource _cts;
@@ -42,8 +39,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger
{ {
_flushLoopStarted = true; _flushLoopStarted = true;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
_flushTask = Task.Factory.StartNew(() => FlushLoop(_cts.Token), _flushTask = Task.Run(() => FlushLoopAsync(_cts.Token));
_cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} }
} }
@@ -58,9 +54,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger
Exception exception, Func<TState, Exception, string> formatter) Exception exception, Func<TState, Exception, string> formatter)
{ {
if (!IsEnabled(logLevel) || formatter == null) if (!IsEnabled(logLevel) || formatter == null)
{
return; return;
}
if (IncludeCorrelationId) if (IncludeCorrelationId)
{ {
@@ -69,7 +63,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger
} }
var message = formatter(state, exception); var message = formatter(state, exception);
var logParts = new List<string> var logParts = new List<string>
{ {
$"**[{DateTime.UtcNow:u}]**", $"**[{DateTime.UtcNow:u}]**",
@@ -79,31 +72,32 @@ namespace EonaCat.Logger.EonaCatCoreLogger
}; };
foreach (var kvp in _context.GetAll()) foreach (var kvp in _context.GetAll())
{
logParts.Add($"`{kvp.Key}`: {kvp.Value}"); logParts.Add($"`{kvp.Key}`: {kvp.Value}");
}
if (exception != null) if (exception != null)
{
logParts.Add($"Exception: {exception}"); logParts.Add($"Exception: {exception}");
}
_messageQueue.Enqueue(string.Join("\n", logParts)); // Limit queue size to prevent memory growth
if (_messageQueue.Count < 1000)
_messageQueue.Enqueue(string.Join("\n", logParts));
else
OnException?.Invoke(this, new Exception("DiscordLogger queue overflow"));
} }
private void FlushLoop(CancellationToken token) private async Task FlushLoopAsync(CancellationToken token)
{ {
try try
{ {
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
Thread.Sleep(TimeSpan.FromSeconds(_options.FlushIntervalSeconds)); await Task.Delay(TimeSpan.FromSeconds(_options.FlushIntervalSeconds), token)
FlushBuffer(token).Wait(token); .ConfigureAwait(false);
await FlushBufferAsync(token).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Expected when cancelling // Expected on cancel
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -111,12 +105,10 @@ namespace EonaCat.Logger.EonaCatCoreLogger
} }
} }
private async Task FlushBuffer(CancellationToken token) private async Task FlushBufferAsync(CancellationToken token)
{ {
if (!await _flushLock.WaitAsync(0, token)) if (!await _flushLock.WaitAsync(0, token).ConfigureAwait(false))
{
return; return;
}
try try
{ {
@@ -125,14 +117,14 @@ namespace EonaCat.Logger.EonaCatCoreLogger
try try
{ {
var payload = new { content = message }; var payload = new { content = message };
using (var content = new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json")) using var content = new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_options.WebhookUrl, content, token)
.ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{ {
var response = await _httpClient.PostAsync(_options.WebhookUrl, content, token); var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!response.IsSuccessStatusCode) OnException?.Invoke(this, new Exception($"Discord webhook failed: {response.StatusCode} {error}"));
{
var error = await response.Content.ReadAsStringAsync();
OnException?.Invoke(this, new Exception($"Discord webhook failed: {response.StatusCode} {error}"));
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -152,7 +144,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger
if (_cts != null && !_cts.IsCancellationRequested) if (_cts != null && !_cts.IsCancellationRequested)
{ {
_cts.Cancel(); _cts.Cancel();
try try
{ {
_flushTask?.Wait(); _flushTask?.Wait();
@@ -161,12 +152,25 @@ namespace EonaCat.Logger.EonaCatCoreLogger
{ {
ae.Handle(e => e is OperationCanceledException); ae.Handle(e => e is OperationCanceledException);
} }
_cts.Dispose(); _cts.Dispose();
_cts = null; _cts = null;
} }
}
// Optional: call this on app shutdown
public static void Shutdown()
{
_cts?.Cancel();
try
{
_flushTask?.Wait();
}
catch (AggregateException ae)
{
ae.Handle(e => e is OperationCanceledException);
}
_flushLock.Dispose(); _flushLock.Dispose();
_httpClient.Dispose();
} }
} }
} }