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ğerdizimi, 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
intalan da metin olarak geliyor, geri kurmak okuyan tarafın kendi tarifini gerektiriyor. - Yanlış bir tarif (bir
boolalanıintdiye 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.