İçeriğe geç
academia.sh

Ders 10 / 12

Yapılandırılmış Günlükleme

Aynı olay iki biçimde günlüğe yazılır: düz metin ile anahtar-değer temelli günlük. Anahtar-değer biçiminin, alan sırası değişse bile alan adlarını taşıdığı ve düz metnin kaybettiği ayrıştırılabilirliği koruduğu ölçülür. Sınırlayıcı ölçüm, alan adlarının korunmasına rağmen tiplerin korunmadığını gösteriyor: bir tam sayı da metin olarak geliyor ve geri kurmak okuyan tarafın kendi tarifini gerektiriyor.

İçindekiler

Önceki ders bir zaman değerinin metne dönüştüğünde alan adı taşımadığını, yalnızca konumdan çıkarılabilen ham bir yazım ürettiğini gösterdi. Bu ders aynı sınırı bir günlük kaydı üzerinde soruyor: bir olayı metne dökerken alan adlarını da taşımak mümkün mü, ve taşımak neyi değiştiriyor? Yapılandırılmış günlükleme kavramının kendisi — neden günlük tutulur, düzey nedir, bir günlük hattı nasıl kurulur — Gözlemlenebilirlik ve İşletim kursu ile Derleme, Test ve Ekosistem kursunda kurulmuştu ve burada tekrar kurulmuyor. Bu dersin payı dar: aynı olayın iki yazımını yan yana koyup, hangi yazımın hangi alanı ayrıştırılabilir bıraktığını ölçmek.

Bu ayrımın pratikte önemli olduğu yer, günlüğü yazan kod değil, onu sonradan okuyan taraf. Bir günlük kaydı yazıldığı anda genelde bir insan tarafından hemen okunmuyor; biriktiriliyor, aranıyor, süzülüyor — bir arama aracı “adet 10’dan büyük olan kayıtları göster” gibi bir soru sorduğunda, o aracın günlük satırının hangi kelimesinin “adet” olduğunu bilmesi gerekiyor. Düz metin bu soruyu insana bırakıyor, anahtar-değer biçimi ise aracın kendisine bir tutamak veriyor.

Belirlenimcilik notu: günlüklenen olayın zaman damgası önceki dersle aynı sabit metin (2026-08-23T10:00:00Z); gerçek saat bu derste de hiç okunmuyor.

Aynı Olay, İki Biçim

Aşağıdaki Olay yapısı, günlüğe yazılacak tek bir olayı taşıyor: bir kullanıcı adı, bir sayı, bir başarı durumu, bir zaman damgası. Bu olay iki ayrı işlevle metne dönüştürülüyor — biri doğal dilde bir cümle kuruyor, öteki alan adlarını ad=değer biçiminde art arda diziyor.

SS13. — Aynı Olay değeri iki biçimde yazdırılıyor ve her iki satırın boşluklarla ayrıldığında kaç parçaya bölündüğü sayılıyor.

// iki-bicim.go — ayni olay duz metin ve anahtar-deger olarak yaziliyor
package main

import (
	"fmt"
	"strings"
)

type Olay struct {
	Kullanici string
	Adet      int
	Basarili  bool
	Zaman     string
}

func duzMetin(o Olay) string {
	return fmt.Sprintf("%s kullanicisi %d adet islem yapti, basarili=%v (%s)",
		o.Kullanici, o.Adet, o.Basarili, o.Zaman)
}

func anahtarDeger(o Olay) string {
	return fmt.Sprintf("zaman=%s kullanici=%s adet=%d basarili=%t",
		o.Zaman, o.Kullanici, o.Adet, o.Basarili)
}

