Added multiple providers
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<TargetFrameworks>.netstandard2.1; net6.0; net7.0; net8.0; net4.8;</TargetFrameworks>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<FileVersion>1.4.6</FileVersion>
|
||||
<FileVersion>1.4.7</FileVersion>
|
||||
<Authors>EonaCat (Jeroen Saey)</Authors>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Company>EonaCat (Jeroen Saey)</Company>
|
||||
@@ -24,7 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EVRevisionFormat>1.4.6+{chash:10}.{c:ymd}</EVRevisionFormat>
|
||||
<EVRevisionFormat>1.4.7+{chash:10}.{c:ymd}</EVRevisionFormat>
|
||||
<EVDefault>true</EVDefault>
|
||||
<EVInfo>true</EVInfo>
|
||||
<EVTagMatch>v[0-9]*</EVTagMatch>
|
||||
|
||||
187
EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs
Normal file
187
EonaCat.Logger/EonaCatCoreLogger/BatchingDatabaseLogger.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class BatchingDatabaseLogger : ILogger, IDisposable
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly BatchingDatabaseLoggerOptions _options;
|
||||
private readonly LoggerScopedContext _context = new();
|
||||
private readonly BlockingCollection<LogEntry> _queue;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private readonly Task _processingTask;
|
||||
|
||||
public bool IncludeCorrelationId { get; set; }
|
||||
public event EventHandler<Exception> OnException;
|
||||
|
||||
public BatchingDatabaseLogger(string categoryName, BatchingDatabaseLoggerOptions options)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_options = options;
|
||||
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||
|
||||
_queue = new BlockingCollection<LogEntry>(new ConcurrentQueue<LogEntry>());
|
||||
_cts = new CancellationTokenSource();
|
||||
_processingTask = Task.Run(ProcessQueueAsync);
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||
|
||||
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 void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||
Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel) || formatter == null) return;
|
||||
|
||||
var message = formatter(state, exception);
|
||||
|
||||
var correlationId = IncludeCorrelationId
|
||||
? _context.Get("CorrelationId") ?? Guid.NewGuid().ToString()
|
||||
: null;
|
||||
|
||||
if (correlationId != null)
|
||||
{
|
||||
_context.Set("CorrelationId", correlationId);
|
||||
}
|
||||
|
||||
_queue.Add(new LogEntry
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
LogLevel = logLevel.ToString(),
|
||||
Category = _categoryName,
|
||||
Message = message,
|
||||
Exception = exception?.ToString(),
|
||||
CorrelationId = correlationId
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ProcessQueueAsync()
|
||||
{
|
||||
var batch = new List<LogEntry>();
|
||||
var timeoutMs = (int)Math.Min(_options.BatchInterval.TotalMilliseconds, int.MaxValue);
|
||||
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_queue.TryTake(out var logEntry, timeoutMs, _cts.Token))
|
||||
{
|
||||
batch.Add(logEntry);
|
||||
|
||||
// Drain the queue quickly without waiting
|
||||
while (_queue.TryTake(out var additionalEntry))
|
||||
{
|
||||
batch.Add(additionalEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (batch.Count > 0)
|
||||
{
|
||||
await InsertBatchSafelyAsync(batch);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnException?.Invoke(this, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Final flush outside the loop
|
||||
if (batch.Count > 0)
|
||||
{
|
||||
await InsertBatchSafelyAsync(batch);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InsertBatchSafelyAsync(List<LogEntry> batch)
|
||||
{
|
||||
try
|
||||
{
|
||||
await InsertBatchAsync(batch);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnException?.Invoke(this, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
batch.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InsertBatchAsync(List<LogEntry> batch)
|
||||
{
|
||||
using var connection = _options.DbProviderFactory.CreateConnection();
|
||||
if (connection == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create database connection.");
|
||||
}
|
||||
|
||||
connection.ConnectionString = _options.ConnectionString;
|
||||
await connection.OpenAsync();
|
||||
|
||||
foreach (var entry in batch)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private DbParameter CreateParameter(DbCommand command, string name, object value)
|
||||
{
|
||||
var param = command.CreateParameter();
|
||||
param.ParameterName = $"@{name}";
|
||||
param.Value = value ?? DBNull.Value;
|
||||
return param;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_queue.CompleteAdding();
|
||||
try
|
||||
{
|
||||
_processingTask.Wait(_options.ShutdownTimeout);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnException?.Invoke(this, ex);
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class BatchingDatabaseLoggerOptions
|
||||
{
|
||||
public DbProviderFactory DbProviderFactory { get; set; }
|
||||
public string ConnectionString { get; set; }
|
||||
public string InsertCommand { get; set; } =
|
||||
@"INSERT INTO Logs (Timestamp, LogLevel, Category, Message, Exception, CorrelationId)
|
||||
VALUES (@Timestamp, @LogLevel, @Category, @Message, @Exception, @CorrelationId)";
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool IncludeCorrelationId { get; set; } = true;
|
||||
|
||||
public TimeSpan BatchInterval { get; set; } = TimeSpan.FromSeconds(5); // 5 sec batch
|
||||
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(10); // wait on shutdown
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class BatchingDatabaseLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly BatchingDatabaseLoggerOptions _options;
|
||||
|
||||
public BatchingDatabaseLoggerProvider(BatchingDatabaseLoggerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new BatchingDatabaseLogger(categoryName, _options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Will be disposed in BatchingDatabaseLogger itself
|
||||
}
|
||||
}
|
||||
}
|
||||
83
EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs
Normal file
83
EonaCat.Logger/EonaCatCoreLogger/DatabaseLogger.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DatabaseLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly DatabaseLoggerOptions _options;
|
||||
private readonly LoggerScopedContext _context = new();
|
||||
|
||||
public bool IncludeCorrelationId { get; set; }
|
||||
public event EventHandler<Exception> OnException;
|
||||
|
||||
public DatabaseLogger(string categoryName, DatabaseLoggerOptions options)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_options = options;
|
||||
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||
|
||||
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 void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||
Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel) || formatter == null) return;
|
||||
|
||||
var message = formatter(state, exception);
|
||||
|
||||
if (IncludeCorrelationId)
|
||||
{
|
||||
var correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
|
||||
_context.Set("CorrelationId", correlationId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var connection = _options.DbProviderFactory.CreateConnection();
|
||||
if (connection == null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create database connection.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
OnException?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
private DbParameter CreateParameter(DbCommand command, string name, object value)
|
||||
{
|
||||
var param = command.CreateParameter();
|
||||
param.ParameterName = $"@{name}";
|
||||
param.Value = value ?? DBNull.Value;
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs
Normal file
17
EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerOptions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Data.Common;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DatabaseLoggerOptions
|
||||
{
|
||||
public DbProviderFactory DbProviderFactory { get; set; }
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
public string InsertCommand { get; set; } =
|
||||
@"INSERT INTO Logs (Timestamp, LogLevel, Category, Message, Exception, CorrelationId)
|
||||
VALUES (@Timestamp, @LogLevel, @Category, @Message, @Exception, @CorrelationId)";
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool IncludeCorrelationId { get; set; } = true;
|
||||
}
|
||||
}
|
||||
24
EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs
Normal file
24
EonaCat.Logger/EonaCatCoreLogger/DatabaseLoggerProvider.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DatabaseLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly DatabaseLoggerOptions _options;
|
||||
|
||||
public DatabaseLoggerProvider(DatabaseLoggerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new DatabaseLogger(categoryName, _options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
80
EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs
Normal file
80
EonaCat.Logger/EonaCatCoreLogger/DiscordLogger.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DiscordLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly DiscordLoggerOptions _options;
|
||||
private readonly LoggerScopedContext _context = new();
|
||||
|
||||
public bool IncludeCorrelationId { get; set; }
|
||||
public event EventHandler<Exception> OnException;
|
||||
|
||||
public DiscordLogger(string categoryName, DiscordLoggerOptions options)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_options = options;
|
||||
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||
|
||||
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 void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||
Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel) || formatter == null) return;
|
||||
|
||||
var 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}",
|
||||
};
|
||||
|
||||
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 { content = fullMessage };
|
||||
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
||||
|
||||
client.PostAsync(_options.WebhookUrl, content).Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnException?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs
Normal file
9
EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerOptions.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DiscordLoggerOptions
|
||||
{
|
||||
public string WebhookUrl { get; set; }
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool IncludeCorrelationId { get; set; } = true;
|
||||
}
|
||||
}
|
||||
24
EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs
Normal file
24
EonaCat.Logger/EonaCatCoreLogger/DiscordLoggerProvider.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class DiscordLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly DiscordLoggerOptions _options;
|
||||
|
||||
public DiscordLoggerProvider(DiscordLoggerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new DiscordLogger(categoryName, _options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
|
||||
{
|
||||
public static class BatchingDatabaseLoggerFactoryExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEonaCatBatchingDatabaseLogger(this ILoggingBuilder builder, Action<BatchingDatabaseLoggerOptions> configure)
|
||||
{
|
||||
var options = new BatchingDatabaseLoggerOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
builder.Services.AddSingleton<ILoggerProvider>(new BatchingDatabaseLoggerProvider(options));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
|
||||
{
|
||||
public static class DatabaseLoggerFactoryExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEonaCatDatabaseLogger(this ILoggingBuilder builder, Action<DatabaseLoggerOptions> configure)
|
||||
{
|
||||
var options = new DatabaseLoggerOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
builder.Services.AddSingleton<ILoggerProvider>(new DatabaseLoggerProvider(options));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
|
||||
{
|
||||
public static class DiscordLoggerFactoryExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEonaCatDiscordLogger(this ILoggingBuilder builder, Action<DiscordLoggerOptions> configure)
|
||||
{
|
||||
var options = new DiscordLoggerOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
builder.Services.AddSingleton<ILoggerProvider>(new DiscordLoggerProvider(options));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
|
||||
{
|
||||
public static class SlackLoggerFactoryExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEonaCatSlackLogger(this ILoggingBuilder builder, Action<SlackLoggerOptions> configure)
|
||||
{
|
||||
var options = new SlackLoggerOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
builder.Services.AddSingleton<ILoggerProvider>(new SlackLoggerProvider(options));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
|
||||
{
|
||||
public static class TelegramLoggerFactoryExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddEonaCatTelegramLogger(this ILoggingBuilder builder, Action<TelegramLoggerOptions> configure)
|
||||
{
|
||||
var options = new TelegramLoggerOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
builder.Services.AddSingleton<ILoggerProvider>(new TelegramLoggerProvider(options));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,6 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
||||
private readonly int _maxTries;
|
||||
private readonly string _path;
|
||||
private string _logFile;
|
||||
private bool _rollingOver;
|
||||
private int _rollOverCount;
|
||||
private ConcurrentDictionary<string,string> _buffer = new ConcurrentDictionary<string, string>();
|
||||
private readonly LoggerScopedContext _context = new();
|
||||
|
||||
|
||||
83
EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs
Normal file
83
EonaCat.Logger/EonaCatCoreLogger/SlackLogger.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
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.
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class SlackLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly SlackLoggerOptions _options;
|
||||
private readonly LoggerScopedContext _context = new();
|
||||
|
||||
public bool IncludeCorrelationId { get; set; }
|
||||
public event EventHandler<Exception> OnException;
|
||||
|
||||
public SlackLogger(string categoryName, SlackLoggerOptions options)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_options = options;
|
||||
IncludeCorrelationId = options.IncludeCorrelationId;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||
|
||||
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 void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
|
||||
Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel) || formatter == null) return;
|
||||
|
||||
var 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}",
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
client.PostAsync(_options.WebhookUrl, content).Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnException?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs
Normal file
13
EonaCat.Logger/EonaCatCoreLogger/SlackLoggerOptions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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 SlackLoggerOptions
|
||||
{
|
||||
public string WebhookUrl { get; set; }
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool IncludeCorrelationId { get; set; } = true;
|
||||
}
|
||||
}
|
||||
27
EonaCat.Logger/EonaCatCoreLogger/SlackLoggerProvider.cs
Normal file
27
EonaCat.Logger/EonaCatCoreLogger/SlackLoggerProvider.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
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 SlackLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly SlackLoggerOptions _options;
|
||||
|
||||
public SlackLoggerProvider(SlackLoggerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new SlackLogger(categoryName, _options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
57
EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs
Normal file
57
EonaCat.Logger/EonaCatCoreLogger/TelegramLogger.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
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.
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class TelegramLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly TelegramLoggerOptions _options;
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
public event EventHandler<Exception> OnException;
|
||||
|
||||
public TelegramLogger(string categoryName, TelegramLoggerOptions options)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => _options.IsEnabled;
|
||||
|
||||
public async void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel)) 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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnException?.Invoke(this, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class TelegramLoggerOptions
|
||||
{
|
||||
public string BotToken { get; set; }
|
||||
public string ChatId { get; set; }
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
24
EonaCat.Logger/EonaCatCoreLogger/TelegramLoggerProvider.cs
Normal file
24
EonaCat.Logger/EonaCatCoreLogger/TelegramLoggerProvider.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace EonaCat.Logger.EonaCatCoreLogger
|
||||
{
|
||||
public class TelegramLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly TelegramLoggerOptions _options;
|
||||
|
||||
public TelegramLoggerProvider(TelegramLoggerOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new TelegramLogger(categoryName, _options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
75
README.md
75
README.md
@@ -380,6 +380,81 @@ private void LogHelper_OnException(object sender, ErrorMessage e)
|
||||
}
|
||||
```
|
||||
|
||||
Example of Discord Logging:
|
||||
```csharp
|
||||
loggingBuilder.AddEonaCatDiscordLogger(options =>
|
||||
{
|
||||
options.WebhookUrl = "https://discord.com/api/webhooks/your_webhook_here";
|
||||
});
|
||||
```
|
||||
|
||||
Example of Slack Logging:
|
||||
```csharp
|
||||
loggingBuilder.AddEonaCatSlackLogger(options =>
|
||||
{
|
||||
options.WebhookUrl = "https://hooks.slack.com/services/your_webhook_here";
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Example of DatabaseLogging:
|
||||
|
||||
Create the table:
|
||||
```csharp
|
||||
CREATE TABLE Logs (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY, -- SERIAL for PostgreSQL, AUTO_INCREMENT for MySQL
|
||||
Timestamp DATETIME NOT NULL,
|
||||
LogLevel NVARCHAR(50),
|
||||
Category NVARCHAR(255),
|
||||
Message NVARCHAR(MAX),
|
||||
Exception NVARCHAR(MAX),
|
||||
CorrelationId NVARCHAR(255)
|
||||
);
|
||||
```
|
||||
|
||||
MySQL:
|
||||
```csharp
|
||||
using MySql.Data.MySqlClient;
|
||||
|
||||
loggingBuilder.AddEonaCatDatabaseLogger(options =>
|
||||
{
|
||||
options.DbProviderFactory = MySqlClientFactory.Instance;
|
||||
options.ConnectionString = "Server=localhost;Database=EonaCatLogs;User=root;Password=yourpassword;";
|
||||
});
|
||||
```
|
||||
|
||||
SQL Server:
|
||||
```csharp
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
loggingBuilder.AddEonaCatDatabaseLogger(options =>
|
||||
{
|
||||
options.DbProviderFactory = SqlClientFactory.Instance;
|
||||
options.ConnectionString = "Server=localhost;Database=EonaCatLogs;User Id=sa;Password=yourpassword;";
|
||||
});
|
||||
```
|
||||
|
||||
PostgreSQL:
|
||||
```csharp
|
||||
using Npgsql;
|
||||
|
||||
loggingBuilder.AddEonaCatDatabaseLogger(options =>
|
||||
{
|
||||
options.DbProviderFactory = NpgsqlFactory.Instance;
|
||||
options.ConnectionString = "Host=localhost;Database=EonaCatLogs;Username=postgres;Password=yourpassword;";
|
||||
});
|
||||
```
|
||||
|
||||
Example of batching database logging:
|
||||
```csharp
|
||||
loggingBuilder.AddEonaCatBatchingDatabaseLogger(options =>
|
||||
{
|
||||
options.DbProviderFactory = MySql.Data.MySqlClient.MySqlClientFactory.Instance;
|
||||
options.ConnectionString = "Server=localhost;Database=EonaCatLogs;User=root;Password=yourpassword;";
|
||||
options.BatchInterval = TimeSpan.FromSeconds(10); // Flush every 10 seconds
|
||||
});
|
||||
```
|
||||
|
||||
Example of adding custom context to the log messages:
|
||||
|
||||
```csharp
|
||||
|
||||
Reference in New Issue
Block a user