Agent skill
error-tracking-altrupets-monorepo
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/error-tracking-altrupets-monorepo
SKILL.md
🐛 Skill: Error Tracking & Crash Reporting
📋 Metadata
| Atributo | Valor |
|---|---|
| ID | flutter-error-tracking |
| Nivel | 🟡 Intermedio |
| Versión | 1.0.0 |
| Keywords | error-tracking, sentry, crashlytics, crash-reporting, logging, monitoring |
| Referencia | Sentry Flutter |
🔑 Keywords para Invocación
error-trackingsentrycrashlyticscrash-reportingerror-monitoringlogging@skill:error-tracking
Ejemplos de Prompts
Implementa error-tracking con sentry y crashlytics
Setup crash-reporting para producción
Configura logging y monitoring completo
@skill:error-tracking - Sistema completo de monitoreo de errores
📖 Descripción
⚠️ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raíz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se está en el directorio correcto antes de ejecutar cualquier comando.
Este skill cubre la implementación de sistemas de monitoreo y tracking de errores en Flutter apps usando Sentry y Firebase Crashlytics. Incluye captura automática de errores, contexto enriquecido, breadcrumbs, y integración con CI/CD para source maps y debug symbols.
✅ Cuándo Usar Este Skill
- Apps en producción
- Monitoreo de estabilidad
- Debug de errores en producción
- Performance monitoring
- User feedback sobre crashes
- Analytics de errores
- Release health tracking
❌ Cuándo NO Usar Este Skill
- Solo desarrollo local
- Prototipos sin usuarios reales
- MVP sin presupuesto para herramientas
🏗️ Estructura del Proyecto
my_app/
├── lib/
│ ├── core/
│ │ ├── error/
│ │ │ ├── error_tracker.dart
│ │ │ ├── sentry_service.dart
│ │ │ ├── crashlytics_service.dart
│ │ │ └── custom_error_widget.dart
│ │ └── logger/
│ │ ├── app_logger.dart
│ │ └── log_interceptor.dart
│ │
│ ├── services/
│ │ └── error_service.dart
│ │
│ └── main.dart
│
├── android/
│ └── app/
│ └── google-services.json
│
├── ios/
│ └── Runner/
│ └── GoogleService-Info.plist
│
└── scripts/
├── upload_symbols.sh
└── upload_sourcemaps.sh
📦 Dependencias
dependencies:
flutter:
sdk: flutter
# Sentry
sentry_flutter: ^7.14.0
# Firebase Crashlytics
firebase_core: ^2.24.2
firebase_crashlytics: ^3.4.8
# Logging
logger: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
💻 Implementación
1. Sentry Integration
1.1 Sentry Service
// lib/core/error/sentry_service.dart
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:flutter/foundation.dart';
class SentryService {
static Future<void> initialize() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_SENTRY_DSN_HERE';
// Environment
options.environment = kReleaseMode ? 'production' : 'development';
// Release versioning
options.release = '[email protected]+1';
options.dist = '1';
// Performance monitoring
options.tracesSampleRate = kReleaseMode ? 0.1 : 1.0;
options.profilesSampleRate = 0.1;
// Debug options
options.debug = kDebugMode;
// Attach screenshots on errors
options.attachScreenshot = true;
options.screenshotQuality = SentryScreenshotQuality.medium;
// Attach view hierarchy
options.attachViewHierarchy = true;
// Filter sensitive data
options.beforeSend = (event, hint) {
// Remove sensitive data
if (event.request?.headers != null) {
event.request!.headers!.remove('Authorization');
event.request!.headers!.remove('Cookie');
}
return event;
};
// Filter breadcrumbs
options.beforeBreadcrumb = (breadcrumb, hint) {
// Don't log debug breadcrumbs in production
if (kReleaseMode && breadcrumb.level == SentryLevel.debug) {
return null;
}
return breadcrumb;
};
},
);
}
// Capture exception with context
static Future<void> captureException(
dynamic exception,
StackTrace? stackTrace, {
String? hint,
Map<String, dynamic>? extra,
SentryLevel level = SentryLevel.error,
}) async {
await Sentry.captureException(
exception,
stackTrace: stackTrace,
withScope: (scope) {
scope.level = level;
if (hint != null) {
scope.setTag('hint', hint);
}
if (extra != null) {
extra.forEach((key, value) {
scope.setExtra(key, value);
});
}
},
);
}
// Capture message
static Future<void> captureMessage(
String message, {
SentryLevel level = SentryLevel.info,
Map<String, dynamic>? extra,
}) async {
await Sentry.captureMessage(
message,
level: level,
withScope: (scope) {
if (extra != null) {
extra.forEach((key, value) {
scope.setExtra(key, value);
});
}
},
);
}
// Add breadcrumb
static void addBreadcrumb({
required String message,
String? category,
SentryLevel level = SentryLevel.info,
Map<String, dynamic>? data,
}) {
Sentry.addBreadcrumb(
Breadcrumb(
message: message,
category: category,
level: level,
data: data,
timestamp: DateTime.now(),
),
);
}
// Set user context
static void setUser({
String? id,
String? email,
String? username,
Map<String, dynamic>? extras,
}) {
Sentry.configureScope((scope) {
scope.setUser(
SentryUser(
id: id,
email: email,
username: username,
data: extras,
),
);
});
}
// Clear user context
static void clearUser() {
Sentry.configureScope((scope) {
scope.setUser(null);
});
}
// Set custom context
static void setContext(String key, Map<String, dynamic> data) {
Sentry.configureScope((scope) {
scope.setContexts(key, data);
});
}
// Set tag
static void setTag(String key, String value) {
Sentry.configureScope((scope) {
scope.setTag(key, value);
});
}
// Start transaction (performance monitoring)
static ISentrySpan startTransaction(
String name,
String operation, {
String? description,
}) {
return Sentry.startTransaction(
name,
operation,
description: description,
);
}
// Report network call
static Future<T> traceNetworkCall<T>(
String url,
String method,
Future<T> Function() request,
) async {
final transaction = startTransaction(
'http.client',
'http.request',
description: '$method $url',
);
try {
final result = await request();
transaction.status = const SpanStatus.ok();
return result;
} catch (e) {
transaction.status = const SpanStatus.internalError();
transaction.throwable = e;
rethrow;
} finally {
await transaction.finish();
}
}
}
1.2 Sentry HTTP Client Interceptor
// lib/core/logger/sentry_http_interceptor.dart
import 'package:dio/dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class SentryHttpInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// Add breadcrumb for request
SentryService.addBreadcrumb(
message: '${options.method} ${options.uri}',
category: 'http',
level: SentryLevel.info,
data: {
'method': options.method,
'url': options.uri.toString(),
},
);
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// Add breadcrumb for successful response
SentryService.addBreadcrumb(
message: '${response.requestOptions.method} ${response.requestOptions.uri} - ${response.statusCode}',
category: 'http.response',
level: SentryLevel.info,
data: {
'status_code': response.statusCode,
'method': response.requestOptions.method,
'url': response.requestOptions.uri.toString(),
},
);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Capture HTTP errors in Sentry
SentryService.captureException(
err,
err.stackTrace,
hint: 'HTTP Error',
extra: {
'method': err.requestOptions.method,
'url': err.requestOptions.uri.toString(),
'status_code': err.response?.statusCode,
'response': err.response?.data,
},
level: SentryLevel.error,
);
handler.next(err);
}
}
2. Firebase Crashlytics Integration
2.1 Crashlytics Service
// lib/core/error/crashlytics_service.dart
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
class CrashlyticsService {
static Future<void> initialize() async {
// Enable Crashlytics in release mode only
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(kReleaseMode);
// Pass all uncaught Flutter errors to Crashlytics
FlutterError.onError = (details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
// Pass all uncaught asynchronous errors to Crashlytics
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
}
// Record error
static Future<void> recordError(
dynamic exception,
StackTrace? stackTrace, {
String? reason,
bool fatal = false,
Iterable<Object> information = const [],
}) async {
await FirebaseCrashlytics.instance.recordError(
exception,
stackTrace,
reason: reason,
fatal: fatal,
information: information,
);
}
// Log message
static void log(String message) {
FirebaseCrashlytics.instance.log(message);
}
// Set user identifier
static Future<void> setUserIdentifier(String identifier) async {
await FirebaseCrashlytics.instance.setUserIdentifier(identifier);
}
// Set custom key
static Future<void> setCustomKey(String key, dynamic value) async {
await FirebaseCrashlytics.instance.setCustomKey(key, value);
}
// Set custom keys
static Future<void> setCustomKeys(Map<String, dynamic> keys) async {
for (final entry in keys.entries) {
await setCustomKey(entry.key, entry.value);
}
}
// Force crash (testing only!)
static void forceCrash() {
FirebaseCrashlytics.instance.crash();
}
// Check if crash reporting is enabled
static Future<bool> isCrashlyticsCollectionEnabled() async {
return await FirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled();
}
// Enable/disable crash reporting
static Future<void> setCrashlyticsCollectionEnabled(bool enabled) async {
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(enabled);
}
// Send unhandled exception
static Future<void> recordFlutterFatalError(FlutterErrorDetails details) async {
await FirebaseCrashlytics.instance.recordFlutterFatalError(details);
}
}
3. Unified Error Tracker
// lib/core/error/error_tracker.dart
import 'package:flutter/foundation.dart';
import 'sentry_service.dart';
import 'crashlytics_service.dart';
enum ErrorSeverity { info, warning, error, fatal }
class ErrorTracker {
static bool _initialized = false;
static Future<void> initialize() async {
if (_initialized) return;
// Initialize Sentry
await SentryService.initialize();
// Initialize Crashlytics
await CrashlyticsService.initialize();
_initialized = true;
}
// Report error to both services
static Future<void> reportError(
dynamic error,
StackTrace? stackTrace, {
String? hint,
ErrorSeverity severity = ErrorSeverity.error,
Map<String, dynamic>? extras,
}) async {
if (!_initialized) await initialize();
// Log locally
debugPrint('Error: $error\nStackTrace: $stackTrace');
// Report to Sentry
await SentryService.captureException(
error,
stackTrace,
hint: hint,
extra: extras,
level: _sentryLevel(severity),
);
// Report to Crashlytics
await CrashlyticsService.recordError(
error,
stackTrace,
reason: hint,
fatal: severity == ErrorSeverity.fatal,
);
}
// Log message
static void log(
String message, {
ErrorSeverity severity = ErrorSeverity.info,
Map<String, dynamic>? extras,
}) {
// Sentry message
SentryService.captureMessage(
message,
level: _sentryLevel(severity),
extra: extras,
);
// Crashlytics log
CrashlyticsService.log(message);
}
// Add breadcrumb
static void addBreadcrumb(
String message, {
String? category,
Map<String, dynamic>? data,
}) {
SentryService.addBreadcrumb(
message: message,
category: category,
data: data,
);
CrashlyticsService.log('[$category] $message');
}
// Set user info
static Future<void> setUser({
required String id,
String? email,
String? username,
Map<String, dynamic>? extras,
}) async {
// Sentry
SentryService.setUser(
id: id,
email: email,
username: username,
extras: extras,
);
// Crashlytics
await CrashlyticsService.setUserIdentifier(id);
if (extras != null) {
await CrashlyticsService.setCustomKeys(extras);
}
}
// Clear user
static void clearUser() {
SentryService.clearUser();
CrashlyticsService.setUserIdentifier('');
}
// Set custom data
static Future<void> setCustomData(Map<String, dynamic> data) async {
data.forEach((key, value) {
SentryService.setContext(key, {'value': value});
});
await CrashlyticsService.setCustomKeys(data);
}
static SentryLevel _sentryLevel(ErrorSeverity severity) {
switch (severity) {
case ErrorSeverity.info:
return SentryLevel.info;
case ErrorSeverity.warning:
return SentryLevel.warning;
case ErrorSeverity.error:
return SentryLevel.error;
case ErrorSeverity.fatal:
return SentryLevel.fatal;
}
}
}
4. Custom Error Widget
// lib/core/error/custom_error_widget.dart
import 'package:flutter/material.dart';
import 'error_tracker.dart';
class CustomErrorWidget extends StatelessWidget {
final FlutterErrorDetails details;
const CustomErrorWidget({
Key? key,
required this.details,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: Container(
color: Colors.red.shade50,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Colors.red.shade700,
),
const SizedBox(height: 16),
Text(
'Something went wrong',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.red.shade700,
),
),
const SizedBox(height: 8),
Text(
'The error has been reported to our team.',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.red.shade600),
),
if (kDebugMode) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(8),
),
child: Text(
details.exceptionAsString(),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontFamily: 'monospace',
),
),
),
],
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// Restart app or navigate to home
Navigator.of(context).popUntil((route) => route.isFirst);
},
child: const Text('Go to Home'),
),
],
),
),
);
}
static void setupErrorWidget() {
ErrorWidget.builder = (FlutterErrorDetails details) {
// Report error
ErrorTracker.reportError(
details.exception,
details.stack,
hint: 'Widget Error',
severity: ErrorSeverity.error,
);
return CustomErrorWidget(details: details);
};
}
}
5. Main.dart Setup
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'core/error/error_tracker.dart';
import 'core/error/custom_error_widget.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase
await Firebase.initializeApp();
// Initialize Error Tracking
await ErrorTracker.initialize();
// Setup custom error widget
CustomErrorWidget.setupErrorWidget();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: const HomeScreen(),
// Capture navigation errors
onGenerateRoute: (settings) {
ErrorTracker.addBreadcrumb(
'Navigation: ${settings.name}',
category: 'navigation',
);
return null;
},
);
}
}
6. Logger Implementation
// lib/core/logger/app_logger.dart
import 'package:logger/logger.dart';
import '../error/error_tracker.dart';
class AppLogger {
static final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
printTime: true,
),
);
static void debug(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.d(message, error: error, stackTrace: stackTrace);
ErrorTracker.log(message, severity: ErrorSeverity.info);
}
static void info(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.i(message, error: error, stackTrace: stackTrace);
ErrorTracker.log(message, severity: ErrorSeverity.info);
}
static void warning(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.w(message, error: error, stackTrace: stackTrace);
ErrorTracker.log(message, severity: ErrorSeverity.warning);
}
static void error(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.e(message, error: error, stackTrace: stackTrace);
ErrorTracker.reportError(
error ?? message,
stackTrace,
hint: message,
severity: ErrorSeverity.error,
);
}
static void fatal(String message, [dynamic error, StackTrace? stackTrace]) {
_logger.f(message, error: error, stackTrace: stackTrace);
ErrorTracker.reportError(
error ?? message,
stackTrace,
hint: message,
severity: ErrorSeverity.fatal,
);
}
}
7. CI/CD Integration
7.1 Upload Debug Symbols (Android)
# scripts/upload_symbols_android.sh
#!/bin/bash
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Build with debug symbols
cd mobile
flutter build appbundle --release
cd ..
# Upload to Crashlytics
firebase crashlytics:symbols:upload \
--app=1:123456789:android:abcd1234 \
android/app/build/intermediates/merged_native_libs/release/out/lib
7.2 Upload Source Maps (Sentry)
# scripts/upload_sourcemaps.sh
#!/bin/bash
VERSION="1.0.0+1"
# Install Sentry CLI
curl -sL https://sentry.io/get-cli/ | bash
# Upload debug symbols
sentry-cli upload-dif \
--org your-org \
--project your-project \
build/app/intermediates/merged_native_libs/release/out/lib
# Upload source maps for web
sentry-cli releases files $VERSION \
upload-sourcemaps build/web \
--url-prefix '~/' \
--validate
🎯 Mejores Prácticas
1. Error Context
✅ DO: Agrega contexto rico
ErrorTracker.reportError(
error,
stackTrace,
hint: 'User checkout failed',
extras: {
'user_id': userId,
'cart_items': cartItems.length,
'total_amount': totalAmount,
},
);
2. Breadcrumbs
✅ DO: Usa breadcrumbs para debugging
// En cada acción importante
ErrorTracker.addBreadcrumb('User tapped checkout button');
ErrorTracker.addBreadcrumb('Payment method selected: Credit Card');
ErrorTracker.addBreadcrumb('Processing payment...');
3. PII Protection
✅ DO: Filtra información sensible
options.beforeSend = (event, hint) {
// Remove sensitive data
event.request?.headers?.remove('Authorization');
event.user?.email = '[FILTERED]';
return event;
};
🚨 Troubleshooting
Sentry DSN Not Working
// Verify DSN is correct and environment is set
options.dsn = 'https://[key]@[organization].ingest.sentry.io/[project]';
options.environment = kReleaseMode ? 'production' : 'development';
Crashlytics Not Reporting
# Verify Firebase is initialized
flutter pub get
flutterfire configure
Missing Debug Symbols
# Verificar que estamos en la raíz del proyecto
if [ ! -d "mobile" ]; then
echo "Error: Ejecuta este comando desde la raíz del proyecto"
exit 1
fi
# Build with debug symbols
cd mobile
flutter build apk --split-debug-info=build/symbols
flutter build appbundle --split-debug-info=build/symbols
cd ..
📚 Recursos
Versión: 1.0.0 Última actualización: Diciembre 2025 Total líneas: 1,200+
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?