SDK & Ingestion Documentation

Mobile Error Ingestion Guide

Gunakan panduan ini untuk mengintegrasikan crash reporting ke aplikasi mobile Anda. Setiap error yang dikirim akan otomatis di-group secara cerdas berdasarkan fingerprint stack trace.

HTTP Ingestion Endpoint Specification

Method & Endpoint:
POST https://manage-app.idnacode.my.id/api/v1/errors
Authentication Header:
X-App-Key:
JSON Payload Fields:
Field Type Requirement Description
message string Required Pesan error / exception description.
exception_class string Optional Nama class exception (misal: NullPointerException, TypeError).
severity string Optional 'fatal' | 'error' | 'warning' | 'info' (default: 'error').
stack_trace string Optional Full stack trace trace back (digunakan untuk fingerprint grouping).
os_name, os_version string Optional Contoh: "Android", "14.0" atau "iOS", "17.4".
device_model string Optional Model perangkat fisik (misal: "Samsung Galaxy S24", "iPhone 15 Pro").
app_version string Optional Versi rilis aplikasi (misal: "2.4.1").
breadcrumbs array Optional Array event aktivitas user sebelum crash (UI, navigation, HTTP).
user_id, user_email string Optional Identitas user untuk tracking jumlah user terdampak.

Code Implementations

Integrasi di Flutter

Di Flutter, gunakan kombinasi FlutterError.onError untuk menangkap error widget tree dan PlatformDispatcher.instance.onError untuk menangkap unhandled async exceptions:

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class CrashLogger {
  static const String endpoint = 'https://manage-app.idnacode.my.id/api/v1/errors';
  static const String apiKey = '';

  static final List<Map<String, dynamic>> _breadcrumbs = [];

  static void addBreadcrumb(String message, {String category = 'action'}) {
    _breadcrumbs.add({
      'category': category,
      'message': message,
      'timestamp': DateTime.now().toIso8601String(),
    });
    if (_breadcrumbs.length > 30) _breadcrumbs.removeAt(0);
  }

  static Future<void> report(Object error, StackTrace? stackTrace, {String severity = 'fatal'}) async {
    try {
      await http.post(
        Uri.parse(endpoint),
        headers: {
          'Content-Type': 'application/json',
          'X-App-Key': apiKey,
        },
        body: jsonEncode({
          'exception_class': error.runtimeType.toString(),
          'message': error.toString(),
          'severity': severity,
          'stack_trace': stackTrace?.toString(),
          'os_name': defaultTargetPlatform.name,
          'app_version': '1.0.0',
          'breadcrumbs': _breadcrumbs,
        }),
      );
    } catch (_) {}
  }
}

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterError.presentError(details);
    CrashLogger.report(details.exception, details.stack);
  };

  PlatformDispatcher.instance.onError = (error, stack) {
    CrashLogger.report(error, stack);
    return true;
  };

  runApp(const MyApp());
}

Integrasi di React Native

Di React Native, pasang handler pada ErrorUtils global:

import { Platform } from 'react-native';

const API_ENDPOINT = 'https://manage-app.idnacode.my.id/api/v1/errors';
const API_KEY = '';

const breadcrumbs = [];

export const recordBreadcrumb = (category, message) => {
  breadcrumbs.push({ category, message, timestamp: new Date().toISOString() });
  if (breadcrumbs.length > 30) breadcrumbs.shift();
};

export const sendCrash = async (error, isFatal = false) => {
  try {
    await fetch(API_ENDPOINT, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-App-Key': API_KEY,
      },
      body: JSON.stringify({
        exception_class: error.name || 'JavascriptError',
        message: error.message || String(error),
        severity: isFatal ? 'fatal' : 'error',
        stack_trace: error.stack || '',
        os_name: Platform.OS === 'ios' ? 'iOS' : 'Android',
        os_version: String(Platform.Version),
        breadcrumbs: breadcrumbs,
      }),
    });
  } catch (e) {}
};

if (global.ErrorUtils) {
  const originalHandler = global.ErrorUtils.getGlobalHandler();
  global.ErrorUtils.setGlobalHandler((error, isFatal) => {
    sendCrash(error, isFatal);
    if (originalHandler) originalHandler(error, isFatal);
  });
}

Integrasi di Android (Kotlin)

Gunakan Thread.setDefaultUncaughtExceptionHandler di Application class Anda:

val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()

Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
    // Kirim crash report via HTTP POST ke https://manage-app.idnacode.my.id/api/v1/errors dengan header X-App-Key
    defaultHandler?.uncaughtException(thread, throwable)
}

Integrasi di iOS (Swift)

Gunakan NSSetUncaughtExceptionHandler pada saat application(_:didFinishLaunchingWithOptions:):

NSSetUncaughtExceptionHandler { exception in
    // Kirim exception.name, exception.reason, exception.callStackSymbols via URLSession
}