.Net Core’da ElasticSearch

Selamlar Arkadaşlar,

Bu makalede, .Net Core ile macOS High Sierra üzerinde Elastic Search konusunu hep beraber inceleyeceğiz. Elastic Search hakkında daha detaylı bilgiye buradaki makaleden erişebilirsiniz. Öncelikle Elastic Search ve buna bağlı componentler için ilgili kurlumları birlikte yapacağız. Daha sonra .Net Core ile bir WebApi projesi oluşturacağız. Ve bu projede kullanılmak üzere 3 Sharding’li, 1 Replicalı ve 3 nodelu bir index  oluşturacağız. Son olarak da aranacak Error Logları Tekil ve  Bulk Insert şeklinde atarak, filitreleme işlemlerini yeni yaratılacak bir Mvc proje üzerinde yapacağız.

Öncelikle gelin bu elastik search için kurulum işlemlerini Macbook üzerinde yapalım:

Makinanızda yok ise önce Homebrew kurulur. Var ise Update edilir.

  • Install :
  • Update:

  • Elastic Search Kurulur:

  • Elasticsearch Monitör’ü için Kabana kurulur:

İşler yolunda gitti ise yeni bir terminal açılıp, “elasticsearch” komutu yazıldığında yukarıdaki gibi bir ekran ile karşılaşılır.

Aynı şekilde kibana için terminalde : “kibana” komutu yazılır. Ve aşağıdaki komut yazıldığında yukarıdaki gibi bir ekran ile karşılaşılır. Kibana dataların ve log işlemlerinin istenir ise grafikler ile gösterilmesini sağlayan bir tooldur.

Kurulumlar bittiğine göre sıra geldi Webservisi uygulamasını .Net Core ile oluşturmaya. Alttaki komut satırı ile ilgili application oluşturulur.

.Net Core’da Elasticsearch kullanabilmek için öncelikle projeye “Nest” kütüphanesinin “dotnet add package NEST” komutu ile eklenmesi gerekmektedir.

Öncelikle kaydedilecek Log Model aşağıdaki gibi oluşturulur.

Log:

Öncelikle alttaki connSettings ve elasticClient static olarak tanımlanır. Böylece ilgili objelerin sadece 1 kere oluşturulması, performans arttırma amacı ile sağlanmıştır. Connection ayarlarında “log_history” default olarak verilmiştir.

Logs/InsertLog() :

  • Aşağıda görüldüğü gibi “!elasticClient.IndexExists(“error_log”).Exists” eğer ‘error_log’ index’i hiç oluşturulmamış ise bu kod satırı çalıştırılır. Yani yeni bir Index oluşturulur.
  • “error_log” index sadece bir eşleniğinin alındığı tek replicali bir yapıya sahiptir.==>”indexSettings.NumberOfReplicas = 1
  • İlgili index’in 3 farklı sharding yani sunucusu vardır. ==>”indexSettings.NumberOfShards = 3“.

Not: Herbir sunucunun yedeği yani replica’sı alınacak ise en az 3 Node’u olması gerekmektedir. Herbir node, tekbir elastic search anlamına gelmektedir. Cluster’da tüm nodeların bulunduğu kümedir. Nodelar içinde bulunan herbir shardingin yedeğinin yani replicasının başka bir node’da çapraz dediğimiz bir şekilde olması gerekmektedir. Bu tamamen güvenlik amaçlıdır. Bir shard yani makina çöker ise, başka bir cluster’da olan replicası yanbi yedeği ya da eşleniği devreye girer. Bu durumda toplam makina sayısı 3 container ve her containerda 2 shard’dan “6” dır.

  • var createIndexDescriptor = new CreateIndexDescriptor(“log_history”)” : “log_history” adında bir index yaratılacaktır.
  • “.Map<Log>(m => m.AutoMap())” ==>İlgili index log sınıfına maplenmiştir.
  • Aliases(a => a.Alias(“error_log”))” ==> Var olan “log_history” index’ine “error_log” adında alies bir isim tanımlanmıştır. Burada amaç eski index kayıtlarının, var olan index silindiğinde ya da silinmesi gerektiğinde kaybolmasını engellemektir. Bunun için eski Index’e ait alies, örneğin burada “error_log” yeni yaratılacak Index’e yeni alies olarak atanması ile mümkün kılınır.
  • Sıra geldi gönderilen hatanın Elastic Search’e kaydedilmesine:
    • var conn=connSettings .DefaultIndex(“log_history”)” : Önceden oluşturulan static connSettings’e default olarak “log_history” index’i atanır.
    • Add(typeof(Log), “log_history”))“: Alınacak data tipi olarak “log” sınıfı tanımlanır.
    • var client = new ElasticClient(conn);” : Yeni bir connection oluşturulur.
    • client.Index<Log>(log, idx => idx.Index(“log_history”))” :Gönderilen log datası ilgili Index altına kaydedilir.
  • //elasticClient.DeleteIndex(“log_history”)” : İstenir ise aşağıda yorum satırı haline getirilen DeleteIndex() methodu ile oluşturulan “error_log” index’i silinebilir.

