Added additional providers and correlationId (by using a LogContext)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<TargetFrameworks>.netstandard2.1; net6.0; net7.0; net8.0; net4.8;</TargetFrameworks>
|
<TargetFrameworks>.netstandard2.1; net6.0; net7.0; net8.0; net4.8;</TargetFrameworks>
|
||||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<FileVersion>1.4.5</FileVersion>
|
<FileVersion>1.4.6</FileVersion>
|
||||||
<Authors>EonaCat (Jeroen Saey)</Authors>
|
<Authors>EonaCat (Jeroen Saey)</Authors>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Company>EonaCat (Jeroen Saey)</Company>
|
<Company>EonaCat (Jeroen Saey)</Company>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<EVRevisionFormat>1.4.5+{chash:10}.{c:ymd}</EVRevisionFormat>
|
<EVRevisionFormat>1.4.6+{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>
|
||||||
@@ -51,15 +51,16 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EonaCat.Json" Version="1.1.4" />
|
<PackageReference Include="EonaCat.Json" Version="1.1.9" />
|
||||||
<PackageReference Include="EonaCat.Versioning" Version="1.2.6">
|
<PackageReference Include="EonaCat.Versioning" Version="1.2.6">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.0.2" />
|
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.0.2" />
|
||||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
182
EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs
Normal file
182
EonaCat.Logger/EonaCatCoreLogger/ElasticSearchLogger.cs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
using System.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
|
||||||
|
{
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class ElasticSearchLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly ElasticSearchLoggerOptions _options;
|
||||||
|
private static readonly HttpClient _httpClient = new HttpClient();
|
||||||
|
private static readonly List<string> _buffer = new List<string>();
|
||||||
|
private static readonly object _lock = new object();
|
||||||
|
private static bool _flushLoopStarted = false;
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
public event EventHandler<string> OnInvalidStatusCode;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
|
||||||
|
public ElasticSearchLogger(string categoryName, ElasticSearchLoggerOptions options)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_options = options;
|
||||||
|
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||||
|
|
||||||
|
if (!_flushLoopStarted)
|
||||||
|
{
|
||||||
|
_flushLoopStarted = true;
|
||||||
|
Task.Run(FlushLoopAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!_options.IsEnabled || formatter == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get correlation ID from context or generate new one
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the log entry
|
||||||
|
var logDoc = new
|
||||||
|
{
|
||||||
|
timestamp = DateTime.UtcNow,
|
||||||
|
level = logLevel.ToString(),
|
||||||
|
category = _categoryName,
|
||||||
|
message = formatter(state, exception),
|
||||||
|
exception = exception?.ToString(),
|
||||||
|
eventId = eventId.Id,
|
||||||
|
customContext = _context.GetAll()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serialize log to JSON
|
||||||
|
string json = JsonSerializer.Serialize(logDoc);
|
||||||
|
|
||||||
|
// Add to the buffer
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_buffer.Add(json);
|
||||||
|
if (_buffer.Count >= _options.RetryBufferSize)
|
||||||
|
{
|
||||||
|
_ = FlushBufferAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FlushLoopAsync()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(_options.FlushIntervalSeconds));
|
||||||
|
await FlushBufferAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FlushBufferAsync()
|
||||||
|
{
|
||||||
|
List<string> toSend;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_buffer.Count == 0) return;
|
||||||
|
toSend = new List<string>(_buffer);
|
||||||
|
_buffer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elasticsearch URL with dynamic index
|
||||||
|
string indexName = $"{_options.IndexName}-{DateTime.UtcNow:yyyy.MM.dd}";
|
||||||
|
string url = $"{_options.Uri.TrimEnd('/')}/{(_options.UseBulkInsert ? "_bulk" : indexName + "/_doc")}";
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
|
request.Headers.Accept.ParseAdd("application/json");
|
||||||
|
|
||||||
|
// Add authentication headers if configured
|
||||||
|
if (!string.IsNullOrWhiteSpace(_options.Username))
|
||||||
|
{
|
||||||
|
var authToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_options.Username}:{_options.Password}"));
|
||||||
|
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom headers (static ones)
|
||||||
|
foreach (var header in _options.CustomHeaders)
|
||||||
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add template headers (dynamic ones based on log data)
|
||||||
|
var dynamicHeaders = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "index", _options.IndexName },
|
||||||
|
{ "date", DateTime.UtcNow.ToString("yyyy-MM-dd") },
|
||||||
|
{ "timestamp", DateTime.UtcNow.ToString("o") },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var header in _options.TemplateHeaders)
|
||||||
|
{
|
||||||
|
var value = ReplaceTemplate(header.Value, dynamicHeaders);
|
||||||
|
request.Headers.TryAddWithoutValidation(header.Key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add context headers (correlationId, custom context)
|
||||||
|
foreach (var kv in _context.GetAll())
|
||||||
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation($"X-Context-{kv.Key}", kv.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the content for the request
|
||||||
|
request.Content = new StringContent(
|
||||||
|
_options.UseBulkInsert
|
||||||
|
? string.Join("\n", toSend.Select(d => $"{{\"index\":{{}}}}\n{d}")) + "\n"
|
||||||
|
: string.Join("\n", toSend),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
OnInvalidStatusCode?.Invoke(this, $"ElasticSearch request failed: {response.StatusCode}, {errorContent}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplaceTemplate(string template, Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
foreach (var kv in values)
|
||||||
|
{
|
||||||
|
template = template.Replace($"{{{kv.Key}}}", kv.Value);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class ElasticSearchLoggerOptions
|
||||||
|
{
|
||||||
|
public string Uri { get; set; } = "http://localhost:9200";
|
||||||
|
public string IndexName { get; set; } = "eonacat-logs";
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public Dictionary<string, string> CustomHeaders { get; set; } = new();
|
||||||
|
public Dictionary<string, string> TemplateHeaders { get; set; } = new();
|
||||||
|
public int RetryBufferSize { get; set; } = 100;
|
||||||
|
public int FlushIntervalSeconds { get; set; } = 5;
|
||||||
|
public bool UseBulkInsert { get; set; } = true;
|
||||||
|
public bool IncludeCorrelationId { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class ElasticSearchLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly ElasticSearchLoggerOptions _options;
|
||||||
|
|
||||||
|
public ElasticSearchLoggerProvider(ElasticSearchLoggerOptions options = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new ElasticSearchLoggerOptions();
|
||||||
|
}
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new ElasticSearchLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// No unmanaged resources, nothing to dispose here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Console;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for adding the built-in Console Logger to the <see cref="ILoggingBuilder" />
|
||||||
|
/// </summary>
|
||||||
|
public static class ConsoleLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the built-in Console Logger to the logging builder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The <see cref="ILoggingBuilder" /> to use.</param>
|
||||||
|
/// <param name="configure">Optional configuration for <see cref="ConsoleLoggerOptions" /></param>
|
||||||
|
public static ILoggingBuilder AddEonaCatConsoleLogger(this ILoggingBuilder builder, Action<ConsoleLoggerOptions> configure = null)
|
||||||
|
{
|
||||||
|
if (configure != null)
|
||||||
|
{
|
||||||
|
builder.AddConsole(configure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AddConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
public static class ElasticSearchLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddEonaCatElasticSearchLogger(this ILoggingBuilder builder, Action<ElasticSearchLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
var options = new ElasticSearchLoggerOptions();
|
||||||
|
configure?.Invoke(options);
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider>(new ElasticSearchLoggerProvider(options));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
public static class HttpLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddEonaCatHttpLogger(this ILoggingBuilder builder, Action<HttpLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
var options = new HttpLoggerOptions();
|
||||||
|
configure?.Invoke(options);
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider>(new HttpLoggerProvider(options));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
public static class JsonFileLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddEonaCatJsonFileLogger(this ILoggingBuilder builder, Action<JsonFileLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
var options = new JsonFileLoggerOptions();
|
||||||
|
configure?.Invoke(options);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider>(new JsonFileLoggerProvider(options));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
public static class TcpLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddEonaCatTcpLogger(this ILoggingBuilder builder, Action<TcpLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
var options = new TcpLoggerOptions();
|
||||||
|
configure?.Invoke(options);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider>(new TcpLoggerProvider(options));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// 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.Extensions
|
||||||
|
{
|
||||||
|
public static class UdpLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
public static ILoggingBuilder AddEonaCatUdpLogger(this ILoggingBuilder builder, Action<UdpLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
var options = new UdpLoggerOptions();
|
||||||
|
configure?.Invoke(options);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider>(new UdpLoggerProvider(options));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,4 +139,9 @@ public class FileLoggerOptions : BatchingLoggerOptions
|
|||||||
/// Custom keywords specified in LoggerSettings
|
/// Custom keywords specified in LoggerSettings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseDefaultMasking { get; set; } = true;
|
public bool UseDefaultMasking { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if we need to include the correlation ID in the log
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeCorrelationId { get; set; } = true;
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
private bool _rollingOver;
|
private bool _rollingOver;
|
||||||
private int _rollOverCount;
|
private int _rollOverCount;
|
||||||
private ConcurrentDictionary<string,string> _buffer = new ConcurrentDictionary<string, string>();
|
private ConcurrentDictionary<string,string> _buffer = new ConcurrentDictionary<string, string>();
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
|
||||||
public event EventHandler<ErrorMessage> OnError;
|
public event EventHandler<ErrorMessage> OnError;
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
_maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
|
_maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
|
||||||
_maxRolloverFiles = loggerOptions.MaxRolloverFiles;
|
_maxRolloverFiles = loggerOptions.MaxRolloverFiles;
|
||||||
_maxTries = loggerOptions.MaxWriteTries;
|
_maxTries = loggerOptions.MaxWriteTries;
|
||||||
|
IncludeCorrelationId = loggerOptions.IncludeCorrelationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,6 +74,10 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages,
|
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
@@ -90,7 +96,22 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
foreach (var group in messages.GroupBy(GetGrouping))
|
foreach (var group in messages.GroupBy(GetGrouping))
|
||||||
{
|
{
|
||||||
LogFile = GetFullName(group.Key);
|
LogFile = GetFullName(group.Key);
|
||||||
var currentMessages = string.Join(string.Empty, group.Select(item => item.Message));
|
|
||||||
|
var currentMessages = string.Join(string.Empty, group.Select(item =>
|
||||||
|
{
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var contextData = _context.GetAll();
|
||||||
|
var contextInfo = contextData.Count > 0
|
||||||
|
? string.Join(" ", contextData.Select(kvp => $"{kvp.Key}={kvp.Value}"))
|
||||||
|
: string.Empty;
|
||||||
|
return $"[Timestamp: {item.Timestamp:u}] {item.Message} {contextInfo}{Environment.NewLine}";
|
||||||
|
}));
|
||||||
|
|
||||||
if (!_buffer.TryAdd(LogFile, currentMessages))
|
if (!_buffer.TryAdd(LogFile, currentMessages))
|
||||||
{
|
{
|
||||||
_buffer[LogFile] += currentMessages;
|
_buffer[LogFile] += currentMessages;
|
||||||
@@ -128,6 +149,7 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsWriting { get; set; }
|
public bool IsWriting { get; set; }
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
|
||||||
private async Task<bool> TryWriteToFileAsync(CancellationToken cancellationToken)
|
private async Task<bool> TryWriteToFileAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
132
EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs
Normal file
132
EonaCat.Logger/EonaCatCoreLogger/HttpLogger.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using EonaCat.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class HttpLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly HttpLoggerOptions _options;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
|
||||||
|
private static readonly HttpClient _client = new();
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
public event EventHandler<string> OnInvalidStatusCode;
|
||||||
|
|
||||||
|
public HttpLogger(string categoryName, HttpLoggerOptions options)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_options = options;
|
||||||
|
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string message = formatter(state, exception);
|
||||||
|
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "timestamp", DateTime.UtcNow },
|
||||||
|
{ "level", logLevel.ToString() },
|
||||||
|
{ "category", _categoryName },
|
||||||
|
{ "message", message },
|
||||||
|
{ "eventId", eventId.Id }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add full log context as a nested dictionary
|
||||||
|
var contextData = _context.GetAll();
|
||||||
|
if (contextData.Count > 0)
|
||||||
|
{
|
||||||
|
payload["context"] = contextData;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-success status codes
|
||||||
|
var statusCode = result.Result.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);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs
Normal file
18
EonaCat.Logger/EonaCatCoreLogger/HttpLoggerOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class HttpLoggerOptions
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
EonaCat.Logger/EonaCatCoreLogger/HttpLoggerProvider.cs
Normal file
31
EonaCat.Logger/EonaCatCoreLogger/HttpLoggerProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
// 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 HttpLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly HttpLoggerOptions _options;
|
||||||
|
|
||||||
|
public HttpLoggerProvider(HttpLoggerOptions options = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new HttpLoggerOptions();
|
||||||
|
}
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new HttpLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// No unmanaged resources, nothing to dispose here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
EonaCat.Logger/EonaCatCoreLogger/JsonFIleLoggerProvider.cs
Normal file
35
EonaCat.Logger/EonaCatCoreLogger/JsonFIleLoggerProvider.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class JsonFileLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly JsonFileLoggerOptions _options;
|
||||||
|
|
||||||
|
public JsonFileLoggerProvider(JsonFileLoggerOptions options = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new JsonFileLoggerOptions();
|
||||||
|
}
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new JsonFileLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
80
EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs
Normal file
80
EonaCat.Logger/EonaCatCoreLogger/JsonFileLogger.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using EonaCat.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class JsonFileLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly JsonFileLoggerOptions _options;
|
||||||
|
private readonly string _filePath;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
|
||||||
|
public JsonFileLogger(string categoryName, JsonFileLoggerOptions options)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_options = options;
|
||||||
|
_filePath = Path.Combine(_options.LogDirectory, _options.FileName);
|
||||||
|
Directory.CreateDirectory(_options.LogDirectory);
|
||||||
|
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string message = formatter(state, exception);
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logObject = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "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 json = JsonHelper.ToJson(logObject);
|
||||||
|
File.AppendAllText(_filePath, json + Environment.NewLine, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs
Normal file
18
EonaCat.Logger/EonaCatCoreLogger/JsonFileLoggerOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class JsonFileLoggerOptions
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
EonaCat.Logger/EonaCatCoreLogger/LogContext.cs
Normal file
30
EonaCat.Logger/EonaCatCoreLogger/LogContext.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class LoggerScopedContext
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, string> _context = new();
|
||||||
|
|
||||||
|
public void Set(string key, string value)
|
||||||
|
{
|
||||||
|
_context[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Get(string key)
|
||||||
|
{
|
||||||
|
return _context.TryGetValue(key, out var value) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> GetAll()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string>(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_context.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs
Normal file
90
EonaCat.Logger/EonaCatCoreLogger/TcpLogger.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class TcpLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly TcpLoggerOptions _options;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
|
||||||
|
public TcpLogger(string categoryName, TcpLoggerOptions options)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_options = options;
|
||||||
|
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string message = formatter(state, exception);
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logParts = new List<string>
|
||||||
|
{
|
||||||
|
$"[{DateTime.UtcNow:u}]",
|
||||||
|
$"[{logLevel}]",
|
||||||
|
$"[{_categoryName}]",
|
||||||
|
$"Message: {message}",
|
||||||
|
};
|
||||||
|
|
||||||
|
var contextData = _context.GetAll();
|
||||||
|
if (contextData.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var kvp in contextData)
|
||||||
|
{
|
||||||
|
logParts.Add($"{kvp.Key}: {kvp.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
logParts.Add($"Exception: {exception}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs
Normal file
17
EonaCat.Logger/EonaCatCoreLogger/TcpLoggerOptions.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class TcpLoggerOptions
|
||||||
|
{
|
||||||
|
public string Host { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public bool IncludeCorrelationId { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
EonaCat.Logger/EonaCatCoreLogger/TcpLoggerProvider.cs
Normal file
31
EonaCat.Logger/EonaCatCoreLogger/TcpLoggerProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class TcpLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly TcpLoggerOptions _options;
|
||||||
|
|
||||||
|
public TcpLoggerProvider(TcpLoggerOptions options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new TcpLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
88
EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs
Normal file
88
EonaCat.Logger/EonaCatCoreLogger/UdpLogger.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class UdpLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly UdpLoggerOptions _options;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
|
||||||
|
public UdpLogger(string categoryName, UdpLoggerOptions options)
|
||||||
|
{
|
||||||
|
_categoryName = categoryName;
|
||||||
|
_options = options;
|
||||||
|
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel) || formatter == null)
|
||||||
|
{
|
||||||
|
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<string>
|
||||||
|
{
|
||||||
|
$"[{DateTime.UtcNow:u}]",
|
||||||
|
$"[{logLevel}]",
|
||||||
|
$"[{_categoryName}]",
|
||||||
|
$"Message: {message}",
|
||||||
|
};
|
||||||
|
|
||||||
|
var contextData = _context.GetAll();
|
||||||
|
if (contextData.Count > 0)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs
Normal file
20
EonaCat.Logger/EonaCatCoreLogger/UdpLoggerOptions.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class UdpLoggerOptions
|
||||||
|
{
|
||||||
|
public string Host { get; set; } = "127.0.0.1";
|
||||||
|
|
||||||
|
public int Port { get; set; } = 514;
|
||||||
|
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public bool IncludeCorrelationId { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
EonaCat.Logger/EonaCatCoreLogger/UdpLoggerProvider.cs
Normal file
32
EonaCat.Logger/EonaCatCoreLogger/UdpLoggerProvider.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
// 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 UdpLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly UdpLoggerOptions _options;
|
||||||
|
|
||||||
|
public UdpLoggerProvider(UdpLoggerOptions options = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new UdpLoggerOptions();
|
||||||
|
}
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new UdpLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// No unmanaged resources, nothing to dispose here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
EonaCat.Logger/EonaCatCoreLogger/XmlFIleLoggerProvider.cs
Normal file
35
EonaCat.Logger/EonaCatCoreLogger/XmlFIleLoggerProvider.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class XmlFileLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly XmlFileLoggerOptions _options;
|
||||||
|
|
||||||
|
public XmlFileLoggerProvider(XmlFileLoggerOptions options = null)
|
||||||
|
{
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
options = new XmlFileLoggerOptions();
|
||||||
|
}
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new XmlFileLogger(categoryName, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
87
EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs
Normal file
87
EonaCat.Logger/EonaCatCoreLogger/XmlFileLogger.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public class XmlFileLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string _categoryName;
|
||||||
|
private readonly XmlFileLoggerOptions _options;
|
||||||
|
private readonly string _filePath;
|
||||||
|
private readonly LoggerScopedContext _context = new();
|
||||||
|
|
||||||
|
public bool IncludeCorrelationId { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler<Exception> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(string key, string value) => _context.Set(key, value);
|
||||||
|
public void ClearContext() => _context.Clear();
|
||||||
|
public string GetContext(string key) => _context.Get(key);
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state) => null;
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||||
|
Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel) || formatter == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IncludeCorrelationId)
|
||||||
|
{
|
||||||
|
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||||
|
_context.Set("CorrelationId", correlationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logElement = new XElement("log",
|
||||||
|
new XElement("timestamp", DateTime.UtcNow.ToString("o")),
|
||||||
|
new XElement("level", logLevel.ToString()),
|
||||||
|
new XElement("category", _categoryName),
|
||||||
|
new XElement("message", formatter(state, exception))
|
||||||
|
);
|
||||||
|
|
||||||
|
var context = _context.GetAll();
|
||||||
|
if (context.Count > 0)
|
||||||
|
{
|
||||||
|
var contextElement = new XElement("context");
|
||||||
|
foreach (var item in context)
|
||||||
|
{
|
||||||
|
contextElement.Add(new XElement(item.Key, item.Value));
|
||||||
|
}
|
||||||
|
logElement.Add(contextElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
logElement.Add(new XElement("exception", exception.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
File.AppendAllText(_filePath, logElement.ToString() + Environment.NewLine);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs
Normal file
18
EonaCat.Logger/EonaCatCoreLogger/XmlFileLoggerOptions.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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.
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||||
|
{
|
||||||
|
public class XmlFileLoggerOptions
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
README.md
11
README.md
@@ -379,3 +379,14 @@ private void LogHelper_OnException(object sender, ErrorMessage e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Example of adding custom context to the log messages:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var jsonLogger = new JsonFileLoggerProvider().CreateLogger("MyCategory") as JsonFileLogger;
|
||||||
|
jsonLogger?.SetContext("CorrelationId", "abc-123");
|
||||||
|
jsonLogger?.SetContext("UserId", "john.doe");
|
||||||
|
jsonLogger?.LogInformation("User logged in");
|
||||||
|
````
|
||||||
|
// Output:
|
||||||
|
// [2025-04-25 17:01:00Z] [Information] MyCategory: User logged in | CorrelationId=abc-123 UserId=john.doe
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ public class Logger
|
|||||||
UseLocalTime = UseLocalTime,
|
UseLocalTime = UseLocalTime,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
_logManager = new LogManager(LoggerSettings);
|
_logManager = new LogManager(LoggerSettings);
|
||||||
_logManager.Settings.TypesToLog.Clear();
|
_logManager.Settings.TypesToLog.Clear();
|
||||||
_logManager.Settings.LogInfo();
|
_logManager.Settings.LogInfo();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
_logManager.WriteAsync("2222", ELogType.INFO, writeToConsole: false);
|
_logManager.WriteAsync("2222", ELogType.INFO, writeToConsole: false);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using EonaCat.Web.RateLimiter;
|
|||||||
using EonaCat.Web.RateLimiter.Endpoints.Extensions;
|
using EonaCat.Web.RateLimiter.Endpoints.Extensions;
|
||||||
using EonaCat.Web.Tracer.Extensions;
|
using EonaCat.Web.Tracer.Extensions;
|
||||||
using EonaCat.Web.Tracer.Models;
|
using EonaCat.Web.Tracer.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -32,6 +33,11 @@ logger.LoggerSettings.UseMask = true;
|
|||||||
Console.WriteLine(DllInfo.EonaCatVersion);
|
Console.WriteLine(DllInfo.EonaCatVersion);
|
||||||
Console.WriteLine(VersionHelper.GetInformationalVersion());
|
Console.WriteLine(VersionHelper.GetInformationalVersion());
|
||||||
|
|
||||||
|
var jsonLogger = new JsonFileLoggerProvider().CreateLogger("MyCategory") as JsonFileLogger;
|
||||||
|
jsonLogger?.SetContext("CorrelationId", "abc-123");
|
||||||
|
jsonLogger?.SetContext("UserId", "john.doe");
|
||||||
|
jsonLogger?.LogInformation("User logged in");
|
||||||
|
|
||||||
void LoggerSettings_OnLog(EonaCatLogMessage message)
|
void LoggerSettings_OnLog(EonaCatLogMessage message)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
@@ -45,6 +51,7 @@ options.MaxRolloverFiles = 5;
|
|||||||
options.UseLocalTime = true;
|
options.UseLocalTime = true;
|
||||||
options.UseMask = true;
|
options.UseMask = true;
|
||||||
builder.Logging.AddEonaCatFileLogger(fileLoggerOptions: options, filenamePrefix: "web");
|
builder.Logging.AddEonaCatFileLogger(fileLoggerOptions: options, filenamePrefix: "web");
|
||||||
|
builder.Logging.AddEonaCatConsoleLogger();
|
||||||
|
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user