Sprawdzony sposób na zdalne logowanie błędów w Blazorze z Serilog

Budując aplikacje wcześniej czy później napotkamy problem monitorowania jej zachowania w sytuacjach wyjątkowych takich jak brak dostępu do zasobu czy nieprzewidziane błędy w kodzie. Jeżeli zdarzyło się to podczas testowania to zawsze możemy spotkać się z osobą u której wystąpił wyjątek i razem przeanalizować ten problem. Sytuacja staje się o wiele gorsza, gdy nasza aplikacja znajduje się już na produkcji, a błąd wystąpił u jednego z klientów oddalonego od nas o wiele kilometrów, a czas reakcji musi być odpowiednio szybki. W końcu niewiele biznesów może pozwolić sobie na wyłączanie usług na wiele godzin w razie awarii.

Doświadczenie w pisaniu aplikacji webowych podpowiada mi, że w przypadku awarii pierwsza rzeczą, która należy sprawdzić są logi z momentu wystąpienia błędu, czyli informacje o tym jakie akcje wykonał użytkownik i czy przypadkiem nie wystąpił tam nieoczekiwany wyjątek.

Co zrobimy?

W tym wpisie przedstawię jak tworzyć logi w aplikacji Blazor WebAssembly po stronie klienta, następnie prześlemy je na serwer oraz zapiszemy do pliku. Taka konfiguracja powinna być wystarczająca do analizy zgłoszonych błędów.

Konfiguracja Seriloga po stronie backendu

Pisząc aplikacje ASP.NET Core mamy wbudowany system logowania wykorzystujący bibliotekę Microsoft.Extensions.Logging. Dostarcza on interfejsu ILogger wraz metoda Log do logowania błędów. Sprawdzi się to w przypadku prostszych aplikacji ale do zapisywania logów w Blazorze potrzebujemy bardziej zaawansowanego loggera. Jednym z najczęściej wykorzystywanych jest Serilog, wiec w dalszej części użyje właśnie jego. Będę wykorzystywać podstawowa aplikacje Blazor WebAssembly z ASP.NET Core Hosted.

Na początek musimy zainstalować niezbędne paczki z Serilogiem, są to:

  • Serilog.AspNetCore – Serilog dla aplikacji ASP.NET Core,
  • Serilog.AspNetCore.Ingestion – rozszerzenie z endpointem na który będą przesyłane logi z frontendu,
  • Serilog.Settings.Configuration – rozszerzenie pozwalające na odczytywanie konfiguracji z pliku,
  • Serilog.Sinks.File – rozszerzenie do zapisywania logów do pliku.

Kolejnym krokiem jest dodanie Seriloga do konfiguracji w Program.cs (lub Startup.cs dla wersji NET.5 i starszych). Po zbudowaniu hosta dodajemy UseSerilog z wczytaniem konfiguracji oraz UseSerilogIngestion, które dodaje do routingu endpointy do wysyłania logów. Wygląda to następująco:

builder.Host.UseSerilog((context, configuration) => configuration
    .ReadFrom.Configuration(context.Configuration));
...
app.UseSerilogIngestion();

Musimy jeszcze dodać konfiguracje Seriloga w appsettings.json. W tym celu dodajemy sekcje Serilog z Using Serilog.Sinks.File i WriteTo File. Oto przykładowa konfiguracja:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Console"
      },
      {
        "Name": "File",
        "Args": { "path": "Logs/log.txt" }
      }
    ]
  }
}

Logi będą zapisywane do pliku Logs/log.txt, dodałem również opcje wyświetlania logów w konsoli, aby widzieć błędy podczas pracy deweloperskiej. Poziom logowania został ustawiony na Information z jednym wyjątkiem. Microsoft ma tendencje do logowania wielu rzeczy, które w pewnych wypadkach tworzą dodatkowy szum utrudniający zrozumienie logów, wiec dla niego ustawiłem poziom logowania na Warning.

Przejdźmy do kontrolera WeatherForecastController. Ma on już utworzony logger, więc wykorzystam go do zalogowania informacji o tym, że została pobrana temperatura dla danego dnia. Można to zrobić wykorzystując metodę LogInformation z _logger. Moja modyfikacja metody Get wygląda następująco:

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    foreach (var forecast in forecasts)
    {   
        _logger.LogInformation($"Retrieve data - day '{forecast.Date}', temperature: '{forecast.TemperatureC}', summary: '{forecast.Summary}' ");
    }

    return forecasts;
}

Teraz po uruchomieniu aplikacji i przejściu do zakładki Fetch data powinniśmy zobaczyć w pliku informacje o pobraniu temperatury. Oto jak wygląda mój plik Logs/Log.txt

2021-12-25 17:28:00.384 +01:00 [INF] Retrieve data - day '12/26/2021 5:28:00 PM', temperature: '22', summary: 'Balmy' 
2021-12-25 17:28:00.404 +01:00 [INF] Retrieve data - day '12/27/2021 5:28:00 PM', temperature: '6', summary: 'Mild' 
2021-12-25 17:28:00.410 +01:00 [INF] Retrieve data - day '12/28/2021 5:28:00 PM', temperature: '8', summary: 'Chilly' 
2021-12-25 17:28:00.412 +01:00 [INF] Retrieve data - day '12/29/2021 5:28:00 PM', temperature: '48', summary: 'Mild' 
2021-12-25 17:28:00.412 +01:00 [INF] Retrieve data - day '12/30/2021 5:28:00 PM', temperature: '-7', summary: 'Cool' 

