# Abrova docs (en) https://abrova.ir — backend services for app developers in Iran. ## Abrova Trace - Flutter SDK: https://abrova.ir/en/docs/trace/flutter-sdk Using an AI assistant? Use **Copy page for AI** at the top of this page and paste it into the chat. See [AI assistants](/docs/trace/ai-assistants). ### Supported Platforms - **Flutter Web** — Full support with JS error catching - **Chrome Extension** — Optimized for extension environment - **Android** — Dart errors plus native crash capture (bundled native Abrova Trace SDK) - **iOS** — Dart errors plus native crash capture (bundled native Abrova Trace SDK) - **Desktop** — Windows, macOS, Linux (Dart errors only; no native crash capture)

Installation

The package comes from Abrova Center, so you do not need access to any other package registry. Two steps: point your project at the Abrova registry, then install. **1. Add the package from the Abrova registry** in `pubspec.yaml`: ```yaml # pubspec.yaml dependencies: abrova_trace_flutter_sdk: hosted: https://center.abrova.ir/api/packages/abrova/pub version: ^0.2.6 ``` **2. Add the native SDKs' sources.** The plugin uses the native Android and iOS SDKs for crash capture. Android: add the Abrova Maven repository in `android/build.gradle` (or `android/settings.gradle`, wherever your project lists repositories): ```groovy allprojects { repositories { google() mavenCentral() maven { url "https://center.abrova.ir/api/packages/abrova/maven" } } } ``` iOS: add this inside the app target in `ios/Podfile`: ```ruby pod 'AbrovaTrace', :git => 'https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git', :tag => '0.2.4' ``` Then install: ```bash flutter pub get ``` **Requirements**: Dart SDK 3.8.1 or newer. Android: API 19 (Android 4.4) or newer. iOS: 12.0 or newer, built with Xcode 15.3+ (Swift 5.10). On web, Dart errors, messages, logs and performance work; native crash capture is Android and iOS only. Use your project's public key `ab_live_…` from Abrova Console. It is safe inside your app. Never put the server secret `ab_srv_…` in an app.

Quick Start

#### Basic Initialization ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize AbrovaTrace await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', )); runApp(MyApp()); } ``` #### Recommended: Initialize with Zone (Better Error Catching) Using initWithZone catches async errors that would otherwise be missed. This is the recommended approach for production apps. ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { // IMPORTANT: Initialize AbrovaTrace FIRST, before WidgetsFlutterBinding await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', captureUncaughtErrors: true, ), () async { // Move all initialization inside this callback WidgetsFlutterBinding.ensureInitialized(); // Any other async initialization goes here // await Firebase.initializeApp(); // await setupDatabase(); runApp(MyApp()); }, ); } ```

Capture Errors

#### Capture Exceptions ```dart try { riskyOperation(); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Failed to perform risky operation', extra: {'user_id': 'user123'}, ); } ``` #### Capture Messages ```dart await AbrovaTrace.captureMessage( 'User completed onboarding', extra: {'step': 'welcome_screen'}, ); // With a level and tags await AbrovaTrace.captureMessage( 'API rate limit approaching', level: MessageLevel.warning, tags: {'feature': 'sync'}, ); ``` Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: `MessageLevel.debug`, `info` (default), `warning`, `error`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. #### Custom Error Context ```dart await AbrovaTrace.captureException( exception, extra: { 'user_id': 'user123', 'feature': 'shopping_cart', 'action': 'checkout', 'cart_items': 5, 'total_amount': 99.99, }, ); ```

User Context

Associate errors and logs with specific users and add custom metadata. If you use [Abrova Auth](/docs/auth/flutter), pass the signed-in user's id. #### Set User ID ```dart // Set after user login AbrovaTrace.setUserId('user_123'); // Get current user ID final userId = AbrovaTrace.getUserId(); ``` #### Custom Extra Data ```dart // Set individual extra AbrovaTrace.setExtra('subscription', 'premium'); // Set multiple extras at once AbrovaTrace.setExtras({ 'subscription': 'premium', 'feature_flags': 'dark_mode,beta', }); // Read extra data final sub = AbrovaTrace.getExtra('subscription'); final allExtras = AbrovaTrace.getExtras(); // Clear all extras AbrovaTrace.clearExtras(); ``` #### Tags ```dart // Set individual tag AbrovaTrace.setTag('team', 'checkout'); // Set multiple tags at once AbrovaTrace.setTags({ 'team': 'checkout', 'module': 'payments', }); // Read tags final team = AbrovaTrace.getTag('team'); final allTags = AbrovaTrace.getTags(); // Clear all tags AbrovaTrace.clearTags(); ``` Breadcrumbs track user actions and events leading up to an error, helping you understand what happened before the crash. ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // User action breadcrumb AbrovaTrace.addUserBreadcrumb('Added item to cart', data: { 'product_id': 'abc', 'quantity': 2, }); // Navigation breadcrumb AbrovaTrace.addNavigationBreadcrumb('HomeScreen', 'ProductDetail'); // HTTP request breadcrumb AbrovaTrace.addHttpBreadcrumb('POST', 'https://api.example.com/orders', statusCode: 201); // Generic breadcrumb AbrovaTrace.addBreadcrumb( 'Order placed', type: BreadcrumbType.info, data: {'order_id': '12345', 'total': 99.99}, ); // Clear all breadcrumbs AbrovaTrace.clearBreadcrumbs(); ```

Auto-Capture Taps (Gesture Breadcrumbs)

Automatically log every user tap as a breadcrumb. Works on every Flutter target — Web, Android, iOS, macOS, Windows, Linux — because it hooks into the framework's pointer router, not platform code. When an error fires, the issue detail shows the widget the user tapped just before — usually enough to reproduce a bug without a perfect stack trace. This is especially useful on Flutter Web, where production stack traces can be partially symbolicated due to dart2js inlining. #### Enable Call once after init, after `WidgetsFlutterBinding.ensureInitialized()`. Idempotent — safe to call multiple times. ```dart await AbrovaTrace.initWithZone( AbrovaTraceConfig(apiKey: 'ab_live_...'), () async { WidgetsFlutterBinding.ensureInitialized(); // Enable automatic tap breadcrumbs AbrovaTrace.enableGestureBreadcrumbs(); runApp(MyApp()); }, ); ``` #### What gets captured Every tap on a recognized interactive widget produces a breadcrumb like: ```json { "message": "Tap on ElevatedButton: \"Pay\"", "type": "user", "timestamp": "2026-05-15T14:32:18.421Z", "data": { "action": "tap", "widget": "ElevatedButton", "label": "Pay", "key": "checkout-pay-button", "path": "CheckoutPage > Scaffold", "route": "/checkout", "position": { "x": 187, "y": 614 } } } ``` The `route` field is filled when you add `AbrovaTraceNavigatorObserver()` to your app's `navigatorObservers` (it also records navigation breadcrumbs automatically). #### Recognized widgets The tracker classifies these widget types out of the box (more specific types take priority): - ElevatedButton - TextButton - OutlinedButton - FilledButton - IconButton - FloatingActionButton - Switch - Checkbox - Radio - Slider - PopupMenuButton - DropdownButton - InkWell - InkResponse - GestureDetector - CupertinoButton #### Tip: Add ValueKey to important widgets The breadcrumb tries to find a label in this order: `Text` child → `Semantics.label` → `Tooltip.message` → `Icon.semanticLabel`. For widgets without text (e.g. `IconButton`), add a `ValueKey` so you can grep for it in your code: ```dart IconButton( key: const ValueKey('cart-remove-row-2'), // ← surfaces in breadcrumb icon: const Icon(Icons.delete), onPressed: _removeItem, ) ``` #### Disable ```dart AbrovaTrace.disableGestureBreadcrumbs(); ``` The tracker captures the visible widget label — typically a button caption like "Pay" or "Add to Cart". It does not capture form input values, text-field contents, or anything entered by the user. A `ValueKey` is reported in addition to the label, not instead of it, so keep sensitive text out of the labels of tappable widgets.

Logging

Structured log ingestion with automatic batching and mobile lifecycle awareness. Enable it once, then use the level helpers from anywhere in your app: ```dart // Enable logging after AbrovaTrace.init() AbrovaTrace.enableLogging(sourceId: 'my-flutter-app'); AbrovaTrace.info('User login successful', metadata: {'user_id': 'user_123'}); ``` See [Flutter SDK Logging](/docs/trace/flutter-logging) for the full pipeline — batching, log levels, background flushing, manual flush, and configuration.

Performance Tracking

Track HTTP request latency and custom operations for APM. #### Automatic HTTP Client ```dart // Create an HTTP client that automatically tracks all requests final client = AbrovaTrace.createPerformanceClient(); final response = await client.get(Uri.parse('https://api.example.com/users')); // Duration, status code, and errors are automatically reported ``` `createPerformanceClient()` takes the same options as `PerformanceHttpClient` (below) and fills in your API key, API URL, environment, release and platform from the config: `inner`, `addBreadcrumbs` (default `true`), `excludedHosts`, `minDurationMs` (default `0`). #### PerformanceHttpClient `PerformanceHttpClient` is an `http.BaseClient` (from `package:http`) that times every request and reports it as an `http` span. Use it directly when you want to wrap an existing client or construct it yourself: ```dart import 'package:http/http.dart' as http; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; final client = PerformanceHttpClient( apiKey: 'ab_live_...', // Required environment: 'production', releaseVersion: '1.0.0', inner: http.Client(), // Default: a new http.Client() addBreadcrumbs: true, // Also add HTTP breadcrumbs (default: true) excludedHosts: {'analytics.example.com'}, // Hosts to skip minDurationMs: 50, // Skip requests faster than this (default: 0) ); final response = await client.get(Uri.parse('https://api.example.com/users')); // Or wrap an existing client final tracked = http.Client().withPerformanceTracking(apiKey: 'ab_live_...'); // Send buffered spans now, and stop the client when done await client.flush(); client.close(); ``` Spans are buffered and sent in batches of 10, or every 10 seconds. Requests to Abrova hosts are never tracked. Breadcrumb URLs have sensitive query parameters (token, password, api_key, ...) replaced with `[REDACTED]`. Spans show up under **Abrova Trace → Performance**. #### Track Custom Operations ```dart final result = await AbrovaTrace.trackOperation( 'load_user_profile', () async { final user = await fetchUser(); await cacheUser(user); return user; }, operationType: 'custom', ); ``` #### Manual Span Reporting ```dart final start = DateTime.now(); // Report a single performance span await AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method: 'GET', url: 'https://api.example.com/products', statusCode: 200, startTime: start, durationMs: 245, ), ); // Report a batch of spans await AbrovaTrace.reportPerformanceSpanBatch([ PerformanceSpan.fromHttpRequest( method: 'GET', url: '/api/users', statusCode: 200, startTime: start, durationMs: 150, ), PerformanceSpan.custom( operation: 'image_processing', startTime: start, durationMs: 800, ), ]); ```

Platform-Specific Usage

#### Chrome Extension ```dart void main() async { await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', debug: false, // Disable in production extensions )); runApp(MyExtensionApp()); } ``` #### Mobile App ```dart void main() async { // Required before init() on Android and iOS WidgetsFlutterBinding.ensureInitialized(); await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', captureUncaughtErrors: true, // Recommended for mobile )); runApp(MyMobileApp()); } ``` #### Web App ```dart void main() async { await AbrovaTrace.init(AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: 'production', debug: kDebugMode, // Enable debug in development )); runApp(MyWebApp()); } ```

Crash Detection

AbrovaTrace provides comprehensive error detection for any Flutter app, regardless of what libraries or native code you use. - **Dart Exceptions** — Captured in real-time - **Async Errors** — Captured in real-time (with initWithZone) - **Platform Exceptions** — Captured in real-time - **Native Crashes** — iOS and Android only; reported on next app launch #### How Native Crash Detection Works The plugin bundles the native Abrova Trace SDKs for iOS and Android and starts them during `init()` / `initWithZone()`: 1. **iOS** — PLCrashReporter records signal and Mach exception crashes to disk 2. **Android** — an uncaught exception handler, plus `ApplicationExitInfo` (Android 11 / API 30+) for native crashes and ANRs 3. On the next launch, the native SDK reads the saved report and sends it to Abrova Trace as an issue No extra code is needed. Crashes are sent on the *next* launch, so a crash appears after the user reopens the app. `AbrovaTrace.close()` flushes pending logs and releases the SDK. It is not needed for crash detection. After `close()`, calls such as `captureException()` throw until you call `init()` again, so only call it when your app is really shutting down.

Configuration Options

```dart AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', // Required environment: 'production', // Default: 'production' release: '1.0.0', // Optional captureUncaughtErrors: true, // Default: true enabled: true, // Default: true debug: false, // Default: false timeout: 30, // Default: 30 seconds sampleRate: 1.0, // 0.0 to 1.0 maxBreadcrumbs: 20, // Default: 20 enableOfflineStorage: true, // Default: true collectDeviceInfo: true, // Default: true logHandler: (level, message, error, stackTrace) {}, // Optional ) ``` #### Offline Storage With `enableOfflineStorage` on (the default), errors that cannot be sent because the device is offline are kept on the device and sent later. Only errors are kept — messages, logs and performance data are not saved to disk. - **Native errors (Android, iOS)** — the option is passed to the bundled native SDKs, which keep up to 100 unsent errors and send them later. Requires version 0.2.5 or newer. - **Dart errors** — when a send fails with a network error or timeout, the error is saved to a file (up to 100; the oldest are dropped first) and re-sent the next time the app starts, with `AbrovaTrace.init()` and with `AbrovaTrace.initWithZone()` alike (`initWithZone()` needs version 0.2.5 or newer). A stored error the server refuses for good is dropped instead of being retried at every start; it is kept when the server asks to try again later (HTTP 408, 429) or has a problem (5xx). - **Web** — nothing is stored. Before 0.2.6 the SDK could print `AbrovaTrace: Failed to initialize offline storage: Binding has not yet been initialized` at start when you used `initWithZone()` with `WidgetsFlutterBinding.ensureInitialized()` inside the app function. It was harmless, and 0.2.6 no longer prints it: offline storage sets itself up once the app has started.