func main() {
	olay := Olay{Kullanici: "ayse", Adet: 12, Basarili: true, Zaman: "2026-08-23T10:00:00Z"}

	duz := duzMetin(olay)
	kv := anahtarDeger(olay)

	fmt.Println("duz metin    :", duz)
	fmt.Println("anahtar-deger:", kv)

	duzParca := strings.Fields(duz)
	kvParca := strings.Fields(kv)
	fmt.Println("duz metin bosluk-ayrilmis parca sayisi     :", len(duzParca))
	fmt.Println("anahtar-deger bosluk-ayrilmis parca sayisi :", len(kvParca))

	adlandirilmisAlan := 0
	for _, parca := range kvParca {
		if _, _, tamamMi := strings.Cut(parca, "="); tamamMi {
			adlandirilmisAlan++
		}
	}
	fmt.Println("anahtar-deger icinde adi olan alan sayisi   :", adlandirilmisAlan)
}
duz metin    : ayse kullanicisi 12 adet islem yapti, basarili=true (2026-08-23T10:00:00Z)
anahtar-deger: zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true
duz metin bosluk-ayrilmis parca sayisi     : 8
anahtar-deger bosluk-ayrilmis parca sayisi : 4
anahtar-deger icinde adi olan alan sayisi   : 4

Aynı dört alan (kullanıcı, adet, başarı durumu, zaman), düz metin sürümünde sekiz ayrı boşlukla-ayrılmış parçaya dağılıyor — “12” ile “adet” ayrı parçalar, “yapti,” noktalama işaretiyle birlikte tek bir parça. Bu satırdan geriye dönük olarak “adet neydi” sorusunu yanıtlamak, cümlenin sözdizimini bilmeyi gerektiriyor: kaçıncı kelimenin sayı olduğunu, noktalamanın nereye düştüğünü. Anahtar-değer sürümü ise dört alanı dört parçaya bölüyor ve her parçanın kendi adını taşıyor; okuyan taraf cümlenin yapısını bilmek zorunda değil, yalnızca = işaretini arıyor.

Bu fark, tek bir günlük satırı elle okunurken küçük görünüyor — bir insan “12 adet” ifadesini bağlamdan anlıyor. Ama bir günlük hattının gerçek boyutu binlerce satırlık bir akış, ve o akışı okuyan taraf çoğu zaman bir insan değil, alan adına göre süzen ya da sayan bir araç. Böyle bir araç için düz metindeki her cümle kalıbı ayrı bir özel durum: “kullanicisi” sözcüğü her zaman ikinci kelime mi, yoksa bazı olay türlerinde üçüncü mü? Anahtar-değer biçiminde bu soru hiç sorulmuyor, çünkü aracın aradığı şey konum değil ad.

Sıra Bağımsızlığı

Bir günlük hattının gerçek dünyadaki bir özelliği: aynı olay türü, farklı kod yollarından farklı sırayla yazılabiliyor — bir yerde önce zaman sonra kullanıcı, başka bir yerde tam tersi. Bu farklılık kasıtlı bir tutarsızlık değil; günlükleme çağrısı genelde işlevin ihtiyaç duyduğu değerleri elde ettiği sırayla diziliyor, ve bu sıra çağıran işleve göre değişiyor. Düz metinde bu, cümlenin tamamen değişmesi demek; anahtar-değer biçiminde alan adları sabit kaldığı sürece sıra önemsiz kalıyor.

SS14. — Aynı olay iki farklı alan sırasıyla yazdırılıyor; bir arama işlevi her iki satırdan da adet alanını adına göre buluyor.

// sira-bagimsizligi.go — anahtar-deger bicimi alan sirasindan bagimsiz araniyor
package main

import (
	"fmt"
	"strings"
)

type Olay struct {
	Kullanici string
	Adet      int
	Basarili  bool
	Zaman     string
}

