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.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Net.Models; using Telegram.Net.Models;
using Telegram.Net.Services; using Telegram.Net.Services;
@@ -8,7 +10,8 @@ public static class ServiceBindings
{ {
public static IServiceCollection ConnectTelegram(this IServiceCollection isc, TelegramBotConfig config) 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; return isc;
} }
} }

View File

@@ -1,7 +1,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Payments; 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 Dictionary<string, Func<ITelegramBotClient, InlineQuery ,CancellationToken, Task>> InlineHandler { get; } = new();
internal Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; } internal Func<ITelegramBotClient, PreCheckoutQuery,CancellationToken, Task>? PreCheckoutHandler { get; set; }
internal List<Func<ITelegramBotClient, Update, CancellationToken, Task>> DefaultUpdateHandler { get; } = new(); 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); try
Config = config; {
this.isc = isc; _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) internal static bool IsValidHandlerMethod(MethodInfo method, Type parameterType)
{ {
var parameters = method.GetParameters(); try
return method.ReturnType == typeof(Task) && {
parameters.Length == 3 && var parameters = method.GetParameters();
parameters[0].ParameterType == typeof(ITelegramBotClient) && return method.ReturnType == typeof(Task) &&
parameters[1].ParameterType == parameterType && parameters.Length == 3 &&
parameters[2].ParameterType == typeof(CancellationToken); 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) internal static Func<ITelegramBotClient, T, CancellationToken, Task> CreateDelegate<T>(MethodInfo method)
{ {
var delegateType = typeof(Func<ITelegramBotClient, T, CancellationToken, Task>); try
return (Delegate.CreateDelegate(delegateType, null, method) as Func<ITelegramBotClient, T, CancellationToken, Task>)!; {
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) internal async Task AddAttributes(CancellationToken cancellationToken)
{ {
await Task.Run(async () => await Task.Run(async () =>
{ {
var implementations = AppDomain.CurrentDomain.GetAssemblies() try
.SelectMany(a => a.GetTypes())
.Where(t => typeof(IUpdatePollingService).IsAssignableFrom(t) && !t.IsInterface);
foreach (var implementation in implementations)
{ {
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 method in methods) 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.");
}
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
var commandAttr = method.GetCustomAttribute<CommandAttribute>(); foreach (var method in methods)
if (commandAttr != null)
{ {
if (IsValidHandlerMethod(method, typeof(Message))) var commandAttr = method.GetCustomAttribute<CommandAttribute>();
if (commandAttr != null)
{ {
var handler = CreateDelegate<Message>(method); if (IsValidHandlerMethod(method, typeof(Message)))
CommandHandler.TryAdd(commandAttr.Command, handler); {
} var handler = CreateDelegate<Message>(method);
continue; if (!CommandHandler.TryAdd(commandAttr.Command, handler))
} throw new Exception($"Failed to add in commandHandler: {commandAttr.Command}");
}
var callbackAttr = method.GetCustomAttribute<CallbackAttribute>(); continue;
if (callbackAttr != null)
{
if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
{
var handler = CreateDelegate<CallbackQuery>(method);
CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler);
} }
continue;
}
var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>(); var callbackAttr = method.GetCustomAttribute<CallbackAttribute>();
if (editMessageAttr != null) if (callbackAttr != null)
{
if (IsValidHandlerMethod(method, typeof(Message)))
{ {
var handler = CreateDelegate<Message>(method); if (IsValidHandlerMethod(method, typeof(CallbackQuery)))
EditedMessageHandler.Add(handler); {
} var handler = CreateDelegate<CallbackQuery>(method);
continue; if (!CallbackQueryHandler.TryAdd(callbackAttr.QueryId, handler))
} throw new Exception($"Failed to add in callbacKQuery: {callbackAttr.QueryId}");;
}
var inlineAttr = method.GetCustomAttribute<InlineAttribute>(); continue;
if (inlineAttr != null)
{
if (IsValidHandlerMethod(method, typeof(InlineQuery)))
{
var handler = CreateDelegate<InlineQuery>(method);
InlineHandler.TryAdd(inlineAttr.InlineId, handler);
} }
continue;
}
var preCheckoutAttr = method.GetCustomAttribute<PreCheckoutAttribute>(); var editMessageAttr = method.GetCustomAttribute<EditMessageAttribute>();
if (preCheckoutAttr != null) if (editMessageAttr != null)
{
if (IsValidHandlerMethod(method, typeof(PreCheckoutQuery)))
{ {
var handler = CreateDelegate<PreCheckoutQuery>(method); if (IsValidHandlerMethod(method, typeof(Message)))
PreCheckoutHandler = handler; {
} var handler = CreateDelegate<Message>(method);
continue; EditedMessageHandler.Add(handler);
} }
var updateAttr = method.GetCustomAttribute<UpdateAttribute>(); continue;
if (updateAttr != null) }
{
if (IsValidHandlerMethod(method, typeof(Update))) var inlineAttr = method.GetCustomAttribute<InlineAttribute>();
{ if (inlineAttr != null)
var handler = CreateDelegate<Update>(method); {
DefaultUpdateHandler.Add(handler); 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); }, cancellationToken);
} }
internal async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx) internal async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken ctx)
{ {
switch (update) try
{ {
case { Message: { } message }: switch (update)
await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key)).Value(client, message, ctx); {
break; case { Message: { } message }:
case { EditedMessage: { } message }: await CommandHandler.FirstOrDefault(k => message.Text!.StartsWith(k.Key))
EditedMessageHandler.ForEach(async k => await k(client, message, ctx)); .Value(client, message, ctx);
break; break;
case { CallbackQuery: { } callbackQuery }: case { EditedMessage: { } message }:
await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key)).Value(client, callbackQuery, ctx); EditedMessageHandler.ForEach(async k => await k(client, message, ctx));
break; break;
case { InlineQuery: { } inlineQuery }: case { CallbackQuery: { } callbackQuery }:
await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key)).Value(client, inlineQuery, ctx); await CallbackQueryHandler.FirstOrDefault(k => callbackQuery.Data!.StartsWith(k.Key))
break; .Value(client, callbackQuery, ctx);
case { PreCheckoutQuery: { } preCheckoutQuery }: break;
if (PreCheckoutHandler != null) await PreCheckoutHandler(client, preCheckoutQuery, ctx); case { InlineQuery: { } inlineQuery }:
break; await InlineHandler.FirstOrDefault(k => inlineQuery.Id.StartsWith(k.Key))
default: .Value(client, inlineQuery, ctx);
DefaultUpdateHandler.ForEach(async k => await k(client, update, ctx)); break;
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")] [SuppressMessage("ReSharper", "AsyncVoidLambda")]
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
await AddAttributes(cancellationToken); try
{
await AddAttributes(cancellationToken);
Client.StartReceiving( Client.StartReceiving(
UpdateHandler, UpdateHandler,
Config.errorHandler ?? ((_, _, _) => Task.CompletedTask), Config.errorHandler ?? ((_, _, _) => Task.CompletedTask),
Config.ReceiverOptions, Config.ReceiverOptions,
cancellationToken); cancellationToken);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Critical, new EventId(), ex, "Failed to start. Catched exception: ");
}
} }
public async Task StopAsync(CancellationToken cancellationToken) 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 System.Reflection;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq; using Moq;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
@@ -22,6 +23,7 @@ namespace Telegram.Tests
{ {
private TelegramBotConfig _configMock; private TelegramBotConfig _configMock;
private ServiceCollection _services; private ServiceCollection _services;
private ILogger<TelegramHostedService> logger;
[SetUp] [SetUp]
public void Setup() public void Setup()
@@ -31,7 +33,8 @@ namespace Telegram.Tests
.AddJsonFile("appsettings.json", false) .AddJsonFile("appsettings.json", false)
.AddEnvironmentVariables() .AddEnvironmentVariables()
.Build(); .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")); _configMock = new TelegramBotConfig(conf.GetValue<string>("telegram_test_token") ?? throw new Exception("Provide telegram token first"));
_services = new ServiceCollection(); _services = new ServiceCollection();
} }
@@ -67,7 +70,7 @@ namespace Telegram.Tests
public void AddAttributes_RegistersCommandHandlersCorrectly() public void AddAttributes_RegistersCommandHandlersCorrectly()
{ {
_services.AddSingleton<TestHandler>(); _services.AddSingleton<TestHandler>();
var service = new TelegramHostedService(_configMock, _services); var service = new TelegramHostedService(_configMock, _services, logger);
service.AddAttributes(CancellationToken.None).Wait(); service.AddAttributes(CancellationToken.None).Wait();
@@ -132,8 +135,8 @@ namespace Telegram.Tests
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddSingleton<TelegramHostedServiceTests.TestHandler>(); services.AddSingleton<TelegramHostedServiceTests.TestHandler>();
_botClientMock = new Mock<ITelegramBotClient>(); _botClientMock = new Mock<ITelegramBotClient>();
var logger = new LoggerFactory().CreateLogger<TelegramHostedService>();
_hostedService = new TelegramHostedService(_configMock.Object, services); _hostedService = new TelegramHostedService(_configMock.Object, services, logger);
_hostedService.AddAttributes(CancellationToken.None).Wait(); _hostedService.AddAttributes(CancellationToken.None).Wait();
} }