Error Information Captured

The SDK automatically captures the following information with every error: - **Error Message** — Exception message or custom message - **Stack Trace** — Full stack trace for debugging - **Platform** — Detected platform (flutter_web, flutter_android, etc.) - **Environment** — Environment name (production, development, etc.) - **Release** — App version/release identifier - **Timestamp** — When the error occurred - **User ID, session ID, extras, tags and breadcrumbs** — Whatever you set on the SDK - **Device context** — Device model, OS and version, app version and build (when `collectDeviceInfo` is on; on web only locale and time zone) - **User Agent** — SDK version, platform and release (on web, the browser's user agent) - **URL** — Current page URL (web only) ### Complete Example ```dart import 'package:flutter/material.dart'; import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; void main() async { await AbrovaTrace.initWithZone( AbrovaTraceConfig( apiKey: 'ab_live_your_api_key', environment: const String.fromEnvironment('ENV', defaultValue: 'development'), release: '1.0.0', captureUncaughtErrors: true, sampleRate: 1.0, maxBreadcrumbs: 20, enableOfflineStorage: true, debug: true, ), () async { WidgetsFlutterBinding.ensureInitialized(); // Auto-capture taps as breadcrumbs (all platforms) AbrovaTrace.enableGestureBreadcrumbs(); // Enable logging AbrovaTrace.enableLogging( sourceId: 'my-flutter-app', sourceName: 'My Flutter App', ); runApp(MyApp()); }, ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: HomeScreen(), ); } } class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { @override void initState() { super.initState(); AbrovaTrace.setUserId('user_123'); AbrovaTrace.setTag('screen', 'HomeScreen'); AbrovaTrace.addNavigationBreadcrumb('Launch', 'HomeScreen'); } Future _handlePurchase() async { AbrovaTrace.addUserBreadcrumb('Initiated purchase', data: {'product': 'Premium'}); try { await AbrovaTrace.trackOperation('process_purchase', () async { await processPurchase(); }); AbrovaTrace.info('Purchase successful', metadata: {'amount': 99.99}); } catch (e, stackTrace) { await AbrovaTrace.captureException( e, stackTrace: stackTrace, message: 'Purchase failed', extra: {'user_id': 'user_123', 'screen': 'HomeScreen'}, ); } } Future processPurchase() async { // ... purchase logic } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('My App')), body: Center( child: ElevatedButton( onPressed: _handlePurchase, child: const Text('Purchase'), ), ), ); } } ``` ### Best Practices - Node.js SDK: https://abrova.ir/en/docs/trace/nodejs-sdk

Installation

Packages come from Abrova Center. Two steps: **1. Add the Abrova registry** to `.npmrc` in your project root (npm, yarn and pnpm all read it): ``` # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` **2. Install the package:** ```bash npm install @abrova-trace/nodejs-sdk # or yarn add @abrova-trace/nodejs-sdk # or pnpm add @abrova-trace/nodejs-sdk ```

Quick Start

```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); // Initialize AbrovaTrace AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', // project API key serverSecret: process.env.ABROVA_SERVER_SECRET, // ab_srv_... (required) environment: 'production', release: '1.0.0' }); // Capture exceptions try { throw new Error('Something went wrong!'); } catch (error) { AbrovaTrace.captureException(error); } // Capture messages AbrovaTrace.captureMessage('User completed checkout'); ```

Capture Errors

#### Capture Exceptions ```javascript AbrovaTrace.captureException(error, { extra: { userId: 123, feature: 'checkout' } }); ``` #### Capture Messages ```javascript AbrovaTrace.captureMessage('Something happened', { level: 'warning', // 'debug' | 'info' (default) | 'warning' | 'error' extra: { context: 'additional info' }, tags: { feature: 'checkout' } }); ``` Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. `'warn'` is sent as `warning`, `'fatal'` as `error` and `'trace'` as `debug`; anything else as `info`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. Breadcrumbs track events leading up to an error, helping you understand what happened before the error occurred. #### Manual Breadcrumbs ```javascript AbrovaTrace.addBreadcrumb({ message: 'User clicked checkout', category: 'user-action', level: 'info', data: { cartTotal: 99.99 } }); ``` #### Breadcrumb Helpers ```javascript const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); // HTTP request breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.http('POST', '/api/order', 200, 150)); // Database query breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.database('SELECT * FROM users', 25)); // User action breadcrumb AbrovaTrace.addBreadcrumb(Breadcrumb.user('clicked_button', { buttonId: 'submit' })); ```

User Context

```javascript AbrovaTrace.setUser({ id: 123, email: 'user@example.com', username: 'john_doe' }); ```

Scoped Context

Use scoped context to set a user and extra values for one synchronous block of code. The user and extra values are restored when the callback returns; breadcrumbs added inside the block are kept. ```javascript AbrovaTrace.withScope((scope) => { scope.setUser({ id: 456 }); scope.setExtra('operation', 'bulk_import'); scope.addBreadcrumb({ message: 'Starting import' }); // Errors captured here include this context AbrovaTrace.captureException(new Error('Import failed')); }); // Context is restored after the block ```

Express.js Integration

Built-in middleware for automatic request tracking and error handling in Express.js applications. ```javascript const express = require('express'); const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); const app = express(); // Initialize AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, environment: process.env.NODE_ENV }); // Add middleware const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); // Your routes app.get('/api/users/:id', async (req, res) => { // Your route logic }); // Error handler (must be last) app.use(middleware.errorHandler()); app.listen(3000); ``` The error handler middleware must be added after all routes. Express requires error-handling middleware to be defined last. Call `AbrovaTrace.init()` before `AbrovaTrace.expressMiddleware()`: middleware created before `init()` does nothing. Errors reported by `errorHandler()` include the request method, URL, path, matched route (for example `/api/users/:id`), query and route parameters, user agent, IP address and the response status (`err.status` / `err.statusCode` when it is 4xx or 5xx, otherwise 500). Request headers and the request body are never sent. Query and route parameters whose name looks sensitive (password, token, secret, auth, API key, session, cookie, signature, OTP, code) are sent as `[REDACTED]`. The same applies to the request address sent with errors and breadcrumbs: the values of sensitive query parameters in it are replaced with `[REDACTED]` (for example `/reset?token=[REDACTED]&page=2`), while parameter names and other values are kept (since 0.2.3).

Database Query Tracking

```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); async function executeQuery(sql, params) { const startTime = Date.now(); try { AbrovaTrace.addBreadcrumb({ message: 'Executing query', category: 'database', data: { sql: sql.substring(0, 100) } }); const results = await db.query(sql, params); AbrovaTrace.addBreadcrumb({ message: 'Query completed', category: 'database', data: { duration_ms: Date.now() - startTime } }); return results; } catch (error) { AbrovaTrace.captureException(error, { extra: { sql, duration_ms: Date.now() - startTime } }); throw error; } } ```

Error Filtering

Use the beforeSend hook to filter or modify events before they are sent to AbrovaTrace. It runs for exceptions and for `captureMessage()` events; return `null` to drop one. To skip whole exception classes or request paths, use the `ignoredExceptions` and `ignoredPaths` options instead. ```javascript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, beforeSend(event) { // Don't send network errors if (event.message.includes('ECONNREFUSED')) { return null; } // Add custom data (errors are AbrovaTraceError objects; messages from // captureMessage() arrive as plain objects without setExtra) if (typeof event.setExtra === 'function') { event.setExtra('server_id', process.env.SERVER_ID); } return event; } }); ```

Graceful Shutdown

Errors, messages, logs and performance spans are sent in the background. `flush()` sends everything that is still waiting, so call it before the process exits: before `process.exit()`, at the end of a serverless handler, and in your shutdown handlers. ```javascript process.on('SIGTERM', async () => { console.log('Shutting down...'); const sent = await AbrovaTrace.flush(2000); // false if 2 seconds pass before everything is sent await AbrovaTrace.close(); // Sends buffered logs and performance spans, then resets the SDK process.exit(0); }); ``` `flush(timeout)` takes a timeout in milliseconds (default: 5000) and returns a `Promise`: `true` when sending finished within the timeout, `false` when the timeout passed first. It never rejects. `close()` also returns a Promise; it sends buffered logs and performance spans but does not wait for errors and messages that are still being sent, so call `flush()` first. After `close()`, call `init()` again before you use the SDK. In a serverless function, flush before the handler returns: ```javascript exports.handler = async (event) => { try { return await processEvent(event); } catch (error) { AbrovaTrace.captureException(error); throw error; } finally { await AbrovaTrace.flush(); } }; ```

Source Maps

If you're using TypeScript, bundlers (webpack, esbuild), or Babel, enable source maps for accurate stack traces: ```bash node --enable-source-maps app.js ``` Or programmatically in your app: ```javascript process.setSourceMapsEnabled(true); ```

Logging

Structured server-side logging, batched with exponential backoff and zero dependencies. Logs go to **Abrova Trace → Logs** and are separate from `captureMessage()` (Messages). After `init()`, enable logging once and log from anywhere: ```javascript AbrovaTrace.enableLogging({ sourceId: 'api-server' }); AbrovaTrace.info('Server started on port 3000'); AbrovaTrace.warn('Database connection slow', { latency: 2500 }); AbrovaTrace.logError('Failed to process payment', { orderId: '12345' }); ``` See [Node.js SDK Logging](/docs/trace/nodejs-logging) for the full pipeline — log levels, Express integration, batching, and graceful shutdown.

Configuration Options

```javascript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', // Required serverSecret: 'ab_srv_your_secret', // Required environment: 'production', // Default: NODE_ENV or 'development' release: '1.0.0', // Default: npm_package_version or '1.0.0' enabled: true, // Default: true debug: false, // Default: false timeout: 5000, // Default: 5000ms captureUncaughtExceptions: true, // Default: true captureUnhandledRejections: true, // Default: true maxBreadcrumbs: 50, // Default: 50 (max 100) sampleRate: 1.0, // Default: 1.0 (captureException and captureMessage) ignoredExceptions: ['ValidationError'], // Error class, error.name, or RegExp ignoredPaths: ['/health*'], // Express paths not reported beforeSend: (event) => event, // Filter/modify events }); ``` ### SDK Stats ```javascript const stats = AbrovaTrace.getStats(); console.log(stats); // { // isEnabled: true, // breadcrumbCount: 5, // rateLimiter: { totalErrorsInWindow: 3, uniqueErrorsInWindow: 2, totalLimit: 100, errorLimit: 10, windowMs: 60000 }, // config: { environment: 'production', release: '1.0.0', apiKey: 'ab_live_abc1234...' } // } ``` ### TypeScript Support The SDK includes full TypeScript definitions. Import types as needed: ```typescript import AbrovaTrace, { AbrovaTraceInitOptions, AbrovaTraceError, Breadcrumb, BreadcrumbOptions, UserContext } from '@abrova-trace/nodejs-sdk'; ``` ### Requirements - **Node.js** — >= 14.0.0 - **Express (optional)** — >= 4.0.0 ### API Reference #### AbrovaTrace - `init(config)` — Initialize SDK - `captureException(error, options?)` — Capture an error - `captureMessage(message, options?)` — Send a message to Abrova Trace → Messages (`options`: `level`, `extra`, `tags`) - `addBreadcrumb(breadcrumb)` — Add a breadcrumb - `setUser(user)` — Set user context - `setRequestContext(context)` — Set request context attached to captured events - `withScope(callback)` — Run code with isolated scope - `expressMiddleware()` — Get Express.js middleware - `flush(timeout?)` — Send pending errors, messages, logs and performance spans; `timeout` in milliseconds (default: 5000). Returns `Promise`: `false` if the timeout passes first - `close()` — Flush logs and performance spans, then clean up (returns a Promise) - `getStats()` — Get SDK statistics - `enableLogging(options?)`, `log()`, `trace()`, `logDebug()`, `info()`, `warn()`, `logError()`, `fatal()`, `flushLogs()` — Logging (see [Node.js SDK Logging](/docs/trace/nodejs-logging)) - `trackOperation(name, fn, options?)`, `reportPerformanceSpan(span)`, `reportPerformanceSpanBatch(spans)`, `flushPerformance()` — Performance spans #### Breadcrumb Helpers - `Breadcrumb.http(method, url, status?, duration?)` — HTTP request breadcrumb - `Breadcrumb.database(query, duration, error?)` — Database query breadcrumb - `Breadcrumb.user(action, data?)` — User action breadcrumb - `Breadcrumb.console(level, message, ...args)` — Console output breadcrumb - `Breadcrumb.navigation(from, to, method?)` — Navigation breadcrumb - `Breadcrumb.custom(message, category, data?)` — Breadcrumb with your own category #### Express Middleware - `requestHandler()` — Returns request tracking middleware - `errorHandler()` — Returns error handling middleware - `userMiddleware()` — Sets user context from `req.user` (id, email, username) - `transactionMiddleware(name)` — Adds start and completion breadcrumbs (duration, status) for a named route ### Complete Example ```javascript const express = require('express'); const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); // Initialize AbrovaTrace AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', serverSecret: process.env.ABROVA_SERVER_SECRET, environment: process.env.NODE_ENV || 'development', release: '1.0.0', captureUncaughtExceptions: true, captureUnhandledRejections: true, maxBreadcrumbs: 50, debug: process.env.NODE_ENV === 'development', }); const app = express(); // Add AbrovaTrace middleware const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); app.use(express.json()); // Set user context from auth app.use((req, res, next) => { if (req.user) { AbrovaTrace.setUser({ id: req.user.id, email: req.user.email, }); } next(); }); app.get('/api/users/:id', async (req, res) => { AbrovaTrace.addBreadcrumb(Breadcrumb.user('view_user', { userId: req.params.id, })); try { const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]); AbrovaTrace.addBreadcrumb(Breadcrumb.database('SELECT * FROM users', 15)); res.json(user); } catch (error) { AbrovaTrace.captureException(error, { extra: { userId: req.params.id }, }); res.status(500).json({ error: 'Internal Server Error' }); } }); // Error handler (must be last) app.use(middleware.errorHandler()); // Graceful shutdown process.on('SIGTERM', async () => { await AbrovaTrace.flush(2000); await AbrovaTrace.close(); process.exit(0); }); app.listen(3000, () => console.log('Server running on port 3000')); ``` ### Best Practices - Next.js SDK: https://abrova.ir/en/docs/trace/nextjs-sdk

Installation

Packages come from Abrova Center. Two steps: **1. Add the Abrova registry** to `.npmrc` in your project root (npm, yarn and pnpm all read it): ``` # .npmrc @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` **2. Install the package:** ```bash npm install @abrova-trace/nextjs-sdk # or yarn add @abrova-trace/nextjs-sdk # or pnpm add @abrova-trace/nextjs-sdk ```

Quick Start

#### App Router (Next.js 13+) Create `app/providers.tsx`: ```tsx 'use client'; import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; import { useAbrovaTraceNavigation } from '@abrova-trace/nextjs-sdk/react'; export function Providers({ children }: { children: React.ReactNode }) { useEffect(() => { AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, debug: process.env.NODE_ENV === 'development', }); }, []); // Track navigation useAbrovaTraceNavigation(); return <>{children}; } ``` `useAbrovaTraceNavigation()` reads `useSearchParams()`. If `next build` complains about a missing Suspense boundary, call it from a small component wrapped in `` instead (see the `NavigationTracker` in Navigation Tracking below). Update `app/layout.tsx`: ```tsx import { Providers } from './providers'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` #### Pages Router (Next.js 12 and earlier) Update `pages/_app.tsx`: ```tsx import { useEffect } from 'react'; import type { AppProps } from 'next/app'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; import { useAbrovaTracePagesRouter } from '@abrova-trace/nextjs-sdk/react'; function MyApp({ Component, pageProps }: AppProps) { useEffect(() => { AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: process.env.NODE_ENV, release: process.env.NEXT_PUBLIC_APP_VERSION, }); }, []); // Track navigation useAbrovaTracePagesRouter(); return ; } export default MyApp; ```

Error Boundary

Wrap your components with the error boundary to automatically catch and report React errors: #### Basic Usage ```tsx import { ErrorBoundary } from '@abrova-trace/nextjs-sdk/react'; export default function Page() { return ( Something went wrong!}> ); } ``` #### With Reset Function ```tsx (

Error: {error.message}

)} >
``` #### With Error Handler ```tsx { console.log('Error occurred:', error); // Custom error handling }} > ``` #### Default Fallback Without a `fallback`, the boundary renders a built-in panel: a "Something went wrong" heading, a short message, and a **Try again** button that resets the boundary. Since 0.1.8 the error message, stack trace and component stack are no longer shown in production: they appear in a collapsible "Error details" block only when `showErrorDetails` is `true`, and it defaults to `process.env.NODE_ENV !== 'production'` (shown in development, hidden in production builds). A custom `fallback` is rendered as you wrote it and is not affected by `showErrorDetails`. ```tsx ``` #### Props

Manual Error Capture

#### Using Hooks ```tsx import { useAbrovaTrace } from '@abrova-trace/nextjs-sdk/react'; function MyComponent() { const { captureException, captureMessage, setUserId } = useAbrovaTrace(); const handleClick = async () => { try { await riskyOperation(); } catch (error) { captureException(error as Error, 'Failed to complete operation', { userId: user.id, action: 'button_click', }); } }; return ; } ``` #### Direct API Usage ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; // Capture exception try { throw new Error('Something broke'); } catch (error) { AbrovaTrace.getInstance()?.captureException(error as Error, 'Custom context', { userId: '123', customData: 'value', }); } // Capture a message (level: 'debug' | 'info' | 'warning' | 'error') AbrovaTrace.getInstance()?.captureMessage( 'User completed checkout', 'info', { orderId: '456' } ); ``` Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: `'debug'`, `'info'` (default), `'warning'`, `'error'`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. Breadcrumbs track user actions and provide context for errors. Navigation changes (once you use a navigation hook) and `console.error` / `console.warn` calls are recorded automatically. #### Manual Breadcrumbs ```typescript import { BreadcrumbService, BreadcrumbType } from '@abrova-trace/nextjs-sdk'; // Generic breadcrumb BreadcrumbService.add('User action', BreadcrumbType.User, { button: 'submit' }); // Specific types BreadcrumbService.addUser('Clicked checkout button'); BreadcrumbService.addHttp('POST', '/api/orders', 200); BreadcrumbService.addState('Cart updated', { items: 3 }); BreadcrumbService.addNavigation('/cart', '/checkout'); ``` #### Using Hooks ```tsx import { useBreadcrumbs } from '@abrova-trace/nextjs-sdk/react'; function Component() { const { addBreadcrumb, addHttp, addState } = useBreadcrumbs(); const handleSubmit = async (data) => { addBreadcrumb('Form submitted', { formName: 'contact' }); const response = await fetch('/api/contact', { method: 'POST', body: JSON.stringify(data), }); addHttp('POST', '/api/contact', response.status); }; return
...
; } ```

Crash Detection

The SDK detects browser tab crashes and unresponsiveness using a localStorage-based heartbeat system. - **Tab Crashes** — Detects browser tab crashes - **Unresponsive Tabs** — Detects frozen/hung tabs - **Force-Close** — Detects force-close scenarios - **Post-Mortem** — Reporting on next launch #### How It Works 1. App starts — Crash marker created in localStorage 2. Heartbeat runs every 5 seconds 3. App closes normally — Marker removed 4. App crashes — Marker remains 5. Next session — Crash detected and reported

API Routes & Server Components

The `init()` call in `Providers` / `_app.tsx` runs in the browser only, so on the server `AbrovaTrace.getInstance()` returns `null` until you initialize the SDK there too. Create a small server helper (`init()` returns the existing instance if one is already set up): ```typescript // lib/abrova-trace-server.ts import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; export const trace = AbrovaTrace.getInstance() ?? AbrovaTrace.init({ apiKey: process.env.ABROVA_TRACE_API_KEY!, environment: process.env.NODE_ENV, }); ``` #### API Routes ```typescript // pages/api/example.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { trace } from '@/lib/abrova-trace-server'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { // Your API logic } catch (error) { trace.captureException(error as Error, 'API Error', { endpoint: '/api/example', method: req.method, }); res.status(500).json({ error: 'Internal Server Error' }); } } ``` #### Server Components (App Router) ```tsx // app/page.tsx import { trace } from '@/lib/abrova-trace-server'; export default async function Page() { try { const data = await fetchData(); return
{data}
; } catch (error) { trace.captureException(error as Error, 'Server Component Error'); return
Error loading data
; } } ```

Source Maps

Enable production source maps for readable stack traces in your `next.config.js`: ```javascript // next.config.js module.exports = { productionBrowserSourceMaps: true, }; ``` After building your app, upload the `.map` files with the [Abrova Trace CLI](/docs/trace/upload-source-maps), using the same `release` you pass to `AbrovaTrace.init()`: ```bash npx @abrova-trace/cli sourcemaps upload .next/static --release 1.0.0 --project YOUR_PROJECT_ID --token ab_pat_xxx ```

