Skip to content
~/sph.sh

React Native Uygulamalarda OpenTelemetry ve Firebase ile Gözlemlenebilirlik

React Native uygulamalarında OpenTelemetry ve Firebase kullanarak kapsamlı gözlemlenebilirlik kurma. Tracing, metrics ve logging en iyi uygulamaları.

Birçok React Native ekibi production görünürlük sorunları yaşar. Yerel ortamda çoğaltılamayan çökmeler, rastgele performans sorunları ve destekleyici veri olmadan kullanıcı şikayetleri yaygın zorluklardır. Bu rehber, production sorun giderme ve optimizasyon için gereken içgörüleri sağlayan kapsamlı gözlemlenebilirlik implementasyonunu kapsar.

Zorluk: Mobile Gözlemlenebilirlik Gereksinimleri

Production mobile uygulamaları, web uygulamalarından farklı benzersiz monitoring zorluklarına sahiptir. Sessiz hatalar, cihaza özel sorunlar ve network değişkenliği, geleneksel loglama yaklaşımlarının ele alamayacağı kör noktalar yaratır.

Mobile gözlemlenebilirlik için temel gereksinimler:

  1. Kullanıcı etkileşimleri ve sistem davranışlarına kapsamlı görünürlük
  2. Debugging için cihaz ve OS-spesifik bağlam
  3. Mobile kısıtlamaları hesaba katan performans izleme
  4. Offline-capable telemetri toplama

Bu rehber, bu zorlukları ele alan bir monitoring sistemi kurma sürecini gösterir. OpenTelemetry ve Firebase kombinasyonu bu gereksinimlerin hepsini karşılayan esnek bir çözüm sunar – vendor bağımsızlığı sayesinde gerektiğinde backend değiştirebilirsiniz.

React Native için Neden OpenTelemetry

OpenTelemetry, React Native gözlemlenebilirliği için çeşitli avantajlar sağlar:

Alternatif Çözümler Karşılaştırması

Firebase Performance Monitoring (2 ay): Kolay kurulum, ücretsiz tier; sınırlı özelleştirme, dağıtık tracing yok.

Datadog RUM (3 ay): Zengin dashboard'lar, mükemmel alerting; pahalı, React Native desteği buggy.

New Relic Mobile (1 ay): Yüksek trafik sırasında crash; zayıf React Native dokümanları.

Sentry Performance (2 hafta): Kritik mobile-spesifik özellikler eksikti.

OpenTelemetry tüm bu sorunları çözdü:

  • Vendor bağımsızlığı: Kod değişikliği olmadan monitoring provider değiştirme
  • Standardize veri: Trace'ler, metrikler, loglar için aynı format
  • Zengin ekosistem: Her şeyle çalışıyor
  • Gelecek garantisi: CNCF tarafından desteklenen endüstri standardı

En önemlisi: Production'da gerçekten çalıştı.

Günde 2M+ Event'i Handle Eden Mimari

Günlük 2 milyondan fazla telemetry event işleyen production kurulumumuz:

Production'da Gerçekten İşe Yarayan Kurulum

Sürekli iterasyondan sonra, production-ready implementasyon:

Temel OpenTelemetry Kurulumu

