İçeriğe geç
academia.sh

Ders 11 / 12

Günlük Düzeyleri ve Yapılandırılmış Günlük

Aynı çağrı kümesinin iki ayrı eşikte kaç kayıt ürettiği koşturularak ölçülür ve eşiğin çağrı yerinde değil koşumda belirlendiği gösterilir; yapılandırılmış bir kaydın okunabilirliğinin alan sayısıyla değil aynı alanın her kayıtta bulunmasıyla ölçüldüğü örneklenir. Sınırlayıcı ölçüm, eşiğin altında kalan bir çağrının dizgi birleştirmeyle yazıldığında yine de değerlendirildiğini, Supplier ile yazıldığında değerlendirilmediğini gösterir.

İçindekiler

Önceki ders bir adın yola nasıl girdiğini ölçtü: bildirimle, sırayla, ya da hiç girmeden. Bu ders aynı çağrı kümesini sabit tutup değişen tek şeyi bir eşik yapıyor. Cephe bir kayıt çağrısını gerçekleştirime taşıdı; şimdiki soru, o çağrının gerçekten bir kayda dönüşüp dönüşmeyeceğini kimin belirlediği. Çağrı yerinde mi, yoksa programı çalıştıran tarafın verdiği bir ayarda mı?

Günlük düzeyi ve yapılandırılmış günlük kavramları bu kursta yeni değil — M22/K06 Gözlemlenebilirlik ve İşletim ve M19/K06 Başarım Karşıt Kalıpları ve İzleme ikisini de kavram olarak kurdu: düzeylerin sıralı bir ölçek olduğu, yapılandırılmış bir kaydın serbest metinden farkı. Bu ders o kavramları tekrar tanımlamıyor; tek sorduğu şey, Java’nın standart günlükleme kütüphanesinde bu iki kavramın koşarken nasıl davrandığı. Ölçüm java.util.logging ile yapılıyor — üçüncü taraf bir günlükleme gerçekleştirimi değil, dilin kendi standart kitaplığı. Önceki dersteki cephe/gerçekleştirim ayrımı burada da geçerli: bir çağrı yerinin gunlukcu.info(...) yazması, o çağrının bir kayda dönüşeceğinin garantisi değil. Aradaki fark, önceki derste “gerçekleştirim bulundu mu” sorusuna, bu derste “düzey eşiği geçti mi” sorusuna bakıyor olması; ikisi de çağrı yerinin bilmediği, koşum kurulumunun verdiği bir yanıt.

Eşik Çağrı Yerinde Değil, Koşumda Belirleniyor

GL9. Bir günlük çağrısının işlenip işlenmeyeceği, çağrının taşıdığı düzeyin günlükçüye o an atanmış eşikle karşılaştırılmasıyla belirlenir. GL10. Bu karşılaştırma koşum zamanında olur; aynı derlenmiş çağrı kümesi, çalıştırma sırasında verilen eşiğe göre farklı sayıda kayıt üretir.

// EsikSayaci.java — ayni cagri kumesi iki ayri kosum esiginde kac kayit uretiyor
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class EsikSayaci {
    static class SayanIsleyici extends Handler {
        List<String> yayimlanan = new ArrayList<>();
        public void publish(LogRecord r) { yayimlanan.add(r.getLevel() + " " + r.getMessage()); }
        public void flush() { }
        public void close() { }
    }

    static int calistir(Level esik) {
        Logger gunlukcu = Logger.getLogger("EsikDenemesi-" + esik);
        gunlukcu.setUseParentHandlers(false);
        SayanIsleyici h = new SayanIsleyici();
        h.setLevel(Level.ALL);
        gunlukcu.addHandler(h);
        gunlukcu.setLevel(esik);

        gunlukcu.fine("ayrinti");
        gunlukcu.info("ilerleme");
        gunlukcu.warning("uyari");
        gunlukcu.severe("kritik");

        return h.yayimlanan.size();
    }

    public static void main(String[] args) {
        int infoEsigindeUretilen = calistir(Level.INFO);
        int uyariEsigindeUretilen = calistir(Level.WARNING);

        System.out.println("dort cagri: fine, info, warning, severe");
        System.out.println("esik INFO iken uretilen kayit    : " + infoEsigindeUretilen);
        System.out.println("esik WARNING iken uretilen kayit : " + uyariEsigindeUretilen);
    }
}
dort cagri: fine, info, warning, severe
esik INFO iken uretilen kayit    : 3
esik WARNING iken uretilen kayit : 2