Configuration Options

error, // Filter/modify errors });`} />

User Identification

```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; // Set user ID after authentication AbrovaTrace.getInstance()?.setUserId('user-123'); // Get user ID const userId = AbrovaTrace.getInstance()?.getUserId(); ``` #### App Router (Next.js 13+) ```tsx 'use client'; import { useAbrovaTraceNavigation } from '@abrova-trace/nextjs-sdk/react'; export function NavigationTracker() { useAbrovaTraceNavigation(); return null; } ``` #### Pages Router ```tsx import { useAbrovaTracePagesRouter } from '@abrova-trace/nextjs-sdk/react'; function MyApp({ Component, pageProps }) { useAbrovaTracePagesRouter(); return ; } ``` #### Manual Tracking ```typescript import { RouterIntegration } from '@abrova-trace/nextjs-sdk'; RouterIntegration.setCurrentRoute('/custom-route'); ```

Context & Tags

Attach persistent metadata to every error and message. Tags are categorical filters; extras are richer per-event payloads. (Logs, spans and Web Vitals do not carry them.) #### Global extras ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.setExtra('organizationId', 'org-123'); sdk?.setExtra('feature', 'checkout-v2'); sdk?.setExtras({ organizationId: 'org-123', feature: 'checkout-v2', experiment: 'new-flow', }); const orgId = sdk?.getExtra('organizationId'); const allExtras = sdk?.getExtras(); sdk?.clearExtras(); ``` #### Tags ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.setTag('team', 'payments'); sdk?.setTag('region', 'us-east'); sdk?.setTags({ team: 'payments', version: '2.1' }); const team = sdk?.getTag('team'); const allTags = sdk?.getTags(); sdk?.clearTags(); ``` #### Session ID A unique session ID is auto-generated when the SDK initializes (16 random hex bytes) and attached to every error and message for session grouping in Abrova Console: ```typescript const sessionId = AbrovaTrace.getInstance()?.getSessionId(); ```

Web Vitals

Track Core Web Vitals using Google's official `web-vitals` library (browser only). Metrics show up in Abrova Console under **Abrova Trace → Performance** alongside HTTP spans. ```typescript const sdk = AbrovaTrace.getInstance(); sdk?.enableWebVitals({ reportAllChanges: false, // Only report final values (default) batchInterval: 5000, // Send 5 s after the last metric (default); also sent when the page is hidden }); ``` **Tracked metrics:** Web Vitals events automatically include device type (mobile/tablet/desktop), connection type, page URL, and route information.

TypeScript

The SDK is written in TypeScript and ships with full type definitions. Import the types when you need explicit annotations on config objects or event payloads: ```typescript import type { AbrovaTraceConfig, AbrovaTraceError, Breadcrumb, } from '@abrova-trace/nextjs-sdk'; import type { PerformanceSpan, WebVitalsData, LogEntry, LogLevel, LogServiceConfig, } from '@abrova-trace/nextjs-sdk'; ```

Logging

Structured server-side logging that works in App Router, Pages Router, and API Routes. Logging uses a dedicated `LogService` instance — create one in `lib/logger.ts` and import it from anywhere: ```typescript import { LogService } from '@abrova-trace/nextjs-sdk'; export const logger = new LogService({ apiKey: process.env.ABROVA_TRACE_API_KEY!, sourceId: 'nextjs-app', platform: 'nextjs', environment: process.env.NODE_ENV, }); logger.log('Users fetched', 'info', { count: 42 }); ``` You can also log through the SDK instance, which reuses the `init()` config: `AbrovaTrace.getInstance()?.info('Users fetched', { count: 42 })` (see the Logging table in the API Reference below). Logs are separate from `captureMessage()`: they are batched and appear under **Abrova Trace → Logs**. See [Next.js SDK Logging](/docs/trace/nextjs-logging) for the full pipeline — App Router, Pages Router, API Routes, log levels, and request middleware. ### API Reference #### `AbrovaTrace` — Initialization & lifecycle #### Error capture #### User & session #### Global context (extras) #### Tags #### Performance monitoring #### Logging #### `BreadcrumbService` #### `RouterIntegration` #### `CrashDetector` #### React hooks #### React components ### Best Practices - Android SDK: https://abrova.ir/en/docs/trace/android-sdk

Installation

The SDK comes from Abrova Center. Two steps: **1. Add the Abrova repository** in `settings.gradle.kts` (or `settings.gradle`): ```kotlin dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://center.abrova.ir/api/packages/abrova/maven") } } } ``` Groovy: `maven { url "https://center.abrova.ir/api/packages/abrova/maven" }` **2. Add the dependency** to your app's `build.gradle.kts`: ```kotlin dependencies { implementation("ir.abrova.trace:abrova-trace-android-sdk:0.2.3") } ``` Then sync your project with Gradle files. Requirements: Android 4.4 (API 19) or later; native crash reports need Android 11 (API 30) or later.

Quick Start