Şimdi gelin bir MVC projeyi .Net Core ile oluşturalım ve ilk hatamızı WebApi servisimize gönderelim.

“dotnet new mvc -o elasticWeb” : Komutu ile ilgili Mvc Web application oluşturulur.

Home/Index() : Test amaçlı Index() Action’ınında “1/0” işlemi yapılarak hataya düşünülmesi sağlanmıştır. Daha sonra oluşturulan Log sınıfı “http://localhost:1453/api/logs” webapi servisine gönderilmiştir. İlk etapta hata yakalama işlemi try{} catch{} blokları içerisinde yapılmaktadır. İlgili Log sınıfına, Kullanıcı ID(UserID), hata mesajı(message) ve hatanın oluştuğu zaman (DateTime.Now) atanmaktadır.

Test amaçlı hata fırlatan static A() methodu aşağıdaki gibi tanımlanır: Bu sınıf About ve Contact sayfalarında çağrılarak, test amaçlı hata oluşması sağlanmıştır.

Home/About() : Aşağıda görüldüğü gibi özellikle test amaçlı hata fırlatmak amacı ile “A(null)” methodu çağrılmıştır. Yine burada hata, şimdilik try{} catch{} blokları arasında yakalanmkatadır.

Home/Contact() :  Aşağıda görüldüğü gibi özellikle test amaçlı hata fırlatmak amacı ile “A(“”)” methodu çağrılmıştır.

Mvc Web appliction çalıştırılır ve sıra ile Home , About ve Contact sayfalarına girilir ise oluşan toplam 3 hata elastic search’e yazılır.

Webapi servisine ilk gelinildiğinde index daha hiç yaratılmadığı için “log_history” ya da alies’i “error_log” index’i create edilir. Daha sonra ilgili “log” hatası index’e yazılır.

Kibana Log: 3 Hata da oluştuktan sonra kibanada aşağıdaki gibi bir log listesi oluşur.

Postman’de : “http://localhost:9200/log_history/log/_search” şeklinde bir search işlemi yapıldığında aşağıdaki gibi bir sonuç listesi dönmektedir. İlk dönen node “log_history” Index, diğer notlar error yani hata loglarıdır.

Gelin şimdi son bir haftaya kadar olan sadece hata mesajlarını çekip, Index sayfasında bir radio List olarak listeleyelim.

Öncelikle WebApi servisine aşağıdaki gibi “GetAllErrors()” methodunu ekleyelim:

elasticService (Project):

