Add logger

This commit is contained in:
Dmitri Shimanski
2025-03-20 23:34:34 +02:00
parent aa4b86d12f
commit 343ab4de7f
3 changed files with 198 additions and 118 deletions

View File

@@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Net.Models;
using Telegram.Net.Services;
@@ -8,7 +10,8 @@ public static class ServiceBindings
{
public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config)
{
isc.AddHostedService<TelegramHostedService>(k => new(config, isc));
var logger = isc.BuildServiceProvider().GetRequiredService<ILogger<TelegramHostedService>>();
isc.AddHostedService<TelegramHostedService>(k => new(config, isc, logger));
return isc;
}
}

View File

@@ -1,7 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments;
@@ -22,169 +24,241 @@ public class TelegramHostedService : IHostedService
internal Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; } = new();
internal Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new();
internal static ILogger<TelegramHostedService> _logger;
public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc)
public TelegramHostedService(ITelegramBotConfig config, IServiceCollection isc, ILogger<TelegramHostedService> logger)
{
Client = new TelegramBotClient(config.Token);
Config = config;
this.isc = isc;
try
{
_logger = logger;
Client = new TelegramBotClient(config.Token);
Config = config;
this.isc = isc;
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception when creating TelegramHostedService: ");
}
}
internal static bool IsValidHandlerMethod(MethodInfo method, Type parameterType)
{
var parameters = method.GetParameters();
return method.ReturnType == typeof(Task) &&
parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ITelegramBotClient) &&
parameters[1].ParameterType == parameterType &&
parameters[2].ParameterType == typeof(CancellationToken);
try
{
var parameters = method.GetParameters();
return method.ReturnType == typeof(Task) &&
parameters.Length == 3 &&
parameters[0].ParameterType == typeof(ITelegramBotClient) &&
parameters[1].ParameterType == parameterType &&
parameters[2].ParameterType == typeof(CancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Catched exception in parsing and checking params.");
return false;
}
}
internal static Func<ITelegramBotClient, T, CancellationToken, Task> CreateDelegate<T>(MethodInfo method)
{
var delegateType = typeof(Func<ITelegramBotClient, T, CancellationToken, Task>);
return (Delegate.CreateDelegate(delegateType, null, method) as Func<ITelegramBotClient, T, CancellationToken, Task>)!;
try
{
var delegateType = typeof(Func<ITelegramBotClient, T, CancellationToken, Task>);
return (Delegate.CreateDelegate(delegateType, null, method) as
Func<ITelegramBotClient, T, CancellationToken, Task>)!;
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched exception in CreateDelegate function: ");
return null;
}
}
internal async Task AddAttributes(CancellationToken cancellationToken)
{
await Task.Run(async () =>
{
var implementations = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface);
foreach (var implementation in implementations)
try
{
isc.AddSingleton(implementation);
}
var implementations = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface);
var methods = implementations
.SelectMany(t => t.GetMethods(
BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.DeclaredOnly))
.Where(m =>
m.GetCustomAttribute<CommandAttribute>() != null ||
m.GetCustomAttribute<CallbackAttribute>() != null ||
m.GetCustomAttribute<EditMessageAttribute>() != null ||
m.GetCustomAttribute<InlineAttribute>() != null ||
m.GetCustomAttribute<PreCheckoutAttribute>() != null ||
m.GetCustomAttribute<UpdateAttribute>() != null);
foreach (var implementation in implementations)
{
isc.AddScoped(implementation);
}
var methods = implementations
.SelectMany(t => t.GetMethods(
BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.DeclaredOnly))
.Where(m =>
m.GetCustomAttribute<CommandAttribute>() != null ||
m.GetCustomAttribute<CallbackAttribute>() != null ||
m.GetCustomAttribute<EditMessageAttribute>() != null ||
m.GetCustomAttribute<InlineAttribute>() != null ||
m.GetCustomAttribute<PreCheckoutAttribute>() != null ||
m.GetCustomAttribute<UpdateAttribute>() != null);
if (methods.Count() == 0)
{
_logger.LogWarning("Not founded methods with attributes.");
}
foreach (var method in methods)
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
foreach (var method in methods)
{
if (IsValidHandlerMethod(method, typeof(Message)))
var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
{
var handler = CreateDelegate<Message>(method);
CommandHandler.TryAdd(commandAttr.Command, handler);
}
continue;
}
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
if (!CommandHandler.TryAdd(commandAttr.Command, handler))
throw new Exception($"Failed to add in commandHandler: {commandAttr.Command}");
}
var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
{
var handler = CreateDelegate<CallbackQuery>(method);
CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler);
continue;
}
continue;
}
var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (editMessageAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Message)))
var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (callbackAttr != null)
{
var handler = CreateDelegate<Message>(method);
EditedMessageHandler.Add(handler);
}
continue;
}
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
{
var handler = CreateDelegate<CallbackQuery>(method);
if (!CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler))
throw new Exception($"Failed to add in callbacKQuery: {callbackAttr.QueryId}");;
}
var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
if (inlineAttr != null)
{
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
{
var handler = CreateDelegate<InlineQuery>(method);
InlineHandler.TryAdd(inlineAttr.InlineId, handler);
continue;
}
continue;
}
var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>();
if (preCheckoutAttr != null)
{
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (editMessageAttr != null)
{
var handler = CreateDelegate<PreCheckoutQuery>(method);
PreCheckoutHandler = handler;
}
continue;
}
if (IsValidHandlerMethod(method, typeof(Message)))
{
var handler = CreateDelegate<Message>(method);
EditedMessageHandler.Add(handler);
}
var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Update)))
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
continue;
}
var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
if (inlineAttr != null)
{
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
{
var handler = CreateDelegate<InlineQuery>(method);
if (!InlineHandler.TryAdd(inlineAttr.InlineId, handler))
throw new Exception($"Failed to add in inlineHandler: {inlineAttr.InlineId}");;
}
continue;
}
var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>();
if (preCheckoutAttr != null)
{
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
{
var handler = CreateDelegate<PreCheckoutQuery>(method);
PreCheckoutHandler = handler;
}
continue;
}
var updateAttr = method.GetCustomAttribute<UpdateAttribute>();
if (updateAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Update)))
{
var handler = CreateDelegate<Update>(method);
DefaultUpdateHandler.Add(handler);
}
continue;
}
continue;
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Catched new exception when added methods: ");
}
}, cancellationToken);
}
internal async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx)
{
switch (update)
try
{
case { Message: { } message }:
await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key)).Value(client, message, ctx);
break;
case { EditedMessage: { } message }:
EditedMessageHandler.ForEach(async k => await k(client, message, ctx));
break;
case { CallbackQuery: { } callbackQuery }:
await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key)).Value(client, callbackQuery, ctx);
break;
case { InlineQuery: { } inlineQuery }:
await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key)).Value(client, inlineQuery, ctx);
break;
case { PreCheckoutQuery: { } preCheckoutQuery }:
if (PreCheckoutHandler != null) await PreCheckoutHandler(client, preCheckoutQuery, ctx);
break;
default:
DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx));
break;
switch (update)
{
case { Message: { } message }:
await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key))
.Value(client, message, ctx);
break;
case { EditedMessage: { } message }:
EditedMessageHandler.ForEach(async k => await k(client, message, ctx));
break;
case { CallbackQuery: { } callbackQuery }:
await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key))
.Value(client, callbackQuery, ctx);
break;
case { InlineQuery: { } inlineQuery }:
await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key))
.Value(client, inlineQuery, ctx);
break;
case { PreCheckoutQuery: { } preCheckoutQuery }:
if (PreCheckoutHandler != null) await PreCheckoutHandler(client, preCheckoutQuery, ctx);
break;
default:
DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx));
break;
}
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, new EventId(), ex, "Catched exception in UpdateHandler: ");
}
}
[SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken cancellationToken)
{
await AddAttributes(cancellationToken);
try
{
await AddAttributes(cancellationToken);
Client.StartReceiving(
UpdateHandler,
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions,
cancellationToken);
Client.StartReceiving(
UpdateHandler,
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions,
cancellationToken);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Failed to start. Catched exception: ");
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Client.DropPendingUpdates(cancellationToken);
try
{
await Client.DropPendingUpdates(cancellationToken);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Failed to stop. Exception: ");
}
}
}