Initialize AbrovaTrace in your `Application` class: ```kotlin import android.app.Application import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.AbrovaTraceConfig class MyApp : Application() { override fun onCreate() { super.onCreate() val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment(if (BuildConfig.DEBUG) "development" else "production") .release(BuildConfig.VERSION_NAME) .debug(BuildConfig.DEBUG) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000L) .build() AbrovaTrace.init(this, config) } override fun onTerminate() { AbrovaTrace.close() super.onTerminate() } } ``` `onTerminate()` is never called on real devices, so also call close() in your main Activity. close() flushes buffered logs, stops the ANR watchdog and shuts down the network client. The API key must start with `ab_live_` — the config throws otherwise. ```kotlin class MainActivity : AppCompatActivity() { override fun onDestroy() { if (isFinishing) { AbrovaTrace.close() // Flush logs, stop the ANR watchdog } super.onDestroy() } } ```

Capture Errors

#### Capture Exceptions ```kotlin try { someRiskyOperation() } catch (e: Exception) { AbrovaTrace.captureException(e) } ``` #### Capture with Extra Context ```kotlin AbrovaTrace.captureException( throwable = exception, message = "Failed to load user profile", extra = mapOf("user_id" to userId, "screen" to "ProfileActivity"), tags = mapOf("feature" to "user_profile") ) ``` #### Capture Messages ```kotlin import ir.abrova.trace.sdk.models.MessageLevel // Info message AbrovaTrace.captureMessage("User completed onboarding", MessageLevel.INFO) // Warning message AbrovaTrace.captureMessage("API rate limit approaching", MessageLevel.WARNING) // Error message with extra data AbrovaTrace.captureMessage( message = "Payment failed", level = MessageLevel.ERROR, extra = mapOf("amount" to 99.99) ) ``` Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: `MessageLevel.DEBUG`, `INFO` (default), `WARNING`, `ERROR`. `FATAL` is accepted and stored as `error`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. Breadcrumbs help you understand what happened before an error occurred. They are automatically added for Activity navigation, app foreground/background, and system events. ```kotlin import ir.abrova.trace.sdk.models.BreadcrumbType // User action AbrovaTrace.addUserBreadcrumb("Added item to cart", mapOf("product_id" to "abc")) // Navigation AbrovaTrace.addNavigationBreadcrumb(from = "MainActivity", to = "ProductDetailActivity") // HTTP request AbrovaTrace.addHttpBreadcrumb( method = "POST", url = "https://api.example.com/orders", statusCode = 201, duration = 342L ) // Custom breadcrumb AbrovaTrace.addBreadcrumb("Order placed", BreadcrumbType.INFO, mapOf( "order_id" to "12345", "total" to 99.99 )) ```

OkHttp Integration

Automatically track HTTP requests as breadcrumbs and capture HTTP errors: #### Basic Setup ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .build() ``` #### With Error Capturing ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTraceErrorInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) // Adds breadcrumbs .addInterceptor(AbrovaTraceErrorInterceptor()) // Captures HTTP errors .build() ``` `AbrovaTraceErrorInterceptor` sends each 5xx response as a **message** at `error` level (Abrova Trace → Messages). 4xx responses are off by default — turn them on with `AbrovaTraceErrorInterceptor(captureClientErrors = true)` (sent at `warning`). Network failures (no response) are captured as exceptions.

Crash Detection

AbrovaTrace detects crashes in-process and, on Android 11+, from the system's own exit records. - **Java/Kotlin Exceptions** — Captured in real-time by an uncaught exception handler (`captureUncaughtExceptions`). The report is sent before the app closes, on any thread including the main thread; if the server does not accept it within 2 seconds it is kept on the device and sent on the next launch. Requires version 0.2.2 or newer. - **ANR Events** — A watchdog thread detects the main thread being blocked longer than `anrTimeoutMs` (`captureAnr`) - **Native Crashes** — SIGSEGV, SIGABRT, etc., reported on the next launch (Android 11 / API 30+ only, `captureSignalCrashes`) #### How It Works 1. On each launch (API 30+), AbrovaTrace reads Android's `ApplicationExitInfo` records for your app 2. Exits since the last launch with reason JVM crash, native crash or ANR are converted into crash reports, including the tombstone for native crashes 3. The timestamp of the last sent record is saved, so each crash is reported once; a failed send is retried on the next launch. A Java/Kotlin crash the in-process handler already reported is skipped here 4. On Android 10 and below this step does nothing — JVM crashes are still captured by the in-process handler

Performance Monitoring (APM)

Track API latency and HTTP request performance automatically. #### Automatic HTTP Performance Tracking with OkHttp ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import okhttp3.Request val client = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) // Breadcrumbs .addInterceptor(AbrovaTracePerformanceInterceptor()) // APM tracking .build() // All API calls are automatically tracked! val request = Request.Builder() .url("https://api.example.com/users") .build() client.newCall(request).execute().use { response -> // Duration, status code, and errors are automatically reported val users = response.body?.string() } ``` #### Using with Retrofit ```kotlin import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory val okHttpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() val api = retrofit.create(MyApiService::class.java) // All API calls through this Retrofit instance are automatically tracked ``` #### Track Custom Operations ```kotlin import ir.abrova.trace.sdk.AbrovaTrace val result = AbrovaTrace.trackOperation("load_user_profile") { val user = fetchUserFromApi() cacheUserLocally(user) user } ``` #### Manual Span Reporting ```kotlin import ir.abrova.trace.sdk.models.PerformanceSpan // HTTP request span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method = "GET", url = "https://api.example.com/products", statusCode = 200, durationMs = 245L, startTime = System.currentTimeMillis() - 245 ) ) // Database query span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType = "SELECT", tableName = "products", durationMs = 15L, startTime = System.currentTimeMillis() - 15, rowsAffected = 50 ) ) // Custom operation span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.custom( operation = "image_processing", durationMs = 1250L, startTime = System.currentTimeMillis() - 1250, tags = mapOf("format" to "png") ) ) ```

Logging

Structured log ingestion with automatic batching and retry. Enable it once, then use the level helpers from anywhere in your app: ```kotlin // Enable logging after AbrovaTrace.init() AbrovaTrace.enableLogging(sourceId = "my-android-app") AbrovaTrace.info("User login successful", mapOf("user_id" to "user_123")) ``` See [Android SDK Logging](/docs/trace/android-logging) for the full pipeline — batching, log levels, lifecycle handling, manual flush, and configuration.

Configuration Options

```kotlin val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment("production") // Environment name .release("1.0.0") // App version .debug(false) // Enable debug logging .enabled(true) // Enable/disable SDK .sampleRate(1.0f) // Error sample rate (0.0 - 1.0) .maxBreadcrumbs(20) // Max breadcrumbs to keep .httpTimeout(30) // HTTP request timeout (seconds) .enableOfflineStorage(true) // Keep unsent errors on the device while offline .captureUncaughtExceptions(true) // Catch uncaught exceptions .captureSignalCrashes(true) // Detect native signal crashes .captureAnr(true) // Detect ANR issues .anrTimeoutMs(5000L) // ANR detection timeout .apiUrl("https://trace.abrova.ir") // Server URL (self-hosted override) .build() ``` When an error cannot be sent because the device has no connection, the SDK keeps it in the app's private storage and sends it later: on the next launch, and as soon as another error is sent successfully. Up to 100 errors are kept; when the limit is reached, the oldest are dropped. This covers errors only — `captureException`, uncaught exceptions and ANR reports. Messages, logs and performance data are not stored. Set `enableOfflineStorage(false)` to keep nothing on the device.

ProGuard / R8

The SDK includes ProGuard rules automatically. If you need custom rules, add to `proguard-rules.pro`: ```text -keep class ir.abrova.trace.sdk.** { *; } -keepattributes SourceFile,LineNumberTable -keepattributes *Annotation* ```

Device Compatibility

- **Android 4.4 KitKat** — API 19 - **Android 5.0 Lollipop** — API 21 - **Android 8.0 Oreo** — API 26 - **Android 10** — API 29 - **Android 12** — API 31 - **Android 14** — API 34

Context & Tags

Attach persistent metadata to every error and message captured by the SDK (tags are also added to performance spans). Useful for filtering by team, region, feature flag, or experiment cohort in the Abrova Console. ANR reports and crash reports read from the system on the next launch do not carry them. #### Global extras Extras are arbitrary key/value pairs (string keys, any serializable value) automatically merged into every event: ```kotlin // Set individual extras AbrovaTrace.setExtra("organizationId", "org-123") AbrovaTrace.setExtra("feature", "checkout-v2") // Set multiple at once AbrovaTrace.setExtras(mapOf( "organizationId" to "org-123", "feature" to "checkout-v2", "experiment" to "new-flow" )) // Clear when no longer needed (e.g. on logout) AbrovaTrace.clearExtras() ``` #### Tags Tags are string-only key/value pairs that are attached to every error and message: ```kotlin AbrovaTrace.setTag("team", "payments") AbrovaTrace.setTag("region", "us-east") AbrovaTrace.setTags(mapOf("team" to "payments", "version" to "2.1")) AbrovaTrace.clearTags() ``` Use **tags** for low-cardinality categorical filters (team, region, build flavor). Use **extras** for richer per-event context (user IDs, request bodies, computed values). ### API Reference Full method surface of `AbrovaTrace` and `PerformanceSpan`. Method signatures use the Kotlin convention; optional parameters are marked with `?`. #### `AbrovaTrace` — Initialization & Lifecycle #### Error capture #### User & session #### Global context (extras) #### Tags #### Breadcrumbs #### Performance monitoring #### Logging #### `PerformanceSpan` factories ### Complete Example ```kotlin import android.app.Application import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.AbrovaTraceConfig import ir.abrova.trace.sdk.models.MessageLevel import ir.abrova.trace.sdk.network.AbrovaTraceInterceptor import okhttp3.OkHttpClient class MyApp : Application() { override fun onCreate() { super.onCreate() val config = AbrovaTraceConfig.Builder("ab_live_YOUR_API_KEY") .environment(if (BuildConfig.DEBUG) "development" else "production") .release(BuildConfig.VERSION_NAME) .debug(BuildConfig.DEBUG) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000L) .maxBreadcrumbs(20) .enableOfflineStorage(true) .build() AbrovaTrace.init(this, config) } override fun onTerminate() { AbrovaTrace.close() super.onTerminate() } } class MainActivity : AppCompatActivity() { private val httpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTraceInterceptor()) .build() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AbrovaTrace.setUserId("user_123") AbrovaTrace.addUserBreadcrumb("Viewed home screen") } private fun handlePurchase() { try { processPurchase() AbrovaTrace.captureMessage( message = "Purchase successful", level = MessageLevel.INFO, extra = mapOf("amount" to 99.99) ) } catch (e: Exception) { AbrovaTrace.captureException( throwable = e, message = "Purchase failed", extra = mapOf("user_id" to "user_123", "screen" to "MainActivity") ) } } override fun onDestroy() { if (isFinishing) { AbrovaTrace.close() } super.onDestroy() } private fun processPurchase() { /* ... */ } } ``` ### Best Practices - iOS SDK: https://abrova.ir/en/docs/trace/ios-sdk

Installation

#### Swift Package Manager (Recommended) In Xcode: **File → Add Packages** → enter the Abrova Center address: ```text https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git ``` Select version `0.2.4` or later. Or add to your `Package.swift`: ```swift dependencies: [ .package(url: "https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git", from: "0.2.4") ] ``` #### CocoaPods Add to your `Podfile`: ```ruby pod 'AbrovaTrace', :git => 'https://center.abrova.ir/abrova/abrova-trace-ios-sdk.git', :tag => '0.2.4' ``` Then run: ```bash pod install ``` Releases are installed from Abrova Center, which works inside Iran. The source code is on GitHub ([Abrova-co/abrova-trace-ios-sdk](https://github.com/Abrova-co/abrova-trace-ios-sdk)) with the same version tags, so `https://github.com/Abrova-co/abrova-trace-ios-sdk.git` also works where GitHub is reachable.

Quick Start

#### UIKit Initialization Initialize AbrovaTrace in your `AppDelegate`: ```swift import UIKit import AbrovaTrace @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .anrTimeoutMs(5000) .build() AbrovaTrace.shared.initialize(config: config) return true } func applicationWillTerminate(_ application: UIApplication) { AbrovaTrace.shared.close() // Optional: the SDK also does this on terminate } } ``` #### SwiftUI Initialization ```swift import SwiftUI import AbrovaTrace @main struct MyApp: App { init() { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .build() AbrovaTrace.shared.initialize(config: config) } var body: some Scene { WindowGroup { ContentView() } } } ```

Capture Errors

#### Capture Swift Errors ```swift do { try riskyOperation() } catch { AbrovaTrace.shared.captureError(error) } ``` Use `captureError(_:)` for Swift `Error` values and `captureException(_:)` for an `NSException`. #### Capture with Context ```swift AbrovaTrace.shared.captureError( error, message: "Failed to load user profile", extra: ["user_id": userId, "screen": "ProfileView"], tags: ["feature": "user_profile"] ) ``` #### Capture Messages ```swift // Info message AbrovaTrace.shared.captureMessage("User completed onboarding", level: .info) // Warning message AbrovaTrace.shared.captureMessage("API rate limit approaching", level: .warning) // Error message with extra data AbrovaTrace.shared.captureMessage( "Payment failed", level: .error, extra: ["amount": 99.99] ) ``` Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. Levels: `.debug`, `.info` (default), `.warning`, `.error`. `.fatal` is accepted and stored as `error`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. ```swift // User action AbrovaTrace.shared.addUserBreadcrumb("Added item to cart", data: ["product_id": "abc"]) // Navigation AbrovaTrace.shared.addNavigationBreadcrumb(from: "HomeView", to: "ProductDetailView") // HTTP request AbrovaTrace.shared.addHttpBreadcrumb( method: "POST", url: "https://api.example.com/orders", statusCode: 201, duration: 0.342 // seconds (TimeInterval) ) // Custom breadcrumb AbrovaTrace.shared.addBreadcrumb("Order placed", type: .info, data: [ "order_id": "12345", "total": 99.99 ]) ```