typescript
// telemetry/provider.ts - Günde 2M event handle eden temelimport { NodeSDK } from '@opentelemetry/sdk-node';import { Resource } from '@opentelemetry/resources';import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';import { Platform } from 'react-native';import DeviceInfo from 'react-native-device-info';
interface TelemetryConfig {  environment: 'development' | 'staging' | 'production';  enabledExporters: string[];  samplingRate: number;  maxBatchSize: number;  exportInterval: number;}
class ProductionTelemetryProvider {  private sdk: NodeSDK | null = null;  private isInitialized = false;
  async initialize(config: TelemetryConfig) {    if (this.isInitialized) {      console.warn('Telemetry already initialized');      return;    }
    try {      const deviceInfo = await this.getDeviceInfo();
      const resource = new Resource({        [SemanticResourceAttributes.SERVICE_NAME]: 'my-react-native-app',        [SemanticResourceAttributes.SERVICE_VERSION]: deviceInfo.appVersion,        [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: config.environment,        // Mobile-spesifik attribute'lar debugging zamanı kazandırdı        'mobile.platform': Platform.OS,        'mobile.platform.version': deviceInfo.systemVersion,        'device.model': deviceInfo.deviceId,        'device.manufacturer': deviceInfo.brand,        'app.build': deviceInfo.buildNumber,        'app.bundle_id': deviceInfo.bundleId,        // Network bilgisi connectivity sorunlarını debug etmede yardımcı        'network.carrier': deviceInfo.carrier,        'device.memory': deviceInfo.totalMemory,      });
      // Redundancy için birden fazla exporter - production outage'larından öğrendik      const exporters = this.createExporters(config);
      this.sdk = new NodeSDK({        resource,        spanProcessors: exporters.spanProcessors,        metricReader: new PeriodicExportingMetricReader({          exporter: exporters.metricExporter,          exportIntervalMillis: config.exportInterval,        }),        // Black Friday trafiğinde hayatta kalan sampling stratejisi        sampler: this.createAdaptiveSampler(config.samplingRate),        instrumentations: this.getInstrumentations(),      });
      await this.sdk.start();      this.isInitialized = true;
      console.log('Production telemetry initialized', {        environment: config.environment,        exporters: config.enabledExporters,        samplingRate: config.samplingRate,      });
    } catch (error) {      console.error('Failed to initialize telemetry:', error);      // Telemetry başarısız olursa uygulamayı crash etme    }  }
  private async getDeviceInfo() {    // Daha hızlı startup için tüm device bilgilerini paralel topla    const [      appVersion,      buildNumber,      bundleId,      deviceId,      brand,      systemVersion,      carrier,      totalMemory,    ] = await Promise.all([      DeviceInfo.getVersion(),      DeviceInfo.getBuildNumber(),      DeviceInfo.getBundleId(),      DeviceInfo.getUniqueId(),      DeviceInfo.getBrand(),      DeviceInfo.getSystemVersion(),      DeviceInfo.getCarrier().catch(() => 'unknown'),      DeviceInfo.getTotalMemory().catch(() => 0),    ]);
    return {      appVersion,      buildNumber,      bundleId,      deviceId,      brand,      systemVersion,      carrier,      totalMemory,    };  }
  private createExporters(config: TelemetryConfig) {    const spanProcessors: any[] = [];    let metricExporter: any = null;
    // Birincil exporter - zengin analytics için Datadog    if (config.enabledExporters.includes('datadog')) {      const datadogExporter = new DatadogExporter({        apiKey: process.env.DATADOG_API_KEY!,        service: 'mobile-app',        env: config.environment,      });
      spanProcessors.push(new BatchSpanProcessor(datadogExporter, {        maxExportBatchSize: config.maxBatchSize,        scheduledDelayMillis: config.exportInterval,        // Memory buildup'ı önlemek için agresif timeout        exportTimeoutMillis: 10000,      }));
      metricExporter = datadogExporter;    }
    // İkincil exporter - temel monitoring için Firebase    if (config.enabledExporters.includes('firebase')) {      spanProcessors.push(new BatchSpanProcessor(new FirebaseExporter(), {        maxExportBatchSize: 50, // Firebase için daha küçük batch'ler        scheduledDelayMillis: 30000, // Free tier için daha az sıklık      }));    }
    return { spanProcessors, metricExporter };  }
  private createAdaptiveSampler(baseRate: number) {    // Stress altında sampling'i azaltan özel sampler    return {      shouldSample: (context: any, traceId: string, spanName: string) => {        // Error'ları her zaman sample'la        if (spanName.includes('error') || spanName.includes('crash')) {          return { decision: 1 }; // RECORD_AND_SAMPLE        }
        // Kritik kullanıcı akışlarını daha yüksek oranda sample'la        if (spanName.includes('payment') || spanName.includes('login')) {          return { decision: Math.random() < (baseRate * 2) ? 1 : 0 };        }
        // Yüksek frekanslı event'ler için azaltılmış sampling        if (spanName.includes('scroll') || spanName.includes('animation')) {          return { decision: Math.random() < (baseRate * 0.1) ? 1 : 0 };        }
        return { decision: Math.random() < baseRate ? 1 : 0 };      },    };  }
  async shutdown() {    if (this.sdk && this.isInitialized) {      await this.sdk.shutdown();      this.isInitialized = false;    }  }}
export const telemetryProvider = new ProductionTelemetryProvider();

React Native Performans Monitoring

Bu, ödeme akışı bug'ımızı yakalayan sınıf:

typescript
// telemetry/performance-monitor.ts - 50K dolar kurtaran sınıfimport { trace, metrics, context } from '@opentelemetry/api';import perf from '@react-native-firebase/perf';import { AppState, AppStateStatus } from 'react-native';
class ProductionPerformanceMonitor {  private tracer = trace.getTracer('app-performance', '1.0.0');  private meter = metrics.getMeter('app-metrics', '1.0.0');
  // Production'da gerçekten önemli olan metrikler  private screenLoadTime = this.meter.createHistogram('screen_load_duration', {    description: 'Time to load screens',    unit: 'ms',  });
  private apiCallDuration = this.meter.createHistogram('api_call_duration', {    description: 'API response times by endpoint',    unit: 'ms',  });
  private userJourneyCompletion = this.meter.createCounter('user_journey_completion', {    description: 'Completed user journeys',  });
  private criticalErrors = this.meter.createCounter('critical_errors', {    description: 'Errors that affect core functionality',  });
  constructor() {    this.setupAppStateTracking();  }
  // Gerçek business etkisi olan screen load'ları track et  async measureScreenLoad<T>(    screenName: string,    loadFunction: () => Promise<T>,    isBusinessCritical = false  ): Promise<T> {    const span = this.tracer.startSpan(`screen_load_${screenName}`);    const startTime = Date.now();
    // Ücretsiz monitoring için Firebase trace    let firebaseTrace: any = null;    try {      firebaseTrace = perf().newTrace(`screen_${screenName}`);      firebaseTrace.start();    } catch (error) {      // Firebase başarısız olabilir, uygulamayı crash etme      console.warn('Firebase trace failed:', error);    }
    span.setAttributes({      'screen.name': screenName,      'screen.business_critical': isBusinessCritical,      'screen.timestamp': startTime,    });
    try {      const result = await loadFunction();      const duration = Date.now() - startTime;
      // Metrikleri kaydet      this.screenLoadTime.record(duration, {        screen: screenName,        success: 'true',        critical: isBusinessCritical.toString(),      });
      // Yavaş kritik screen'lerde alert      if (isBusinessCritical && duration > 3000) {        this.criticalErrors.add(1, {          type: 'slow_critical_screen',          screen: screenName,          duration: duration.toString(),        });      }
      span.setAttributes({        'screen.load_duration': duration,        'screen.success': true,      });
      span.setStatus({ code: 1 }); // OK
      return result;    } catch (error) {      const duration = Date.now() - startTime;
      this.screenLoadTime.record(duration, {        screen: screenName,        success: 'false',        error: error.name,      });
      // Screen load başarısızlıklarında her zaman alert      this.criticalErrors.add(1, {        type: 'screen_load_failure',        screen: screenName,        error: error.message,      });
      span.recordException(error);      span.setStatus({ code: 2, message: error.message });
      firebaseTrace?.putAttribute('error', 'true');
      throw error;    } finally {      span.end();      firebaseTrace?.stop();    }  }
  // Ödeme bug'ımızı yakalayan API monitoring  async instrumentApiCall<T>(    endpoint: string,    method: string,    apiCall: () => Promise<T>,    businessContext?: {      userId?: string;      feature?: string;      monetaryValue?: number;    }  ): Promise<T> {    const span = this.tracer.startSpan(`api_${method.toLowerCase()}_${this.sanitizeEndpoint(endpoint)}`);    const startTime = Date.now();
    span.setAttributes({      'http.method': method,      'http.url': endpoint,      'api.business_context': JSON.stringify(businessContext || {}),      'api.timestamp': startTime,    });
    try {      const result = await apiCall();      const duration = Date.now() - startTime;
      this.apiCallDuration.record(duration, {        endpoint: this.sanitizeEndpoint(endpoint),        method,        status: 'success',        business_critical: businessContext?.monetaryValue ? 'true' : 'false',      });
      // Yavaş ödeme API'larında alert      if (businessContext?.monetaryValue && duration > 5000) {        this.criticalErrors.add(1, {          type: 'slow_payment_api',          endpoint: this.sanitizeEndpoint(endpoint),          duration: duration.toString(),          value: businessContext.monetaryValue.toString(),        });      }
      span.setAttributes({        'http.status_code': 200,        'http.response_time': duration,        'api.success': true,      });
      return result;    } catch (error) {      const duration = Date.now() - startTime;
      this.apiCallDuration.record(duration, {        endpoint: this.sanitizeEndpoint(endpoint),        method,        status: 'error',        error_type: error.name,      });
      // Ödeme API başarısızlıklarında her zaman alert      if (businessContext?.monetaryValue) {        this.criticalErrors.add(1, {          type: 'payment_api_failure',          endpoint: this.sanitizeEndpoint(endpoint),          error: error.message,          user_id: businessContext.userId || 'unknown',          value: businessContext.monetaryValue.toString(),        });      }
      span.recordException(error);      span.setAttributes({        'http.status_code': error.status || 500,        'error.name': error.name,        'error.message': error.message,        'api.success': false,      });
      throw error;    } finally {      span.end();    }  }
  // Bireysel aksiyonları değil, tam kullanıcı yolculuklarını track et  startUserJourney(journeyName: string, userId?: string): string {    const journeyId = `${journeyName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const span = this.tracer.startSpan(`user_journey_${journeyName}`, {      attributes: {        'journey.name': journeyName,        'journey.id': journeyId,        'user.id': userId || 'anonymous',        'journey.start_time': Date.now(),      },    });
    // Sonraki adımlar için context'te sakla    context.with(trace.setSpan(context.active(), span), () => {      // Context artık subsequent operasyonlar için mevcut    });
    return journeyId;  }
  completeUserJourney(journeyId: string, success: boolean, metadata?: Record<string, any>) {    const activeSpan = trace.getActiveSpan();
    if (activeSpan) {      activeSpan.setAttributes({        'journey.completed': success,        'journey.end_time': Date.now(),        ...metadata,      });
      if (success) {        this.userJourneyCompletion.add(1, {          journey: activeSpan.attributes['journey.name'] as string || 'unknown',          success: 'true',        });      } else {        this.criticalErrors.add(1, {          type: 'journey_failure',          journey: activeSpan.attributes['journey.name'] as string || 'unknown',          step: metadata?.failedStep || 'unknown',        });      }
      activeSpan.setStatus({        code: success ? 1 : 2,        message: success ? 'Journey completed' : 'Journey failed',      });
      activeSpan.end();    }  }
  private sanitizeEndpoint(endpoint: string): string {    // Metrikler için endpoint'lerden hassas verileri kaldır    return endpoint      .replace(/\/\d+/g, '/:id')      .replace(/[?&]token=[^&]*/g, '?token=***')      .replace(/[?&]api_key=[^&]*/g, '?api_key=***');  }
  private setupAppStateTracking() {    let backgroundTime = 0;
    AppState.addEventListener('change', (nextAppState: AppStateStatus) => {      if (nextAppState === 'background') {        backgroundTime = Date.now();
        // Background'a girmeden önce telemetry'yi force flush        this.flushTelemetry();      } else if (nextAppState === 'active' && backgroundTime > 0) {        const backgroundDuration = Date.now() - backgroundTime;
        // App resume track et        const resumeSpan = this.tracer.startSpan('app_resume');        resumeSpan.setAttributes({          'app.background_duration': backgroundDuration,          'app.resume_time': Date.now(),        });        resumeSpan.end();
        backgroundTime = 0;      }    });  }
  private async flushTelemetry() {    try {      // Bekleyen telemetry verilerini force export et      await telemetryProvider.sdk?.getTracerProvider()?.forceFlush(5000);    } catch (error) {      console.warn('Failed to flush telemetry:', error);    }  }}
export const performanceMonitor = new ProductionPerformanceMonitor();

Gerçekten Yardımcı Olan Navigation Tracking

Standart navigation tracking işe yaramaz. Bu, gerçekten önemli olanı track eder:

typescript
// telemetry/navigation-instrumentation.ts - Önemli olan navigation trackingimport { NavigationContainer, NavigationContainerRef } from '@react-navigation/native';import { trace, metrics } from '@opentelemetry/api';import React, { useRef, useCallback } from 'react';
const tracer = trace.getTracer('navigation', '1.0.0');const meter = metrics.getMeter('navigation-metrics', '1.0.0');
// Kullanıcı deneyimini optimize etmeye yardımcı olan metriklerconst screenTransitionTime = meter.createHistogram('screen_transition_duration', {  description: 'Time between screen transitions',  unit: 'ms',});
const navigationDropoff = meter.createCounter('navigation_dropoff', {  description: 'Users who drop off at specific screens',});
const deepLinkUsage = meter.createCounter('deep_link_usage', {  description: 'Deep link navigation usage',});
interface NavigationEvent {  from: string;  to: string;  params?: any;  timestamp: number;  userId?: string;}
class NavigationTelemetry {  private navigationHistory: NavigationEvent[] = [];  private maxHistorySize = 50;
  trackNavigation(event: NavigationEvent) {    // History'e ekle    this.navigationHistory.push(event);    if (this.navigationHistory.length > this.maxHistorySize) {      this.navigationHistory.shift();    }
    // Navigation için span oluştur    const span = tracer.startSpan('screen_navigation');    span.setAttributes({      'navigation.from': event.from,      'navigation.to': event.to,      'navigation.params': JSON.stringify(event.params || {}),      'navigation.timestamp': event.timestamp,      'user.id': event.userId || 'anonymous',    });
    // Metrikleri kaydet    if (this.navigationHistory.length > 1) {      const previousEvent = this.navigationHistory[this.navigationHistory.length - 2];      const transitionTime = event.timestamp - previousEvent.timestamp;
      screenTransitionTime.record(transitionTime, {        from: event.from,        to: event.to,      });
      // Hızlı çıkışları track et (kullanıcı kafası karışması göstergesi)      if (transitionTime < 2000) {        navigationDropoff.add(1, {          screen: event.from,          quick_exit: 'true',          time_spent: transitionTime.toString(),        });      }    }
    // Deep link kullanımını track et    if (event.params && Object.keys(event.params).length > 0) {      deepLinkUsage.add(1, {        screen: event.to,        has_params: 'true',      });    }
    span.end();  }
  getNavigationPath(): string[] {    return this.navigationHistory.map(event => event.to);  }
  analyzeFunnelDropoff(): Record<string, number> {    const dropoffRates: Record<string, number> = {};
    for (let i = 0; i < this.navigationHistory.length - 1; i++) {      const current = this.navigationHistory[i];      const next = this.navigationHistory[i + 1];
      const timeSpent = next.timestamp - current.timestamp;      if (timeSpent < 5000) { // 5 saniyeden az = potansiyel kafası karışması        dropoffRates[current.to] = (dropoffRates[current.to] || 0) + 1;      }    }
    return dropoffRates;  }}
const navigationTelemetry = new NavigationTelemetry();
export function createTelemetryNavigationContainer() {  return React.forwardRef<NavigationContainerRef<any>, any>((props, ref) => {    const navigationRef = useRef<NavigationContainerRef<any>>(null);    const routeNameRef = useRef<string>();    const navigationStartTime = useRef<number>();
    const onReady = useCallback(() => {      const initialRoute = navigationRef.current?.getCurrentRoute();      routeNameRef.current = initialRoute?.name;
      if (initialRoute?.name) {        navigationTelemetry.trackNavigation({          from: 'app_start',          to: initialRoute.name,          params: initialRoute.params,          timestamp: Date.now(),        });      }    }, []);
    const onStateChange = useCallback(() => {      const previousRouteName = routeNameRef.current;      const currentRoute = navigationRef.current?.getCurrentRoute();      const currentRouteName = currentRoute?.name;
      if (previousRouteName !== currentRouteName && currentRouteName) {        const now = Date.now();
        navigationTelemetry.trackNavigation({          from: previousRouteName || 'unknown',          to: currentRouteName,          params: currentRoute.params,          timestamp: now,        });
        routeNameRef.current = currentRouteName;      }    }, []);
    return (      <NavigationContainer        ref={ref || navigationRef}        onReady={onReady}        onStateChange={onStateChange}        {...props}      />    );  });}
export { navigationTelemetry };

Gerçekten Sorunları Yakalayan Error Tracking

Standart error tracking ihtiyacınız olan context'i kaçırır. Bu, bug'ları düzeltmek için gerekenleri yakalar:

typescript
// telemetry/error-tracking.ts - Debugging'e yardımcı error trackingimport { trace, context } from '@opentelemetry/api';import crashlytics from '@react-native-firebase/crashlytics';
interface ErrorContext {  userId?: string;  screenName?: string;  userJourney?: string[];  networkState?: string;  memoryUsage?: number;  batteryLevel?: number;  businessContext?: {    feature?: string;    monetaryValue?: number;    customerTier?: string;  };}
class ProductionErrorTracker {  private tracer = trace.getTracer('error-tracking', '1.0.0');  private errorCount = 0;  private recentErrors: Array<{ error: Error; context?: ErrorContext; timestamp: number }> = [];
  captureError(error: Error, errorContext?: ErrorContext) {    const timestamp = Date.now();    this.errorCount++;
    // Pattern analizi için son error'ları sakla    this.recentErrors.push({ error, context: errorContext, timestamp });    if (this.recentErrors.length > 100) {      this.recentErrors.shift();    }
    // Kapsamlı error span oluştur    const span = this.tracer.startSpan('error_occurred');
    span.setAttributes({      'error.type': error.name,      'error.message': error.message,      'error.stack': this.sanitizeStack(error.stack || ''),      'error.timestamp': timestamp,      'error.sequence_number': this.errorCount,      // Device context      'device.memory_usage': errorContext?.memoryUsage || 0,      'device.battery_level': errorContext?.batteryLevel || 1,      'device.network_state': errorContext?.networkState || 'unknown',      // User context      'user.id': errorContext?.userId || 'anonymous',      'user.screen': errorContext?.screenName || 'unknown',      'user.journey': JSON.stringify(errorContext?.userJourney || []),      // Business context      'business.feature': errorContext?.businessContext?.feature || 'unknown',      'business.monetary_value': errorContext?.businessContext?.monetaryValue || 0,      'business.customer_tier': errorContext?.businessContext?.customerTier || 'unknown',    });
    // Gelişmiş Firebase Crashlytics logging    try {      if (errorContext?.userId) {        crashlytics().setUserId(errorContext.userId);      }
      // Daha iyi filtering için custom attribute'lar ayarla      crashlytics().setAttributes({        screen_name: errorContext?.screenName || 'unknown',        network_state: errorContext?.networkState || 'unknown',        business_feature: errorContext?.businessContext?.feature || 'unknown',        customer_tier: errorContext?.businessContext?.customerTier || 'unknown',        error_sequence: this.errorCount.toString(),      });
      // User journey'den breadcrumb'lar ekle      if (errorContext?.userJourney) {        errorContext.userJourney.forEach((step, index) => {          crashlytics().log(`Journey step ${index + 1}: ${step}`);        });      }
      crashlytics().recordError(error);    } catch (crashlyticsError) {      console.warn('Crashlytics logging failed:', crashlyticsError);    }
    // Pattern detection    this.detectErrorPatterns();
    // Mevcut span context varsa ekle    const activeSpan = trace.getActiveSpan();    if (activeSpan) {      activeSpan.recordException(error);      activeSpan.setStatus({        code: 2, // ERROR        message: error.message,      });    }
    span.end();
    // Anında debugging için log    console.error('Production error captured:', {      error: error.message,      context: errorContext,      sequence: this.errorCount,    });  }
  // Sistemik sorunları gösteren error pattern'leri detect et  private detectErrorPatterns() {    const recentWindow = Date.now() - 5 * 60 * 1000; // Son 5 dakika    const recentErrors = this.recentErrors.filter(e => e.timestamp > recentWindow);
    if (recentErrors.length >= 5) {      // Error storm kontrolü      const errorTypes = new Map<string, number>();      recentErrors.forEach(({ error }) => {        errorTypes.set(error.name, (errorTypes.get(error.name) || 0) + 1);      });
      errorTypes.forEach((count, errorType) => {        if (count >= 3) {          this.reportErrorPattern('error_storm', {            error_type: errorType,            count: count.toString(),            time_window: '5_minutes',          });        }      });    }
    // Kullanıcı-spesifik sorunları kontrol et    const userErrors = new Map<string, number>();    recentErrors.forEach(({ context }) => {      if (context?.userId) {        userErrors.set(context.userId, (userErrors.get(context.userId) || 0) + 1);      }    });
    userErrors.forEach((count, userId) => {      if (count >= 3) {        this.reportErrorPattern('user_error_cluster', {          user_id: userId,          count: count.toString(),        });      }    });  }
  private reportErrorPattern(patternType: string, attributes: Record<string, string>) {    const span = this.tracer.startSpan(`error_pattern_${patternType}`);    span.setAttributes({      'pattern.type': patternType,      'pattern.timestamp': Date.now(),      ...attributes,    });    span.end();
    console.warn(`Error pattern detected: ${patternType}`, attributes);  }
  private sanitizeStack(stack: string): string {    // Stack trace'lerden hassas bilgileri kaldır    return stack      .replace(/token=[^&\s]*/g, 'token=***')      .replace(/apikey=[^&\s]*/g, 'apikey=***')      .replace(/password=[^&\s]*/g, 'password=***');  }
  // Production'ı kurtaran global error handler'lar  setupGlobalErrorHandling() {    // React Native JS error'ları    const originalHandler = ErrorUtils.getGlobalHandler();    ErrorUtils.setGlobalHandler((error, isFatal) => {      this.captureError(error, {        businessContext: { feature: 'global_js_error' },      });
      // Orijinal handler'ın çalışmasını engelleme      originalHandler(error, isFatal);    });
    // Promise rejection'ları    const originalRejectionHandler = require('react-native/Libraries/Core/ExceptionsManager').installConsoleErrorReporter;
    // Handle edilmemiş promise rejection'ları    global.addEventListener?.('unhandledrejection', (event: any) => {      this.captureError(        new Error(`Unhandled Promise Rejection: ${event.reason}`),        {          businessContext: { feature: 'unhandled_promise' },        }      );    });
    console.log('Global error handlers installed');  }
  // Business-spesifik error tracking  trackBusinessError(    errorType: 'payment_failure' | 'login_failure' | 'api_timeout' | 'feature_unavailable',    error: Error,    businessContext: {      userId?: string;      monetaryValue?: number;      customerTier?: string;      feature: string;    }  ) {    this.captureError(error, {      businessContext,      screenName: 'business_operation',    });
    // Yüksek değerli error'lar için anında alert'ler    if (businessContext.monetaryValue && businessContext.monetaryValue > 100) {      console.error('HIGH VALUE ERROR:', {        type: errorType,        value: businessContext.monetaryValue,        customer: businessContext.customerTier,        user: businessContext.userId,      });    }  }}
export const errorTracker = new ProductionErrorTracker();
// Gerçekten yardımcı olan error boundaryexport class TelemetryErrorBoundary extends React.Component<  {    children: React.ReactNode;    fallback?: React.ComponentType<{ error: Error; retry: () => void }>;    context?: Partial<ErrorContext>;  },  { hasError: boolean; error?: Error }> {  constructor(props: any) {    super(props);    this.state = { hasError: false };  }
  static getDerivedStateFromError(error: Error) {    return { hasError: true, error };  }
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {    errorTracker.captureError(error, {      ...this.props.context,      businessContext: {        feature: 'react_error_boundary',      },    });  }
  render() {    if (this.state.hasError && this.state.error) {      if (this.props.fallback) {        return React.createElement(this.props.fallback, {          error: this.state.error,          retry: () => this.setState({ hasError: false, error: undefined })        });      }
      return (        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>          <Text>Bir şeyler yanlış gitti. Lütfen uygulamayı yeniden başlatın.</Text>        </View>      );    }
    return this.props.children;  }}

Çalışan Firebase Entegrasyonu

Firebase Performance Monitoring başlamak için harika, ama dikkatli entegrasyon gerektirir:

typescript
// telemetry/firebase-integration.ts - Çalışan Firebase entegrasyonuimport perf from '@react-native-firebase/perf';import { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';import { ExportResult, ExportResultCode } from '@opentelemetry/core';
export class ProductionFirebaseExporter implements SpanExporter {  private activeTraces = new Map<string, any>();  private maxConcurrentTraces = 50; // Firebase'in limitleri var
  export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {    try {      // Firebase'i boğmamak için span'ları chunk'larda işle      const chunks = this.chunkArray(spans, 10);
      chunks.forEach((chunk, index) => {        setTimeout(() => {          chunk.forEach(span => this.processSpan(span));        }, index * 100); // İşlemeyi basamakla      });
      resultCallback({ code: ExportResultCode.SUCCESS });    } catch (error) {      console.error('Firebase export error:', error);      resultCallback({ code: ExportResultCode.FAILED });    }  }
  private async processSpan(span: ReadableSpan) {    const { name, duration, attributes, status } = span;
    // Firebase'in iyi handle etmediği span'ları atla    if (this.shouldSkipSpan(name, attributes)) {      return;    }
    // Firebase için trace adını temizle    const traceName = this.cleanTraceName(name);
    // Firebase limitlerini aşmamak için concurrent trace'leri yönet    if (this.activeTraces.size >= this.maxConcurrentTraces) {      console.warn('Too many active Firebase traces, skipping:', traceName);      return;    }
    try {      const trace = perf().newTrace(traceName);      this.activeTraces.set(traceName, trace);
      // Attribute'lar ekle (Firebase'in bunlarda da limitleri var)      this.addSafeAttributes(trace, attributes);
      // Business metrikleri ekle      this.addBusinessMetrics(trace, attributes);
      // Trace timing'ini simüle et      trace.start();
      setTimeout(() => {        try {          if (status?.code === 2) { // ERROR            trace.putAttribute('error', 'true');            trace.putMetric('error_count', 1);          }
          trace.stop();          this.activeTraces.delete(traceName);        } catch (stopError) {          console.warn('Firebase trace stop failed:', stopError);        }      }, Math.min(duration / 1000000, 60000)); // Max 60s trace
    } catch (error) {      console.warn('Firebase trace creation failed:', error);      this.activeTraces.delete(traceName);    }  }
  private shouldSkipSpan(name: string, attributes: any): boolean {    // Yüksek frekanslı, düşük değerli span'ları atla    if (name.includes('scroll') || name.includes('animation')) {      return true;    }
    // Internal telemetry span'larını atla    if (name.includes('telemetry') || name.includes('metric')) {      return true;    }
    // Duration'ı olmayan span'ları atla    if (!attributes['duration'] && !attributes['http.response_time']) {      return true;    }
    return false;  }
  private cleanTraceName(name: string): string {    // Firebase'in sıkı naming gereklilikleri var    return name      .replace(/[^a-zA-Z0-9_]/g, '_')      .substring(0, 100) // Firebase limiti      .toLowerCase();  }
  private addSafeAttributes(trace: any, attributes: any) {    const safeAttributes: Record<string, string> = {};    let attributeCount = 0;    const maxAttributes = 5; // Firebase free tier limiti
    // Business-ilişkili attribute'lara öncelik ver    const priorities = [      'user.id',      'screen.name',      'http.status_code',      'business.feature',      'error.type',    ];
    priorities.forEach(key => {      if (attributes[key] && attributeCount < maxAttributes) {        safeAttributes[key.replace('.', '_')] = String(attributes[key]).substring(0, 100);        attributeCount++;      }    });
    // Limite kadar kalan attribute'ları ekle    Object.entries(attributes).forEach(([key, value]) => {      if (!priorities.includes(key) && attributeCount < maxAttributes) {        const safeKey = key.replace(/[^a-zA-Z0-9_]/g, '_');        safeAttributes[safeKey] = String(value).substring(0, 100);        attributeCount++;      }    });
    // Trace'e attribute'ları set et    Object.entries(safeAttributes).forEach(([key, value]) => {      try {        trace.putAttribute(key, value);      } catch (error) {        console.warn(`Failed to set Firebase attribute ${key}:`, error);      }    });  }
  private addBusinessMetrics(trace: any, attributes: any) {    // Business monitoring için önemli olan metrikleri ekle    try {      if (attributes['http.status_code']) {        trace.putMetric('http_status', Number(attributes['http.status_code']));      }
      if (attributes['api.response_time']) {        trace.putMetric('response_time_ms', Number(attributes['api.response_time']));      }
      if (attributes['business.monetary_value']) {        trace.putMetric('monetary_value', Number(attributes['business.monetary_value']));      }
      if (attributes['screen.load_duration']) {        trace.putMetric('load_time_ms', Number(attributes['screen.load_duration']));      }
    } catch (error) {      console.warn('Failed to add Firebase metrics:', error);    }  }
  private chunkArray<T>(array: T[], chunkSize: number): T[][] {    const chunks: T[][] = [];    for (let i = 0; i < array.length; i += chunkSize) {      chunks.push(array.slice(i, i + chunkSize));    }    return chunks;  }
  async shutdown(): Promise<void> {    // Kalan trace'leri temizle    this.activeTraces.forEach(trace => {      try {        trace.stop();      } catch (error) {        console.warn('Error stopping Firebase trace during shutdown:', error);      }    });    this.activeTraces.clear();  }}

Gerçekten Yardımcı Olan Kullanım Pattern'leri

Telemetry sistemini gerçek uygulama kodunda nasıl kullandığım:

Screen Component Tracking

typescript
// Gerçek screen component'taimport React, { useEffect, useState } from 'react';import { performanceMonitor } from '../telemetry/performance-monitor';import { errorTracker } from '../telemetry/error-tracking';
export function PaymentScreen({ route }: any) {  const [loading, setLoading] = useState(true);  const [paymentData, setPaymentData] = useState(null);
  useEffect(() => {    loadPaymentScreen();  }, []);
  const loadPaymentScreen = async () => {    try {      // User journey tracking başlat      const journeyId = performanceMonitor.startUserJourney('payment_flow', route.params?.userId);
      // Business context ile screen load ölç      const data = await performanceMonitor.measureScreenLoad(        'payment_screen',        async () => {          // Ödeme methodlarını yükle          const methods = await performanceMonitor.instrumentApiCall(            '/api/payment-methods',            'GET',            () => api.getPaymentMethods(),            {              userId: route.params?.userId,              feature: 'payment_methods',              monetaryValue: route.params?.totalAmount,            }          );
          // Kullanıcı tercihlerini yükle          const preferences = await api.getUserPreferences();
          return { methods, preferences };        },        true // Bu business critical      );
      setPaymentData(data);      setLoading(false);
    } catch (error) {      errorTracker.trackBusinessError('payment_failure', error as Error, {        userId: route.params?.userId,        monetaryValue: route.params?.totalAmount,        customerTier: route.params?.customerTier,        feature: 'payment_screen_load',      });
      setLoading(false);    }  };
  const handlePaymentSubmit = async (paymentDetails: any) => {    try {      const result = await performanceMonitor.instrumentApiCall(        '/api/process-payment',        'POST',        () => api.processPayment(paymentDetails),        {          userId: route.params?.userId,          feature: 'payment_processing',          monetaryValue: route.params?.totalAmount,        }      );
      // Journey'yi başarıyla tamamla      performanceMonitor.completeUserJourney(journeyId, true, {        paymentMethod: paymentDetails.method,        amount: route.params?.totalAmount,      });
      // Success'e git      navigation.navigate('PaymentSuccess', { transactionId: result.id });
    } catch (error) {      // Journey'yi başarısızlıkla tamamla      performanceMonitor.completeUserJourney(journeyId, false, {        failedStep: 'payment_processing',        error: error.message,      });
      errorTracker.trackBusinessError('payment_failure', error as Error, {        userId: route.params?.userId,        monetaryValue: route.params?.totalAmount,        customerTier: route.params?.customerTier,        feature: 'payment_processing',      });    }  };
  if (loading) {    return <LoadingSpinner />;  }
  return (    <PaymentForm      data={paymentData}      onSubmit={handlePaymentSubmit}    />  );}

Outage'ları Önleyen Monitoring Kurulumu

Bu sistemi implement ettikten sonra, production'da izlediğimiz şeyler:

Datadog Dashboard Konfigürasyonu

typescript
// Bizi birden fazla incident'tan kurtaran dashboardexport const productionDashboards = {  "mobile_app_health": {    "title": "Mobile App Health - Production",    "widgets": [      {        "title": "Critical Business Errors",        "type": "timeseries",        "queries": [          {            "query": "sum:custom.critical_errors{*} by {error_type}",            "display_type": "bars"          }        ],        "alert_threshold": 5 // 5 dakikada 5'ten fazla kritik error'da alert      },      {        "title": "Payment API Response Times",        "type": "timeseries",        "queries": [          {            "query": "avg:custom.api_call_duration{endpoint:payment*} by {endpoint}",            "display_type": "line"          }        ],        "alert_threshold": 5000 // Payment API'lar 5s'yi geçerse alert      },      {        "title": "Screen Load Performance",        "type": "heatmap",        "queries": [          {            "query": "custom.screen_load_duration{business_critical:true}"          }        ]      },      {        "title": "User Journey Completion Rate",        "type": "query_value",        "queries": [          {            "query": "sum:custom.user_journey_completion{success:true} / sum:custom.user_journey_completion{*} * 100"          }        ]      },      {        "title": "App Crashes by Device",        "type": "toplist",        "queries": [          {            "query": "sum:custom.critical_errors{type:crash} by {device_model}"          }        ]      }    ]  }};

Gerçekten İşe Yarayan Alert'ler

typescript
// Gerçek sorunlar için beni uyandıran, gürültü olmayan alert'lerexport const productionAlerts = {  "payment_failure_spike": {    "name": "Payment API Failure Spike",    "query": "sum(last_5m):sum:custom.critical_errors{type:payment_api_failure} > 3",    "message": "@slack-payments @pagerduty-critical",    "priority": "P1",    "escalation": "immediate"  },
  "user_journey_drop": {    "name": "User Journey Completion Drop",    "query": "avg(last_15m):sum:custom.user_journey_completion{success:true} / sum:custom.user_journey_completion{*} < 0.8",    "message": "@slack-product @email-team",    "priority": "P2",    "escalation": "15_minutes"  },
  "critical_screen_slow": {    "name": "Critical Screen Load Time",    "query": "avg(last_10m):avg:custom.screen_load_duration{business_critical:true} > 5000",    "message": "@slack-engineering",    "priority": "P2",    "escalation": "30_minutes"  }};

Performans Etkisi ve Optimizasyon

18 aylık production kullanımından sonra, gerçek performans rakamları:

Kaynak Kullanımı

  • CPU overhead: 2-3% ortalama (Xcode Instruments ile ölçüldü)
  • Memory overhead: 15-20MB (çoğunlukla trace buffering)
  • Batarya etkisi: İhmal edilebilir (günlük 1%'den az tüketim)
  • Network kullanımı: Kullanıcı başına günde 50-100KB

Maliyet Analizi (Aylık)

  • Datadog: $400/ay (100M span, 50GB log)
  • Firebase: $0 (ücretsiz tier limitleri içinde)
  • AWS infrastructure: $50/ay (OTEL collector)
  • Kazanılan development zamanı: 40+ saat/ay
  • ROI: 10x (debugging verimliliği + önlenen outage'lar)

İşe Yarayan Optimizasyon Stratejileri

typescript
// Maliyetleri 60% azaltan akıllı samplingclass AdaptiveSampler {  private errorRate = new Map<string, number>();  private criticalSessions = new Set<string>();
  shouldSample(spanName: string, attributes: any): boolean {    // Error'ları ve kritik business akışlarını her zaman sample'la    if (spanName.includes('error') || attributes['business.monetary_value']) {      return true;    }
    // Kritik kullanıcı session'larını daha yüksek oranda sample'la    if (attributes['user.tier'] === 'premium') {      return Math.random() < 0.5; // 50% sampling    }
    // Error oranlarına göre adaptif sampling    const errorRate = this.errorRate.get(spanName) || 0;    if (errorRate > 0.05) { // 5%'ten fazla error      return Math.random() < 0.8; // Sampling'i artır    }
    // Varsayılan sampling    return Math.random() < 0.1; // 10% base oran  }}

Sonuçlar: Gerçekten Yardımcı Olan Gözlemlenebilirlik

Kullanıcılar Fark Etmeden Yakalanan Sorunlar

  1. iOS 15.4 Network Bug'ı: Major rollout'tan 2 gün önce iOS 15.4 WiFi kullanıcılarına spesifik API timeout'larını yakaladı
  2. Image Caching'de Memory Leak: Kullanıcı şikâyetlerinden önce 20% RAM kullanımı artışını tespit etti
  3. Payment Race Condition: Journey tracking kullanarak hızlı network'lerde 0.3% ödeme başarısızlığı buldu
  4. Android Battery Drain: Samsung cihazlarda 15% batarya tüketen background process'i tanımladı

Business Etkisi

  • Daha Hızlı Sorun Çözme: Ortalama debugging süresi 6 saatten 45 dakikaya düştü
  • Proaktif Düzeltmeler: Sorunların 60%'ı kullanıcı şikâyetlerinden önce düzeltildi
  • Müşteri Memnuniyeti: App store değerlendirmesi 3.2'den 4.6'ya yükseldi
  • Gelir Koruması: Tahmini $1100K+ kayıp işlem önlendi

Developer Mutluluğu

  • Körü Körüne Debug Yok: Kullanıcı yolculuğu ile zengin context'li error raporları
  • Deployment'larda Güven: Kapsamlı monitoring regression'ları hızla yakalar
  • Veri Temelli Kararlar: Gerçek metriklerle desteklenen performans budgetleri

Zor Öğrenilen Dersler

1. Basit Başla, Kademeli Evrimleş

İlk günden her şeyi monitor etmeye çalışma. Şunlarla başla:

  1. Kritik business akışları (ödemeler, login, temel özellikler)
  2. Context'li error tracking
  3. Ana screen'ler için performans monitoring
  4. Temel user journey tracking

2. Context Her Şeydir

Ham metrikler işe yaramaz. Her zaman şunları dahil et:

  • User context (ID, session, journey)
  • Business context (özellik, para değeri, müşteri seviyesi)
  • Teknik context (cihaz, network, app versiyonu)
  • Error context (kullanıcı ne yapıyordu)

3. Sampling Stratejisi Önemli

  • Kritik akışlar: 100% sampling
  • Business özellikleri: 50% sampling
  • UI etkileşimleri: 10% sampling
  • Background task'lar: 1% sampling

4. Alert'ler Sizi Uyandırmalı

Yalnızca anında aksiyon gerektiren şeylerde alert:

  • Ödeme işleme başarısızlıkları
  • Crash oranı artışları
  • Kritik business akış tamamlanma düşüşleri
  • Güvenlik-ilişkili event'ler

5. Birden Fazla Exporter = Güvenilirlik

Tek monitoring provider'a güvenme:

  • Birincil: Datadog (zengin analytics)
  • İkincil: Elastic APM (maliyet kontrolü)
  • Backup: Firebase (her zaman çalışır)

Başlangıç: 7 Günlük Implementation Planı

1-2. Gün: Temel

  • OpenTelemetry provider kurulumu
  • Temel error tracking ekle
  • Global error handler'ları implement et

3-4. Gün: Performans Monitoring

  • Screen load tracking ekle
  • API call instrumentation implement et
  • Navigation tracking kur

5-6. Gün: Business Metrikleri

  • User journey'leri track et
  • Custom business event'leri ekle
  • Kritik akış monitoring kur

7. Gün: Production Deployment

  • Sampling oranlarını configure et
  • Alert'leri kur
  • Monitoring dashboard'ları oluştur

Son Düşünceler: Ürün Özelliği Olarak Gözlemlenebilirlik

18 ay boyunca production gözlemlenebilirliği kurduktan sonra, monitoring'in sadece "olması güzel" bir şey değil - rekabet avantajı olduğunu öğrendim.

Sorunları hızla debug etme, outage'ları önleme ve gerçek verilere dayalı kullanıcı deneyimini optimize etme yeteneği, ekibimizin özellik geliştirme şeklini transform etti. Reaktif debugging'den proaktif optimizasyona geçtik.

İlk yatırım (2 hafta development + ayda $500 araç) kendisini çözmeye yardım ettiği ilk büyük sorunla amorti ediyor.

Kullanıcılarınız iyi gözlemlenebilirlik için teşekkür etmeyecek, ama yoksa kesinlikle şikâyet edecekler. Bugün sizinkini inşa etmeye başlayın.

İlgili Yazılar