ارسال خطاهای رخدادهی در برنامههای سمت کلاینت Blazor WASM، به تلگرام
نویسنده: وحید نصیری
تاریخ: ۱۴۰۰/۰۲/۲۳ ۱۰:۵۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div> using System;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace BlazorWasmTelegramLogger.Client.Logging
{
public class ClientLoggerProvider : ILoggerProvider
{
private readonly HttpClient _httpClient;
private readonly WebApiLoggerOptions _options;
private readonly NavigationManager _navigationManager;
public ClientLoggerProvider(
IServiceProvider serviceProvider,
IOptions<WebApiLoggerOptions> options,
NavigationManager navigationManager)
{
if (serviceProvider is null)
{
throw new ArgumentNullException(nameof(serviceProvider));
}
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
_httpClient = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<HttpClient>();
_options = options.Value;
_navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager));
}
public ILogger CreateLogger(string categoryName)
{
return new WebApiLogger(_httpClient, _options, _navigationManager);
}
public void Dispose()
{
}
}
} using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace BlazorWasmTelegramLogger.Client.Logging
{
public static class ClientLoggerProviderExtensions
{
public static ILoggingBuilder AddWebApiLogger(this ILoggingBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.AddSingleton<ILoggerProvider, ClientLoggerProvider>();
return builder;
}
}
} public ClientLoggerProvider(
IServiceProvider serviceProvider,
IOptions<WebApiLoggerOptions> options,
NavigationManager navigationManager) public ILogger CreateLogger(string categoryName)
{
return new WebApiLogger(_httpClient, _options, _navigationManager);
} using System;
using System.Net.Http;
using System.Net.Http.Json;
using BlazorWasmTelegramLogger.Shared;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorWasmTelegramLogger.Client.Logging
{
public class WebApiLogger : ILogger
{
private readonly WebApiLoggerOptions _options;
private readonly HttpClient _httpClient;
private readonly NavigationManager _navigationManager;
public WebApiLogger(HttpClient httpClient, WebApiLoggerOptions options, NavigationManager navigationManager)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
_navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager));
}
public IDisposable BeginScope<TState>(TState state) => default;
public bool IsEnabled(LogLevel logLevel) => logLevel >= _options.LogLevel;
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception exception,
Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter is null)
{
throw new ArgumentNullException(nameof(formatter));
}
try
{
ClientLog log = new()
{
LogLevel = logLevel,
EventId = eventId,
Message = formatter(state, exception),
Exception = exception?.Message,
StackTrace = exception?.StackTrace,
Url = _navigationManager.Uri
};
_httpClient.PostAsJsonAsync(_options.LoggerEndpointUrl, log);
}
catch
{
// don't throw exceptions from the logger
}
}
}
} using Microsoft.Extensions.Logging;
namespace BlazorWasmTelegramLogger.Client.Logging
{
public class WebApiLoggerOptions
{
public string LoggerEndpointUrl { set; get; }
public LogLevel LogLevel { get; set; } = LogLevel.Information;
}
} {
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WebApiLogger": {
"LogLevel": "Warning",
"LoggerEndpointUrl": "/api/logs"
}
} namespace BlazorWasmTelegramLogger.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.Configure<WebApiLoggerOptions>(options => builder.Configuration.GetSection("WebApiLogger").Bind(options));
// …
}
}
} using Microsoft.Extensions.Logging;
namespace BlazorWasmTelegramLogger.Shared
{
public class ClientLog
{
public LogLevel LogLevel { get; set; }
public EventId EventId { get; set; }
public string Message { get; set; }
public string Exception { get; set; }
public string StackTrace { get; set; }
public string Url { get; set; }
}
} namespace BlazorWasmTelegramLogger.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.Configure<WebApiLoggerOptions>(options => builder.Configuration.GetSection("WebApiLogger").Bind(options));
builder.Services.AddLogging(configure =>
{
configure.AddWebApiLogger();
});
await builder.Build().RunAsync();
}
}
} public bool IsEnabled(LogLevel logLevel) => logLevel >= _options.LogLevel;
using System;
using System.Text;
using System.Threading.Tasks;
using BlazorWasmTelegramLogger.Shared;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegram.Bot.Types.Enums;
namespace BlazorWasmTelegramLogger.Server.Services
{
public class TelegramLoggingBotOptions
{
public string AccessToken { get; set; }
public string ChatId { get; set; }
}
public interface ITelegramBotService
{
Task SendLogAsync(ClientLog log);
}
public class TelegramBotService : ITelegramBotService
{
private readonly string _chatId;
private readonly TelegramBotClient _client;
public TelegramBotService(IOptions<TelegramLoggingBotOptions> options)
{
_chatId = options.Value.ChatId;
_client = new TelegramBotClient(options.Value.AccessToken);
}
public async Task SendLogAsync(ClientLog log)
{
var text = formatMessage(log);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
await _client.SendTextMessageAsync(_chatId, text, ParseMode.Markdown);
}
private static string formatMessage(ClientLog log)
{
if (string.IsNullOrWhiteSpace(log.Message))
{
return string.Empty;
}
var sb = new StringBuilder();
sb.Append(toEmoji(log.LogLevel))
.Append(" *")
.AppendFormat("{0:hh:mm:ss}", DateTime.Now)
.Append("* ")
.AppendLine(log.Message);
if (!string.IsNullOrWhiteSpace(log.Exception))
{
sb.AppendLine()
.Append('`')
.AppendLine(log.Exception)
.AppendLine(log.StackTrace)
.AppendLine("`")
.AppendLine();
}
sb.Append("*Url:* ").AppendLine(log.Url);
return sb.ToString();
}
private static string toEmoji(LogLevel level) =>
level switch
{
LogLevel.Trace => "⬜️",
LogLevel.Debug => "🟦",
LogLevel.Information => "⬛️️️",
LogLevel.Warning => "🟧",
LogLevel.Error => "🟥",
LogLevel.Critical => "❌",
LogLevel.None => "🔳",
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
};
}
} <Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<PackageReference Include="Telegram.Bot" Version="15.7.1" />
</ItemGroup>
</Project> public class TelegramLoggingBotOptions
{
public string AccessToken { get; set; }
public string ChatId { get; set; }
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"TelegramLoggingBot": {
"AccessToken": "1826…",
"ChatId": "-1001…"
}
} namespace BlazorWasmTelegramLogger.Server
{
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TelegramLoggingBotOptions>(options =>
Configuration.GetSection("TelegramLoggingBot").Bind(options));
services.AddSingleton<ITelegramBotService, TelegramBotService>();
// ...
}
// ...
}
} public class TelegramBotService : ITelegramBotService
{
private readonly string _chatId;
private readonly TelegramBotClient _client;
public TelegramBotService(IOptions<TelegramLoggingBotOptions> options)
{
_chatId = options.Value.ChatId;
_client = new TelegramBotClient(options.Value.AccessToken);
} using System;
using System.Threading.Tasks;
using BlazorWasmTelegramLogger.Server.Services;
using BlazorWasmTelegramLogger.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace BlazorWasmTelegramLogger.Server.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class LogsController : ControllerBase
{
private readonly ILogger<LogsController> _logger;
private readonly ITelegramBotService _telegramBotService;
public LogsController(ILogger<LogsController> logger, ITelegramBotService telegramBotService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_telegramBotService = telegramBotService;
}
[HttpPost]
public async Task<IActionResult> PostLog(ClientLog log)
{
// TODO: Save the client's `log` in the database
_logger.Log(log.LogLevel, log.EventId, log.Url + Environment.NewLine + log.Message);
await _telegramBotService.SendLogAsync(log);
return Ok();
}
}
} @page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
throw new InvalidOperationException("This is an exception message from the client!");
}
}