Automatic HTTP Tracking

#### Global HTTP Tracking Enable automatic tracking for URLSession requests. Each request is reported as a performance span and added as an HTTP breadcrumb: ```swift // Registers AbrovaTraceURLProtocol globally (URLSession.shared and default-configured sessions) AbrovaTrace.shared.enablePerformanceTracking() // Requests are now tracked automatically let (data, response) = try await URLSession.shared.data(from: url) ``` #### Session-Based Tracking ```swift // Create a tracked URLSession let config = AbrovaTrace.shared.performanceSessionConfiguration() // Equivalent to: // config.protocolClasses = [AbrovaTraceURLProtocol.self] + (config.protocolClasses ?? []) let session = URLSession(configuration: config) // Requests through this session are tracked let task = session.dataTask(with: url) { data, response, error in // Handle response } task.resume() ``` Performance spans from enablePerformanceTracking() record the full request URL, including query strings, so avoid putting secrets in URLs or exclude those hosts with setPerformanceExcludedHosts(_:). The separate AbrovaTraceHttpBreadcrumbProtocol.enable() adds HTTP breadcrumbs with sensitive query parameters (token, api_key, apikey, key, secret, password, pwd, auth, authorization, access_token, refresh_token, session) redacted, and reports 5xx responses and failed requests as messages.

Crash Detection

AbrovaTrace captures crashes with a vendored copy of PLCrashReporter: - **Uncaught NSExceptions** — Sent immediately, before the app exits (`captureUncaughtExceptions`) - **ANR Events** — Main thread blocked longer than `anrTimeoutMs` (`captureAnr`) - **Native Crashes** — POSIX signals (SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE, SIGTRAP) and Mach exceptions (`captureSignalCrashes`) 1. On initialization, AbrovaTrace installs PLCrashReporter's async-signal-safe handlers 2. When the app crashes, a crash report is written to disk inside the app sandbox 3. On the next launch, `initialize` reads the pending report and sends it to Abrova Trace in the background, so app launch never waits for the network. The report is removed only after it was delivered; if the server or the network fails it is kept and sent on a later launch. Requires version 0.2.3 or newer. Native crashes are reported on the next launch, so they only arrive once the user opens the app again. The SDK calls AbrovaTrace.shared.close() itself when it receives the app's will-terminate notification; calling it yourself is optional and flushes buffered logs.

Performance Monitoring (APM)

Track API latency and HTTP request performance. #### Automatic HTTP Tracking with URLSession ```swift // Enable performance tracking globally AbrovaTrace.shared.enablePerformanceTracking() // All URLSession requests are now tracked with timing data let (data, response) = try await URLSession.shared.data(from: url) // Method, URL, status code, and duration are automatically reported ``` #### Using with Alamofire ```swift import Alamofire // Create a session with AbrovaTrace tracking let configuration = URLSessionConfiguration.default configuration.protocolClasses = [AbrovaTraceURLProtocol.self] + (configuration.protocolClasses ?? []) let session = Session(configuration: configuration) // All requests through this session are tracked session.request("https://api.example.com/users").response { response in // Handle response } ``` #### Track Custom Operations ```swift // Async (iOS 13+); use PerformanceTracker.track(_:) for synchronous work let result = try await PerformanceTracker.trackAsync("load_user_profile") { let user = try await fetchUser() try await cacheUser(user) return user } // Or time it manually let tracker = PerformanceTracker(operation: "image_processing") // ... do work tracker.finish(status: .ok) ``` #### Manual Span Reporting ```swift // HTTP request span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method: "GET", url: "https://api.example.com/products", statusCode: 200, durationMs: 245, startTime: Date().addingTimeInterval(-0.245) ) ) // Database query span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType: "SELECT", tableName: "products", durationMs: 15, startTime: Date().addingTimeInterval(-0.015), rowsAffected: 50 ) ) // Custom operation span AbrovaTrace.shared.reportPerformanceSpan( PerformanceSpan.custom( operation: "image_processing", durationMs: 1250, startTime: Date().addingTimeInterval(-1.25), tags: ["format": "png"] ) ) ```

Logging

Structured log ingestion with automatic batching and retry. Enable it once, then use the level helpers from anywhere in your app: ```swift // Enable logging after initialization AbrovaTrace.shared.enableLogging(sourceId: "my-ios-app") AbrovaTrace.shared.info("User login successful", metadata: ["user_id": "user_123"]) ``` See [iOS SDK Logging](/docs/trace/ios-logging) for the full pipeline — batching, log levels, lifecycle handling, manual flush, and configuration.

Configuration Options

Upload Debug Symbols (dSYM)

To see file names and line numbers in your iOS stack traces, upload your app's dSYM files. Each build produces unique dSYMs — upload a new one for every release version. #### 1. Find your dSYM files After building your app in Xcode, dSYMs are located in your build products: ```bash # For debug builds (simulator) ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/YourApp.app.dSYM # For release/archive builds ~/Library/Developer/Xcode/Archives/*.xcarchive/dSYMs/ ``` #### 2. Zip the dSYM bundle ```bash # Zip the .dSYM bundle cd ~/Library/Developer/Xcode/DerivedData/YourApp-*/Build/Products/Debug-iphonesimulator/ zip -r YourApp.dSYM.zip YourApp.app.dSYM ``` #### 3. Upload via Dashboard In the Abrova Console, go to **Abrova Trace → Source maps** and open the **iOS dSYMs** tab. Drop the .zip file, optionally enter the release version, and click Upload. Uploading needs the member role or higher; deleting uploaded dSYMs needs admin or owner — see [Roles & Permissions](/docs/trace/error-dashboard#roles). The dSYM upload endpoint accepts only a Console login session (no API key or personal access token), and `@abrova-trace/cli` uploads source maps only, so upload dSYMs from the Console. Each time you rebuild your app, the dSYM UUIDs change. If stack traces show function names but no file/line info, you need to upload the dSYM matching that build.

Platform Support

- **iOS** — 12.0+ - **macOS** — 10.14+ - **tvOS** — 12.0+ (Swift Package Manager only; the CocoaPods spec covers iOS and macOS) watchOS is not supported. Building the SDK requires Swift 5.10 or later (Xcode 15.3+).

Context & Tags

Attach persistent metadata to every error and message captured by the SDK (native crash and ANR reports, logs and performance spans do not carry them). Useful for filtering by team, region, feature flag, or experiment cohort in the Abrova Console. #### Global extras Extras are arbitrary key/value pairs (string keys, any serializable value) automatically merged into every event: ```swift // Set individual extras AbrovaTrace.shared.setExtra("organizationId", value: "org-123") AbrovaTrace.shared.setExtra("feature", value: "checkout-v2") // Set multiple at once AbrovaTrace.shared.setExtras([ "organizationId": "org-123", "feature": "checkout-v2", "experiment": "new-flow" ]) // Clear when no longer needed (e.g. on logout) AbrovaTrace.shared.clearExtras() ``` #### Tags Tags are string-only key/value pairs for categorizing events: ```swift AbrovaTrace.shared.setTag("team", value: "payments") AbrovaTrace.shared.setTag("region", value: "us-east") AbrovaTrace.shared.setTags(["team": "payments", "version": "2.1"]) AbrovaTrace.shared.clearTags() ``` Use **tags** for low-cardinality categorical filters (team, region, build flavor). Use **extras** for richer per-event context (user IDs, request bodies, computed values).

SwiftUI Integration

The SDK works seamlessly with the SwiftUI `@main` app entry point. Initialize from the `App` struct's `init()` so errors are captured from the moment your view hierarchy is constructed: ```swift import SwiftUI import AbrovaTrace @main struct MyApp: App { init() { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_your_api_key") .environment("production") .build() AbrovaTrace.shared.initialize(config: config) } var body: some Scene { WindowGroup { ContentView() } } } ``` For UIKit apps, initialize from `application(_:didFinishLaunchingWithOptions:)` in your `AppDelegate` — see the Complete Example below. ### API Reference Full method surface of `AbrovaTrace.shared` and `PerformanceSpan`. Selectors use the Swift convention; optional parameters end with `?` in the description. #### `AbrovaTrace.shared` — Initialization & lifecycle #### Error capture #### User & session #### Global context (extras) #### Tags #### Breadcrumbs #### Performance monitoring #### Logging #### `PerformanceSpan` factories ### Complete Example ```swift import UIKit import AbrovaTrace @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let config = AbrovaTraceConfigBuilder(apiKey: "ab_live_YOUR_API_KEY") .environment("production") .release(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) .debug(false) .captureUncaughtExceptions(true) .captureSignalCrashes(true) .captureAnr(true) .build() AbrovaTrace.shared.initialize(config: config) AbrovaTrace.shared.enablePerformanceTracking() return true } func applicationWillTerminate(_ application: UIApplication) { AbrovaTrace.shared.close() } } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() AbrovaTrace.shared.setUserId("user_123") AbrovaTrace.shared.addNavigationBreadcrumb(from: "Launch", to: "HomeView") } func handlePurchase() { AbrovaTrace.shared.addUserBreadcrumb("Initiated purchase", data: ["product": "Premium"]) do { try processPurchase() AbrovaTrace.shared.captureMessage("Purchase successful", level: .info, extra: [ "amount": 99.99 ]) } catch { AbrovaTrace.shared.captureError( error, message: "Purchase failed", extra: ["user_id": "user_123", "screen": "HomeView"] ) } } func processPurchase() throws { /* ... */ } } ``` ### Best Practices - Laravel SDK: https://abrova.ir/en/docs/trace/laravel-sdk

Installation

The package comes from Abrova Center. Two steps: **1. Add the Abrova repository** to your `composer.json`: ```json "repositories": [ { "type": "composer", "url": "https://center.abrova.ir/api/packages/abrova/composer" } ] ``` **2. Install the package:** ```bash composer require abrova-trace/laravel-sdk ``` Publish the configuration file: ```bash php artisan vendor:publish --tag=abrovatrace-config ```

Quick Start

Add your credentials to `.env`: ```bash ABROVATRACE_API_KEY=ab_live_your_api_key_here ABROVATRACE_SERVER_SECRET=ab_srv_your_server_secret_here ABROVATRACE_ENVIRONMENT=production ABROVATRACE_RELEASE=1.0.0 ``` The SDK uses Laravel package auto-discovery. No manual registration of service providers or facades is needed. The facade is `AbrovaTrace\Laravel\AbrovaTraceFacade` (auto-aliased as `AbrovaTrace`). The SDK stays inactive until both `ABROVATRACE_API_KEY` and `ABROVATRACE_SERVER_SECRET` are set.

Capture Errors

#### Automatic Capture All unhandled exceptions are automatically captured via Laravel's exception handler. No code changes needed. The exception is still written to your application's own log (`laravel.log` or your configured log channels), so you do not need to change your logging setup. Versions before 0.2.3 stopped Laravel's default logging for captured exceptions. #### Manual Capture ```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; // Capture an exception try { riskyOperation(); } catch (\Throwable $e) { AbrovaTrace::captureException($e, [ 'extra' => ['userId' => 123, 'feature' => 'checkout'], ]); } // Capture a message AbrovaTrace::captureMessage('User completed checkout', [ 'extra' => ['orderId' => 'ORD-456'], ]); // With a level and tags ('debug', 'info' (default), 'warning', 'error') AbrovaTrace::captureMessage('Payment retried', [ 'level' => 'warning', 'tags' => ['feature' => 'checkout'], ]); ``` The same exception is reported once. If you capture it with `captureException()` and then rethrow it, the automatic capture does not send it a second time (since 0.2.3). Messages appear in the Console under **Abrova Trace → Messages**, with their level, platform, extra data and breadcrumbs. They never create an issue. `'warn'` is sent as `warning`; `'fatal'`, `'critical'`, `'alert'` and `'emergency'` as `error`; `'trace'` as `debug`; anything else as `info`. Use messages for things your app reports on purpose ("checkout started", "sync finished"); for high-volume diagnostics use Logs. Breadcrumbs track events leading up to an error. HTTP requests and database queries are tracked automatically. #### Manual Breadcrumbs ```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Models\Breadcrumb; // HTTP breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::http('POST', '/api/orders', 201, 150.5)); // Database breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::database('SELECT * FROM users WHERE id = ?', 12.5)); // User action breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::user('clicked_checkout', ['cartTotal' => 99.99])); // Custom breadcrumb AbrovaTrace::addBreadcrumb(Breadcrumb::custom('Cache cleared', 'cache', ['keys' => 15])); ``` The SDK automatically records HTTP request/response breadcrumbs via middleware and database query breadcrumbs via the QueryExecuted event listener. No manual setup required.

User Context