calistir yöntemi ikisinde de birebir aynı dört çağrıyı yapıyor: fine, info, warning, severe. Değişen tek şey gunlukcu.setLevel(esik) satırına verilen değer. INFO eşiğinde fine çağrısı süzülüyor, geri kalan üçü (info, warning, severe) işleyiciye ulaşıyor (GL9). Eşik WARNING‘e çekildiğinde info de süzülüyor, yalnız ikisi kalıyor. Bu iki koşum aynı sınıf dosyasını kullanıyor — hiçbir yeniden derleme yok — ve yalnızca çalıştırma anında verilen bir değerle farklı sayıda kayıt üretiyor (GL10). Çağrı yerindeki gunlukcu.info(...) satırı ne INFO’nun geçeceğini ne geçmeyeceğini biliyor; bunu bilen taraf, günlükçüye eşiği atayan koşum kurulumu.

GL11. Eşik tek bir ayar değildir: günlükçünün kendi eşiği ve ona bağlı her işleyicinin kendi eşiği ayrı ayrı uygulanır; bir kaydın yayımlanması için ikisini de geçmesi gerekir.

// IkiKatmanliEsik.java — gunlukcunun ve isleyicinin ayri esikleri var, kayit ikisini de gecmek zorunda
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class IkiKatmanliEsik {
    static class SuzenIsleyici extends Handler {
        List<String> yayimlanan = new ArrayList<>();
        public void publish(LogRecord r) {
            if (!isLoggable(r)) return;
            yayimlanan.add(r.getLevel() + " " + r.getMessage());
        }
        public void flush() { }
        public void close() { }
    }

    static int calistir(Level gunlukcuEsigi, Level isleyiciEsigi) {
        Logger gunlukcu = Logger.getLogger("IkiKatman-" + gunlukcuEsigi + "-" + isleyiciEsigi);
        gunlukcu.setUseParentHandlers(false);
        SuzenIsleyici h = new SuzenIsleyici();
        h.setLevel(isleyiciEsigi);
        gunlukcu.addHandler(h);
        gunlukcu.setLevel(gunlukcuEsigi);

        gunlukcu.info("ilerleme");
        gunlukcu.warning("uyari");
        gunlukcu.severe("kritik");

        return h.yayimlanan.size();
    }

    public static void main(String[] args) {
        System.out.println("uc cagri: info, warning, severe");
        System.out.println("gunlukcu ALL, isleyici ALL     -> uretilen: " + calistir(Level.ALL, Level.ALL));
        System.out.println("gunlukcu ALL, isleyici WARNING -> uretilen: " + calistir(Level.ALL, Level.WARNING));
        System.out.println("gunlukcu WARNING, isleyici ALL -> uretilen: " + calistir(Level.WARNING, Level.ALL));
    }
}
uc cagri: info, warning, severe
gunlukcu ALL, isleyici ALL     -> uretilen: 3
gunlukcu ALL, isleyici WARNING -> uretilen: 2
gunlukcu WARNING, isleyici ALL -> uretilen: 2

Üç kurulumun üçü de aynı üç çağrıyı yapıyor. Günlükçü de işleyici de eşiği ALL iken üçü de geçiyor. İkisinden yalnız biri WARNING’e çekildiğinde — fark etmez hangisi — sonuç aynı: iki kayıt (GL11). Eşiği yalnız günlükçüde arayan bir okuma yanlış olurdu; SuzenIsleyici.publish kendi isLoggable denetimini yapmasaydı, işleyicinin setLevel çağrısı hiçbir şeyi süzmezdi ve son iki satır da üç kayıt verirdi. Eşiğin koşumda belirlenmesi bu yüzden tek bir sayı değil, zincirdeki her halkanın kendi kararı. Bu, bir günlükçüye birden fazla işleyici bağlandığında pratik bir sonuç doğuruyor: aynı kayıt kümesi bir işleyicide görünüp ötekinde görünmeyebilir, çünkü her işleyici kendi eşiğini kendi kararıyla uyguluyor ve günlükçünün eşiği yalnız ilk süzgeç.

