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
}