User context is automatically extracted from authenticated users via middleware. You can also set it manually: ```php AbrovaTrace::setUser([ 'id' => 123, 'email' => 'user@example.com', 'username' => 'john_doe', ]); ```

Scoped Context

Use scoped context to isolate user and request context per operation. Both are automatically restored afterward (breadcrumbs added inside the closure are kept). The closure receives the SDK instance. ```php AbrovaTrace::withScope(function ($scope) { $scope->setUser(['id' => 456]); $scope->setRequestContext(['operation' => 'bulk_import']); $scope->addBreadcrumb(Breadcrumb::custom('Starting import', 'import')); // Errors captured here include this context AbrovaTrace::captureException(new \RuntimeException('Import failed')); }); // Context is restored after the closure ```

Laravel Middleware

The middleware is automatically registered on web and api groups. It provides: - **Request Tracking** — Records the incoming request as a breadcrumb (method and path) and sets request context (method, URL, path, route, IP, user agent) - **Response Tracking** — Records response breadcrumb with status code and duration - **User Extraction** — Automatically sets user context (id, email, name) from the authenticated user (`middleware.track_user`) - **Performance Spans** — Creates an HTTP performance span for every request (when `performance.enabled` is true) To customize ignored paths, edit `config/abrovatrace.php`: ```php 'middleware' => [ 'enabled' => true, 'ignored_paths' => [ 'health', 'api/health', '_debugbar/*', 'telescope/*', ], ], ```

Database Query Tracking

Database queries are automatically tracked via Laravel's QueryExecuted event (`performance.track_db_queries`). Every query becomes a breadcrumb; queries at or above the threshold (default 500ms) also generate performance spans. ```php // config/abrovatrace.php 'performance' => [ 'enabled' => true, 'track_db_queries' => true, 'slow_query_threshold_ms' => 100, // Span for queries taking 100ms or more ], ```

Monolog Log Channel

Send logs to AbrovaTrace using Laravel's built-in logging system: ```php // config/logging.php 'channels' => [ 'abrovatrace' => [ 'driver' => 'custom', 'via' => AbrovaTrace\Laravel\Logging\AbrovaTraceLogChannel::class, ], ], ``` ```php use Illuminate\Support\Facades\Log; Log::channel('abrovatrace')->info('User signed up', ['userId' => 123]); Log::channel('abrovatrace')->error('Payment failed', ['orderId' => 'ORD-456']); Log::channel('abrovatrace')->warning('Rate limit approaching', ['remaining' => 5]); ``` You can add abrovatrace to a stack channel to send logs to multiple destinations simultaneously. See [Laravel SDK Logging](/docs/trace/laravel-logging) for batching behavior, log levels, and manual flush.

Performance Tracking

```php use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Performance\PerformanceSpan; // Track a custom operation with automatic timing $result = AbrovaTrace::trackOperation('cache.rebuild', function () { return rebuildCache(); }, ['tags' => ['cache' => 'products']]); // Manual performance span AbrovaTrace::reportPerformanceSpan(PerformanceSpan::custom([ 'operation' => 'email.send', 'durationMs' => 250.5, 'startTime' => now()->subMilliseconds(250), 'tags' => ['mailer' => 'smtp'], ])); ```

Configuration Options

```php // config/abrovatrace.php return [ 'api_key' => env('ABROVATRACE_API_KEY', ''), 'server_secret' => env('ABROVATRACE_SERVER_SECRET', ''), 'enabled' => env('ABROVATRACE_ENABLED', true), 'environment' => env('ABROVATRACE_ENVIRONMENT', env('APP_ENV', 'production')), 'release' => env('ABROVATRACE_RELEASE', '0.1.0'), 'sample_rate' => env('ABROVATRACE_SAMPLE_RATE', 1.0), 'before_send' => null, // set a closure from a service provider 'debug' => env('ABROVATRACE_DEBUG', false), 'timeout' => env('ABROVATRACE_TIMEOUT', 5), 'max_breadcrumbs' => 50, ]; ```

Ignored Exceptions

The SDK ships with sensible defaults for exceptions Laravel apps typically don't want as errors. By default these are **not** reported: - `AuthenticationException` — unauthenticated requests - `ValidationException` — form validation failures - `NotFoundHttpException` — 404s - All 4xx HTTP exceptions Customize via `config/abrovatrace.php`: ```php 'exception_handler' => [ 'ignored_exceptions' => [ \App\Exceptions\BusinessException::class, ], 'report_4xx' => true, ], ``` The `ignored_exceptions` list you set replaces the default one, so keep the default entries you still want ignored. 4xx responses are client errors — duplicating them as exceptions makes the error feed noisy. Flip `report_4xx` to `true` if you genuinely want them captured (e.g. for analytics). ### API Reference #### AbrovaTrace Facade - `captureException($e, $options?)` — Capture an exception - `captureMessage($msg, $options?)` — Send a message to Abrova Trace → Messages (`$options`: `level`, `extra`, `tags`) - `addBreadcrumb($breadcrumb)` — Add a breadcrumb - `setUser($user)` — Set user context - `setRequestContext($context)` — Set request context - `withScope($callback)` — Run code with isolated user/request context - `log($message, $level?, $metadata?)` — Send a log entry (user id comes from `setUser()`) - `trace()`, `logDebug()`, `info()`, `warn()`, `logError()`, `fatal()` — Log at a level (`$message, $metadata?`) - `trackOperation($name, $callback, $options?)` — Track operation with auto-timing (`$options`: `operationType`, `tags`) - `reportPerformanceSpan($span)` — Report a performance span - `flush()` — Flush buffered logs and performance spans (also runs automatically at shutdown) - `isEnabled()` — Check if SDK is enabled - `getStats()` — Get SDK statistics #### Breadcrumb Helpers - `Breadcrumb::http($method, $url, $status?, $duration?)` — HTTP request breadcrumb - `Breadcrumb::database($query, $duration, $error?)` — Database query breadcrumb - `Breadcrumb::user($action, $data?)` — User action breadcrumb - `Breadcrumb::navigation($from, $to, $method?)` — Navigation breadcrumb - `Breadcrumb::custom($message, $category, $data?)` — Custom breadcrumb ### Requirements - **PHP** — >= 8.1 - **Laravel** — 10.x / 11.x / 12.x - **Guzzle** — >= 7.0 (ships with Laravel) ### Complete Example ```php // routes/api.php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Log; use AbrovaTrace\Laravel\AbrovaTraceFacade as AbrovaTrace; use AbrovaTrace\Laravel\Models\Breadcrumb; Route::get('/users/{id}', function ($id) { // Add a breadcrumb (DB queries are tracked automatically) AbrovaTrace::addBreadcrumb(Breadcrumb::user('view_user', [ 'userId' => $id, ])); try { $user = User::findOrFail($id); return response()->json($user); } catch (\Throwable $e) { AbrovaTrace::captureException($e, [ 'extra' => ['userId' => $id], ]); return response()->json(['error' => 'User not found'], 404); } }); // Log via Monolog channel Route::post('/orders', function (Request $request) { $order = Order::create($request->validate([ 'total' => 'required|numeric', ])); Log::channel('abrovatrace')->info('Order created', [ 'orderId' => $order->id, 'amount' => $order->total, ]); // Track performance of external API call $result = AbrovaTrace::trackOperation('payment.process', function () use ($order) { return PaymentGateway::charge($order); }); return response()->json($result); }); ``` ### Best Practices - Installing inside Iran: https://abrova.ir/en/docs/trace/install-in-iran The Abrova SDKs install from [Abrova Center](https://center.abrova.ir), which is inside Iran. Each SDK also needs a few common packages (for example AndroidX, OkHttp or `http` for Dart). Those come from the public registries, and some of them refuse Iranian addresses or stop answering when the international internet is cut. This page shows how to get them from mirrors inside Iran. Every address on this page was checked from a server inside Iran on 27 October 2026, with the exact packages the Abrova SDKs need. The mirrors are run by other Iranian companies, not by Abrova, so their addresses can change. | Platform | Abrova SDK | Its dependencies, inside Iran | |---|---|---| | Android | Abrova Center | Myket Maven mirror: Google and Maven Central packages, and the Android Gradle plugin | | iOS | Abrova Center | None needed: the SDK has no other dependencies | | Node.js, Next.js, CLI | Abrova Center | Runflare npm mirror | | Flutter | Abrova Center | No mirror inside Iran: pub.dev refuses Iranian addresses (see below) | | Laravel / PHP | Abrova Center | No mirror inside Iran: Packagist works from Iran today |

Android (Gradle)

In `settings.gradle`, list the Iranian mirror before Google and Maven Central, for both plugins and libraries: ```groovy pluginManagement { repositories { maven { url "https://maven.myket.ir" } gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositories { maven { url "https://center.abrova.ir/api/packages/abrova/maven" } maven { url "https://maven.myket.ir" } google() mavenCentral() } } ``` Gradle tries the repositories in this order, so `google()` and `mavenCentral()` are only used for a package the mirror does not have. Gradle itself is downloaded once per version from `services.gradle.org` (the `distributionUrl` in `gradle/wrapper/gradle-wrapper.properties`). Keep that download on your machine, or put the file on a server you control and point `distributionUrl` at it.

iOS

Nothing extra: the iOS SDK installs from Abrova Center and brings everything it needs. See [iOS SDK](/docs/trace/ios-sdk) for the Swift Package Manager and CocoaPods lines.

Node.js, Next.js and the CLI

In the project's `.npmrc`, use the Iranian npm mirror for everything and Abrova Center for the Abrova packages: ```ini registry=https://mirror-npm.runflare.com/ @abrova-trace:registry=https://center.abrova.ir/api/packages/abrova/npm/ ``` Then install as usual (`npm install`, `pnpm install` or `yarn`).

Flutter

The Abrova Flutter package comes from Abrova Center. Its dependencies (`http`, `path_provider`, `device_info_plus` and a few more) come from pub.dev, and **pub.dev refuses Iranian addresses**. We found no pub mirror inside Iran. What works: - Run `flutter pub get` once through a connection that reaches pub.dev. The packages stay in your pub cache, and later builds work without pub.dev. - Commit `pubspec.lock`, so every machine and your CI resolve the same versions from that cache. - Or set `PUB_HOSTED_URL=https://pub.flutter-io.cn` (a public mirror run by the Flutter community in China). It answers from Iran, but it is outside Iran, so it stops working when the international internet is cut.

Laravel and PHP

The Abrova package comes from Abrova Center. Its dependencies (`guzzlehttp/guzzle`, `illuminate/*`) come from Packagist, which answers from Iran today; we found no Packagist mirror inside Iran. Composer keeps what it downloads in its cache, and `composer.lock` pins the versions.

When the international internet is cut

