OpenTelemetry ve Zipkin Exporter İle .NET Uygulama Tracelerini İzlemek
Gereklilikler: Docker, .NET 7.0
Uygulamaları geliştirirken productionda uygulamayı izlemek de önemli. Gerek metrikleri, gerekse de traceleri incelemek için bolca açık kaynak çözüm mevcut. Bunlar da kendine has şekillerde koda implement edilebiliyor. .NET 7.0 ile birlikte OpenTelemetry implementasyonu için de kolaylık sağlandı. Bu sayede OpenTelemetry’yi sadece bootstrap aşamasında konfigüre ederek metrikleri ve traceleri toplamaya başlıyoruz. Tabi traceleri sadece toplamak bir işimize yaramayacak. Bundan dolayı da bir exportera daha ihtiyacımız var. OpenTelemetry hali hazırda bir kaç adet dağıtık trace takibini kolaylaştıran uygulama için bu extensionları hazırlamış. Bunları implement etmek de tabi bize düşüyor.
Kullanıcı “Yavaşlık var” dediği zaman “Benim bilgisayarımda çalışıyor” demek her zaman mümkün olmuyor. O yüzden de trace toplamak önemli. O işlem yapılırken backendde hangi servis kullanılmış, hangi sorgu çalıştırılmış vs. konularını takip edeceğimiz bir ortama ihtiyacımız bulunmakta.
Özellikle cloud mimarilerindeki dağıtık yapıyı izlemek için geliştirilen ve hala da geliştirilmekte olan açık kaynak tracing uygulamaları, bu tarz problemler için bize yol gösterebiliyor. Öncelikle .NET 7.0 uygulamamıza OpenTelemetry’yi nasıl ekleyeceğimize bakalım. GitHub linkini yazının en sonunda bulabilirsiniz.
Öncelikle yeni bir proje oluşturuyorum. Projemin adı “OpenTelemetryExample” olsun.
mkdir OpenTelemetryExample
cd OpenTelemetryExample
dotnet new webapi -o ./OpenTelemetryExample.API
Projemizi favori IDE’mizde açabiliriz. Ben VS Code kullandığımdan yazımı ona göre anlatacağım.
Program.cs dosyası bizim .NET uygulamamız başlarken kullanması gereken konfigürasyonları belirlediğimiz classı içerir. Bunun içerisinde yapmamız gereken injectionlar, servis parametreleri vs. gibi farklı konfigürasyonlar yapılabilir.
Burada OpenTelemetry için bazı paketler yüklememiz gerekiyor. Bunları ya komut satırıyla, ya da Visual Studio kullanıyorsanız NuGet paket yöneticisinden edinebilirsiniz.
cd OpenTelemetryExample.API
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease
dotnet restore
Paketlerimizi yükledikten sonra Program.cs içerisinde konfigürasyonlarımızı yapmaya başlayabiliriz.
İlk önce importlarımızı yapalım
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Exporter;
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddConsoleExporter());
Açıklamak gerekirse;
.ConfigureResource() aşamasında metriğimizin sahibi olan servis için bir isim verdik.
.WithTracing() ile trace konfigürasyonunu yaptık.
.AddAspNetCoreInstrumentation() ile .NET’in kendi tracing sınıflarını kullanarak (System.Diagnostics) her controllerımıza kod eklemek yerine, tüm aksiyonları otomatik tanıması için extensionımızı ekledik.
.AddConsoleExporter() ile de tracing bilgilerini konsola yazdırabildik.
API uygulamamızın konumundayken aşağıdaki komutu kullanarak uygulamamızı başlatalım.
dotnet run
Çıktısının aşağıdaki gibi olması gerekiyor. “Now listening on” yazan endpoint, uygulamamızın çalıştığı portu belirtiyor.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5049
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /Users/idylmz/Projects/OpenTelemetryExample/OpenTelemetryExample.API
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Browserımızdan uygulamamıza bir istek atalım. .NET Api uygulamasının bize bir adet örnek endpoint vermiş olması gerekiyor. Test amaçlı burayı kullanabiliriz. Benim uygulamam 5049 portunda ayağa kalktığı için aşağıdaki linki browserıma girerek testimi yapıyorum.
http://localhost:5049/weatherforecast
İlk isteğimi attıktan sonra ConsoleExporter’ım sayesinde konsolda aşağıdaki gibi bir çıktı görmem gerekiyor.
ctivity.TraceId: aaa58fe52611059db2b55b38d63ea4ec
Activity.SpanId: 5987a9685d6fa084
Activity.TraceFlags: Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName: WeatherForecast
Activity.Kind: Server
Activity.StartTime: 2023-05-31T14:48:51.4780440Z
Activity.Duration: 00:00:00.1581850
Activity.Tags:
net.host.name: localhost
net.host.port: 5049
http.method: GET
http.scheme: http
http.target: /weatherforecast
http.url: http://localhost:5049/weatherforecast
http.flavor: 1.1
http.user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
http.route: WeatherForecast
http.status_code: 200
Resource associated with Activity:
service.name: OpenTelemetryExample
service.instance.id: eb77edbd-0dc4-484f-8cc6-157f7a73a5b2
Eğer buraya kadar gelebildiyseniz tebrikler. OpenTelemetry’yi .NET uygulamanıza entegre edebildiniz. Ancak kullanıcılarımız productionda uygulamayı kullanırlarken, konsola bakamayacağım. Baksam da bir sonuç bulmam zor olacak. Bunun yerine benim bu metrikleri daha kalıcı bir ortama taşımam gerekiyor. Zipkin ise burada devreye giriyor.
Zipkin (Zipkin.io)
Zipkin, dağıtık traceleri toplamaya ve servislerin birbirleriyle olan haberleşmesini görselleştiren (dependency) bir izleme uygulamasıdır. OpenTelemetry’nin Zipkin’a verilerimi export edebileceğim bir extensionı da mevcut. Aşağıdaki komut ile bu kütüphaneyi .NET projemize ekleyebiliriz.
dotnet add package OpenTelemetry.Exporter.Zipkin
dotnet restore
Zipkin kütüphanesini indirdik ancak Zipkin’in kendisi ortada henüz yok. Bunun için de dökümantasyondaki yönergeleri inceleyerek ben Docker kurulumunu tercih ediyorum. Docker’ı çalıştırdıktan sonra
docker pull openzipkin/zipkin
komutu ile Zipkin imajını indiriyoruz. İmajın indirilmesi tamamlandıktan sonra da aşağıdaki komutla imajımızı çalıştırıyoruz.
docker run -d -p 9411:9411 openzipkin/zipkin
Bu komutla konteynırın 9411 portunu fiziki bilgisayarımın (VM’de çalışıyor da olabilirsiniz.) 9411 portuna bağlıyarak Zipkin’i çalıştırıyorum. http://localhost:9411 ‘e giderek Zipkin’in ayakta olup olmadığını kontrol edin. Eğer ayaktaysa, aşağıdaki gibi bir ekranla karşılaşacaksınız.
Şimdi de metriklerimiz Zipkin’e gönderme vakti. Tekrar koda dönecek olursak, OpenTelemetry ile ilgili ayarları yaptığımız noktada, bir satır daha eklememiz gerekecek.
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddConsoleExporter());
Şeklinde olan kod bloğumuza .AddZipkinExporter() satırını da ekleyeceğiz. Yani son durumumuz şu şekilde:
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddConsoleExporter()
.AddZipkinExporter());
Şimdi tekrar aşağıdaki komutu kullanarak .NET uygulamamızı çalıştıralım.
dotnet run
Tekrardan http://localhost:5049/weatherforecast adresine browserımdan bir istek atıyorum. Sonrasında ise Zipkin’in arayüzüne girerek tracelerimin düşüp düşmediğini kontrol ediyorum. Arayüzde “Run Query” butonuna basarak tracelerimi getiriyorum.
Görüldüğü gibi OpenTelemetry ile toplanan tracelerim ZipkinExporter sayesinde Zipkin üzerinden erişilebilir halde.
.NET API uygulamamızda pek fazla konfigürasyon yapmadık. Zipkin’in endpointini bile vermedik. Bunun sebebi ise her şeyi olduğu gibi default haliyle kullandık.
Mesela aşağıdaki konfigürasyonla private ağımdaki başka bir sunucuda ayağa kaldırdığım Zipkin endpointine de bu metrikleri gönderebilirim.
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddConsoleExporter()
.AddZipkinExporter(services =>
{
services.Endpoint = new Uri("http://10.0.0.5:9411/api/v2/spans");
}));
9411 Zipkin’in default kullandığı port. Eğer aksine karar vermediyseniz bu portu bu şekilde kullanmanız gerekiyor. /api/v2/spans ise Zipkin’in traceleri karşıladığı endpoint.
Bonus: SQL Traceleri
OpenTelemetry DB tracelerini de toplayıp bunları Zipkin’a iletebiliyor. Bunun için uygun NuGet paketini de kurmanız gerekiyor.
dotnet add package OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCore --version 1.0.0-beta.6
dotnet restore
Sonrasında ise OpenTelemetry’yi konfigüre ettiğiniz bölüme bir satır daha eklemeniz gerekecek. Son hali aşağıdaki gibi olacak. Eklediğimiz satır “.AddEntityFrameworkCoreInstrumentation()”
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddConsoleExporter()
.AddZipkinExporter(services =>
{
services.Endpoint = new Uri("http://10.0.0.5:9411/api/v2/spans");
}));
Yapmak istediğimiz konfigürasyonları appsettings.json dosyasında tutarak, buraya inject edebilirz. Mesela SQL çağrımlarını görmek istediğimizde uygulamadan gönderilen raw SQL cümlecikleri default olarak görmüyoruz. Bunu .ConfigureServices() ile çözebiliriz.
Öncelikle appsettings.json dosyamıza şu JSON değerlerini ekleyelim.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
}
Dosyamıza ek olarak;
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"OpenTelemetrySettings":{
"SQLTracingSettings":{
"SetDbStatementForText": true,
"SetDbStatementForStoredProcedure": true
},
"ZipkinSettings":{
"Endpoint":"http://localhost:9411/api/v2/spans"
}
}
}
Keylerimizi ekleyelim. OpenTelemetry kodlarımıza ise aşağıdaki gibi düzenleme yapalım.
builder.Services.AddOpenTelemetry()
.ConfigureResource(otelBuilder => otelBuilder
.AddService(serviceName: "OpenTelemetryExample"))
.WithTracing(otelBuilder => otelBuilder
.AddAspNetCoreInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddConsoleExporter()
.AddZipkinExporter()
.ConfigureServices(services =>
{
services.Configure<ZipkinExporterOptions>(builder.Configuration.GetSection("OpenTelemetrySettings:ZipkinSettings"));
services.Configure<EntityFrameworkInstrumentationOptions>(builder.Configuration.GetSection("OpenTelemetrySettings:SQLTracingSettings"));
}));
Bu şekilde ayarları appsettings.json dosyasından okumaya başlayacaktır. Aşağıda da görüldüğü gibi uygulamamı tekrardan başlatınca gelen isteklerle tracelerim konfigürasyona göre düşmeye başlıyor.
SQL ve başka çağrımlar da bu şekilde görünecektir.
GitHub Linki: https://github.com/idylmz/OpenTelemetryExample