func anahtarDegerSirali(o Olay, sira []string) string {
	parcalar := make([]string, 0, len(sira))
	for _, ad := range sira {
		switch ad {
		case "zaman":
			parcalar = append(parcalar, "zaman="+o.Zaman)
		case "kullanici":
			parcalar = append(parcalar, "kullanici="+o.Kullanici)
		case "adet":
			parcalar = append(parcalar, fmt.Sprintf("adet=%d", o.Adet))
		case "basarili":
			parcalar = append(parcalar, fmt.Sprintf("basarili=%t", o.Basarili))
		}
	}
	return strings.Join(parcalar, " ")
}

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	olay := Olay{Kullanici: "ayse", Adet: 12, Basarili: true, Zaman: "2026-08-23T10:00:00Z"}

	satir1 := anahtarDegerSirali(olay, []string{"zaman", "kullanici", "adet", "basarili"})
	satir2 := anahtarDegerSirali(olay, []string{"basarili", "adet", "kullanici", "zaman"})

	fmt.Println("birinci kod yolunun gunlugu:", satir1)
	fmt.Println("ikinci kod yolunun gunlugu :", satir2)

	adet1, _ := alanBul(satir1, "adet")
	adet2, _ := alanBul(satir2, "adet")
	fmt.Println("adet alani iki siradan da ayni geldi mi:", adet1 == adet2)
}
birinci kod yolunun gunlugu: zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true
ikinci kod yolunun gunlugu : basarili=true adet=12 kullanici=ayse zaman=2026-08-23T10:00:00Z
adet alani iki siradan da ayni geldi mi: true

satir1 ile satir2 metin olarak birbirinden tamamen farklı — hiçbir alan aynı konumda değil. Yine de alanBul her ikisinden de aynı adet değerini buluyor, çünkü aradığı şey konum değil ad. Düz metin biçiminde bu arama mümkün olmazdı: “12” kelimesinin adet mi, başka bir sayı mı olduğunu ayırt etmenin tek yolu cümlenin tam yapısını bilmek, ve iki farklı kod yolu iki farklı cümle yapısı üretirse aynı ayrıştırıcı ikisinde de çalışamaz.

Değerin İçinde Boşluk Olduğunda

Yukarıdaki ölçüm alan değerlerinin hiçbirinin boşluk içermediğini varsaydı. alanBul yalnızca strings.Fields ile boşluktan bölüyor; bir değerin kendisi boşluk içerdiğinde bu varsayım bozuluyor ve sonuç bir hata değil, sessiz bir kırpılma.

SS15.kullanici alanına iki kelimelik bir değer (“ayse yilmaz”) veriliyor; aynı alanBul işlevi hem bu alanı hem etkilenmeyen adet alanını arıyor.

// bosluklu-deger.go — deger icinde bosluk olunca alan sessizce kirpiliyor
package main

import (
	"fmt"
	"strings"
)

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	gunlukSatiri := fmt.Sprintf("zaman=2026-08-23T10:00:00Z kullanici=%s adet=%d basarili=%t",
		"ayse yilmaz", 12, true)
	fmt.Println("gunluk satiri:", gunlukSatiri)

	kullanici, _ := alanBul(gunlukSatiri, "kullanici")
	fmt.Printf("cikarilan kullanici alani: %q (beklenen: %q)\n", kullanici, "ayse yilmaz")

	adet, _ := alanBul(gunlukSatiri, "adet")
	fmt.Printf("cikarilan adet alani     : %q\n", adet)
}
gunluk satiri: zaman=2026-08-23T10:00:00Z kullanici=ayse yilmaz adet=12 basarili=true
cikarilan kullanici alani: "ayse" (beklenen: "ayse yilmaz")
cikarilan adet alani     : "12"

kullanici alanı sessizce "ayse"ye kırpılıyor — "yilmaz" kelimesi kendi başına, adı olmayan bir parça olarak kalıyor, ve alanBul bir hata döndürmüyor. adet alanı bu örnekte etkilenmiyor, çünkü ondan önceki kırpılma boşlukla ayrılmış parça sayısını değiştiriyor ama sonraki alanların kendi = işaretleri yine doğru yerde duruyor — ama bu şans eseri, biçimin kendi güvencesi değil. Anahtar-değer biçiminin adlandırılmış ve sıra bağımsız olması, değerlerin kendi içinde ayırıcı karakter (bu örnekte boşluk) taşımayacağını hiç garanti etmiyor; bu biçimi gerçekten güvenilir kılan üretim sistemleri, değerleri tırnak içine alıp kaçış karakterleriyle koruyan bir yazım kullanıyor — bu ders bunu göstermiyor, çünkü amaç biçimin kendisini değil, adsız düz metinle adlandırılmış anahtar-değer arasındaki temel farkı ölçmek.

Bu kırılganlığı tamamen ortadan kaldıran bir yazım Ağ ve Veri Biçimleri konusunda zaten ölçülmüştü: JSON, her alanı adıyla ve açık sınırlarla (tırnak, virgül, süslü parantez) kodluyor, bu yüzden bir değerin içindeki boşluk hiçbir alanı kaydırmıyor. O ölçüm burada tekrar kurulmuyor; burada vurgulanan, basit bir ad=değer diziminin bu güvenceyi kendiliğinden taşımadığı — adlandırılmış olmak başka, sınırları açık olmak başka.

Sınırlayıcı Ölçüm: Alan Adları Korunuyor, Tipler Korunmuyor

Anahtar-değer biçiminin sıra bağımsız ve adlandırılmış olması, alan adlarının korunduğunu gösteriyor. Ama korunan yalnızca ad; alanın kendi Go tipi (int, bool) günlüğe hiç yazılmıyor, hepsi metne dönüşüyor.

SS16.adet alanı günlükten çıkarılıyor ve tipi %T ile okunuyor; ardından yalnızca alan adına bakan bir kod, olası hedef tipi (int) kendi tarifiyle varsayıp değeri geri kuruyor.

// tip-kaybi.go — alan adi korunuyor, tip korunmuyor; geri kurma okuyanin tarifine bakiyor
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	gunlukSatiri := "zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true"

	adetMetin, bulunduMu := alanBul(gunlukSatiri, "adet")
	fmt.Printf("gunlukten cikan adet degeri: %q tip=%T bulundu=%t\n", adetMetin, adetMetin, bulunduMu)

	adetSayi, hataParse := strconv.Atoi(adetMetin)
	fmt.Printf("tarifle (strconv.Atoi) geri kurulan: tip=%T deger=%d hatasiz=%t\n",
		adetSayi, adetSayi, hataParse == nil)

	basariliMetin, _ := alanBul(gunlukSatiri, "basarili")
	_, hataYanlisTarif := strconv.Atoi(basariliMetin)
	fmt.Println("basarili alanini int tarifiyle okumak hata verdi mi:", hataYanlisTarif != nil)
}
gunlukten cikan adet degeri: "12" tip=string bulundu=true
tarifle (strconv.Atoi) geri kurulan: tip=int deger=12 hatasiz=true
basarili alanini int tarifiyle okumak hata verdi mi: true

İlk satır konunun ölçü ekseninde beklenen sonucu doğruluyor: alanBulün döndürdüğü değer, adet alanının Go tarafında bir int olmasına rağmen, günlükten string olarak geliyor — %T bunu string diye gösteriyor. Alan adı günlükte durduğu için alanBul doğru satırı buluyor, ama alanın tipi hiç günlüğe yazılmadığı için okuyan taraf onu yeniden tahmin etmek zorunda. strconv.Atoi çağrısı bu tahmini somutlaştırıyor: metni bir inte çeviriyor ve bu kez başarılı oluyor, çünkü çağıran taraf adet alanının bir sayı olduğunu zaten biliyor — bu bilgi günlükten gelmiyor, günlüğü yazan kodun sözleşmesinden geliyor. Son satır bu tahminin yanlış olabileceğini gösteriyor: basarili alanı metin olarak "true", ve onu bir tam sayı tarifiyle okumaya çalışmak hata veriyor — yanlış tarif, günlüğün kendisinden değil okuyan kodun varsayımından kaynaklanıyor. Bu hata en azından açık: strconv.Atoi bir hata değeri döndürüyor, program çökmüyor ya da sessizce yanlış bir sayı üretmiyor. Ama bu açıklık yalnızca yanlış tarif denendiğinde ortaya çıkıyor; günlüğün kendisine bakan bir kod, hangi alanın hangi tarifle okunması gerektiğini önceden söyleyen hiçbir işaret bulamıyor.