Log/GetAllErrors() :

  • await elasticClient.SearchAsync<Log>(p => p : Log Document’a göre asenkron bir arama yapılacağı anlamına gelmektedir.
  • “.Source(f=>f.Includes(p2=>p2.Field(f2=>f2.message)))” : Eğer istenirse performans amaçlı sadece tekbir alanın(message) çekilmesi yandaki gibi “Includes()” methodu ile belirlenebilir. Bu yazılmadığı takdirde document’e ait tüm fieldlar çekilir.
  • “.Query(q => q .MatchAll()” : Tüm kayıtların çekileceği anlamına gelmektedir.
  • “.PostFilter(f => f.DateRange(r => r.Field(f2 => f2.PostDate)” : Query işleminden sonra dönen kayıtlar arasında, Tarihe göre “Log.PostDate” filed’ında bir filitreleme işlemi yapılacağı belirtilir.
  • “.GreaterThanOrEquals(DateTime.Now.AddDays(-7))))” : İlgili tarih alanınıdan son 7 günün kayıtları filitrelenir.
  • “foreach (var document in response.Documents)” : Dönen cevabın içindeki Documentler gezilir.
  • “result.Add(document.message)” : Sadece Log.message field’ı, bir string[ ] diziye atılır.
  • “return result.Distinct().ToList()” => Ve string[ ] sonucu tekilleştirilerek asenkron olarak geriye dönülür.

Not: Tüm data çekildikten sonra Distinct() methodu yerine, elasticsearch’de “Aggregation“‘lar yazılarak da unique kayıt gelmesi sağlanabilirdi.

elasticWeb (Project):

Home/Index (): İlgili Mvc Action’ına aşağıdaki kodlar eklenir. Bu kodlar hem hatanın olmadığı, hem de hatanın olabileceği durumlar için eklenmiştir.

  • using (HttpClient client = new HttpClient()) : Yeni bir HttpClient oluşturulur.
  • var result = await client.GetAsync(“http://localhost:1453/api/logs”) : Yukarıda WebApi servisinde yazılan “GetAllErrors()” methodu çağrılır.
  • var data = JsonConvert.DeserializeObject<List<string>>(result.Content.ReadAsStringAsync().Result) : Geriye dönülen değer, List of String’e(List<string>) deserialize edilir.
  • return View(data) : WebApi’den List<string>’e deserialize edilmiş data Index view’ına gönderilir.

Index.cshtm: Yukarıda görüldüğü gibi Unique olarak çekilen List<string> hata mesajları, ekrana Radio Button Listesi olarak basılmaktadır.

Şimdi gelin seçilen hata mesajının toplam sayısını ve detayını nasıl bulacağımızı hep beraber inceleyelim.

Yukarıda görüldüğü gibi seçilen radio button’daki hata mesajı, “Home/Detail” Action’ına aşağıdaki gibi gönderilmektedir.

Home/Detail:  Gelen string error değer parametre olarak, ilgili webapi servisine post edilir. Servisden geriye dönen List<Log> result, Detail.cshtml sayfaya view model olarak gönderilir.

Home/Detail.cshtml: Yukarıda görüldüğü gibi aranan hataya göre bulunan toplam kayıt sayısı ve hataların detayı ekrana zamana göre descending yani yakın zamana göre sıralanarak basılmıştır. Makalenin devamında detay için yazılan bu webservisini hep beraber inceleyeceğiz.

elasticService(WebApi Project):

Logs/Get(): Aşağıda görüldüğü gibi Web tarafında ilgili radio listesinden seçilen hata mesajına göre , elastic search’den ilgili message sorgulanıp, önceden bu tipte oluşmuş tüm hatalar bulunur ve geri dönülür.

  • .Query(q => q.Term(f => f.UserID, 1)” : İlk yorumlanan “Term” kısmı, UserID’ye göre filitreleme işleminin de yapılabildiği kısımdır.
  • var response = await elasticClient.SearchAsync<Log>(p => p” : ElasticSearch’den “Log” document tipinde bir asenkron aramanın yapılacağı tanımlanmıştır.
  • .Query(q => q .Match(m => m .Field(f => f.message) .Query(error) )” :  “Log” sınıfının “message” field’ında, gelen error string değeri aranır.
  • .Operator(Operator.And)” : Burası önemli arkadaşlar! Diyelim ki “Zero(1)-length string invalid Parameter(2) name(3): argument(4)” hatasını arattık. “Match()” default query methodu, bu cümlede geçen tüm kelimeleri tek tek “OR” ile arar. Yani eğer yandaki “Operator.And” eklenmez ise sonuç olarak
    • Attempted to divide by zero(1).
    •  “Value cannot be null. Parameter(2) name(3): argument(4)” hata mesajları da, aranan cümledeki renkli olarak gösterilen kelimeleri içerdikleri için gelirler. Bunu önlemenin yolu, kelimeleri tek tek değilde cümle bazında eşleniğine bakılmasıdır. Bunun yolu kelimeler arası “And” koymaktan geçer.
  • .Sort(s=>s.Descending(f=>f.PostDate))” : Çekilen Query “PostDate” alanına göre, en yakın tarih en üstte olucak şekilde sıralanır.
  • foreach (var document in response.Documents)” : Bulunan dökümanlar “List<Log>” bir diziye eklenerek geri dönülür.

Sonu en güzeli:) Bu makalenin sonunda bir çoğunuzun kafasında projede her yere try{} cath{}’mi yazacağız ya da var olan projeme elastik search’ü en hızlı şekilde nasıl entegre ederim gibi sorular vardır. Bu son bölümde golabal olarak hataları yakalayacağız. Yani şu ana kadar yazdığımız try{} catch{}’lerin hepsini kaldırıp kodları kısaltacağız. Ve hata sistemini tekbir yerden yöneteceğiz.

Global Exception Handling :

Öncelikle Startup.cs’e “UseMvc()” methodundan önce, “app.UseExceptionHandler(“/Home/Error”)” tanımlaması yazılır. Yakalanamayan hatalarda çalıştırılacak(Unhandled Exception) Action burada en tepede tanımlanır. Hata olması durumunda “Home/Error” action’ına gidilecektir.

Startup.cs:

elasticWeb (Application): Sayfalara göre yapılacak değişiklikler.

Home/Index : Aşağıda görüldüğü gibi try{} catch{} bloğu kaldırılmıştır. Hata globalde yakalanacaktır.

Home/About: Aşağıda görüldüğü gibi hata blokları tamamen kaldırıldı. Hata globalde yakalanacaktır.

Home/Contact:  try{} catch{} bloğu, yine burada da kaldırılmıştır. Hata globalde yakalanacaktır.

Home/Error: Global olarak hatanın yakalandığı ve elastic search’e yazıldığı yerdir. 

  • var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>()“: Yakalanan hata exceptionFeature’a atanır.
    • “message = exceptionFeature.Error.Message” : Gelen hatanın mesajı bu şekilde alınıp atanır.
    • UserID =GetUserIDByPage(exceptionFeature.Path.Replace(“/Home/”,””))” : Burada amaç hatanın geldiği sayfanın nasıl yakaladığını ve herbiri için farklı işlemlerin nasıl yapıldığı göstermektir. Mesela burada örnek amaçlı olarak, hatanın gelindiği sayfaya göre UserID değişmektedir. Bu case “GetUserIDByPage()” methodu ile yapılmaktadır.

GetUserIDByPage(): Kendisine gelen sayfalara göre farklı UserID’ler i geriye döndürmektedir.

Geldik bir makalenin daha sonuna. Bu makalede .Net Core ile Elastic Search’ün ne kadar da güzel çalıştığını, kurulumundan kodlanmasına kadar beraberce inceledik. Kurulumu sırasında Elastic Search üzerinde Mapping, Replica, Sharding, Aliases gibi terimleri kullandık. Bunun haricinde hata loglarının, proje bazında globalde nasıl yakalanacağını ve bunların elastik search’de nasıl saklanacağını gördük. Son olarak oluşan hataların Unique bir listesini, yine yazdığımız bir WebApi ile çekip, seçilen hatanın detay listesini ekrana döktük. Bu işlemleri yaparken Elastic Search üzerinde Query, Fitler, Term, Operator, Match, PostFilter gibi kavramları inceledik.

Yeni bir makalede  görüşmek üzere hoşçakalın.

Source Code: https://github.com/borakasmer/CoreElasticSearch

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

4 Cevaplar

  1. Elif dedi ki:

    Super ve cok faydali bir yazi. Emeginiz icin cok tesekkur ediyorum.

  2. Üstadım Merhaba, Uzun zamandır Apache Solr kullanmaktayım. Config olarak daha kolay gibi geldi Solr dan. Kibana ile kullanımına dikkat etmemiştim bu makalede basic olarak kullanımı çok işime yaradı. Detaylı bir şekililde bir projeye dahil edilebilir. Yalnız burada Elastic artı ve eksileri diye bir makale göremedim bence bizler için o da çok faydalı olur :) Ağzına sağlık.

    • borsoft dedi ki:

      Selam Hocam,

      Öncelikle teşekkürler. Evet kesinlikle eksikleri de yazmak lazım. Ama genelde Elastic çok başarılı hocam. Solr da bu da lucene altyapısını kullanıyor.
      Zamanım olunca gördüğüm yaşadığım eksikleri de kalem alırım.

      Hoşçakal.
      Bora.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir