Python'da Datadog APM'i OpenTelemetry ile Değiştirmek

Selamlar, bu yazımda Python tarafında uzun zamandır 'standart' kabul edilen Datadog ddtrace kütüphanesinden OpenTelemetry SDK'larına nasıl geçileceğine bakacağız. Konu basit gibi duruyor ama detayda biraz dirsek çürütmek gerekiyor; özellikle custom span'leri ve DogStatsD metriklerini çoktan unuttuysanız geri dönüp bakacağınız epey kod birikmiş olabilir.

Açıkçası ddtrace'in en sevmediğim yanı, vendor lock-in'i. Datadog faturanız büyüdükçe çıkış kapısı dar gelmeye başlıyor. OpenTelemetry tam burada işe yarıyor: aynı kavramların satıcıdan bağımsız bir karşılığı, OTLP konuşan herhangi bir backend'e (Tempo, Jaeger, Honeycomb, OneUptime, Grafana Cloud, fark etmiyor) gönderebilirsiniz.

Neden ddtrace'ten ayrılalım?

Bence asıl gerekçe teknik değil, ekonomik. Üç maddeyle özetlersem:

  • Vendor lock-in yok: Aynı kodla farklı backend'lere yayın yapabiliyorsunuz. Bugün Datadog, yarın self-hosted Tempo.
  • Maliyet kontrolü: Datadog'un host başına fiyatlandırması belli bir ölçeğin üzerinde canınızı yakar. Backend'i siz seçince bütçeyi siz kurarsınız.
  • Endüstri standardı: OpenTelemetry artık CNCF'in incubating projesi değil, mezunu. Ekibe yeni katılan biri büyük ihtimalle zaten biliyor.

Kavramlar birebir oturuyor: ddtrace'teki Tracer OpenTelemetry'de Tracer, set_tag yerine set_attribute, tracer.trace() yerine start_as_current_span(), ddtrace-run yerine opentelemetry-instrument. Yani çoğu yerde 'arama-değiştirme' düzeyinde bir migration.

Paketleri kurmak ve eski izleri silmek

Önce ddtrace'i ve onun çevresindeki Datadog paketlerini sökün:

pip uninstall ddtrace datadog datadog-api-client

Sonra OpenTelemetry tarafını kurun. opentelemetry-bootstrap komutu sizin sanal ortamdaki paketleri tarayıp uygun instrumentation'ları otomatik olarak yüklüyor; bu çok değerli bir kolaylık:

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp \
            opentelemetry-distro opentelemetry-instrumentation
opentelemetry-bootstrap -a install

Uygulama kodunda da temizlik şart. from ddtrace import patch_all gibi satırları, TraceMiddleware(...) çağrılarını ve DD_* çevre değişkenlerini deployment yapılandırmanızdan çıkarın. Tek tek aramak zorsa, grep -ri ddtrace . iyi bir başlangıç.

Auto-instrumentation: en kolay göç yolu

Eğer kodda manuel ddtrace kullanmadıysanız işiniz şaşırtıcı derecede kolay. ddtrace-runopentelemetry-instrument ile değiştiriyorsunuz ve birkaç çevre değişkeni ayarlıyorsunuz:

export OTEL_SERVICE_NAME='odeme-servisi'
export OTEL_EXPORTER_OTLP_ENDPOINT='http://otel-collector:4317'
export OTEL_RESOURCE_ATTRIBUTES='deployment.environment=production,service.version=2.4.0'

opentelemetry-instrument gunicorn myapp.wsgi:application --workers 4

Bu kadar. Flask, Django, FastAPI, requests, psycopg2, redis, celery ve daha onlarcası kutudan çıkar çıkmaz izlenir. Şahsi kanaatim, çoğu ekip bu noktada zaten %80 yolu almış oluyor.

Manuel span'leri taşımak

Custom trace yazdıysanız iş biraz daha el oyunu gerektiriyor. Patternler benziyor ama metot adları farklı:

from opentelemetry import trace

# Modül başına bir tracer almak iyi pratik
tracer = trace.get_tracer('myapp.orders', '1.0.0')