Bu, konunun ölçü ekseninde açık bir sınır çiziyor: anahtar-değer biçimi alan adlarını sınırın öbür tarafına taşıyor, ama alan tiplerini taşımıyor. Aynı sayı, bir kaydın “korunuyor” değil “hiçbir zaman kaybedilmemiş gibi görünen ama aslında hiç taşınmamış” sınıfına giriyor — düz metinden farkı, en azından hangi alanın hangi değer olduğunun belirsiz olmaması; ama değerin tipi, tıpkı önceki dersteki saat dilimi adı gibi, yalnızca okuyan tarafın elindeki bir tarifle geri kuruluyor.

Bu tarif nereden geliyor sorusunun yanıtı, günlüğün kendisinde değil. alanBulü çağıran kod, adet alanının bir sayı olduğunu günlük satırından öğrenmiyor — bunu, günlüğü yazan kodun kaynağına bakarak, ya da bir sözleşme belgesinden öğreniyor. İki taraf (yazan ve okuyan) bu sözleşmeyi paylaştığı sürece sistem doğru çalışıyor; sözleşme değiştiğinde (adet bir gün ondalıklı bir sayıya dönüştürüldüğünde) günlüğün kendisi bunu hiçbir şekilde haber vermiyor — strconv.Atoi yeni biçimi de “geçerli bir tam sayı değil” diye reddedecek, ama neden değiştiğini söylemeyecek.

Özet

  • Aynı olay düz metin olarak sekiz, anahtar-değer olarak dört boşlukla-ayrılmış parçaya dönüşüyor; anahtar-değer biçiminde dört parçanın dördü de adıyla birlikte geliyor.
  • Anahtar-değer biçimi alan sırasından bağımsız: iki farklı sırayla yazılmış aynı olay, aynı alan adına göre arandığında aynı değeri veriyor.
  • Düz metinde bir alanı adına göre bulmanın güvenilir bir yolu yok; cümlenin tam yapısını bilmek gerekiyor, ve o yapı kod yoluna göre değişebiliyor.
  • Basit bir ad=değer dizimi, bir değerin kendisi boşluk içerdiğinde sessizce kırpılabiliyor; adlandırılmış olmak, sınırların açık olmasıyla aynı şey değil.
  • Sınırlayıcı ölçüm: anahtar-değer biçimi alan adlarını koruyor ama tiplerini korumuyor — bir int alan da metin olarak geliyor, geri kurmak okuyan tarafın kendi tarifini gerektiriyor.
  • Yanlış bir tarif (bir bool alanı int diye okumaya çalışmak) sessizce geçmiyor, açık bir hatayla reddediliyor — ama bu ret ancak yanlış tarif fiilen denendiğinde ortaya çıkıyor.

Sonraki Adım

Bu derste alan adları korunuyordu ama tipler her zaman metne dönüşüyordu; okuyan taraf hangi alanın hangi Go tipine karşılık geldiğini kendi bildiğinden çıkarmak zorundaydı. Bu tarif hep kod dışında, insan bilgisinde ya da bir sözleşme belgesinde durdu — günlüğün kendisi hiçbir zaman “bu alan bir tam sayı” diye bir şey söylemedi.

Sıradaki ve bu konunun son dersi, tipin bu kez metne değil, standart kitaplığın kendi tanıdığı dar bir tip kümesine indirgendiği bir sınırı ölçüyor: bir veritabanı sürücüsünün elinden geçen bir satır, hangi tiplerde gelebiliyor, ve hedef tip bu satırdan neyi nasıl geri kuruyor? Bu kez tarif metinde değil, çağıranın Scan çağrısına verdiği hedef değişkenin kendi tipinde duracak — bu derste strconv.Atoinin oynadığı rolü, orada standart kitaplığın kendisi üstlenecek.

İ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