Mamy skonfigurowana cześć backendowa, sprawdziliśmy że całość działa i logi zapisują sie do pliku. Pora na aplikacje frontendowa napisaną w Blazorze. Jest to minimalna konfiguracja na potrzeby tego wpisu, zachęcam do zapoznania się z dodatkowymi opcjami Seriloga, które znajdują się tutaj. Szczególnie zachęcam do zapoznania się z formatowaniem wyjścia oraz zawijaniem plików.

Konfiguracja Seriloga w aplikacji Blazor

Tak jak w przypadku backendu konfiguracje zaczynamy od instalacji paczek z Serilogiem, w tym wypadku będą to:

  • Serilog – Serilog dla aplikacji .NET,
  • Serilog.Sinks.BrowserHttp – rozszerzenie do przesyłania logów z przeglądarki,

Następnie dodajemy konfiguracje Serilog w Program.cs. Będzie to inicjalizacja loggera w Log.Logger oraz rejestrujemy serwis Logger z Serilog. Ten kawałek kodu powinien załatwić sprawę:

Log.Logger = new LoggerConfiguration()
    .WriteTo.BrowserHttp($"{builder.HostEnvironment.BaseAddress}ingest")
    .CreateLogger();

builder.Services.AddScoped<Serilog.ILogger>(_ => Log.Logger);

Utworzyłem instancje loggera z wykorzystaniem BrowserHttp oraz podaną ścieżka do endpointu ingest naszego API, jeżeli w Twoim wypadku frontend i backend będą znajdować się pod innymi adresami nie zapomnij dostosować tej części oraz zezwolić im na CORS.

Teraz moge pokryc logami czesc frotnendowa zakladki fetch data, na poczatek wstrzyknimy ILoggera, a nastepnie dodajmy logowanie do metody OnInitializedAsync. Moja implementacja wyglada nastepujaco:

@inject Serilog.ILogger
...
protected override async Task OnInitializedAsync()
{
    Logger.Information("Call Get/WeatherForecast endpoint");
    forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    Logger.Information($"Initialized {nameof(FetchData)} component Successfully");
}

Dodałem log z informacja o tym, że próbujemy pobrać dane oraz że uda się zainicjalizować komponent. Po uruchomieniu aplikacji i wejściu do zakładki fetch data mój plik z logami wygląda następująco:

2021-12-25 19:22:47.100 +01:00 [INF] Retrieve data - day '12/26/2021 7:22:47 PM', temperature: '1', summary: 'Hot' 
2021-12-25 19:22:47.114 +01:00 [INF] Retrieve data - day '12/27/2021 7:22:47 PM', temperature: '-8', summary: 'Balmy' 
2021-12-25 19:22:47.114 +01:00 [INF] Retrieve data - day '12/28/2021 7:22:47 PM', temperature: '24', summary: 'Warm' 
2021-12-25 19:22:47.115 +01:00 [INF] Retrieve data - day '12/29/2021 7:22:47 PM', temperature: '20', summary: 'Warm' 
2021-12-25 19:22:47.115 +01:00 [INF] Retrieve data - day '12/30/2021 7:22:47 PM', temperature: '5', summary: 'Chilly' 
2021-12-25 18:22:46.961 +00:00 [INF] Call Get/WeatherForecast endpoint
2021-12-25 18:22:47.175 +00:00 [INF] Initialized FetchData component Successfully

Warto zauważyć, że logi z frontendu przychodzą z lekkim opóźnieniem. Dzieje się tak z powodu batchowania logów, czyli zbierania ich większej ilości przed wysłaniem. Uruchomienie narzędzia, które je nam posortuje odpowiednio po dacie na pewno uprości analizę logów.

Jest to bardzo prosty przykład, myślę że kolejnymi krokami byłoby stworzenie podstawowego komponentu, który logowałby akcje inicjalizacji czy zmiany parametrów. Należy również uważać, aby nie przesadzić z ilością informacji, które logujemy. Zbyt wiele logów może spowolnić nasza aplikacje. W bardziej zaawansowanych scenariuszach możesz wykorzystać SignalR lub gRPC do przesyłania logów do backendu zamiast HTTP.

Podsumowanie

Udało nam się zsetupować Seriloga po stronie backendu, a dzięki rozszerzeniom Serilog.AspNetCore.Ingestion i Serilog.Sinks.BrowserHttp możliwe było przesyłanie logów z aplikacji frontendowej w Blazorze. Sam stosuje tego typu konfiguracje w moich aplikacjach, a dzięki niej mam więcej informacji o błędach i mogę na nie odpowiednio reagować. Mam nadzieje, że i Ty skorzystasz z tego wpisu, a dostarczone logi uchronią Cię przed godzinami debugowania w poszukiwaniu błędu.

Jeżeli stosujesz inne metody logowania w swoich projektach Blazorowych to proszę daj znać, chętnie dowiem się o innych sposobach!

Jeżeli uznasz ten post za przydatny to proszę udostępnij go innym. Zapraszam również do mojego newslettera na którym regularnie udostępniam przedpremierowo artykuły, materiały wideo i wiele więcej.

Share via
Copy link
Powered by Social Snap