- **Keeps working:** Abrova Center, the Myket and Runflare mirrors, and everything already in your local caches. - **Stops:** pub.dev, Packagist, CocoaPods trunk, GitHub and `services.gradle.org`. Keep your lock files (`pubspec.lock`, `package-lock.json`, `composer.lock`, `Package.resolved`, `Podfile.lock`) in git, so a fresh checkout needs as few downloads as possible. - AI assistants (coming soon): https://abrova.ir/en/docs/trace/ai-assistants Abrova does not have a docs server for AI assistants yet. This page describes how it will work and what you can use today. When it is ready, you connect your AI assistant (any tool that supports MCP, the open standard AI tools use to connect to outside sources) once. Then, when you ask it to add Abrova Trace to your project, it looks up the current SDK version, install lines and setup steps from these docs instead of answering from memory. ### What works today - **Copy page for AI.** Every docs page has a **Copy page for AI** link. Paste the page into your assistant's chat before you ask it to write code. - **Plain-text docs.** Give your assistant the address `https://abrova.ir/docs/llms.txt`. It lists every docs page as plain text that assistants read well. Tell your assistant to follow the page you gave it, to install the SDK from Abrova Center as the page shows, and to use the placeholder keys from the docs. ### What it will do The docs server will have read-only tools. Your assistant chooses them by itself; you do not call them by hand. Examples of what you will be able to ask: - **"Set up Abrova Trace in this Next.js project and upload source maps."** It would answer from the [Next.js SDK](/docs/trace/nextjs-sdk) and [Upload source maps](/docs/trace/upload-source-maps) pages. - **"Add Abrova Trace to my Laravel app and record a breadcrumb before the payment call."** It would answer from the [Laravel SDK](/docs/trace/laravel-sdk) and [Breadcrumbs](/docs/trace/breadcrumbs) pages. - **"Add a check-in to my nightly cron job and send Abrova Trace alerts to Telegram."** It would answer from [Cron monitoring](/docs/trace/cron-monitoring) and [Telegram](/docs/trace/telegram). - **"Stream the logs of every Docker container on my server to Abrova Trace."** It would answer from [Abrova Trace agent](/docs/trace/agent). - **"Which Abrova key goes in the app and which one stays on the server?"** It would answer from [Server secret & server routes](/docs/trace/server-secret). ### Privacy - **What it reads:** the public Abrova docs, the current SDK versions, and the words your assistant sends (the search words, the page it wants, the product and platform names). - **What it never sees:** your code, your files, your Abrova project, your keys or your users. The tools have no input for them, and they cannot change any setting. Never paste a key into a chat with an AI assistant. Your assistant writes the placeholder from the docs (`ab_live_your_api_key`); you put the real value in your project's configuration yourself. The server secret (`ab_srv_...`) belongs only on your server, in an environment variable. More about AI assistants and Abrova: [AI assistants](/ai-assistants). Questions? [Contact us](/contact). - APM overview: https://abrova.ir/en/docs/trace/apm AbrovaTrace APM provides real-time performance monitoring across all your applications. Track HTTP request latency, database queries, and custom operations with p50/p95/p99 percentile metrics. For web apps, monitor Core Web Vitals (LCP, INP, CLS, plus TTFB and FCP). APM is included in Abrova Trace at no extra cost. Spans and Web Vitals are not counted as new issues, so they do not use your free monthly allowance or your wallet. ### Data Retention Spans and Web Vitals are kept for your project's data retention period: 7 days free, or 30 days for +50,000 Toman per project a month from your wallet — see [Pricing](/pricing). ### Performance Span Structure Each tracked operation creates a Performance Span with the following fields: ```typescript interface PerformanceSpan { operation: string; // e.g., "GET /api/users" operationType: 'http' | 'db' | 'custom'; httpMethod?: string; // GET, POST, PUT, DELETE httpUrl?: string; // Full request URL httpStatusCode?: number; // 200, 404, 500, etc. httpHost?: string; // api.example.com durationMs: number; // Request duration startTime: string; // ISO timestamp endTime?: string; // ISO timestamp traceId?: string; // Distributed trace ID spanId?: string; // Unique span ID parentSpanId?: string; // Parent span for nesting platform: string; // nextjs, flutter, web, android, ios, nodejs, laravel environment?: string; // production, staging, dev releaseVersion?: string; // App version tags?: Record; // Custom tags metadata?: Record; // Additional data status: 'ok' | 'error'; errorMessage?: string; } ``` ### Quick Start by SDK #### Next.js / React ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', }); // Enable automatic HTTP tracking (intercepts fetch) AbrovaTrace.getInstance()?.enablePerformanceTracking(); // Enable Web Vitals (LCP, INP, CLS, TTFB, FCP) AbrovaTrace.getInstance()?.enableWebVitals(); ``` #### Flutter ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Create a performance-tracking HTTP client final client = PerformanceHttpClient( apiKey: 'ab_live_your_api_key', environment: 'production', ); // All requests are automatically tracked final response = await client.get( Uri.parse('https://api.example.com/users'), ); ``` #### Android (Kotlin) ```kotlin import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient val client = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() // All requests through this client are automatically tracked ``` #### iOS (Swift) ```swift import AbrovaTrace // Enable automatic URLSession tracking AbrovaTrace.shared.enablePerformanceTracking() // Or use a custom session with tracking let config = AbrovaTrace.shared.performanceSessionConfiguration() let session = URLSession(configuration: config) ``` #### Node.js ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); // Time any async operation and report it as a span const users = await AbrovaTrace.trackOperation( 'fetch-users', () => db.query('SELECT * FROM users'), { operationType: 'db' } ); ``` ### Viewing Data in Dashboard Once tracking is enabled, open **Trace → Performance** in Abrova Console to see: - **Overview Stats** — Requests, error rate, p50, p95 and p99 latency - **Latency** — p50, p95, p99 over time, plus requests over time with errors stacked - **Operations** — Each operation with its type, request count, latency percentiles and errors - **Status codes, Web Vitals and Failed requests** cards - **Filtering** — Time range (1h, 24h, 7d, 30d or custom), platform, environment, release ### API Endpoints SDKs report spans to these endpoints:
{['Endpoint', 'Method', 'Description'].map((h) => ( ))} {[ ['/api/performance/spans', 'POST', 'Report single span'], ['/api/performance/spans/batch', 'POST', 'Report batch (max 50)'], ['/api/performance/web-vitals', 'POST', 'Report Web Vitals data'], ].map(([endpoint, method, desc]) => ( ))}
{h}
{endpoint} {method} {desc}
### Best Practices - API latency tracking: https://abrova.ir/en/docs/trace/api-latency API Latency Tracking captures timing data for all HTTP requests made by your application. The SDK automatically instruments HTTP clients to measure request duration and reports this data to AbrovaTrace for analysis with p50, p95, and p99 percentile metrics.

Next.js / React

#### Automatic Tracking Enable automatic fetch interception to track all HTTP requests: ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', }); // Enable automatic HTTP tracking // This intercepts window.fetch and reports timing AbrovaTrace.getInstance()?.enablePerformanceTracking({ batchSize: 5, // Send spans in batches of 5 (default) flushInterval: 5000, // Flush at most 5 seconds after a span is buffered (default) }); ``` #### Manual Operation Tracking ```typescript const sdk = AbrovaTrace.getInstance(); // Track an async operation const result = await sdk?.trackOperation( 'fetchUserProfile', async () => { const response = await fetch('/api/users/123'); return response.json(); }, { operationType: 'http', tags: { userId: '123' } } ); ``` #### Performance Fetch Wrapper ```typescript const sdk = AbrovaTrace.getInstance(); // Create a tracked fetch function const performanceFetch = sdk?.createPerformanceFetch() ?? fetch; // Use it like regular fetch — timing is automatically captured const response = await performanceFetch('/api/users'); const data = await response.json(); ``` The wrapper reports spans only after `enablePerformanceTracking()`, `trackOperation()` or `reportPerformanceSpan()` has been called. In the browser, `enablePerformanceTracking()` already tracks every `fetch`, so use the wrapper on the server. #### Manual Span Reporting ```typescript import { createHttpSpan, PerformanceSpanFactory } from '@abrova-trace/nextjs-sdk'; // Create and report an HTTP span const span = createHttpSpan({ method: 'GET', url: 'https://api.example.com/users', statusCode: 200, durationMs: 245, startTime: new Date(), }); AbrovaTrace.getInstance()?.reportPerformanceSpan(span); // Or use the factory for other span types const dbSpan = PerformanceSpanFactory.forDbQuery({ queryType: 'SELECT', tableName: 'users', durationMs: 15, startTime: new Date(), }); AbrovaTrace.getInstance()?.reportPerformanceSpan(dbSpan); ```

Flutter

#### Performance HTTP Client Use the PerformanceHttpClient to automatically track all HTTP requests: ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Create a performance-tracking HTTP client final client = PerformanceHttpClient( apiKey: 'ab_live_your_api_key', environment: 'production', releaseVersion: '1.0.0', addBreadcrumbs: true, // Also add HTTP breadcrumbs excludedHosts: {'analytics.example.com'}, // Hosts to skip (Abrova's own API is always skipped) minDurationMs: 0, // Track all requests (set higher to filter fast ones) ); // All requests are automatically tracked final response = await client.get( Uri.parse('https://api.example.com/users'), ); // Don't forget to flush and close await client.flush(); client.close(); ``` #### Custom Operation Tracking ```dart import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart'; // Automatic tracking with closure final result = await PerformanceTracker.track( operation: 'processOrder', apiKey: 'ab_live_your_api_key', block: () async => await orderService.process(orderId), ); // Manual tracking final tracker = PerformanceTracker( operation: 'processPayment', apiKey: 'ab_live_your_api_key', operationType: 'custom', environment: 'production', ); // ... perform operation await tracker.finish(status: SpanStatus.ok); ```

Android (Kotlin)

#### OkHttp Performance Interceptor ```kotlin import ir.abrova.trace.sdk.network.AbrovaTracePerformanceInterceptor import okhttp3.OkHttpClient import okhttp3.Request val client = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) // APM tracking .build() // All API calls are automatically tracked val request = Request.Builder() .url("https://api.example.com/users") .build() client.newCall(request).execute().use { response -> // Duration, status code, and errors are automatically reported val users = response.body?.string() } ``` #### With Retrofit ```kotlin import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory val okHttpClient = OkHttpClient.Builder() .addInterceptor(AbrovaTracePerformanceInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() // All API calls through this Retrofit instance are automatically tracked ``` #### Manual Span Reporting ```kotlin import ir.abrova.trace.sdk.AbrovaTrace import ir.abrova.trace.sdk.models.PerformanceSpan // Track a custom operation val result = AbrovaTrace.trackOperation("load_user_profile") { val user = fetchUserFromApi() cacheUserLocally(user) user } // Manual span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.fromHttpRequest( method = "GET", url = "https://api.example.com/products", statusCode = 200, durationMs = 245L, startTime = System.currentTimeMillis() - 245 ) ) // Database query span AbrovaTrace.reportPerformanceSpan( PerformanceSpan.forDbQuery( queryType = "SELECT", tableName = "products", durationMs = 15L, startTime = System.currentTimeMillis() - 15, rowsAffected = 50 ) ) ```

iOS (Swift)

#### Automatic URLSession Tracking ```swift import AbrovaTrace // Enable automatic tracking for all URLSession requests AbrovaTrace.shared.enablePerformanceTracking() // Or create a custom session with tracking let config = AbrovaTrace.shared.performanceSessionConfiguration() let session = URLSession(configuration: config) // All requests through this session are automatically tracked let (data, response) = try await session.data(from: url) ``` #### Configuration ```swift // Exclude specific hosts from tracking // (Abrova Trace's own API is always excluded) AbrovaTrace.shared.setPerformanceExcludedHosts(["analytics.example.com"]) // Set minimum duration to track (filter very fast requests) AbrovaTrace.shared.setPerformanceMinDuration(10) // Skip requests shorter than 10ms ``` #### Manual Tracking ```swift import AbrovaTrace // Track with closure let profile = try await PerformanceTracker.trackAsync("fetchProfile") { await api.fetchProfile() } // Manual tracker let tracker = PerformanceTracker(operation: "processOrder") tracker.setTag("orderId", value: "12345") // ... perform operation tracker.finish(status: .ok) ```

Node.js

#### Operation Tracking The Node.js SDK has no automatic HTTP span capture. Wrap the work you want timed with `trackOperation`, or send spans yourself with `reportPerformanceSpan`: ```javascript const AbrovaTrace = require('@abrova-trace/nodejs-sdk'); app.get('/api/users/:id', async (req, res) => { const user = await AbrovaTrace.trackOperation( 'GET /api/users/:id', () => db.users.findById(req.params.id), { operationType: 'db', tags: { route: '/api/users/:id' } } ); res.json(user); }); ``` #### Express.js Middleware The Express middleware records each request (method, URL, status, duration) as an HTTP **breadcrumb** attached to errors — not as a Performance span: ```javascript const middleware = AbrovaTrace.expressMiddleware(); app.use(middleware.requestHandler()); // Your routes // Error handler (must be last) app.use(middleware.errorHandler()); ``` #### Database Query Tracking ```javascript const { Breadcrumb } = require('@abrova-trace/nodejs-sdk'); async function executeQuery(sql, params) { const startTime = Date.now(); try { AbrovaTrace.addBreadcrumb(Breadcrumb.database(sql.substring(0, 100), 0)); const results = await db.query(sql, params); AbrovaTrace.addBreadcrumb(Breadcrumb.database( 'Query completed', Date.now() - startTime )); return results; } catch (error) { AbrovaTrace.captureException(error, { extra: { sql, duration_ms: Date.now() - startTime } }); throw error; } } ``` ### Dashboard View The Performance dashboard shows latency data with the following views:
{[ ['Overview Stats', ['Requests', 'Error rate', 'p50 latency', 'p95 / p99 latency']], ['Latency', ['p50, p95, p99 over time', 'Requests over time, errors stacked']], ['Operations', ['Operation name and type', 'Request count and latency percentiles', 'Error count', 'Status codes, Web Vitals and failed requests cards']], ['Filtering', ['Time range (1h, 24h, 7d, 30d or custom)', 'Platform', 'Environment', 'Release']], ].map(([title, items]) => (

{title}

    {items.map((item) => (
  • • {item}
  • ))}
))}
### Best Practices - Core Web Vitals: https://abrova.ir/en/docs/trace/web-vitals Core Web Vitals are a set of metrics defined by Google that measure real-world user experience for loading performance, interactivity, and visual stability. These metrics directly impact SEO rankings and user satisfaction. AbrovaTrace uses Google's official **web-vitals** library for accurate measurement. Web Vitals are browser-specific metrics. Today only the Next.js SDK (`@abrova-trace/nextjs-sdk`) reports them. For mobile apps (iOS, Android, Flutter), use API Latency Tracking instead. ### Metrics Explained

LCP — Largest Contentful Paint

Measures loading performance. Marks when the largest content element (image, video, or text block) becomes visible.

Good
≤2.5s
Needs improvement
2.5–4s
Poor
>4s

INP — Interaction to Next Paint

Measures overall responsiveness. Observes all interactions and reports the worst latency.

Good
≤200ms
Needs improvement
200–500ms
Poor
>500ms

CLS — Cumulative Layout Shift

Measures visual stability. Sum of all unexpected layout shifts during the page's lifecycle.

Good
≤0.1
Needs improvement
0.1–0.25
Poor
>0.25
{[ ['TTFB', 'Time to First Byte', 'Server response time', '≤800ms'], ['FCP', 'First Contentful Paint', 'Perceived load speed', '≤1.8s'], ].map(([abbr, name, desc, good]) => (

{abbr}

{name}

{desc}

Good: {good}

))}

Next.js