def siparisi_isle(siparis_id, urunler):
    # tracer.trace() yerine start_as_current_span
    with tracer.start_as_current_span('siparisi_isle') as span:
        # set_tag yerine set_attribute
        span.set_attribute('order.id', siparis_id)
        span.set_attribute('order.item_count', len(urunler))

        with tracer.start_as_current_span('odeme_al') as alt_span:
            try:
                sonuc = odeme_yap(siparis_id, hesapla(urunler))
                alt_span.set_attribute('payment.success', sonuc.basarili)
            except Exception as hata:
                # Tüm stack trace otomatik kaydedilir
                alt_span.record_exception(hata)
                alt_span.set_status(
                    trace.Status(trace.StatusCode.ERROR, str(hata))
                )
                raise

Burada gözden kaçan iki nokta var. Birincisi, record_exception zaten stack trace'i tüm detayıyla yakalıyor; eski error.msg, error.type etiketlerini elle koymanıza gerek yok. İkincisi, span'i ERROR durumuna çekmek için set_status çağırmayı unutmayın - bu olmazsa backend'de hata olarak görünmez.

Metrikleri OTLP'ye çevirmek

DogStatsD'den OpenTelemetry Metrics API'sine geçiş, trace tarafından biraz daha kafa yorucu çünkü kavramlar farklı: statsd.gauge callback tabanlı observable_gauge'a dönüşüyor.

from opentelemetry import metrics

meter = metrics.get_meter('myapp.orders', '1.0.0')

# statsd.increment karşılığı
siparis_sayaci = meter.create_counter(
    name='orders.placed',
    description='Verilen toplam siparis sayisi',
    unit='orders',
)

# statsd.histogram karşılığı
isleme_suresi = meter.create_histogram(
    name='orders.processing_time',
    unit='ms',
)

def siparis_ver(siparis):
    baslangic = time.time()
    isle(siparis)
    sure_ms = (time.time() - baslangic) * 1000

    siparis_sayaci.add(1, {'region': 'tr-west', 'order.type': siparis.tip})
    isleme_suresi.record(sure_ms, {'region': 'tr-west'})

Tag'ler artık 'attribute' olarak adlandırılıyor ama mantık aynı: zenginleştirici metadata. Cardinality'ye dikkat edin; user_id'yi attribute yaparsanız metrik backend'iniz dakikalar içinde teslim olur.

Framework notları

Auto-instrumentation çoğu zaman yeter ama programmatic kurulum gerektiğinde Flask için tek satır:

from opentelemetry.instrumentation.flask import FlaskInstrumentor
FlaskInstrumentor().instrument_app(app)

Django için DjangoInstrumentor().instrument() çağrısı wsgi.py ya da manage.py içinde, FastAPI için FastAPIInstrumentor.instrument_app(app). Üçü de aynı imza, aynı mantık.

Sık karşılaşılan tuzaklar

  • Eski DD_* değişkenlerini bırakmak: Konteynerde unutulan DD_AGENT_HOST sizi yanıltır; ddtrace kalkmış görünse de log'larda hâlâ Datadog izi ararsanız sebep budur. Deployment manifest'lerini grep'leyin.
  • Hem opentelemetry-instrument hem programmatic kurulum: İkisi birden çalışınca span'ler ya iki kere açılır ya da tracer provider çakışır. Tek bir yol seçin.
  • Cardinality patlatmak: set_attribute('user.email', ...) veya add(1, {'request.id': ...}) gibi şeyler hem traces hem metrics tarafında backend'inizi şişirir. Yüksek-cardinality alanları sadece span attribute olarak ve gerektikçe kullanın.
  • Sampling'i unutmak: ddtrace'in DD_TRACE_SAMPLE_RATE karşılığı OTEL_TRACES_SAMPLER ve OTEL_TRACES_SAMPLER_ARG. Production'da parentbased_traceidratio ile başlayın, %100 örneklemeyle dev ortamı yakarsınız.

Kapanış

Bu yazıda ddtrace'ten OpenTelemetry'ye geçişi adım adım gördük: paketler, auto-instrumentation, manuel span'ler, metrikler ve framework özel notları. Bana sorarsanız ekibinizdeki bir hafta sonu refactor'ü olarak başlayıp, ileride Datadog faturasını yarıya indiren stratejik bir hamleye dönüşebilir. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.