Go OpenTelemetry ile uçtan uca API izleme
Go OpenTelemetry izlemeyi, HTTP istekleri, arka plan işler ve üçüncü taraf çağrılar arasında izleri, metrikleri ve logları ilişkilendirmek için uygulanabilir adımlarla açıklayan rehber.

Go API için uçtan uca izleme ne anlama gelir
Bir trace, bir isteğin sisteminizde ilerlerken izlediği zamansal çizelgedir. Bir API çağrısı geldiğinde başlar ve yanıtı gönderdiğinizde biter.
Bir trace içinde span'lar vardır. Span, “isteği ayrıştırma”, “SQL çalıştırma” veya “ödeme sağlayıcısını çağırma” gibi zamanlanan bir adımdır. Span'lar ayrıca HTTP durum kodu, güvenli bir kullanıcı kimliği veya bir sorgunun kaç satır döndürdüğü gibi faydalı ayrıntılar taşıyabilir.
“Uçtan uca”, trace'in ilk handler'ınızda durmadığı anlamına gelir. Trace, sorunların genellikle saklandığı yerleri takip eder: middleware, veritabanı sorguları, cache çağrıları, arka plan işler, üçüncü taraf API'leri (ödeme, e-posta, haritalar) ve diğer dahili servisler.
İzleme, sorunlar aralıklı olduğunda en değerlidir. 200 isteğin birinde yavaşlık varsa, loglar genellikle hızlı ve yavaş durumlar için aynı görünür. Bir trace farkı netleştirir: bir istek 800 ms dış bir çağrıda beklemiş, iki kez yeniden denemiş ve sonra bir takip işi başlatmıştır.
Logları servisler arasında bağlamak da zordur. API'de bir log satırınız, worker'da başka bir log olabilir ve arada hiçbir şey olmayabilir. İzleme ile bu olaylar aynı trace ID'yi paylaşır, böylece tahmin yürütmeden zinciri takip edebilirsiniz.
Trace'ler, metrikler ve loglar: nasıl birbirini tamamlar
Trace'ler, metrikler ve loglar farklı soruları cevaplar.
Trace'ler, gerçek bir istekte ne olduğunu gösterir. Zamanın handler, veritabanı çağrıları, cache aramaları ve üçüncü taraf istekleri arasında nereye gittiğini anlatır.
Metrikler eğilimi gösterir. Uyarılar için en iyi araçtır çünkü stabil ve toplaması ucuzdur: gecikme yüzdeleri, istek oranı, hata oranı, kuyruk derinliği ve doygunluk.
Loglar ise düz metin olarak “neden”dir: doğrulama hataları, beklenmedik girdiler, kenar durumları ve kodunuzun aldığı kararlar.
Gerçek kazanç korelasyondadır. Aynı trace ID span'larda ve yapılandırılmış loglarda göründüğünde, bir hata kaydından tam trace'e atlayabilir ve hangi bağımlılığın yavaşladığını veya hangi adımın başarısız olduğunu hemen görebilirsiniz.
Basit bir zihinsel model
Her sinyali en iyi yaptığı iş için kullanın:
- Metrikler bir şeyin yanlış olduğunu söyler.
- Trace'ler bir isteğin zamanının nereye gittiğini gösterir.
- Loglar kodunuzun ne yaptığını ve nedenini açıklar.
Örnek: POST /checkout uç noktanız zaman aşımına girmeye başlıyor. Metrikler p95 gecikmesinin arttığını gösteriyor. Bir trace, zamanın çoğunun ödeme sağlayıcısı çağrısında olduğunu gösteriyor. O span içindeki ilişkilendirilmiş bir log satırı 502 nedeniyle yeniden denemeleri gösteriyorsa, bu sizi backoff ayarlarına veya yukarı akışta bir olaya yönlendirir.
Koda eklemeden önce: adlandırma, örnekleme ve neyi izleyeceğiniz
Başta biraz planlama, ileride trace'lerin aranabilir olmasını sağlar. Bunu yapmazsanız, yine veri toplayabilirsiniz ama temel sorular zorlaşır: “Bu staging miydi yoksa prod mu?” “Sorunu hangi servis başlattı?”
Tutarlı kimlikle başlayın. Her Go API için net bir service.name seçin (örneğin, checkout-api) ve deployment.environment=dev|staging|prod gibi tek bir environment alanı kullanın. Bunları sabit tutun. İsimler haftanın ortasında değişirse, grafikler ve aramalar farklı sistemler gibi görünür.
Sonra örnekleme (sampling) kararını verin. Geliştirmede her isteği izlemek harika olsa da, üretimde genellikle çok maliyetli olur. Yaygın bir yaklaşım, normal trafiğin küçük bir yüzdesini örneklemek ve hatalar ile yavaş istekleri her zaman saklamaktır. Zaten yüksek hacimli bazı uç noktalarınız varsa (health check'ler, polling), onları daha az veya hiç izlemeden geçebilirsiniz.
Son olarak, span'lara hangi etiketleri ekleyeceğinize ve hangilerini asla toplamayacağınıza karar verin. Hizmetler arasında olayları bağlamanıza yardımcı olacak kısa bir izin listesi tutun ve basit gizlilik kuralları yazın.
İyi etiketler genellikle stabil kimlikler ve kaba istek bilgisi içerir (route şablonu, method, status code). Hassas yükleri tamamen atlayın: parolalar, ödeme verileri, tam e-posta adresleri, kimlik belirteçleri ve ham istek gövdeleri. Kullanıcıyla ilgili bir değeri eklemek zorundaysanız, eklemeden önce hash'leyin veya sansürleyin.
Adım adım: Go HTTP API'ye OpenTelemetry izlemesi ekleyin
Başlangıçta bir kere tracer provider kuracaksınız. Bu, span'ların nereye gideceğini ve her span'a bağlanacak resource attribute'larını belirler.
1) OpenTelemetry'yi başlatın
service.namei ayarladığınızdan emin olun. Bunu yapmazsanız, farklı servislerden gelen trace'ler karışır ve grafikler okunması zor hale gelir.
// main.go (startup)
exp, _ := stdouttrace.New(stdouttrace.WithPrettyPrint())
res, _ := resource.New(context.Background(),
resource.WithAttributes(
semconv.ServiceName("checkout-api"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
Bu, Go OpenTelemetry izleme için temel yapı taşıdır. Sonra gelen her istek için bir span oluşturmanız gerekir.
2) HTTP middleware ekleyin ve ana alanları yakalayın
Otomatik olarak bir span başlatan ve durum kodu ile süresini kaydeden bir HTTP middleware kullanın. Span adına route şablonunu (örneğin /users/:id) kullanın, ham URL'yi değil; aksi halde binlerce benzersiz yol oluşur.
Temiz bir temel hedefleyin: isteğe bir sunucu span'ı, route tabanlı span isimleri, yakalanmış HTTP durum kodu, handler hatalarının span'larda hata olarak görünmesi ve iz görüntüleyicinizde sürenin görünür olması.
3) Hataları belirgin hale getirin
Bir şey ters gittiğinde bir hata döndürün ve mevcut span'ı başarısız olarak işaretleyin. Bu, trace'in loglara bakmadan önce göze çarpmasını sağlar.
Handler'larda şu şekilde yapabilirsiniz:
span := trace.SpanFromContext(r.Context())
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
4) Trace ID'leri yerelde doğrulayın
API'yi çalıştırın ve bir uç noktaya istek gönderin. İstek bağlamından trace ID'sini bir kez loglayın ve her istek için değiştiğini doğrulayın. Eğer hep boşsa, middleware handler'ın aldığı aynı context'i kullanmıyor demektir.
Bağlamı DB ve üçüncü taraf çağrıları boyunca taşıyın
Uçtan uca görünürlük, context.Contexti kaybettiğiniz anda bozulur. Gelen istek bağlamı, her DB çağrısına, HTTP çağrısına ve yardımcı fonksiyona geçirdiğiniz ana iplik olmalıdır. Eğer bunu context.Background() ile değiştirir veya aşağı doğru iletmeyi unutursanız, trace ayrı, ilgisiz işlere dönüşür.
Giden HTTP için, her Do(req) çağrısını mevcut isteğin altında bir child span'a dönüştüren enstrümente edilmiş bir transport kullanın. Aşağı akış servislerin aynı trace'e span ekleyebilmesi için W3C trace başlıklarını (traceparent vb.) yönlendirin.
Veritabanı çağrıları da aynı muameleyi gerektirir. Enstrümente edilmiş bir sürücü kullanın veya QueryContext ve ExecContext etrafında span'lar açın. Sadece güvenli ayrıntıları kaydedin. Amaç, yavaş sorguları bulmak, veri sızdırmak değil.
Yararlı, düşük riskli attribute'lar arasında işlem adı (örneğin SELECT user_by_id), tablo veya model adı, satır sayısı (sadece sayıyı kaydedin), süre, yeniden deneme sayısı ve kaba hata türü (timeout, canceled, constraint) bulunur.
Zaman aşımı olayın bir parçasıdır, sadece hata değil. DB ve üçüncü taraf çağrılar için context.WithTimeout ile zaman aşımı ayarlayın ve iptallerin yukarı doğru yayılmasına izin verin. Bir çağrı iptal olduğunda, span'ı hata olarak işaretleyin ve deadline_exceeded gibi kısa bir neden ekleyin.
Arka plan işleri ve kuyruklarda izleme
Arka plan işleri, trace'lerin sıkça durduğu yerdir. Bir HTTP isteği sona erer, sonra bir worker farklı bir makinede bir mesajı alır ve paylaşılmış bağlam yoktur. Hiçbir şey yapmazsanız iki ayrı hikâye elde edersiniz: API trace'i ve nereden başladığı belirsiz görünen bir iş trace'i.
Çözüm basittir: bir işi kuyruğa koyarken mevcut trace bağlamını yakalayın ve iş metadata'sına (payload, başlıklar veya attribute'lar) kaydedin. Worker başladığında o bağlamı çıkarın ve orijinal isteğin child'ı olarak yeni bir span başlatın.
Bağlamı güvenli şekilde aktarma
Sadece trace bağlamını kopyalayın, kullanıcı verilerini değil.
- Yalnızca trace tanımlayıcılarını ve örnekleme bayraklarını enjekte edin (W3C traceparent tarzı).
- Bunu iş alanlarından ayrı tutun (örneğin, özel bir "otel" veya "trace" alanı).
- Okurken onu güvenilmez girdi gibi ele alın (formatı doğrulayın, eksik veriyi yönetin).
- Tokenleri, e-postaları veya istek gövdelerini iş metadata'sına koymaktan kaçının.
Fazlalığa yol açmadan eklenebilecek span'lar
Okunabilir trace'ler genellikle birkaç anlamlı span içerir, onlarca küçük span değil. Boundary'ler ve "bekleme noktaları" etrafında span'lar oluşturun. İyi bir başlangıç noktası, API handler'da bir enqueue span'ı ve worker'da bir job.run span'ıdır.
Az miktarda bağlam ekleyin: deneme numarası, kuyruk adı, iş türü ve payload boyutu (içerik değil). Yeniden denemeler oluyorsa, bunları ayrı span'lar veya event'ler olarak kaydedin ki backoff gecikmelerini görebilesiniz.
Zamanlanmış görevlerin de bir ebeveyni olmalı. Eğer gelen bir istek yoksa, her çalıştırma için yeni bir root span oluşturun ve ona bir schedule adı etiketleyin.
Logları trace'lerle ilişkilendirme (ve logları güvenli tutma)
Trace'ler zamanın nereye gittiğini söyler. Loglar ne olduğunu ve nedenini anlatır. Bunları bağlamanın en basit yolu, her log girdisine trace_id ve span_id gibi alanlar eklemektir.
Go'da aktif span'ı context.Contextten alın ve logger'ınızı her istek (veya iş) için zenginleştirin. Böylece her log satırı belirli bir trace'e işaret eder.
span := trace.SpanFromContext(ctx)
sc := span.SpanContext()
logger := baseLogger.With(
"trace_id", sc.TraceID().String(),
"span_id", sc.SpanID().String(),
)
logger.Info("charge_started", "order_id", orderID)
Bu, bir log girdisinden o sırada çalışan tam span'a atlamanız için yeterlidir. Ayrıca eksik bağlamı görünür kılar: trace_id boş olacaktır.
PII sızdırmadan logları kullanışlı tutun
Loglar genellikle daha uzun süre yaşar ve daha uzak yerlere gider, bu yüzden daha katı olun. Stabil tanımlayıcılar ve sonuçları tercih edin: user_id, order_id, payment_provider, status ve error_code. Kullanıcı girdisini loglamak zorundaysanız, önce redakte edin ve uzunlukları sınırlandırın.
Hataları gruplayacak şekilde kolay bulunur yapın
Saymak ve aramak için tutarlı event adları ve hata türleri kullanın. Eğer her seferinde ifade değişirse, aynı sorun birçok farklı şeymiş gibi görünür.
Sorun bulmanıza gerçekten yardımcı olacak metrikler ekleyin
Metrikler erken uyarı sisteminizdir. Go OpenTelemetry izleme ile kurulu bir sistemde, metrikler şu soruları cevaplamalı: ne sıklıkta, ne kadar kötü ve ne zamandan beri.
Her API için neredeyse her zaman işe yarayan küçük bir setle başlayın: istek sayısı, hata sayısı (durum sınıfına göre), gecikme yüzdeleri (p50, p95, p99), eşzamanlı (in-flight) istekler ve DB ile önemli üçüncü taraf çağrılar için bağımlılık gecikmesi.
Metrikleri trace'lerle hizalı tutmak için aynı route şablonlarını ve isimleri kullanın. Span'larınız /users/{id} kullanıyorsa, metrikleriniz de öyle olmalı. Böylece bir grafik “/checkout için p95 yükseldi” dediğinde, o route'a filtrelenmiş trace'lere doğrudan atlayabilirsiniz.
Etiketler (attribute'lar) konusunda dikkatli olun. Tek bir kötü etiket maliyetleri patlatabilir ve dashboard'ları kullanışsız hale getirebilir. Route şablonu, method, status sınıfı ve servis adı genellikle güvenlidir. Kullanıcı ID'leri, e-postalar, tam URL'ler ve ham hata mesajları genellikle güvenli değildir.
İş açısından kritik olaylar için birkaç özel metrik ekleyin (örneğin checkout başlatıldı/tamamlandı, ödeme hataları sonuç kodu grubuna göre, arka plan işi başarı vs retry). Kümeyi küçük tutun ve kullanmadığınız metrikleri kaldırın.
Telemetriyi dışa aktarma ve güvenli açılım
Dışa aktarma, OpenTelemetry'yi gerçek kılan adımdır. Servisiniz span'ları, metrikleri ve logları güvenilir bir yere göndermeli, istekleri yavaşlatmadan.
Yerelde geliştirme için basit tutun. Konsol exporter (veya yerel bir collector'a OTLP) span'leri hızlı görmenizi ve span isimleri ile attribute'ları doğrulamanızı sağlar. Üretimde, servise yakın bir agent veya OpenTelemetry Collector'a OTLP göndermeyi tercih edin. Bu size retry, yönlendirme ve filtreleme için tek bir yer sağlar.
Batching önemlidir. Telemetriyi kısa aralıklarla partiler halinde gönderin, ağın tıkanması uygulamanızı engellemesin diye sıkı zaman aşımları koyun. Telemetri kritik yolun üzerinde olmamalıdır. Eğer exporter yetişemiyorsa, bellek dolması yerine veriyi düşürmelidir.
Örnekleme maliyetleri tahmin edilebilir kılar. Başlangıçta head-based sampling ile başlayın (örneğin isteklerin %1-10'u), sonra basit kurallar ekleyin: hataları her zaman örnekle, eşik üstündeki yavaş istekleri her zaman örnekle. Yüksek hacimli arka plan işleriniz varsa, onları daha düşük oranlarda örnekleyin.
Açılımı küçük adımlarla yapın: geliştirmede %100 örnekleme, staging'de gerçekçi trafik ve daha düşük örnekleme, sonra üretimde muhafazakâr örnekleme ve exporter hatalarına dair uyarılar.
Uçtan uca görünürlüğü bozacak yaygın hatalar
Uçtan uca görünürlük en sık basit sebeplerle başarısız olur: veri vardır ama bağlanmaz.
Go'da dağıtık izlemeyi bozan sorunlar genellikle şunlardır:
- Katmanlar arasında bağlamın düşürülmesi. Bir handler span oluşturur ama bir DB çağrısı, HTTP client veya goroutine request bağlamı yerine
context.Background()kullanır. - Hataları span'ları işaretlemeden döndürmek. Hata kaydetmez ve span durumunu ayarlamazsanız, trace'ler kullanıcıların 500 gördüğü durumlarda bile "yeşil" görünür.
- Her şeyi enstrümante etmek. Her yardımcı küçük bir span olursa, trace'ler gürültüye dönüşür ve maliyet artar.
- Yüksek kardinaliteli attribute'lar eklemek. ID'ler içeren tam URL'ler, e-postalar, ham SQL değerleri, istek gövdeleri veya ham hata dizeleri milyonlarca benzersiz değer oluşturabilir.
- Performansı ortalamalara göre değerlendirmek. Olaylar percentil (p95/p99) ve hata oranında ortaya çıkar, ortalama gecikmede değil.
Hızlı bir kontrol listesi olarak gerçek bir isteği seçin ve sınırlar boyunca takip edin. Gelen istekte, DB sorgusunda, üçüncü taraf çağrıda ve async worker'da aynı trace ID'nin akıp akmadığını göremiyorsanız, uçtan uca görünürlüğünüz yok demektir.
Pratik bir “tam” kontrol listesi
Bir kullanıcı raporundan tam isteğe ve sonra her hop boyunca onu izlemeye gidebildiğinizde yakınsınız.
- Bir API log satırı seçin ve tam trace'i
trace_idile bulun. Aynı isteğe ait daha derin logların (DB, HTTP client, worker) aynı trace bağlamını taşıdığını doğrulayın. - Trace'i açın ve iç içe geçmişliği kontrol edin: en üstte bir HTTP server span, altında DB çağrıları ve üçüncü taraf API'lar için child span'lar. Düz bir liste genellikle bağlamın kaybolduğu anlamına gelir.
- Bir API isteğinden bir arka plan işi tetikleyin (örneğin e-posta fişi gönderme) ve worker span'ının isteğe bağlandığını doğrulayın.
- Metrikler için temel kontrolü yapın: istek sayısı, hata oranı ve gecikme yüzdeleri. Route veya operasyon bazında filtreleyebildiğinizi doğrulayın.
- Attribute'lar ve loglar için güvenliği tarayın: parolalar, token'lar, tam kredi kartı numaraları veya ham kişisel veriler olmasın.
Basit bir gerçeklik testi, ödeme sağlayıcısının geciktiği yavaş bir checkout simüle etmektir. Tek bir trace'de açıkça etiketlenmiş bir dış çağrı span'ı ve checkout route için p95 gecikme grafiğinde bir sıçrama görmelisiniz.
Eğer Go backend'leri üretiyorsanız (örneğin AppMaster), bu kontrol listesini sürüm rutininizin bir parçası yapmak yeni uç noktalar ve worker'ların uygulama büyüdükçe izlenebilir kalmasına yardımcı olur. AppMaster (appmaster.io) gerçek Go servisleri üretir, böylece tek bir OpenTelemetry kurulumu standartlaştırıp servisler ve arka plan işler arasında taşıyabilirsiniz.
Örnek: Hizmetler arasındaki yavaş checkout'u debug etme
Bir müşteri mesajı diyor ki: “Checkout bazen takılıyor.” Bunu rastgele yeniden üretemiyorsunuz; işte Go OpenTelemetry izleme burada fayda sağlar.
Sorunun şekli için önce metriklere bakın. Checkout uç noktası için istek oranı, hata oranı ve p95 veya p99 gecikmeyi inceleyin. Yavaşlama kısa patlamalar halinde ve yalnızca bazı isteklerde oluyorsa, genelde bir bağımlılık, kuyruklanma veya yeniden deneme davranışına işaret eder, CPU'ya değil.
Sonra aynı zaman penceresinden yavaş bir trace açın. Genellikle bir trace yeterlidir. Sağlıklı bir checkout 300–600 ms arası olabilir. Kötü bir tane 8–12 saniye olabilir ve zamanın çoğu tek bir span içinde toplanır.
Yaygın bir desen şu şekildedir: API handler hızlı, DB işi çoğunlukla iyi, sonra ödeme sağlayıcı span'ı yeniden denemelerle birlikte görünür ve aşağı akış bir kilit veya kuyruğun arkasında bekliyordur. Yanıt hala 200 dönebilir, bu yüzden sadece hatalara dayalı alarmlar hiçbir zaman tetiklenmez.
İlişkilendirilmiş loglar size düz metin olarak tam yolu söyler: “retrying Stripe charge: timeout”, ardından “db tx aborted: serialization failure”, sonra “retry checkout flow”. Bu, birkaç küçük sorunun birleşerek kötü bir kullanıcı deneyimi yarattığını açıkça işaret eder.
Bottleneck'i bulduktan sonra, okunabilirliği zaman içinde koruyan şey tutarlılıktır. Span isimlerini, attribute'ları (güvenli kullanıcı ID hash'i, order ID, bağımlılık adı) ve örnekleme kurallarını servisler arasında standardize edin ki herkes trace'leri aynı şekilde okusun.