#### Basic Setup ```typescript import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production', release: '1.0.0', }); // Enable Web Vitals tracking // Automatically captures LCP, INP, CLS, TTFB, FCP AbrovaTrace.getInstance()?.enableWebVitals(); ``` #### App Router (Next.js 13+) ```tsx // app/providers.tsx 'use client'; import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; export function Providers({ children }: { children: React.ReactNode }) { useEffect(() => { AbrovaTrace.init({ apiKey: process.env.NEXT_PUBLIC_ABROVA_TRACE_KEY!, environment: process.env.NODE_ENV, }); // Enable Web Vitals tracking AbrovaTrace.getInstance()?.enableWebVitals(); // Enable HTTP performance tracking AbrovaTrace.getInstance()?.enablePerformanceTracking(); return () => AbrovaTrace.getInstance()?.close(); }, []); return <>{children}; } ``` #### Pages Router ```tsx // pages/_app.tsx import { useEffect } from 'react'; import { AbrovaTrace } from '@abrova-trace/nextjs-sdk'; function MyApp({ Component, pageProps }) { useEffect(() => { AbrovaTrace.init({ apiKey: process.env.NEXT_PUBLIC_ABROVA_TRACE_KEY!, environment: process.env.NODE_ENV, }); AbrovaTrace.getInstance()?.enableWebVitals(); AbrovaTrace.getInstance()?.enablePerformanceTracking(); return () => AbrovaTrace.getInstance()?.close(); }, []); return ; } export default MyApp; ``` #### Advanced Options ```typescript // Report all metric changes (not just final values) AbrovaTrace.getInstance()?.enableWebVitals({ reportAllChanges: true, }); // Custom batch interval (default: 5000ms): vitals are sent this long after // the last metric arrives, and also when the page is hidden or unloaded AbrovaTrace.getInstance()?.enableWebVitals({ batchInterval: 10000, // 10 seconds }); ```

Data Structure

Web Vitals data is reported with the following structure: ```typescript interface WebVitalsData { pageUrl: string; // Full page URL pageRoute?: string; // Route path (e.g., /dashboard) // Core Web Vitals lcpMs?: number; // Largest Contentful Paint cls?: number; // Cumulative Layout Shift inpMs?: number; // Interaction to Next Paint ttfbMs?: number; // Time to First Byte fcpMs?: number; // First Contentful Paint // Context deviceType?: string; // 'mobile' | 'tablet' | 'desktop' connectionType?: string; // from Navigator API (4g, 3g, etc.) sessionId?: string; // Per page-load ID from the Web Vitals tracker (not getSessionId()) platform: 'nextjs'; environment?: string; // production, staging releaseVersion?: string; // App version } ```

Dashboard View

In Abrova Console, **Abrova Trace → Performance** shows Web Vitals in a dedicated card: the 75th percentile (p75) of each metric over the selected period, rated Good / Needs improvement / Poor with the thresholds above.

Core Web Vitals Card

{[ ['LCP', '1.8s', 'Good', 'green'], ['INP', '120ms', 'Good', 'green'], ['CLS', '0.15', 'Needs improvement', 'yellow'], ['FCP', '1.2s', 'Good', 'green'], ].map(([name, value, status, color]) => (
{name}
{value}
{status}
))}

Improving Your Scores

Improve LCP

  • • Optimize and compress images
  • • Use a CDN for static assets
  • • Preload critical resources
  • • Minimize render-blocking JavaScript

Improve INP

  • • Break up long JavaScript tasks
  • • Use web workers for heavy computation
  • • Optimize event handlers
  • • Defer non-critical JavaScript

Improve CLS

  • • Always include size attributes on images
  • • Reserve space for dynamic content
  • • Avoid inserting content above existing content
  • • Use transform animations instead of layout changes
### Best Practices - Performance score: https://abrova.ir/en/docs/trace/performance-score Your application's Core Web Vitals health is shown in the **Web Vitals** card on **Trace → Performance**. Each vital is summarised by its **p75** across the selected time range and rated **Good**, **Needs improvement** or **Poor** against Google's standard thresholds, so you can spot regressions at a glance. Abrova Console does not currently combine the vitals into one 0–100 score. Read the per-vital ratings instead — the worst-rated vital is where to start. Web Vitals are browser-specific and come from the browser SDK (call `enableWebVitals()` in the Next.js / web SDK). Mobile and backend SDKs report request latency only, so for non-web projects the card shows "Web Vitals need the browser SDK". ### How Vitals Are Rated Each vital's p75 is compared with Google's standard thresholds: at or below **Good** is Good, above **Poor** is Poor, anything in between is Needs improvement.
{['Metric', 'Name', 'Good', 'Poor'].map((h) => ( ))} {[ ['LCP', 'Largest Contentful Paint', '≤2.5s', '>4s'], ['INP', 'Interaction to Next Paint', '≤200ms', '>500ms'], ['CLS', 'Cumulative Layout Shift', '≤0.1', '>0.25'], ['FCP', 'First Contentful Paint', '≤1.8s', '>3s'], ['TTFB', 'Time to First Byte', '≤800ms', '>1.8s'], ].map(([metric, weight, good, poor]) => ( ))}
{h}
{metric} {weight} {good} {poor}
Vitals that haven't reported yet are not shown. ### Rating Bands ### What You See in the Dashboard The Web Vitals card on the Performance page shows: - **Header** — "p75 · N page loads · browser SDK only" - **Per-metric breakdown** — every reported vital with its p75 value, rating icon and label - **Good / Needs improvement / Poor split** — how the individual page loads for each vital were rated ### Reading the Breakdown The breakdown rows tell you *what* is off. Rows look like: ``` LCP 2840ms ● Needs improvement INP 190ms ● Good CLS 0.08 ● Good FCP 1650ms ● Good TTFB 720ms ● Good ``` Scan the rating icons: fix Poor rows first, then Needs improvement. ### Best Practices - Apdex score: https://abrova.ir/en/docs/trace/apdex Apdex (**Application Performance Index**) is an industry-standard KPI that condenses latency into a single number between **0.00 and 1.00**. Unlike percentiles, it tells you what fraction of your users were actually *happy* with response times — not just how slow the slow tail was. AbrovaTrace calculates it across every performance span in the selected time range. Apdex measures request duration, complementary to Web Vitals (which measure frontend rendering). Use both side-by-side for a full UX picture. ### How It's Calculated Every span is bucketed against a target latency **T** (`T = 500ms`):
{['Bucket', 'Range', 'Weight', 'Meaning'].map((h) => ( ))} {[ ['Satisfied', 'duration ≤ T (≤500ms)', '1.0', 'User got an instant response'], ['Tolerating', 'T < duration ≤ 4T (500ms-2s)', '0.5', 'Slow but usable'], ['Frustrated', 'duration > 4T (>2s)', '0.0', 'User likely abandoned or retried'], ].map(([bucket, range, weight, meaning]) => ( ))}
{h}
{bucket} {range} {weight} {meaning}
The score is then: ``` Apdex = (satisfied + tolerating × 0.5) / total ``` ### Score Bands (Guidance) ### Why Apdex (Not Just p95)? Percentiles answer "how slow is the slow tail?" — Apdex answers "how many users had a bad experience?" Two examples: - **Service A** — p95 = 1.8s but p50 = 50ms. Apdex ≈ 0.93. Most users are happy; a slow tail exists. - **Service B** — p95 = 1.8s and p50 = 1.7s. Apdex ≈ 0.51. *Half* the users are frustrated. Same p95, very different user reality. Apdex catches the second case immediately. ### Reading the Score Apdex follows the same time range, platform, environment and release filters as the rest of **Trace → Performance**. Alongside the score you get the satisfied, tolerating and frustrated request counts behind it. When there are no spans in the selected range, there is no score. ### Adjusting the Threshold T is fixed at **500ms** because it matches the industry-standard web SLA for "feels instant." It can't be configured per project. If your workload is genuinely slower (e.g. ML inference, video transcode), the score will look pessimistic — focus on the **trend** of the score rather than its absolute value. ### Best Practices - Platform breakdown: https://abrova.ir/en/docs/trace/platform-breakdown Platform Breakdown shows how your performance data splits across SDKs — iOS, Android, web, Node, Flutter, Laravel — so you can quickly see *which* platform is driving traffic and *how it's performing relative to the rest*. AbrovaTrace groups every span by the `platform` field each SDK sets on its spans. If your app's overall p95 jumps after a release, Platform Breakdown is the fastest way to find whether it's the iOS update, the Android rollout, or the new web build dragging the number up. ### What You See in the Dashboard On **Trace → Performance** in Abrova Console, use the **Platform** filter in the toolbar to narrow every card (stats, latency, operations, status codes, failed requests) to one platform. Switch between platforms to compare their request counts, latency and error rate side by side. ### How Platform Is Detected The `platform` field is set by the SDK on every span it reports:
{['SDK', 'Reported Platform', 'Source'].map((h) => ( ))} {[ ['Next.js / Web', 'nextjs', 'Fixed by the SDK'], ['Flutter', 'flutter_android / flutter_ios / flutter_web_* …', 'Detected at runtime via AbrovaTrace.createPerformanceClient / trackOperation; a standalone PerformanceHttpClient reports flutter (or web) unless you pass platform, and spans you build yourself for reportPerformanceSpan carry only the platform you set'], ['Android', 'android', 'Fixed by the SDK'], ['iOS', 'ios', 'Fixed by the SDK'], ['Node.js', 'nodejs', 'Fixed by the SDK'], ['Laravel', 'laravel', 'Fixed by the SDK'], ].map(([sdk, platform, source]) => ( ))}
{h}
{sdk} {platform} {source}
If a span arrives without a platform tag (custom span, legacy SDK, etc.), it falls into the **"unknown"** bucket. ### Separating Products There is no init option to override the platform. If one project ingests both your customer web app and your admin panel, tell them apart with a distinct `environment` (a Performance filter), or with tags on the spans you report yourself: ```typescript AbrovaTrace.init({ apiKey: 'ab_live_your_api_key', environment: 'production-admin', }); await AbrovaTrace.getInstance()?.trackOperation('loadReport', loadReport, { tags: { app: 'web-admin' }, }); ``` ### Best Practices - Top hosts: https://abrova.ir/en/docs/trace/top-hosts Top Hosts shows which external hostnames your app talks to most frequently — your own APIs, third-party services such as payment gateways and SMS providers, CDNs, analytics endpoints. Each host is ranked by request volume and shows p95 latency + error count so you can instantly spot a misbehaving third-party that's silently dragging your UX down. Only outbound HTTP requests appear here. Database queries, custom operations, and cache spans don't appear here. ### What You See For each host: - **Host** — e.g. `api.payments.example.com`, `cdn.example.com` - **Requests** — total for the selected time range (hosts are ranked by this) - **Average** and **p95** latency, in milliseconds - **Errors** — requests that returned HTTP 400 or higher To drill into slow or failing outbound calls, use the **Operations** and **Failed requests** cards on **Trace → Performance** in Abrova Console. ### What Counts as a Host The host is automatically extracted from the request URL by every official SDK. For example, a request to `https://api.payments.example.com/v1/charges` is bucketed under `api.payments.example.com`. Path differences don't matter at this level — that's what [URL Performance](/docs/trace/url-performance) is for. ### Common Patterns to Watch ### Real-World Use Case A common scenario: your app's overall p95 spikes from 300ms to 1.1s overnight. The latency chart confirms it, but the Operations card looks normal — every operation is similarly slow. Top Hosts immediately reveals the culprit: `api.thirdparty.com` p95 jumped from 200ms to 2.4s. *Their* incident is now *your* incident — but at least you know it's not your code, and you can mitigate (cache, async, fallback) rather than chase a ghost. ### Best Practices can save 100-200ms on the first call'], ['Watch for New Hosts', 'A brand-new host appearing near the top of the list is usually either a planned launch or an exfiltration risk — verify either way'], ]} /> - URL performance: https://abrova.ir/en/docs/trace/url-performance URL Performance is a per-URL latency table that groups requests by their **full HTTP URL** (not by logical operation name). It complements the **Operations** card ([API Latency](/docs/trace/api-latency)) — operations aggregate by operation (`GET /users/:id`), URLs aggregate by what the user's browser actually fetched (`/users/4710`). Use this when you need to know *which specific pages* are slow, not just which routes. Use Operations when looking at backend routes (parameterised paths). Use URL Performance when looking at per-page client-side calls or when you don't have a route template at all (e.g. static assets, third-party calls). ### What the Data Contains Each row has:
{['Column', 'Description'].map((h) => ( ))} {[ ['method', 'HTTP method (GET, POST, PUT, PATCH, DELETE…)'], ['url', 'The full request URL'], ['request_count', 'Total request count for this URL in the selected time range'], ['avg_duration', 'Mean duration (ms) — coarse but useful when paired with p95'], ['p50', 'Median — what a typical user experienced'], ['p95', 'Slow tail — the 5% slowest. Most user-facing pain lives here'], ['max_duration', 'The single slowest request in the window — outlier detection'], ['error_count', 'Number of responses with HTTP status 400 or higher'], ].map(([col, desc]) => ( ))}
{h}
{col} {desc}
Rows are sorted by **p95 descending** — the slowest tails surface to the top. ### URL vs Operation Explained The distinction matters when reading the table: - **Operation** is a logical route template — `GET /api/users/:id` is one row in Operations regardless of which user ID was hit. - **URL** is the literal string the SDK saw — `https://api.example.com/users/4710` and `/users/4711` are two different rows here. If your backend has high-cardinality URLs (every user ID is its own row), URL Performance can get noisy fast. That's by design — it tells you *which specific users / records / pages* are slow. For aggregate route-level monitoring, prefer Operations. ### Common Patterns to Watch