View File

@@ -10,6 +10,7 @@ using Telegram.Net.Services;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
@@ -22,6 +23,7 @@ namespace Telegram.Tests
{
private TelegramBotConfig _configMock;
private ServiceCollection _services;
private ILogger<TelegramHostedService> logger;
[SetUp]
public void Setup()
@@ -31,7 +33,8 @@ namespace Telegram.Tests
.AddJsonFile("appsettings.json", false)
.AddEnvironmentVariables()
.Build();
var loggerFactory = new LoggerFactory();
logger = loggerFactory.CreateLogger<TelegramHostedService>();
_configMock = new TelegramBotConfig(conf.GetValue<string>("telegram_test_token") ?? throw new Exception("Provide telegram token first"));
_services = new ServiceCollection();
}
@@ -67,7 +70,7 @@ namespace Telegram.Tests
public void AddAttributes_RegistersCommandHandlersCorrectly()
{
_services.AddSingleton<TestHandler>();
var service = new TelegramHostedService(_configMock, _services);
var service = new TelegramHostedService(_configMock, _services, logger);
service.AddAttributes(CancellationToken.None).Wait();
@@ -132,8 +135,8 @@ namespace Telegram.Tests
var services = new ServiceCollection();
services.AddSingleton<TelegramHostedServiceTests.TestHandler>();
_botClientMock = new Mock<ITelegramBotClient>();
_hostedService = new TelegramHostedService(_configMock.Object, services);
var logger = new LoggerFactory().CreateLogger<TelegramHostedService>();
_hostedService = new TelegramHostedService(_configMock.Object, services, logger);
_hostedService.AddAttributes(CancellationToken.None).Wait();
}