Yapılandırılmış Kayıt: Alan Sayısı Değil Alan Tutarlılığı

GL12. Yapılandırılmış bir kaydın alan adları çağrı yerinde rastgele seçilmez; üretici tarafın uyduğu sabit bir bildirimde durur, ve okuyan taraf hangi alanı arayacağını bu bildirime bakarak bilir. GL13. Bir günlük akışının okunabilirliği tek bir kaydın taşıdığı alan sayısıyla değil, aynı alanın her kayıtta bulunmasıyla ölçülür.

Aşağıdaki ölçüm iki tasarımı karşılaştırıyor. Birincisinde her kayıt aynı iki alanı taşıyor: olay ve sayi. İkincisinde çağrı yerleri kendi başlarına buyruk: bazıları fazladan bir kaynak alanı ekliyor, bazıları sayi alanını hiç yazmıyor. Biçimlendirici ikisinde de aynı — kayıt zaten çağrı yerinde tam metin olarak kuruluyor, biçimlendirici yalnız zaman alanı eklemeden onu iletiyor (ortama bağlı veri yasağı burada da geçerli: zaman damgası hiç basılmıyor).

// AlanTutarliligi.java — okunabilirlik toplam alan sayisiyla degil, ayni alanin her kayitta bulunmasiyla olculuyor
import java.util.logging.*;
import java.util.ArrayList;
import java.util.List;

public class AlanTutarliligi {
    static class AlanBicimlendirici extends Formatter {
        public String format(LogRecord r) { return r.getMessage() + System.lineSeparator(); }
    }

    static class ToplayanIsleyici extends Handler {
        List<String> kayitlar = new ArrayList<>();
        Formatter b = new AlanBicimlendirici();
        public void publish(LogRecord r) { kayitlar.add(b.format(r)); }
        public void flush() { }
        public void close() { }
    }

    static boolean ikiAlanDaVar(String kayit) {
        return kayit.contains("olay=") && kayit.contains("sayi=");
    }

    static int toplamAlanSayisi(List<String> kayitlar) {
        int toplam = 0;
        for (String k : kayitlar) {
            for (String parca : k.trim().split(" ")) if (parca.contains("=")) toplam++;
        }
        return toplam;
    }

    public static void main(String[] args) {
        List<String> tutarli = new ArrayList<>();
        for (int i = 1; i <= 5; i++) tutarli.add("olay=siparis sayi=" + i);

        List<String> tutarsiz = List.of(
                "olay=siparis sayi=1 kaynak=web",
                "olay=siparis sayi=2 kaynak=web",
                "olay=siparis sayi=3 kaynak=mobil",
                "olay=siparis",
                "olay=siparis");

        long tutarliTam = tutarli.stream().filter(AlanTutarliligi::ikiAlanDaVar).count();
        long tutarsizTam = tutarsiz.stream().filter(AlanTutarliligi::ikiAlanDaVar).count();

        System.out.println("tutarli tasarim  - toplam alan: " + toplamAlanSayisi(tutarli)
                + ", iki alani da tasiyan kayit: " + tutarliTam + "/" + tutarli.size());
        System.out.println("tutarsiz tasarim - toplam alan: " + toplamAlanSayisi(tutarsiz)
                + ", iki alani da tasiyan kayit: " + tutarsizTam + "/" + tutarsiz.size());
    }
}
tutarli tasarim  - toplam alan: 10, iki alani da tasiyan kayit: 5/5
tutarsiz tasarim - toplam alan: 11, iki alani da tasiyan kayit: 3/5

AlanBicimlendirici ve ToplayanIsleyici bu ölçümde asıl işi yapmıyor — kaydı olduğu gibi topluyorlar, çünkü asıl fark çağrı yerinde kuruluyor. Tutarsız tasarımın topladığı alan sayısı tutarlıdan daha yüksek: 11’e karşı 10 (GL12’nin bildirime uyma zorunluluğu burada yok, her çağrı yeri kendi kararını veriyor). Buna rağmen sayi alanını her kayıtta arayan bir ayrıştırıcı tutarlı tasarımda beş kaydın beşini de okuyor, tutarsız tasarımda yalnız üçünü (GL13). İki kayıt yalnızca olay taşıyor; bu kayıtlarda sayi yok, ve ayrıştırıcı için “yok” ile “sıfır” aynı şey değil. Fazladan alan eklemek — üçüncü ve dördüncü kayıtlardaki kaynak — ayrıştırılabilirliği artırmıyor; yalnız eksik kalan alan azaltıyor. Bir günlük akışını makineyle okunabilir yapan şey, her kaydın aynı sabit alan kümesini taşıması; hangi alanların bu kümede olduğu değil.

