diff --git a/EonaCat.Logger/DllInfo.cs b/EonaCat.Logger/DllInfo.cs index 7fa913e..723d361 100644 --- a/EonaCat.Logger/DllInfo.cs +++ b/EonaCat.Logger/DllInfo.cs @@ -7,7 +7,7 @@ namespace EonaCat.Logger; public static class DllInfo { public const string NAME = "EonaCatLogger"; - public const string VERSION = "1.1.0"; + public const string VERSION = "1.4.8"; static DllInfo() { diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index 9a87a35..3f2d3b3 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -1,9 +1,10 @@  - .netstandard2.1; net6.0; net7.0; net8.0; net4.8; + .netstandard2.1; net8.0; net4.8; icon.ico + 1.4.8 latest - 1.4.7 + 1.4.8 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -24,7 +25,7 @@ - 1.4.7+{chash:10}.{c:ymd} + 1.4.8+{chash:10}.{c:ymd} true true v[0-9]* @@ -58,10 +59,11 @@ - - - + + + + diff --git a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs index de276e4..11a60e9 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class BatchingDatabaseLogger : ILogger, IDisposable { private readonly string _categoryName; diff --git a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerOptions.cs index 3647d79..f77607b 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerOptions.cs @@ -3,6 +3,8 @@ using System.Data.Common; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class BatchingDatabaseLoggerOptions { public DbProviderFactory DbProviderFactory { get; set; } @@ -12,7 +14,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger VALUES (@Timestamp, @LogLevel, @Category, @Message, @Exception, @CorrelationId)"; public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } public TimeSpan BatchInterval { get; set; } = TimeSpan.FromSeconds(5); // 5 sec batch public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(10); // wait on shutdown diff --git a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerProvider.cs index 4258db1..f55d2b9 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerProvider.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLoggerProvider.cs @@ -2,6 +2,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class BatchingDatabaseLoggerProvider : ILoggerProvider { private readonly BatchingDatabaseLoggerOptions _options; diff --git a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs index ac2e715..8f61b0e 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs @@ -1,17 +1,25 @@ using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Data.Common; -using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { - public class DatabaseLogger : ILogger + // 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. + public class DatabaseLogger : ILogger, IDisposable { private readonly string _categoryName; private readonly DatabaseLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private static readonly ConcurrentQueue _queue = new(); + private static readonly SemaphoreSlim _flushLock = new(1, 1); + private static bool _flushLoopStarted = false; + private static CancellationTokenSource _cts; + public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -20,6 +28,13 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; IncludeCorrelationId = options.IncludeCorrelationId; + + if (!_flushLoopStarted) + { + _flushLoopStarted = true; + _cts = new CancellationTokenSource(); + Task.Run(() => FlushLoopAsync(_cts.Token)); + } } public IDisposable BeginScope(TState state) => null; @@ -33,11 +48,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger Exception exception, Func formatter) { if (!IsEnabled(logLevel) || formatter == null) - { return; - } - - var message = formatter(state, exception); if (IncludeCorrelationId) { @@ -45,33 +56,75 @@ namespace EonaCat.Logger.EonaCatCoreLogger _context.Set("CorrelationId", correlationId); } + var message = formatter(state, exception); + + var entry = new LogEntry + { + Timestamp = DateTime.UtcNow, + LogLevel = logLevel.ToString(), + Category = _categoryName, + Message = message, + Exception = exception?.ToString(), + CorrelationId = _context.Get("CorrelationId") + }; + + _queue.Enqueue(entry); + + if (_queue.Count >= _options.FlushBatchSize) + { + _ = FlushBufferAsync(); + } + } + + private async Task FlushLoopAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(_options.FlushIntervalSeconds), token); + await FlushBufferAsync(); + } + } + + private async Task FlushBufferAsync() + { + if (!await _flushLock.WaitAsync(0)) + return; // Another flush is in progress + try { - using var connection = _options.DbProviderFactory.CreateConnection(); - if (connection == null) + while (_queue.TryDequeue(out var entry)) { - throw new InvalidOperationException("Failed to create database connection."); + try + { + using var connection = _options.DbProviderFactory.CreateConnection(); + if (connection == null) + throw new InvalidOperationException("Failed to create database connection."); + + connection.ConnectionString = _options.ConnectionString; + await connection.OpenAsync(); + + using var command = connection.CreateCommand(); + command.CommandText = _options.InsertCommand; + command.Parameters.Clear(); + + command.Parameters.Add(CreateParameter(command, "Timestamp", entry.Timestamp)); + command.Parameters.Add(CreateParameter(command, "LogLevel", entry.LogLevel)); + command.Parameters.Add(CreateParameter(command, "Category", entry.Category)); + command.Parameters.Add(CreateParameter(command, "Message", entry.Message)); + command.Parameters.Add(CreateParameter(command, "Exception", entry.Exception)); + command.Parameters.Add(CreateParameter(command, "CorrelationId", entry.CorrelationId)); + + await command.ExecuteNonQueryAsync(); + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } } - - connection.ConnectionString = _options.ConnectionString; - connection.Open(); - - using var command = connection.CreateCommand(); - command.CommandText = _options.InsertCommand; - command.Parameters.Clear(); - - command.Parameters.Add(CreateParameter(command, "Timestamp", DateTime.UtcNow)); - command.Parameters.Add(CreateParameter(command, "LogLevel", logLevel.ToString())); - command.Parameters.Add(CreateParameter(command, "Category", _categoryName)); - command.Parameters.Add(CreateParameter(command, "Message", message)); - command.Parameters.Add(CreateParameter(command, "Exception", exception?.ToString())); - command.Parameters.Add(CreateParameter(command, "CorrelationId", _context.Get("CorrelationId"))); - - command.ExecuteNonQuery(); } - catch (Exception e) + finally { - OnException?.Invoke(this, e); + _flushLock.Release(); } } @@ -82,5 +135,22 @@ namespace EonaCat.Logger.EonaCatCoreLogger param.Value = value ?? DBNull.Value; return param; } + + public void Dispose() + { + _cts?.Cancel(); + _flushLock.Dispose(); + _cts?.Dispose(); + } + + private class LogEntry + { + public DateTime Timestamp { get; set; } + public string LogLevel { get; set; } + public string Category { get; set; } + public string Message { get; set; } + public string Exception { get; set; } + public string CorrelationId { get; set; } + } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs index 5a03fe4..ae3d8a9 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs @@ -2,6 +2,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class DatabaseLoggerOptions { public DbProviderFactory DbProviderFactory { get; set; } @@ -12,6 +14,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger VALUES (@Timestamp, @LogLevel, @Category, @Message, @Exception, @CorrelationId)"; public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } + public int FlushIntervalSeconds { get; set; } = 5; + public int FlushBatchSize { get; set; } = 100; } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs index 07bedcb..e33f9a1 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs @@ -2,6 +2,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class DatabaseLoggerProvider : ILoggerProvider { private readonly DatabaseLoggerOptions _options; diff --git a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs index f8b2cfb..619e904 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs @@ -1,18 +1,29 @@ -using Microsoft.Extensions.Logging; +using EonaCat.Json; +using Microsoft.Extensions.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Net.Http; using System.Text; -using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { - public class DiscordLogger : ILogger + // 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. + public class DiscordLogger : ILogger, IDisposable { private readonly string _categoryName; private readonly DiscordLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private static readonly ConcurrentQueue _messageQueue = new(); + private static readonly HttpClient _httpClient = new(); + private static readonly SemaphoreSlim _flushLock = new(1, 1); + private static bool _flushLoopStarted = false; + private static CancellationTokenSource _cts; + public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -21,6 +32,13 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; IncludeCorrelationId = options.IncludeCorrelationId; + + if (!_flushLoopStarted) + { + _flushLoopStarted = true; + _cts = new CancellationTokenSource(); + _ = Task.Run(() => FlushLoopAsync(_cts.Token)); + } } public IDisposable BeginScope(TState state) => null; @@ -34,11 +52,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger Exception exception, Func formatter) { if (!IsEnabled(logLevel) || formatter == null) - { return; - } - - var message = formatter(state, exception); if (IncludeCorrelationId) { @@ -46,6 +60,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger _context.Set("CorrelationId", correlationId); } + var message = formatter(state, exception); + var logParts = new List { $"**[{DateTime.UtcNow:u}]**", @@ -66,18 +82,56 @@ namespace EonaCat.Logger.EonaCatCoreLogger string fullMessage = string.Join("\n", logParts); + // Enqueue and return immediately - non-blocking + _messageQueue.Enqueue(fullMessage); + } + + private async Task FlushLoopAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(_options.FlushIntervalSeconds), token); + await FlushBufferAsync(); + } + } + + private async Task FlushBufferAsync() + { + if (!await _flushLock.WaitAsync(0)) + return; // Already flushing + try { - using var client = new HttpClient(); - var payload = new { content = fullMessage }; - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - - client.PostAsync(_options.WebhookUrl, content).Wait(); + while (_messageQueue.TryDequeue(out var message)) + { + try + { + var payload = new { content = message }; + var content = new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync(_options.WebhookUrl, content); + if (!response.IsSuccessStatusCode) + { + var error = await response.Content.ReadAsStringAsync(); + OnException?.Invoke(this, new Exception($"Discord webhook failed: {response.StatusCode} {error}")); + } + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } + } } - catch (Exception e) + finally { - OnException?.Invoke(this, e); + _flushLock.Release(); } } + + public void Dispose() + { + _cts?.Cancel(); + _flushLock.Dispose(); + _cts?.Dispose(); + } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs index f6077cf..3de6603 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs @@ -1,9 +1,12 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class DiscordLoggerOptions { public string WebhookUrl { get; set; } public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } + public int FlushIntervalSeconds { get; set; } = 5; } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs index 53e72d4..5b7a2b1 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs @@ -2,6 +2,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class DiscordLoggerProvider : ILoggerProvider { private readonly DiscordLoggerOptions _options; diff --git a/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs index 718eed7..07e1550 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs @@ -5,6 +5,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger { + using EonaCat.Json; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -15,6 +16,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public class ElasticSearchLogger : ILogger { + private readonly string _categoryName; private readonly ElasticSearchLoggerOptions _options; private static readonly HttpClient _httpClient = new HttpClient(); @@ -74,7 +76,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger }; // Serialize log to JSON - string json = JsonSerializer.Serialize(logDoc); + string json = JsonHelper.ToJson(logDoc); // Add to the buffer lock (_lock) diff --git a/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLoggerOptions.cs index 7f35120..ede4602 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLoggerOptions.cs @@ -19,6 +19,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger public int RetryBufferSize { get; set; } = 100; public int FlushIntervalSeconds { get; set; } = 5; public bool UseBulkInsert { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs index 04bac3c..8008735 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs @@ -141,7 +141,7 @@ public class FileLoggerOptions : BatchingLoggerOptions public bool UseDefaultMasking { get; set; } = true; /// - /// Determines if we need to include the correlation ID in the log + /// Determines if we need to include the correlation ID in the log (default: false) /// - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } \ No newline at end of file diff --git a/EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs index 27f84f0..08dd6ba 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs @@ -4,21 +4,21 @@ using System; using System.Net.Http; using System.Text; using System.Collections.Generic; - -// 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. +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class HttpLogger : ILogger { private readonly string _categoryName; private readonly HttpLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private static readonly HttpClient _client = new(); public bool IncludeCorrelationId { get; set; } - private static readonly HttpClient _client = new(); public event EventHandler OnException; public event EventHandler OnInvalidStatusCode; @@ -39,7 +39,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (!IsEnabled(logLevel)) + if (!IsEnabled(logLevel) || formatter == null) { return; } @@ -61,72 +61,72 @@ namespace EonaCat.Logger.EonaCatCoreLogger { "eventId", eventId.Id } }; - // Add full log context as a nested dictionary var contextData = _context.GetAll(); if (contextData.Count > 0) { payload["context"] = contextData; } - try + Task.Run(async () => { - var content = _options.SendAsJson - ? new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json") - : new StringContent(message); - - var result = _client.PostAsync(_options.Endpoint, content); - if (result.Result.IsSuccessStatusCode) + try { - return; + var content = _options.SendAsJson + ? new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json") + : new StringContent(message, Encoding.UTF8, "text/plain"); + + var response = await _client.PostAsync(_options.Endpoint, content); + + if (!response.IsSuccessStatusCode) + { + var statusCode = response.StatusCode; + var statusCodeMessage = statusCode switch + { + System.Net.HttpStatusCode.BadRequest => "400 Bad Request", + System.Net.HttpStatusCode.Unauthorized => "401 Unauthorized", + System.Net.HttpStatusCode.Forbidden => "403 Forbidden", + System.Net.HttpStatusCode.NotFound => "404 Not Found", + System.Net.HttpStatusCode.MethodNotAllowed => "405 Method Not Allowed", + System.Net.HttpStatusCode.NotAcceptable => "406 Not Acceptable", + System.Net.HttpStatusCode.ProxyAuthenticationRequired => "407 Proxy Authentication Required", + System.Net.HttpStatusCode.RequestTimeout => "408 Request Timeout", + System.Net.HttpStatusCode.Conflict => "409 Conflict", + System.Net.HttpStatusCode.Gone => "410 Gone", + System.Net.HttpStatusCode.LengthRequired => "411 Length Required", + System.Net.HttpStatusCode.PreconditionFailed => "412 Precondition Failed", + System.Net.HttpStatusCode.RequestEntityTooLarge => "413 Request Entity Too Large", + System.Net.HttpStatusCode.RequestUriTooLong => "414 Request URI Too Long", + System.Net.HttpStatusCode.UnsupportedMediaType => "415 Unsupported Media Type", + System.Net.HttpStatusCode.RequestedRangeNotSatisfiable => "416 Requested Range Not Satisfiable", + System.Net.HttpStatusCode.ExpectationFailed => "417 Expectation Failed", + (System.Net.HttpStatusCode)418 => "418 I'm a teapot", + (System.Net.HttpStatusCode)421 => "421 Misdirected Request", + (System.Net.HttpStatusCode)422 => "422 Unprocessable Entity", + (System.Net.HttpStatusCode)423 => "423 Locked", + (System.Net.HttpStatusCode)424 => "424 Failed Dependency", + (System.Net.HttpStatusCode)425 => "425 Too Early", + (System.Net.HttpStatusCode)426 => "426 Upgrade Required", + (System.Net.HttpStatusCode)428 => "428 Precondition Required", + (System.Net.HttpStatusCode)429 => "429 Too Many Requests", + (System.Net.HttpStatusCode)431 => "431 Request Header Fields Too Large", + (System.Net.HttpStatusCode)451 => "451 Unavailable For Legal Reasons", + System.Net.HttpStatusCode.InternalServerError => "500 Internal Server Error", + System.Net.HttpStatusCode.NotImplemented => "501 Not Implemented", + System.Net.HttpStatusCode.BadGateway => "502 Bad Gateway", + System.Net.HttpStatusCode.ServiceUnavailable => "503 Service Unavailable", + System.Net.HttpStatusCode.GatewayTimeout => "504 Gateway Timeout", + System.Net.HttpStatusCode.HttpVersionNotSupported => "505 HTTP Version Not Supported", + _ => statusCode.ToString() + }; + + OnInvalidStatusCode?.Invoke(this, statusCodeMessage); + } } - - // Handle non-success status codes - var statusCode = result.Result.StatusCode; - var statusCodeMessage = statusCode switch + catch (Exception ex) { - System.Net.HttpStatusCode.BadRequest => "400 Bad Request", - System.Net.HttpStatusCode.Unauthorized => "401 Unauthorized", - System.Net.HttpStatusCode.Forbidden => "403 Forbidden", - System.Net.HttpStatusCode.NotFound => "404 Not Found", - System.Net.HttpStatusCode.MethodNotAllowed => "405 Method Not Allowed", - System.Net.HttpStatusCode.NotAcceptable => "406 Not Acceptable", - System.Net.HttpStatusCode.ProxyAuthenticationRequired => "407 Proxy Authentication Required", - System.Net.HttpStatusCode.RequestTimeout => "408 Request Timeout", - System.Net.HttpStatusCode.Conflict => "409 Conflict", - System.Net.HttpStatusCode.Gone => "410 Gone", - System.Net.HttpStatusCode.LengthRequired => "411 Length Required", - System.Net.HttpStatusCode.PreconditionFailed => "412 Precondition Failed", - System.Net.HttpStatusCode.RequestEntityTooLarge => "413 Request Entity Too Large", - System.Net.HttpStatusCode.RequestUriTooLong => "414 Request URI Too Long", - System.Net.HttpStatusCode.UnsupportedMediaType => "415 Unsupported Media Type", - System.Net.HttpStatusCode.RequestedRangeNotSatisfiable => "416 Requested Range Not Satisfiable", - System.Net.HttpStatusCode.ExpectationFailed => "417 Expectation Failed", - (System.Net.HttpStatusCode)418 => "418 I'm a teapot", - (System.Net.HttpStatusCode)421 => "421 Misdirected Request", - (System.Net.HttpStatusCode)422 => "422 Unprocessable Entity", - (System.Net.HttpStatusCode)423 => "423 Locked", - (System.Net.HttpStatusCode)424 => "424 Failed Dependency", - (System.Net.HttpStatusCode)425 => "425 Too Early", - (System.Net.HttpStatusCode)426 => "426 Upgrade Required", - (System.Net.HttpStatusCode)428 => "428 Precondition Required", - (System.Net.HttpStatusCode)429 => "429 Too Many Requests", - (System.Net.HttpStatusCode)431 => "431 Request Header Fields Too Large", - (System.Net.HttpStatusCode)451 => "451 Unavailable For Legal Reasons", - System.Net.HttpStatusCode.InternalServerError => "500 Internal Server Error", - System.Net.HttpStatusCode.NotImplemented => "501 Not Implemented", - System.Net.HttpStatusCode.BadGateway => "502 Bad Gateway", - System.Net.HttpStatusCode.ServiceUnavailable => "503 Service Unavailable", - System.Net.HttpStatusCode.GatewayTimeout => "504 Gateway Timeout", - System.Net.HttpStatusCode.HttpVersionNotSupported => "505 HTTP Version Not Supported", - _ => statusCode.ToString() - }; - - OnInvalidStatusCode?.Invoke(this, statusCodeMessage); - } - catch (Exception e) - { - OnException?.Invoke(this, e); - } + OnException?.Invoke(this, ex); + } + }); } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs index d505b00..fc6f69a 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs @@ -12,7 +12,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public string Endpoint { get; set; } = "http://localhost:5000/logs"; public bool IsEnabled { get; set; } = true; public bool SendAsJson { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs index b39b3cf..ef25a27 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs @@ -4,18 +4,20 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; - -// 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. +using System.Threading; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class JsonFileLogger : ILogger { private readonly string _categoryName; private readonly JsonFileLoggerOptions _options; private readonly string _filePath; private readonly LoggerScopedContext _context = new(); + private static readonly SemaphoreSlim _fileLock = new(1, 1); + public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -24,8 +26,16 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; _filePath = Path.Combine(_options.LogDirectory, _options.FileName); - Directory.CreateDirectory(_options.LogDirectory); IncludeCorrelationId = options.IncludeCorrelationId; + + try + { + Directory.CreateDirectory(_options.LogDirectory); + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } } public void SetContext(string key, string value) => _context.Set(key, value); @@ -38,42 +48,52 @@ namespace EonaCat.Logger.EonaCatCoreLogger public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (!IsEnabled(logLevel)) + if (!IsEnabled(logLevel) || formatter == null) { return; } - string message = formatter(state, exception); - if (IncludeCorrelationId) - { - var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); - _context.Set("CorrelationId", correlationId); - } - - var logObject = new Dictionary - { - { "timestamp", DateTime.UtcNow }, - { "level", logLevel.ToString() }, - { "category", _categoryName }, - { "message", message }, - { "exception", exception?.ToString() }, - { "eventId", eventId.Id } - }; - - var contextData = _context.GetAll(); - if (contextData.Count > 0) - { - logObject["context"] = contextData; - } - try { + string message = formatter(state, exception); + + if (IncludeCorrelationId) + { + var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); + _context.Set("CorrelationId", correlationId); + } + + var logObject = new Dictionary + { + { "timestamp", DateTime.UtcNow }, + { "level", logLevel.ToString() }, + { "category", _categoryName }, + { "message", message }, + { "exception", exception?.ToString() }, + { "eventId", eventId.Id } + }; + + var contextData = _context.GetAll(); + if (contextData.Count > 0) + { + logObject["context"] = contextData; + } + string json = JsonHelper.ToJson(logObject); - File.AppendAllText(_filePath, json + Environment.NewLine, Encoding.UTF8); + + _fileLock.Wait(); + try + { + File.AppendAllText(_filePath, json + Environment.NewLine, Encoding.UTF8); + } + finally + { + _fileLock.Release(); + } } - catch (Exception e) + catch (Exception ex) { - OnException?.Invoke(this, e); + OnException?.Invoke(this, ex); } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs index a162304..1cd5593 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs @@ -12,7 +12,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public string LogDirectory { get; set; } = "Logs"; public string FileName { get; set; } = "log.json"; public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs index 23e7b62..de1b34f 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs @@ -1,20 +1,23 @@ -using Microsoft.Extensions.Logging; +using EonaCat.Json; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Net.Http; using System.Text; -using System.Text.Json; - -// 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. +using System.Threading.Channels; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class SlackLogger : ILogger { private readonly string _categoryName; private readonly SlackLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private readonly Channel _logChannel = Channel.CreateUnbounded(); + private readonly HttpClient _httpClient; public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -24,6 +27,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; IncludeCorrelationId = options.IncludeCorrelationId; + + _httpClient = new HttpClient(); + Task.Run(ProcessLogQueueAsync); } public IDisposable BeginScope(TState state) => null; @@ -41,46 +47,58 @@ namespace EonaCat.Logger.EonaCatCoreLogger return; } - var message = formatter(state, exception); - - if (IncludeCorrelationId) - { - var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); - _context.Set("CorrelationId", correlationId); - } - - var logParts = new List - { - $"*[{DateTime.UtcNow:u}]*", - $"*[{logLevel}]*", - $"*[{_categoryName}]*", - $"Message: {message}", - }; - - foreach (var kvp in _context.GetAll()) - { - logParts.Add($"_{kvp.Key}_: {kvp.Value}"); - } - - if (exception != null) - { - logParts.Add($"Exception: {exception}"); - } - - string fullMessage = string.Join("\n", logParts); - try { - using var client = new HttpClient(); - var payload = new { text = fullMessage }; - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); + var message = formatter(state, exception); - client.PostAsync(_options.WebhookUrl, content).Wait(); + if (IncludeCorrelationId) + { + var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); + _context.Set("CorrelationId", correlationId); + } + + var logParts = new List + { + $"*[{DateTime.UtcNow:u}]*", + $"*[{logLevel}]*", + $"*[{_categoryName}]*", + $"Message: {message}", + }; + + foreach (var kvp in _context.GetAll()) + { + logParts.Add($"_{kvp.Key}_: {kvp.Value}"); + } + + if (exception != null) + { + logParts.Add($"Exception: {exception}"); + } + + string fullMessage = string.Join("\n", logParts); + _logChannel.Writer.TryWrite(fullMessage); // non-blocking } catch (Exception e) { OnException?.Invoke(this, e); } } + + private async Task ProcessLogQueueAsync() + { + await foreach (var message in _logChannel.Reader.ReadAllAsync()) + { + try + { + var payload = new { text = message }; + var content = new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json"); + await _httpClient.PostAsync(_options.WebhookUrl, content); + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } + } + } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs index 5525b8a..d831254 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs @@ -8,6 +8,6 @@ { public string WebhookUrl { get; set; } public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs index 69828a0..d26d2e3 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs @@ -3,17 +3,21 @@ using System; using System.Collections.Generic; using System.IO; using System.Net.Sockets; - -// 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. +using System.Text; +using System.Threading.Channels; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class TcpLogger : ILogger { private readonly string _categoryName; private readonly TcpLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private readonly Channel _logChannel = Channel.CreateUnbounded(); + public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -22,6 +26,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; IncludeCorrelationId = options.IncludeCorrelationId; + + // Start background log writer + Task.Run(ProcessLogQueueAsync); } public void SetContext(string key, string value) => _context.Set(key, value); @@ -34,14 +41,12 @@ namespace EonaCat.Logger.EonaCatCoreLogger public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (!IsEnabled(logLevel)) - { - return; - } + if (!IsEnabled(logLevel)) return; try { string message = formatter(state, exception); + if (IncludeCorrelationId) { var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); @@ -73,19 +78,36 @@ namespace EonaCat.Logger.EonaCatCoreLogger string fullLog = string.Join(" | ", logParts); - using var client = new TcpClient(); - client.Connect(_options.Host, _options.Port); - using var stream = client.GetStream(); - using var writer = new StreamWriter(stream, System.Text.Encoding.UTF8) - { - AutoFlush = true - }; - writer.WriteLine(fullLog); + _logChannel.Writer.TryWrite(fullLog); } catch (Exception e) { OnException?.Invoke(this, e); } } + + private async Task ProcessLogQueueAsync() + { + await foreach (var log in _logChannel.Reader.ReadAllAsync()) + { + try + { + using var client = new TcpClient(); + await client.ConnectAsync(_options.Host, _options.Port); + + using var stream = client.GetStream(); + using var writer = new StreamWriter(stream, Encoding.UTF8) + { + AutoFlush = true + }; + + await writer.WriteLineAsync(log); + } + catch (Exception e) + { + OnException?.Invoke(this, e); + } + } + } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs index bc436ee..9356350 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs @@ -12,6 +12,6 @@ namespace EonaCat.Logger.EonaCatCoreLogger public string Host { get; set; } public int Port { get; set; } public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs index f404025..8f81304 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs @@ -1,60 +1,80 @@ -using Microsoft.Extensions.Logging; +using EonaCat.Json; +using Microsoft.Extensions.Logging; using System; using System.Net.Http; using System.Text; -using System.Text.Json; - -// 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. +using System.Threading.Channels; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class TelegramLogger : ILogger { private readonly string _categoryName; private readonly TelegramLoggerOptions _options; - private readonly HttpClient _httpClient = new HttpClient(); + private readonly HttpClient _httpClient = new(); + private readonly Channel _logChannel = Channel.CreateUnbounded(); + public event EventHandler OnException; public TelegramLogger(string categoryName, TelegramLoggerOptions options) { _categoryName = categoryName; _options = options; + + // Start background task + Task.Run(ProcessLogQueueAsync); } public IDisposable BeginScope(TState state) => null; public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled; - public async void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - if (!IsEnabled(logLevel)) + if (!IsEnabled(logLevel) || formatter == null) { return; } - var message = $"[{DateTime.UtcNow:u}] [{logLevel}] {_categoryName}: {formatter(state, exception)}"; - if (exception != null) - { - message += $"\nException: {exception}"; - } - - var url = $"https://api.telegram.org/bot{_options.BotToken}/sendMessage"; - var payload = new - { - chat_id = _options.ChatId, - text = message - }; - - var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"); - try { - await _httpClient.PostAsync(url, content); + var message = $"[{DateTime.UtcNow:u}] [{logLevel}] {_categoryName}: {formatter(state, exception)}"; + if (exception != null) + { + message += $"\nException: {exception}"; + } + + _logChannel.Writer.TryWrite(message); } catch (Exception ex) { OnException?.Invoke(this, ex); } } + + private async Task ProcessLogQueueAsync() + { + await foreach (var message in _logChannel.Reader.ReadAllAsync()) + { + try + { + var url = $"https://api.telegram.org/bot{_options.BotToken}/sendMessage"; + var payload = new + { + chat_id = _options.ChatId, + text = message + }; + + var content = new StringContent(JsonHelper.ToJson(payload), Encoding.UTF8, "application/json"); + await _httpClient.PostAsync(url, content); + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } + } + } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs index a65c99e..f161ab5 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs @@ -3,17 +3,21 @@ using System; using System.Collections.Generic; using System.Net.Sockets; using System.Text; - -// 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. +using System.Threading.Channels; +using System.Threading.Tasks; namespace EonaCat.Logger.EonaCatCoreLogger { + // 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. public class UdpLogger : ILogger { private readonly string _categoryName; private readonly UdpLoggerOptions _options; private readonly LoggerScopedContext _context = new(); + private readonly Channel _logChannel = Channel.CreateUnbounded(); + private readonly UdpClient _udpClient = new(); + public bool IncludeCorrelationId { get; set; } public event EventHandler OnException; @@ -22,6 +26,8 @@ namespace EonaCat.Logger.EonaCatCoreLogger _categoryName = categoryName; _options = options; IncludeCorrelationId = options.IncludeCorrelationId; + + Task.Run(ProcessLogQueueAsync); } public void SetContext(string key, string value) => _context.Set(key, value); @@ -40,49 +46,61 @@ namespace EonaCat.Logger.EonaCatCoreLogger return; } - string message = formatter(state, exception); - - // Get correlation ID from context or generate new one - if (IncludeCorrelationId) - { - var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); - _context.Set("CorrelationId", correlationId); - } - - var logParts = new List - { - $"[{DateTime.UtcNow:u}]", - $"[{logLevel}]", - $"[{_categoryName}]", - $"Message: {message}", - }; - - var contextData = _context.GetAll(); - if (contextData.Count > 0) - { - logParts.Add("Context:"); - foreach (var kvp in contextData) - { - logParts.Add($"{kvp.Key}: {kvp.Value}"); - } - } - - if (exception != null) - { - logParts.Add($"Exception: {exception}"); - } - - string fullMessage = string.Join(" | ", logParts); - try { - using var client = new UdpClient(); - byte[] bytes = Encoding.UTF8.GetBytes(fullMessage); - client.Send(bytes, bytes.Length, _options.Host, _options.Port); + string message = formatter(state, exception); + + if (IncludeCorrelationId) + { + var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString(); + _context.Set("CorrelationId", correlationId); + } + + var logParts = new List + { + $"[{DateTime.UtcNow:u}]", + $"[{logLevel}]", + $"[{_categoryName}]", + $"Message: {message}", + }; + + var contextData = _context.GetAll(); + if (contextData.Count > 0) + { + logParts.Add("Context:"); + foreach (var kvp in contextData) + { + logParts.Add($"{kvp.Key}: {kvp.Value}"); + } + } + + if (exception != null) + { + logParts.Add($"Exception: {exception}"); + } + + string fullMessage = string.Join(" | ", logParts); + _logChannel.Writer.TryWrite(fullMessage); } - catch (Exception e) + catch (Exception ex) { - OnException?.Invoke(this, e); + OnException?.Invoke(this, ex); + } + } + + private async Task ProcessLogQueueAsync() + { + await foreach (var message in _logChannel.Reader.ReadAllAsync()) + { + try + { + byte[] bytes = Encoding.UTF8.GetBytes(message); + await _udpClient.SendAsync(bytes, bytes.Length, _options.Host, _options.Port); + } + catch (Exception ex) + { + OnException?.Invoke(this, ex); + } } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs index 29491fb..9dff491 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs @@ -14,7 +14,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public int Port { get; set; } = 514; public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs index 0af6283..27018d9 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs @@ -1,32 +1,39 @@ using Microsoft.Extensions.Logging; using System; using System.IO; +using System.Threading; using System.Xml.Linq; -// 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 { + // 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. public class XmlFileLogger : ILogger { private readonly string _categoryName; private readonly XmlFileLoggerOptions _options; private readonly string _filePath; private readonly LoggerScopedContext _context = new(); + private static readonly SemaphoreSlim _fileLock = new(1, 1); public bool IncludeCorrelationId { get; set; } - public event EventHandler OnException; - public XmlFileLogger(string categoryName, XmlFileLoggerOptions options) { _categoryName = categoryName; _options = options; _filePath = Path.Combine(_options.LogDirectory, _options.FileName); - Directory.CreateDirectory(_options.LogDirectory); IncludeCorrelationId = options.IncludeCorrelationId; + + try + { + Directory.CreateDirectory(_options.LogDirectory); + } + catch (Exception e) + { + OnException?.Invoke(this, e); + } } public void SetContext(string key, string value) => _context.Set(key, value); @@ -41,9 +48,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger Exception exception, Func formatter) { if (!IsEnabled(logLevel) || formatter == null) - { return; - } try { @@ -76,7 +81,15 @@ namespace EonaCat.Logger.EonaCatCoreLogger logElement.Add(new XElement("exception", exception.ToString())); } - File.AppendAllText(_filePath, logElement.ToString() + Environment.NewLine); + _fileLock.Wait(); + try + { + File.AppendAllText(_filePath, logElement + Environment.NewLine); + } + finally + { + _fileLock.Release(); + } } catch (Exception e) { diff --git a/EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs index 6cb2940..5e8e792 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs @@ -12,7 +12,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger public string LogDirectory { get; set; } = "Logs"; public string FileName { get; set; } = "log.xml"; public bool IsEnabled { get; set; } = true; - public bool IncludeCorrelationId { get; set; } = true; + public bool IncludeCorrelationId { get; set; } } } diff --git a/Testers/EonaCat.Logger.Test.Web/EonaCat.Logger.Test.Web.csproj b/Testers/EonaCat.Logger.Test.Web/EonaCat.Logger.Test.Web.csproj index 2f93c25..1bb9026 100644 --- a/Testers/EonaCat.Logger.Test.Web/EonaCat.Logger.Test.Web.csproj +++ b/Testers/EonaCat.Logger.Test.Web/EonaCat.Logger.Test.Web.csproj @@ -8,7 +8,7 @@ - + diff --git a/Testers/EonaCat.Logger.Test.Web/Program.cs b/Testers/EonaCat.Logger.Test.Web/Program.cs index 90c9764..cbb5f8a 100644 --- a/Testers/EonaCat.Logger.Test.Web/Program.cs +++ b/Testers/EonaCat.Logger.Test.Web/Program.cs @@ -53,6 +53,18 @@ options.UseMask = true; builder.Logging.AddEonaCatFileLogger(fileLoggerOptions: options, filenamePrefix: "web"); builder.Logging.AddEonaCatConsoleLogger(); +TcpLoggerOptions tcpLoggerOptions = new TcpLoggerOptions +{ + Host = "192.168.1.1", + Port = 12345, +}; + +builder.Logging.AddEonaCatTcpLogger((tcpLoggerOptions) => +{ + tcpLoggerOptions.Port = 12345; + tcpLoggerOptions.Host = "192.168.1.1"; +}); + builder.Services.AddRazorPages(); //var rateLimitOptions = new RateLimitOptions