diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index 1ef5afb..87ded70 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -23,7 +23,7 @@ - 1.5.3+{chash:10}.{c:ymd} + 1.5.4+{chash:10}.{c:ymd} true true v[0-9]* diff --git a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs index 8c0030a..1766667 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs @@ -8,9 +8,6 @@ using System.Text; using System.Threading; 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 { public class DiscordLogger : ILogger, IDisposable @@ -20,9 +17,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger private readonly LoggerScopedContext _context = new LoggerScopedContext(); // Static shared resources - private static readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); private static readonly HttpClient _httpClient = new HttpClient(); private static readonly SemaphoreSlim _flushLock = new SemaphoreSlim(1, 1); + private static readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); private static bool _flushLoopStarted = false; private static CancellationTokenSource _cts; @@ -42,8 +39,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger { _flushLoopStarted = true; _cts = new CancellationTokenSource(); - _flushTask = Task.Factory.StartNew(() => FlushLoop(_cts.Token), - _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + _flushTask = Task.Run(() => FlushLoopAsync(_cts.Token)); } } @@ -58,9 +54,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger Exception exception, Func formatter) { if (!IsEnabled(logLevel) || formatter == null) - { return; - } if (IncludeCorrelationId) { @@ -69,7 +63,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger } var message = formatter(state, exception); - var logParts = new List { $"**[{DateTime.UtcNow:u}]**", @@ -79,31 +72,32 @@ namespace EonaCat.Logger.EonaCatCoreLogger }; foreach (var kvp in _context.GetAll()) - { logParts.Add($"`{kvp.Key}`: {kvp.Value}"); - } if (exception != null) - { 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 { while (!token.IsCancellationRequested) { - Thread.Sleep(TimeSpan.FromSeconds(_options.FlushIntervalSeconds)); - FlushBuffer(token).Wait(token); + await Task.Delay(TimeSpan.FromSeconds(_options.FlushIntervalSeconds), token) + .ConfigureAwait(false); + await FlushBufferAsync(token).ConfigureAwait(false); } } catch (OperationCanceledException) { - // Expected when cancelling + // Expected on cancel } 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; - } try { @@ -125,14 +117,14 @@ namespace EonaCat.Logger.EonaCatCoreLogger try { 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); - if (!response.IsSuccessStatusCode) - { - var error = await response.Content.ReadAsStringAsync(); - OnException?.Invoke(this, new Exception($"Discord webhook failed: {response.StatusCode} {error}")); - } + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + OnException?.Invoke(this, new Exception($"Discord webhook failed: {response.StatusCode} {error}")); } } catch (Exception ex) @@ -152,7 +144,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger if (_cts != null && !_cts.IsCancellationRequested) { _cts.Cancel(); - try { _flushTask?.Wait(); @@ -161,12 +152,25 @@ namespace EonaCat.Logger.EonaCatCoreLogger { ae.Handle(e => e is OperationCanceledException); } - _cts.Dispose(); _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(); + _httpClient.Dispose(); } } }