Bu ayrımın pratik sonucu, bir günlük akışını sonradan işleyen tarafın davranışında görünür. Binlerce kayıt üzerinden sayi alanına göre bir toplam çıkarmak isteyen bir okuyucu, tutarlı akışta beş kaydın beşini de güvenle işleyebilir; tutarsız akışta iki kaydı ya atlamak ya da özel bir durum olarak ele almak zorunda kalır. Alan sayısının kayıttan kayda değişmesi bir hata değildir — AlanTutarliligi örneğinde hiçbir istisna fırlamıyor, hiçbir çağrı reddedilmiyor — ama okuyucunun her alanı var sayamaması, o akışı tek tek elle okumaya zorlar. Yapılandırılmış günlüğün asıl kazancı burada: alan adlarını sabit bir bildirime bağlamak, okuyucuyu bu varsayımdan kurtarıyor.

Sınırlayıcı Ölçüm: Eşiğin Altındaki Çağrı Bedelsiz Değil

GL14. Eşiğin altında kalan bir çağrının ilettiği ifade, dizgi birleştirmeyle yazıldığında düzeyden bağımsız olarak değerlendirilir. GL15. Aynı çağrı bir Supplier ile yazıldığında ifade yalnız düzey etkinken değerlendirilir; eşiğin altında kalan çağrılarda değerlendirme hiç çalışmaz.

Önceki iki ölçüm eşiğin üretilen kayıt sayısını belirlediğini gösterdi. Bundan “eşiğin altında kalan çağrı bedelsizdir” sonucu çıkarılabilir gibi görünüyor, ama bu yanlış: eşik yalnız kaydın yayımlanıp yayımlanmayacağını belirliyor, çağrıya verilen argümanın hesaplanıp hesaplanmayacağını değil — hesaplama zamanı Java’nın yöntem çağırma kuralının konusu, günlükleme çerçevesinin değil.

// BedelliCagri.java — esigin altinda kalan cagrinin argumani dizgi birlestirmeyle her zaman, Supplier ile yalniz duzey acikken hesaplaniyor
import java.util.logging.*;

public class BedelliCagri {
    static int hesaplananIfade = 0;

    static String pahaliIfade() {
        hesaplananIfade++;
        return "deger=" + hesaplananIfade;
    }

    public static void main(String[] args) {
        Logger gunlukcu = Logger.getLogger("BedelDenemesi");
        gunlukcu.setUseParentHandlers(false);
        gunlukcu.addHandler(new Handler() {
            public void publish(LogRecord r) { }
            public void flush() { }
            public void close() { }
        });
        gunlukcu.setLevel(Level.WARNING);

        hesaplananIfade = 0;
        for (int i = 0; i < 5; i++) {
            gunlukcu.fine("ayrinti: " + pahaliIfade());
        }
        int istekliDegerlendirmeSayisi = hesaplananIfade;

        hesaplananIfade = 0;
        for (int i = 0; i < 5; i++) {
            gunlukcu.log(Level.FINE, () -> "ayrinti: " + pahaliIfade());
        }
        int tembelDegerlendirmeSayisi = hesaplananIfade;

        System.out.println("gunlukcunun esigi WARNING, bes cagrinin besi de FINE seviyesinde");
        System.out.println("dizgi birlestirmeyle (istekli) degerlendirilen ifade sayisi : " + istekliDegerlendirmeSayisi);
        System.out.println("Supplier ile (tembel) degerlendirilen ifade sayisi          : " + tembelDegerlendirmeSayisi);
    }
}
gunlukcunun esigi WARNING, bes cagrinin besi de FINE seviyesinde
dizgi birlestirmeyle (istekli) degerlendirilen ifade sayisi : 5
Supplier ile (tembel) degerlendirilen ifade sayisi          : 0

