W jednym z moich ostatnich artykułów (Application Insights – Ukryty Skarb Azure) przeszliśmy przez podstawową konfigurację usługi application insights oraz bardzo podstawową integrację. Jako, że obiecałem wam więcej treści z tematu, tak oto powstał ten artykuł.  Zajmiemy się wprowadzeniem integracji z Application Insights na kolejny poziom. Jako, że to kontynuacja, bardzo zachęcam abyś przeczytał wcześniejszy artykuł.

SeriLog

Zacznijmy od rzeczy podstawowej. Jak przy większości moich projektów zaczynam od dogrania i skonfigurowania biblioteki wspomagającej semantyczne logowanie. W moim przypadku jest to SeriLog. Jeżeli korzystasz z np. Log4Net, nie ma czym się przejmować. Dopóki Twoja biblioteka bazuje na interfejsie ILogger wszystko powinno być w porządku.

Oczywiście, jak to w świecie .NET-a, aby zacząć używać gotowej paczki, musimy ją pobrać do projektu za pomocą nuget-a. Paczka nazwy się Serilog.AspNetCore.

Następnie należy, w pliku Program.cs, utworzyć konfigurację logger-a. Ja to robię w następujący sposób:

public class Program
{
    public static void Main(string[] args)
    {
        var configuration = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Verbose)
            .MinimumLevel.Override("System", LogEventLevel.Verbose)
            .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Verbose)
            .Enrich.FromLogContext()
            .Enrich.WithProperty("ApplicationName", typeof(Program).Assembly.GetName().Name)
            .WriteTo
            .Console();

        Log.Logger = configuration.CreateLogger();
        
        try
        {
            Log.Information("Starting Up");
            CreateHostBuilder(args).Build().Run();
        }
        catch (Exception e)
        {
            Log.Fatal(e, "Application startup failed");
        }
        finally
        {
            Log.CloseAndFlush();
        }

    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
            .UseSerilog();
}

Pewnie zastanawiasz się dlaczego w liniach 7-10 ustawiłem minimalny poziom logowania na Verbose. Jest to jak najbardziej świadomy i celowy zabieg. Jako, że obecnie dodajemy wszystko na pustym projekcie, wysoki poziom szczegółowości logów pomoże zapełnić usługę application insights. W produkcyjnej aplikacji nie radziłbym go stosować. Czasami zbyt duża liczba logów generuje niepotrzebny szum informacyjny i ciężej znaleźć istotne elementy.

W tym miejscu przydałoby się omówić jeszcze jedną kwestię, mianowicie ulokowanie tego kodu w pliku Program.cs. Wiem, że często spotykaną praktyką jest konfiguracja logger-a w pliku Startup.cs. Mamy tam dostęp do całej konfiguracji znajdującej się w pliku appsettings.json co pozwala nam na uniknięcie problemu typu “kury i jajka”. Na szczęście jest łatwy do rozwiązania za pomocą np. zmiennych środowiskowych, dzięki czemu uzyskamy logi z startu aplikacji. Dlatego podejście zaprezentowane wyżej jest, przynajmniej moim odczuciu, zdecydowanie lepsze.

Integracja logów i Application Insights

Kolejnym krokiem, który musisz wykonać, jest skonfigurowanie sdk Application Insights w taki sposób, aby zapisywało logi w swojej usłudze. Kiedyś zadanie było dużo prostsze, wystarczyła pomoc biblioteki Serilog.Sinks.ApplicationInsights. Czas jednak mija i wiele się zmienia.  Podczas próby wykorzystania kodu znajdującego się w przykładzie tej biblioteki zobaczysz, że TelemetryConfiguration.Active jest już oznaczone jako depracted i nie powinieneś opierać swojej implementacji na tym polu statycznym.

Aby zintegrować logi z Application Insights będziesz musiał dograć jeszcze jedną paczkę nuget-a – Microsoft.Extensions.Logging.ApplicationInsights, która doda swoją implementacje interfejsu ILogger. Następnie będziesz musiał wrócić do pliku Startup.cs i dodać konfigurację przedstawioną poniżej:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
                .UseSerilog()
                .ConfigureLogging(builder =>
                {
                    builder.AddApplicationInsights("InstrumentationKey");
                });

Cloud Role i Cloud Instance

Ostatnimi elementami, jakie chce poruszyć, są Cloud Role Name i Cloud Role Instance.

Cloud Role Name to nazwa procesu. Może to być nazwa naszej aplikacji, serwisu etc. Jest wykorzystywany np. w panelu ApplicationMap, aby dokonać wizualizacji przepływu informacji pomiędzy procesami.

Uważam to za szczególnie przydatne,  tym bardziej w momencie, kiedy mamy więcej procesów, jak np. Frontend i Backend czy też środowisko mikroserwisowe. W momencie kiedy ta wartość nie jest ustawiona, Application Insights będzie robiło wszystko co w jego mocy, aby nazwać ten proces (np. może użyć nazwy aplikacji jeżeli aplikacja będzie uruchomiona w Azure App Service).

Natomiast Cloud Role Instance powinno zawierać informację na jakiej instancji procesu Cloud Role Name działa. Jest to bardzo ważne w momencie, kiedy próbujemy skalować naszą aplikację. Np. gdybyś spróbował zwiększyć liczbę instancji swojej aplikacji i wystawił ją na świat za load balancerem prawdopodobnie chciałbyś mieć informacje, na jakiej instancji występują błędy. Dlatego możemy nadać identyfikator naszej instancji np. “My_Api_Prod_1”.

Wymaga to dodania własnej implementacji interfejsu ITelemetryInitializer gdzie w metodzie Initialize możesz ustawić wszystkie wartości.

public class ApiTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        if (string.IsNullOrEmpty(telemetry.Context.Cloud.RoleName))
        {
            telemetry.Context.Cloud.RoleName = "My.Api";
            telemetry.Context.Cloud.RoleInstance = "My_Api_Prod_1";
        }
    }
}

Na koniec trzeba powrócić do pliku Startup.cs, aby zarejestrować utworzoną klasę w kontenerze DI.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITelemetryInitializer, ApiTelemetryInitializer>();
    services.AddApplicationInsightsTelemetry();
    // ...
}

Podsumowanie

To by było na tyle, konfiguracja powinna śmigać i wyglądać całkiem składnie. W następnym artykule zagłębimy się w panel usługi Application Insights, aby zobaczyć jakie możliwości oferuje.

Jak zwykle mam nadzieje że artykuł się podobał!

Do Następnego!

PS. Spodobał Ci się ten artykuł?

Referencje