Bir Nokta Yüzünden Gelen Rider Uyarısı
Kod yazarken IDE'nin verdiği her uyarıyı aynı ciddiyetle karşılamıyorum. Bazılarının nedenini zaten biliyorum, bazılarını ise görünce ister istemez durup, bu neden çıktı karşıma diye kısa bi düşünüyorum. Geçenlerde review ederken, Rider'da bir log mesajı için karşıma şu uyarı çıktı:
Log event messages should be fragments, not sentences. Avoid a trailing period/full stop.
Kabaca, log mesajlarının tam cümleler yerine kısa ifadeler şeklinde yazılması ve sonunda nokta bulunmaması gerektiğini söylüyor. Aslında yazdığım mesajda öyle karmaşık bir durum yoktu:
_logger.LogInformation("User logged in successfully.");
Rider, sondaki noktayı kaldırmamı istiyordu:
_logger.LogInformation("User logged in successfully");
Açıkçası ilk tepkim, "Noktadan ne olacak ki?" oldu. Sonuçta iki kullanım da aynı bilgiyi veriyor, okuyan kişilerin ise bunu hiç umursadığını da sanmıyorum. Ee uygulamanın çalışmasında da bir fark yok, log kaydı her durumda oluşuyor.
Ama Rider'ın neden özellikle buna takıldığını merak ettim. Konuyu biraz kurcalayınca meselenin yalnızca noktalama işaretinden ibaret olmadığını gördüm.
Log Yazarken Bir Cümle mi Kuruyoruz, Olay mı Kaydediyoruz?
Normal bir metin yazdığımızda okuyucuya bir şey anlatmaya çalışırız. Cümleyi tamamlar, sonuna noktamızı koyarız. Loglarda ise önceliğimiz biraz farklı. Bir işlemin gerçekleştiğini, başarısız olduğunu veya sistemin belirli bir duruma geçtiğini kayıt altına alıyoruz.
Örneğin:
User logged in successfully
Order created
Payment failed
Database connection established
Bunları okuyunca birer cümleden çok, uygulamada yaşanan olayların kısa açıklamalarını görüyorum. Rider'ın önerisi de bu anlayışa dayanıyor. Mesajı tamamlanmış bir cümle gibi yazmak yerine, gerçekleşen durumu doğrudan ifade etmemizi istiyor.
Peki bu herkesin uyması gereken bir kural mı? Bence, hayır. Zaten log mesajlarının sonunda nokta bulunmaması, C# veya .NET tarafından zorunlu tutulan bir standart değil.
Rider'ın ilgili kod analizinde benimsediği bir stil tercihi. Noktayı kaldırmadığımızda uygulama bozulmuyor, herhangi bir performans kaybı da yaşamıyoruz. Hatta ekip olarak bütün kayıtların noktayla bitmesini tercih ediyorsak bunu da uygulayabiliriz. Yine de bu uyarı bana log yazarken üzerinde durulması gereken başka bir konuyu hatırlattı: Structured logging.
Structured Logging ile Bağlantısı Ne?
Günlük geliştirme sırasında bazen log mesajlarını hızlıca oluşturuyoruz.
Diyelim ki bir sipariş oluşturma işlemi üzerinde çalışıyorum ve başarılı işlemleri kaydetmek istiyorum. İlk akla gelen yöntemlerden biri şu:
_logger.LogInformation($"Order {orderId} created successfully");
Bu kod çalışır. Sipariş numarası metnin içerisine yerleştirilir ve kayıt oluşturulur.
Örneğin:
Order 1024 created successfully
Order 1025 created successfully
Order 1026 created successfully
Buraya kadar bir problem görünmüyor. Ancak daha sonra belirli bir siparişe ait kayıtları bulmak istediğimizi düşünelim. Elimizde yalnızca düz metin varsa mesajın içerisinde arama yapmamız gerekebilir. Üstelik aynı olayın farklı sipariş numaralarıyla oluşan kayıtlarını gruplamak da kullandığımız altyapıya bağlı olarak ek işlem gerektirebilir. İşte burada structured logging, yani yapılandırılmış loglama devreye giriyor.
Aynı örneği şöyle yazabiliriz:
_logger.LogInformation("Order {OrderId} created successfully", orderId);
Bu kez sipariş numarasını doğrudan metne gömmek yerine bir mesaj şablonu kullanıyoruz. {OrderId} ifadesi, ilgili değerin ayrı bir özellik olarak taşınabilmesini sağlıyor. Kullandığımız log sağlayıcısı ve kayıt hedefi destekliyorsa bu bilgi bağımsız bir alan olarak saklanabilir.
Basitleştirilmiş bir JSON örneği üzerinden düşünelim:
{
"Level": "Information",
"Message": "Order 1024 created successfully",
"OrderId": 1024
}
Bu, belirli bir sağlayıcının birebir çıktı formatı değil. Yapılandırılmış kaydın nasıl temsil edilebileceğini göstermek için hazırladığım bir örnek. Burada OrderId artık yalnızca metnin içerisinde geçen bir sayı değil. Log altyapımızın sunduğu imkanlara göre bu alan üzerinden filtreleme yapabilir, ilgili siparişin kayıtlarını inceleyebiliriz.
Benzer şekilde sabit mesaj şablonları, aynı türdeki olayları bir arada değerlendirmeyi de kolaylaştırabilir. Tabii burada önemli bir ayrım var. Structured logging kullanmak için mesajın sonundaki noktayı kaldırmak zorunda değiliz. Şu kod da yapılandırılmış loglama açısından geçerli:
_logger.LogInformation("Order {OrderId} created successfully.", orderId);
Yani Rider'ın uyarısıyla structured logging arasında doğrudan bir teknik bağımlılık bulunmuyor. Biri mesajın yazım biçimiyle, diğeri ise veriyi nasıl kaydettiğimizle ilgili. Benim için bu uyarının asıl faydası, logları yalnızca metin olarak düşünmemek gerektiğini yeniden hatırlatması oldu.
Aynı Olayı Her Seferinde Farklı Yazarsak Ne Olur?
Bir projede birkaç log kaydı varken mesajların nasıl yazıldığına çok dikkat etmeyebiliriz. Ancak uygulama büyüdükçe ve farklı geliştiriciler aynı kod tabanında çalışmaya başladıkça tutarlılık daha önemli hale geliyor.
Örneğin sipariş oluşturma işlemi için üç farklı yerde şu kayıtların tutulduğunu düşünelim:
_logger.LogInformation("Order created.");
_logger.LogInformation("Order has been created");
_logger.LogInformation("Successfully created order");
Hepsi aşağı yukarı aynı olayı anlatıyor. Ama mesaj şablonları birbirinden farklı. Bu kayıtları şablon bazında gruplamak istediğimizde, kullandığımız sistem bunları ayrı olaylar olarak değerlendirebilir. Burada sorunun nokta olmadığını söylemeye gerek bile yok sanırım. Asıl mesele, aynı işlemin farklı biçimlerde tanımlanması. Bunun yerine tüm projede ortak bir şablon kullanabiliriz:
_logger.LogInformation("Order {OrderId} created successfully", orderId);
Böylece hem olay açıklamasını standartlaştırmış hem de değişken bilgiyi ayrı tutmuş oluruz. Özellikle merkezi loglama sistemlerinde bu tür bir tutarlılığın işimizi kolaylaştıracağını düşünüyorum. Yine de yalnızca mesajların aynı şekilde yazılması yeterli değil. Parametre isimlerinin ve log seviyelerinin de belirli bir düzene sahip olması gerekiyor.
Kısa Yazalım Derken Önemli Bilgileri Kaybetmeyelim
Rider'ın uyarısındaki bir diğer ifade de mesajların tam cümleler yerine kısa ifadeler olması gerektiği. Burada biraz dikkatli olmak lazım. Çünkü kısa bir log mesajı her zaman yeterli bilgi veren bir kayıt anlamına gelmiyor. Örneğin bir ödeme işlemi başarısız olduğunda şöyle yazdığımızı düşünelim:
_logger.LogWarning("Payment failed");
Kısa, temiz ve anlaşılır. Ama neden başarısız olduğunu bilmiyoruz, pek bir açıklama yok. Bunun yerine hesapta yeterli bakiye bulunmadığı için işlem reddedildiyse bunu belirtmek daha faydalı olabilir:
_logger.LogWarning("Payment failed due to insufficient funds");
Hatta ödeme numarasını da ekleyebiliriz:
_logger.LogWarning("Payment {PaymentId} failed due to insufficient funds", paymentId);
Bu şekilde hem sorunun nedenini belirtiyoruz hem de ilgili işlemi bulabilmek için bir referans bırakıyoruz. Tabii her bilgiyi loglamak da doğru değil. Özellikle ödeme sistemleri gibi hassas verilerle çalıştığımız alanlarda kart numarası, parola veya benzeri bilgilerin kayıtlara yazılmaması gerekiyor. Kısacası, sırf kısa olsun diye mesajın anlamını azaltmanın bir faydası yok.
Ben log yazarken, ileride bu kaydı gördüğümde ne olduğunu anlayabilecek miyim sorusunu daha önemli buluyorum.
Bir de Exception Loglama Meselesi Var
Konuyu biraz genişletmişken, günlük geliştirme sırasında sık karşılaştığımız bir başka örneğe de değinmek istiyorum. Diyelim ki sipariş oluşturma sırasında beklenmeyen bir hata meydana geldi. Bazen şöyle bir kullanım görebiliyoruz:
try
{
await orderService.CreateOrderAsync(order);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
throw;
}
Burada yalnızca exception mesajını kaydediyoruz. Oysa hatanın nerede oluştuğunu anlamak için stack trace gibi bilgilere de ihtiyaç duyabiliriz. Bunun yerine exception nesnesini doğrudan loglama metoduna iletmek daha faydalı:
try
{
await orderService.CreateOrderAsync(order);
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Order {OrderId} could not be created",
order.Id);
throw;
}
Böylece kullandığımız log sağlayıcısının desteğine bağlı olarak hata mesajı, stack trace ve diğer exception bilgileri kaydedilebilir. Ayrıca hangi sipariş üzerinde işlem yapıldığını da ayrı bir parametreyle belirtiyoruz. Yalnız burada başka bir ayrıntıyı da unutmamak gerekiyor. Hatayı bu katmanda loglayıp yeniden fırlatıyorsak, üst katmanlarda aynı exception'ın tekrar tekrar kaydedilmemesine dikkat etmeliyiz. Aksi halde tek bir hata için birden fazla kayıt oluşturup inceleme sırasında gereksiz kalabalığa neden olabiliriz, bu logları da kimse okumaz, boşuna bir kalabalık yaratmış oluruz.
Bu örneği özellikle eklemek istedim çünkü loglamada mesajın nasıl yazıldığından çok, hangi bilgiyi nasıl kaydettiğimizin önemli olduğunu güzel anlatıyor.
Peki Rider'ın Uyarısını Kapatmalı mıyız?
Buraya kadar geldikten sonra baştaki soruya dönelim. Log mesajının sonundaki nokta gerçekten önemli mi? Teknik açıdan hayır. Şu iki kullanım arasında loglama davranışı bakımından zorunlu bir fark bulunmuyor:
_logger.LogInformation("Order created successfully.");
_logger.LogInformation("Order created successfully");
Nokta, mesaj metninin bir parçası. Mesaj şablonları birebir karşılaştırılıyorsa iki farklı şablon söz konusu olabilir; ancak noktanın varlığı tek başına bir hata veya performans problemi yaratmaz. Projede genel olarak noktasız kullanım benimsenmişse Rider'ın uyarısını dikkate almak mantıklı. Mevcut projede farklı bir yazım standardı varsa sırf bu uyarı nedeniyle bütün kayıtları değiştirmek zorunda değiliz. Benzer şekilde, bu kontrolü gereksiz buluyorsak Rider'ın ilgili inceleme ayarlarından devre dışı bırakmayı da tercih edebiliriz.
Burada önemli olan, hangi biçimi seçtiğimizden çok aynı yaklaşımı proje genelinde sürdürebilmek.
Sonuç
Rider'ın log mesajının sonundaki noktaya takılması, başlangıçta bana biraz gereksiz gelmişti. Konuyu araştırınca bunun teknik bir zorunluluk değil, olay açıklamalarının yazım biçimiyle ilgili bir stil önerisi olduğunu gördüm ve küçük bir uyarı sayesinde loglama alışkanlıklarımı da yeniden düşünmüş oldum.
Günlük geliştirme sırasında bazen yalnızca hata oluştuğunda bakacağımızı düşünerek kayıt tutuyoruz. Oysa doğru hazırlanmış loglar, bir işlemin geçmişini takip etmekten beklenmeyen davranışları araştırmaya kadar pek çok noktada işimizi kolaylaştırabiliyor. Bu yüzden benim için asıl önemli olan, mesajın sonundaki nokta değil; doğru seviyede, yeterli bilgi içeren ve gerektiğinde sorgulanabilen kayıtlar oluşturmak.
Noktayı koyup koymamak ise ekipçe, proje seviyesinde karar verebileceğimiz küçük bir detay.
Keyifli kodlamalar 🖖