Günlükçünün eşiği WARNING; beş çağrının beşi de bundan daha düşük olan FINE düzeyinde ve hiçbiri bir kayda dönüşmüyor. Buna rağmen gunlukcu.fine("ayrinti: " + pahaliIfade()) satırında pahaliIfade() beş kez de çalışıyor ve sayaç beşe çıkıyor (GL14): Java bir yöntemi çağırmadan önce argümanlarını hesaplar, çağrının kendisi eşiğin altında kalacak olsa bile bu sıra değişmiyor. İkinci döngü aynı beş çağrıyı Logger.log(Level, Supplier<String>) biçimiyle yapıyor; bu aşırı yüklenmiş biçim, Supplier’ı yalnızca düzey etkinse çağırıyor — eşiğin altında kalan beş çağrının beşinde de get() hiç işletilmiyor ve sayaç sıfırda kalıyor (GL15). İki yazım da aynı dört düzeyi, aynı eşiği kullanıyor; fark yalnız argümanın ne zaman hesaplandığında. Bu dersin ölçtüğü şey bir süre değil — hiçbir yerde bir zaman ölçülmüyor — yalnız değerlendirilen ifade sayısı.

Bu fark Java’nın yöntem çağırma kuralından geliyor, günlükleme çerçevesinin bir eksikliği değil: gunlukcu.fine(mesaj) çağrılmadan önce mesaj ifadesinin değeri hazır olmak zorunda, çünkü Java bir çağrının argümanlarını çağrı gerçekleşmeden önce hesaplar — bu sıra fine yönteminin içinde eşik denetimi yapıp yapmamasından bağımsız. Supplier bu sırayı değiştirmiyor; yalnız hesaplamayı bir başka yöntemin — get()’in — arkasına saklıyor ve o yöntemi çağırıp çağırmama kararını fine yönteminin içine, eşik denetiminden sonraya taşıyor. Cephenin kendisi de aynı seçime açık: bir günlükleme cephesi çağırana yalnız Supplier alan bir imza sunuyorsa, gerçekleştirim eşiği reddettiğinde ifade hiç çalışmaz; cephe yalnız dizgi alan bir imza sunuyorsa, çağıran ne kadar dikkatli yazarsa yazsın bu bedeli ödemekten kaçamaz. Bu, bir cephenin yalnız hangi yöntemleri çağırmayı değil, hangi yöntemleri sunmayı seçtiğinin de ölçülebilir bir sonucu olduğunu gösteriyor.

Özet

  • Bir günlük çağrısının kayda dönüşüp dönüşmeyeceği, çağrının düzeyiyle günlükçüye atanmış eşiğin koşum zamanında karşılaştırılmasıyla belirlenir; aynı derlenmiş çağrı kümesi farklı eşiklerde farklı sayıda kayıt üretir.
  • Yapılandırılmış bir kaydın alan adları üretici tarafın bildirimde uyduğu sabit bir kümedir; okuyan taraf bu kümeye bakarak hangi alanı arayacağını bilir.
  • Bir günlük akışının okunabilirliği toplam alan sayısıyla değil, aynı alanın her kayıtta bulunmasıyla ölçülür; fazladan alan eklemek eksik kalan bir alanı telafi etmez.
  • Sınırlayıcı ölçüm: eşiğin altında kalan bir çağrının argümanı dizgi birleştirmeyle yazıldığında yine de hesaplanır; Supplier ile yazıldığında yalnız düzey etkinken hesaplanır.
  • Ölçülen şey bir süre değil, bir sayıdır: kaç kayıt üretildi, kaç alan tutarlıydı, kaç ifade hesaplandı.

Sonraki Adım

Bu iki ders boyunca ölçülen her şey aynı çalışma zamanının içindeydi: aynı sanal makine, aynı sınıf dosyası biçimi, tek bir kaynak dilden gelen kaynaklar. Sıradaki ders bu son varsayımı kaldırıyor: koşum sınıf yoluna giren bir sınıf dosyası, kaynağı hiç Java diliyle yazılmamış olsa bile aynı biçimde mi karşılanıyor, ve çağıran bunu fark ediyor mu?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat