- Privacidade First: Nunca coletar dados pessoais sensíveis sem consentimento
- Eficiência: Logs e métricas devem ter impacto mínimo na performance
- Rastreabilidade: Todas as operações devem ser rastreáveis via correlation IDs
- Conformidade: Respeitar LGPD/GDPR e políticas de privacidade
- Observabilidade Completa: Cobrir todos os aspectos críticos do app
Métricas, Logs e Exceções - Arah Flutter App
Versão: 1.0
Data: 2025-01-20
Status: 📋 Especificação Completa
Tipo: Documentação Técnica de Observabilidade
🎯 Visão Geral
Objetivo
Este documento especifica a estratégia completa de observabilidade para o app Flutter Arah, cobrindo:
- Métricas: Performance, uso, engajamento, negócio
- Logs: Estruturados, categorizados, rastreáveis
- Exceções: Captura, rastreamento, reportagem
Stack Tecnológica
- Métricas: Firebase Analytics, Custom Analytics (mixpanel/amplitude)
- Logs:
loggerpackage + Firebase Crashlytics - Exceções: Sentry + Firebase Crashlytics
- Performance: Firebase Performance Monitoring
- A/B Testing: Firebase Remote Config
🏗️ Arquitetura de Observabilidade
┌─────────────────────────────────────────────────────────┐
│ App Flutter │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Métricas │ │ Logs │ │ Exceções │ │
│ │ Service │ │ Service │ │ Service │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┴──────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Observability │ │
│ │ Manager │ │
│ └─────────┬─────────┘ │
└────────────────────────┼────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│Firebase │ │ Sentry │ │ Custom │
│Services │ │ │ │ Backend│
└─────────┘ └─────────┘ └─────────┘
Componentes Principais
- MetricsService: Coleta e envia métricas
- LoggingService: Registra logs estruturados
- ExceptionService: Captura e reporta exceções
- ObservabilityManager: Coordena todos os serviços
📊 Métricas de Frontend
Categorias de Métricas
Objetivo: Medir performance do app e experiência do usuário
Métricas Principais:
- App Start Time: Tempo de inicialização do app (cold start, warm start)
- Screen Load Time: Tempo de carregamento de telas
- API Response Time: Latência de requisições HTTP
- Image Load Time: Tempo de carregamento de imagens
- Frame Rendering: FPS (Frames Per Second), jank rate
- Memory Usage: Uso de memória (heap, native)
- Battery Usage: Consumo de bateria (estimado)
Implementação:
// Exemplo de métrica de performance
class PerformanceMetrics {
static Future<void> trackScreenLoadTime(String screenName) async {
final stopwatch = Stopwatch()..start();
// Aguarda construção da tela
await Future.delayed(Duration(milliseconds: 100));
stopwatch.stop();
await FirebaseAnalytics.instance.logEvent(
name: 'screen_load_time',
parameters: {
'screen_name': screenName,
'load_time_ms': stopwatch.elapsedMilliseconds,
'platform': Platform.operatingSystem,
},
);
}
static Future<void> trackApiResponseTime(
String endpoint,
int durationMs,
) async {
await FirebaseAnalytics.instance.logEvent(
name: 'api_response_time',
parameters: {
'endpoint': endpoint,
'duration_ms': durationMs,
'platform': Platform.operatingSystem,
},
);
}
}
Objetivo: Entender como usuários interagem com o app
Métricas Principais:
- Screen Views: Telas visualizadas
- User Actions: Ações do usuário (taps, swipes, scrolls)
- Feature Usage: Uso de funcionalidades específicas
- Session Duration: Duração de sessões
- Daily/Monthly Active Users: DAU/MAU
- Retention: Taxa de retenção (D1, D7, D30)
Implementação:
// Exemplo de métrica de uso
class UsageMetrics {
static Future<void> trackScreenView(String screenName) async {
await FirebaseAnalytics.instance.logScreenView(
screenName: screenName,
);
}
static Future<void> trackUserAction(
String action,
Map<String, dynamic>? parameters,
) async {
await FirebaseAnalytics.instance.logEvent(
name: 'user_action',
parameters: {
'action': action,
...?parameters,
},
);
}
static Future<void> trackFeatureUsage(String featureName) async {
await FirebaseAnalytics.instance.logEvent(
name: 'feature_usage',
parameters: {
'feature_name': featureName,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
}
Objetivo: Medir engajamento e participação na comunidade
Métricas Principais:
- Posts Created: Posts criados por usuário
- Events Created: Eventos criados por usuário
- Events Participated: Eventos em que usuário participou
- Comments Made: Comentários feitos
- Likes Given: Likes dados
- Shares Made: Compartilhamentos feitos
- Territory Engagement: Interação com territórios
- Marketplace Usage: Uso do marketplace (compras/vendas)
Implementação:
// Exemplo de métrica de engajamento
class EngagementMetrics {
static Future<void> trackPostCreated({
required String territoryId,
required String postType,
required String visibility,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'post_created',
parameters: {
'territory_id': territoryId,
'post_type': postType, // NOTICE, ALERT, ANNOUNCEMENT
'visibility': visibility, // PUBLIC, RESIDENTS_ONLY
'timestamp': DateTime.now().toIso8601String(),
},
);
}
static Future<void> trackEventParticipated({
required String eventId,
required String territoryId,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'event_participated',
parameters: {
'event_id': eventId,
'territory_id': territoryId,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
static Future<void> trackLikeGiven({
required String targetType, // POST, EVENT, COMMENT
required String targetId,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'like_given',
parameters: {
'target_type': targetType,
'target_id': targetId,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
}
Objetivo: Medir objetivos de negócio e KPIs
Métricas Principais:
- User Registrations: Novos registros
- Membership Requests: Solicitações de residência
- Membership Approvals: Aprovações de residência
- Territory Selections: Seleção de territórios
- Feature Adoption: Adoção de novas funcionalidades
- Conversion Funnels: Funis de conversão
Implementação:
// Exemplo de métrica de negócio
class BusinessMetrics {
static Future<void> trackUserRegistration({
required String authProvider, // GOOGLE, APPLE, EMAIL
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'user_registration',
parameters: {
'auth_provider': authProvider,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
static Future<void> trackMembershipRequest({
required String territoryId,
required bool hasDocument,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'membership_request',
parameters: {
'territory_id': territoryId,
'has_document': hasDocument,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
static Future<void> trackConversion({
required String funnelName,
required String step,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'conversion',
parameters: {
'funnel_name': funnelName,
'step': step,
'timestamp': DateTime.now().toIso8601String(),
},
);
}
}
Objetivo: Medir qualidade do app e frequência de erros
Métricas Principais:
- Crash Rate: Taxa de crashes por sessão
- Error Rate: Taxa de erros (não-crash) por ação
- API Error Rate: Taxa de erros de API
- Validation Error Rate: Taxa de erros de validação
- Network Error Rate: Taxa de erros de rede
Implementação:
// Exemplo de métrica de erro
class ErrorMetrics {
static Future<void> trackError({
required String errorType,
required String errorMessage,
String? stackTrace,
Map<String, dynamic>? context,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'error_occurred',
parameters: {
'error_type': errorType,
'error_message': errorMessage.substring(0, 100), // Limita tamanho
'platform': Platform.operatingSystem,
...?context,
},
);
}
static Future<void> trackApiError({
required String endpoint,
required int statusCode,
String? errorMessage,
}) async {
await FirebaseAnalytics.instance.logEvent(
name: 'api_error',
parameters: {
'endpoint': endpoint,
'status_code': statusCode,
'error_message': errorMessage?.substring(0, 100),
'timestamp': DateTime.now().toIso8601String(),
},
);
}
}
📝 Logs Estruturados
Estrutura de Logs
- TRACE: Logs muito detalhados para debug (desenvolvimento apenas)
- DEBUG: Informações de debug para desenvolvimento
- INFO: Informações gerais sobre operações normais
- WARNING: Avisos sobre situações inesperadas mas não críticas
- ERROR: Erros que não causam crash mas merecem atenção
- FATAL: Erros críticos que podem causar crash
Categorias de Logs
- AUTH: Autenticação e autorização
- API: Requisições HTTP e respostas
- NAVIGATION: Navegação entre telas
- STATE: Gerenciamento de estado (Riverpod)
- STORAGE: Operações de armazenamento local
- NETWORK: Operações de rede
- UI: Interações de interface
- BUSINESS: Eventos de negócio
// Exemplo de serviço de logging estruturado
class LoggingService {
static final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 2,
errorMethodCount: 8,
lineLength: 120,
colors: true,
printEmojis: true,
printTime: true,
),
);
// Correlation ID para rastreabilidade
static String? _correlationId;
static String get correlationId {
_correlationId ??= _generateCorrelationId();
return _correlationId!;
}
static String _generateCorrelationId() {
return '${DateTime.now().millisecondsSinceEpoch}-${Random().nextInt(10000)}';
}
static void resetCorrelationId() {
_correlationId = null;
}
// Log estruturado com categoria
static void log({
required LogLevel level,
required LogCategory category,
required String message,
Map<String, dynamic>? parameters,
Object? error,
StackTrace? stackTrace,
}) {
final logData = {
'correlation_id': correlationId,
'category': category.name,
'message': message,
'timestamp': DateTime.now().toIso8601String(),
'platform': Platform.operatingSystem,
'app_version': _getAppVersion(),
...?parameters,
};
// Log local (console/file)
switch (level) {
case LogLevel.TRACE:
_logger.t(message, error: error, stackTrace: stackTrace);
break;
case LogLevel.DEBUG:
_logger.d(message, error: error, stackTrace: stackTrace);
break;
case LogLevel.INFO:
_logger.i(message, error: error, stackTrace: stackTrace);
break;
case LogLevel.WARNING:
_logger.w(message, error: error, stackTrace: stackTrace);
break;
case LogLevel.ERROR:
_logger.e(message, error: error, stackTrace: stackTrace);
break;
case LogLevel.FATAL:
_logger.f(message, error: error, stackTrace: stackTrace);
break;
}
// Log remoto (Firebase Crashlytics) apenas para WARNING+
if (level.index >= LogLevel.WARNING.index) {
_logToCrashlytics(level, category, message, logData, error, stackTrace);
}
}
static void _logToCrashlytics(
LogLevel level,
LogCategory category,
String message,
Map<String, dynamic> data,
Object? error,
StackTrace? stackTrace,
) async {
try {
await FirebaseCrashlytics.instance.setCustomKey(
'log_category',
category.name,
);
for (final entry in data.entries) {
await FirebaseCrashlytics.instance.setCustomKey(
entry.key,
entry.value.toString(),
);
}
if (level == LogLevel.FATAL || level == LogLevel.ERROR) {
await FirebaseCrashlytics.instance.recordError(
error ?? Exception(message),
stackTrace,
reason: message,
information: data.entries.map((e) => '${e.key}: ${e.value}').toList(),
);
} else {
await FirebaseCrashlytics.instance.log('[$level] [$category] $message');
}
} catch (e) {
// Falha silenciosa para não interferir no app
debugPrint('Failed to log to Crashlytics: $e');
}
}
static String _getAppVersion() {
try {
final packageInfo = PackageInfo.fromPlatform();
return '${packageInfo.version}+${packageInfo.buildNumber}';
} catch (e) {
return 'unknown';
}
}
// Métodos de conveniência por categoria
static void logAuth(String message, {Map<String, dynamic>? params}) {
log(
level: LogLevel.INFO,
category: LogCategory.AUTH,
message: message,
parameters: params,
);
}
static void logApi(String message, {Map<String, dynamic>? params, Object? error, StackTrace? stackTrace}) {
log(
level: error != null ? LogLevel.ERROR : LogLevel.INFO,
category: LogCategory.API,
message: message,
parameters: params,
error: error,
stackTrace: stackTrace,
);
}
static void logNavigation(String screenName, {Map<String, dynamic>? params}) {
log(
level: LogLevel.INFO,
category: LogCategory.NAVIGATION,
message: 'Navigated to $screenName',
parameters: {
'screen_name': screenName,
...?params,
},
);
}
static void logBusiness(String event, {Map<String, dynamic>? params}) {
log(
level: LogLevel.INFO,
category: LogCategory.BUSINESS,
message: event,
parameters: params,
);
}
}
// Enums para níveis e categorias
enum LogLevel {
TRACE,
DEBUG,
INFO,
WARNING,
ERROR,
FATAL,
}
enum LogCategory {
AUTH,
API,
NAVIGATION,
STATE,
STORAGE,
NETWORK,
UI,
BUSINESS,
}
{
"correlation_id": "1705756800000-1234",
"level": "INFO",
"category": "API",
"message": "GET /api/v1/feed completed",
"timestamp": "2025-01-20T10:00:00.000Z",
"platform": "ios",
"app_version": "1.0.0+1",
"parameters": {
"endpoint": "/api/v1/feed",
"method": "GET",
"status_code": 200,
"duration_ms": 245,
"territory_id": "123e4567-e89b-12d3-a456-426614174000"
}
}
🚨 Captura e Rastreamento de Exceções
Tipos de Exceções
Captura: Todas as exceções não tratadas na aplicação
Informações Coletadas:
- Tipo de exceção
- Mensagem de erro
- Stack trace completo
- Contexto da aplicação (tela atual, estado, etc.)
- Correlation ID
- Informações do dispositivo (OS, versão, modelo)
- Informações do app (versão, build)
2. Exceções de API
Captura: Erros HTTP e respostas de erro da API
Informações Coletadas:
- Endpoint e método HTTP
- Status code
- Corpo da resposta de erro
- Headers de requisição (sem dados sensíveis)
- Correlation ID da requisição
3. Exceções de Rede
Captura: Erros de conectividade e timeouts
Informações Coletadas:
- Tipo de erro (timeout, connection refused, etc.)
- Endpoint tentado
- Duração da tentativa
- Estado da conexão (WiFi, celular, sem conexão)
4. Exceções de Validação
Captura: Erros de validação de formulários e dados
Informações Coletadas:
- Campo validado
- Regra de validação violada
- Valor fornecido (hasheado se sensível)
- Formulário/tela
// Exemplo de serviço de exceções
class ExceptionService {
static Future<void> initialize() async {
// Configurar Sentry
await Sentry.init(
(options) {
options.dsn = Config.sentryDsn;
options.environment = Config.environment; // dev, staging, prod
options.release = await _getAppVersion();
options.tracesSampleRate = Config.environment == 'prod' ? 0.1 : 1.0;
options.enableAutoSessionTracking = true;
options.beforeSend = _beforeSend;
options.maxBreadcrumbs = 100;
},
appRunner: () => runApp(MyApp()),
);
// Configurar Firebase Crashlytics
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(
Config.environment == 'prod',
);
// Capturar erros não tratados do Flutter
FlutterError.onError = (FlutterErrorDetails details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
Sentry.captureException(
details.exception,
stackTrace: details.stack,
hint: Hint.withMap({'details': details.toString()}),
);
};
// Capturar erros não tratados do Zone
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(
error,
stack,
fatal: true,
);
Sentry.captureException(
error,
stackTrace: stack,
);
return true;
};
}
static BeforeSendCallback _beforeSend = (event, {hint}) {
// Filtrar dados sensíveis antes de enviar
if (event.request?.data != null) {
event.request?.data = _sanitizeData(event.request!.data);
}
// Filtrar breadcrumbs sensíveis
event.breadcrumbs?.removeWhere((b) => _isSensitive(b.message ?? ''));
return event;
};
static dynamic _sanitizeData(dynamic data) {
if (data is Map) {
return data.map((key, value) {
if (_isSensitive(key.toString())) {
return MapEntry(key, '[REDACTED]');
}
return MapEntry(key, _sanitizeData(value));
});
}
return data;
}
static bool _isSensitive(String text) {
final sensitivePatterns = [
'password',
'token',
'secret',
'authorization',
'cookie',
'session',
];
return sensitivePatterns.any((pattern) =>
text.toLowerCase().contains(pattern),
);
}
static Future<String> _getAppVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
return '${packageInfo.version}+${packageInfo.buildNumber}';
} catch (e) {
return 'unknown';
}
}
// Capturar exceção manualmente
static Future<void> captureException(
Object exception, {
StackTrace? stackTrace,
Map<String, dynamic>? context,
String? level, // fatal, error, warning, info, debug
bool fatal = false,
}) async {
try {
// Adicionar contexto
if (context != null) {
await FirebaseCrashlytics.instance.setCustomKeys(context);
await Sentry.configureScope((scope) {
context.forEach((key, value) {
scope.setContext(key, {'value': value.toString()});
});
});
}
// Adicionar correlation ID
await FirebaseCrashlytics.instance.setCustomKey(
'correlation_id',
LoggingService.correlationId,
);
// Capturar exceção
if (fatal) {
await FirebaseCrashlytics.instance.recordError(
exception,
stackTrace,
fatal: true,
);
await Sentry.captureException(
exception,
stackTrace: stackTrace,
hint: Hint.withMap({'level': 'fatal'}),
);
} else {
await FirebaseCrashlytics.instance.recordError(
exception,
stackTrace,
);
await Sentry.captureException(
exception,
stackTrace: stackTrace,
hint: Hint.withMap({'level': level ?? 'error'}),
);
}
} catch (e) {
// Falha silenciosa
debugPrint('Failed to capture exception: $e');
}
}
// Capturar exceção de API
static Future<void> captureApiException({
required String endpoint,
required int statusCode,
required String method,
String? responseBody,
Map<String, String>? requestHeaders,
}) async {
await captureException(
Exception('API Error: $method $endpoint - $statusCode'),
context: {
'endpoint': endpoint,
'method': method,
'status_code': statusCode,
'response_body': responseBody?.substring(0, 500) ?? 'null',
'request_headers': _sanitizeHeaders(requestHeaders ?? {}),
},
level: statusCode >= 500 ? 'error' : 'warning',
fatal: statusCode >= 500,
);
}
static Map<String, String> _sanitizeHeaders(Map<String, String> headers) {
final sensitiveHeaders = ['authorization', 'cookie', 'x-api-key'];
return headers.map((key, value) {
if (sensitiveHeaders.contains(key.toLowerCase())) {
return MapEntry(key, '[REDACTED]');
}
return MapEntry(key, value);
});
}
// Adicionar breadcrumb (contexto de ações)
static Future<void> addBreadcrumb({
required String message,
String? category,
Map<String, dynamic>? data,
}) async {
try {
await Sentry.addBreadcrumb(
Breadcrumb(
message: message,
category: category,
data: data,
level: SentryLevel.info,
timestamp: DateTime.now(),
),
);
await FirebaseCrashlytics.instance.log(
'[$category] $message',
);
} catch (e) {
debugPrint('Failed to add breadcrumb: $e');
}
}
// Definir usuário para rastreamento
static Future<void> setUser({
String? userId,
String? email,
String? username,
Map<String, dynamic>? additionalData,
}) async {
try {
await Sentry.configureScope((scope) {
scope.setUser(
SentryUser(
id: userId,
email: email,
username: username,
data: additionalData,
),
);
});
await FirebaseCrashlytics.instance.setUserIdentifier(userId ?? 'anonymous');
if (email != null) {
await FirebaseCrashlytics.instance.setCustomKey('user_email', email);
}
} catch (e) {
debugPrint('Failed to set user: $e');
}
}
}
🔌 Integração com Serviços Externos
Firebase Services
Firebase Analytics
Configuração:
// main.dart
await Firebase.initializeApp();
// Habilitar/desabilitar analytics baseado em consentimento
await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(
await ConsentService.hasAnalyticsConsent(),
);
Uso:
// Já documentado em Métricas de Frontend
Firebase Crashlytics
Configuração:
// main.dart
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
Uso:
// Já documentado em Logs Estruturados e Exceções
Configuração:
// main.dart
await FirebasePerformance.instance.setPerformanceCollectionEnabled(true);
Uso:
// Rastrear performance de operações
class PerformanceTracking {
static Future<T> trackOperation<T>(
String operationName,
Future<T> Function() operation,
) async {
final trace = FirebasePerformance.instance.newTrace(operationName);
await trace.start();
try {
final result = await operation();
await trace.stop();
return result;
} catch (e) {
trace.putAttribute('error', e.toString());
await trace.stop();
rethrow;
}
}
// Uso
static Future<List<Post>> loadFeed() async {
return trackOperation('load_feed', () async {
// Lógica de carregamento
return await apiService.getFeed();
});
}
}
Sentry
Configuração:
// Já documentado em ExceptionService.initialize()
Uso:
// Já documentado em ExceptionService
Endpoint de Métricas:
POST /api/v1/metrics
Payload:
{
"metric_name": "screen_view",
"value": 1,
"timestamp": "2025-01-20T10:00:00.000Z",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"parameters": {
"screen_name": "FeedScreen",
"territory_id": "..."
}
}
🔒 Privacidade e Conformidade
LGPD/GDPR Compliance
Implementação:
class ConsentService {
static Future<bool> hasAnalyticsConsent() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('analytics_consent') ?? false;
}
static Future<void> setAnalyticsConsent(bool consent) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('analytics_consent', consent);
// Atualizar estado do Firebase Analytics
await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(consent);
}
static Future<bool> hasCrashlyticsConsent() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('crashlytics_consent') ?? false;
}
static Future<void> setCrashlyticsConsent(bool consent) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('crashlytics_consent', consent);
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(consent);
}
}
Políticas:
-
Nunca coletar:
- Senhas
- Tokens de autenticação completos
- Informações de pagamento completas
- Dados biométricos
-
Anonimizar/Hashear:
- Emails (usar hash)
- IDs de usuário (usar hash ou pseudonimizar)
- Endereços completos (usar apenas região/território)
-
Sanitizar antes de enviar:
- Logs
- Exceções
- Métricas
Implementação:
class PrivacyService {
static String hashEmail(String email) {
return sha256.convert(utf8.encode(email.toLowerCase())).toString();
}
static String hashUserId(String userId) {
return sha256.convert(utf8.encode(userId)).toString();
}
static Map<String, dynamic> sanitizeParameters(Map<String, dynamic> params) {
final sensitiveKeys = [
'password',
'token',
'secret',
'authorization',
'email',
'phone',
'cpf',
'address',
];
return params.map((key, value) {
if (sensitiveKeys.any((sensitive) =>
key.toLowerCase().contains(sensitive.toLowerCase()),
)) {
return MapEntry(key, '[REDACTED]');
}
return MapEntry(key, value);
});
}
}
Retenção de Dados
Políticas:
- Logs: Retenção de 30 dias
- Métricas: Retenção de 1 ano
- Exceções: Retenção de 90 dias
- Dados de usuário: Deletar quando usuário excluir conta
📈 Estratégias de Análise e Alertas
Dashboards Recomendados
Dashboard de Performance
- App Start Time (média, P95, P99)
- Screen Load Time por tela
- API Response Time por endpoint
- Frame Rendering (FPS, jank rate)
- Memory Usage
Dashboard de Uso
- Daily/Monthly Active Users
- Screen Views por tela
- Feature Usage
- Session Duration
- Retention (D1, D7, D30)
Dashboard de Engajamento
- Posts Created por dia/semana
- Events Created/Participated
- Likes/Comments/Shares
- Territory Engagement
- Marketplace Usage
Dashboard de Erros
- Crash Rate por versão
- Error Rate por tipo
- API Error Rate por endpoint
- Top Errors
- Error Trends
Alertas Recomendados
Alertas Críticos (Pager)
- Crash Rate > 1%: Crash rate acima de 1% em 5 minutos
- Error Rate > 5%: Error rate acima de 5% em 5 minutos
- API Error Rate > 10%: API error rate acima de 10% em 5 minutos
- App Start Time > 3s: App start time acima de 3s (P95)
- Screen Load Time > 2s: Screen load time acima de 2s (P95)
- API Response Time > 1s: API response time acima de 1s (P95)
- Memory Usage > 200MB: Memory usage acima de 200MB
- Frame Rendering < 50 FPS: Frame rendering abaixo de 50 FPS
🛠️ Implementação e Configuração
# pubspec.yaml
dependencies:
firebase_core: ^2.24.2
firebase_analytics: ^10.7.4
firebase_crashlytics: ^3.4.9
firebase_performance: ^0.9.3+4
sentry_flutter: ^7.15.0
logger: ^2.0.2
crypto: ^3.0.3
package_info_plus: ^5.0.1
dev_dependencies:
firebase_core_platform_interface: ^4.8.0
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Inicializar Firebase
await Firebase.initializeApp();
// Inicializar observabilidade
await ExceptionService.initialize();
await MetricsService.initialize();
await LoggingService.initialize();
// Verificar consentimento
await ConsentService.checkAndRequestConsent();
runApp(MyApp());
}
lib/
├── services/
│ ├── observability/
│ │ ├── exception_service.dart
│ │ ├── logging_service.dart
│ │ ├── metrics_service.dart
│ │ ├── performance_tracking.dart
│ │ └── observability_manager.dart
│ └── privacy/
│ ├── consent_service.dart
│ └── privacy_service.dart
├── models/
│ └── observability/
│ ├── log_level.dart
│ ├── log_category.dart
│ └── metric_event.dart
└── utils/
└── sanitizers/
└── data_sanitizer.dart
✅ Boas Práticas e Recomendações
Métricas
- Não coletar métricas excessivas: Focar apenas no que é útil
- Usar batch de métricas: Agrupar múltiplas métricas antes de enviar
- Definir limites: Limitar tamanho de parâmetros e número de eventos
- Validar métricas: Garantir que métricas são válidas antes de enviar
Logs
- Usar níveis apropriados: Não usar ERROR para INFO
- Incluir contexto: Sempre incluir contexto relevante
- Evitar logs sensíveis: Nunca logar senhas, tokens, etc.
- Usar correlation IDs: Para rastrear operações relacionadas
- Limitar verbosidade: Logs DEBUG apenas em desenvolvimento
- Capturar apenas o necessário: Não capturar exceções que não são úteis
- Adicionar contexto: Sempre adicionar contexto relevante
- Sanitizar dados: Remover dados sensíveis antes de enviar
- Agrupar exceções similares: Para evitar spam de exceções
- Priorizar exceções críticas: Fatal > Error > Warning
Performance
- Operações assíncronas: Logs e métricas devem ser não-bloqueantes
- Batch de envio: Agrupar múltiplos eventos antes de enviar
- Retry com backoff: Retry exponencial para falhas de rede
- Cache local: Cachear eventos localmente antes de enviar
- Consentimento explícito: Sempre pedir consentimento antes de coletar
- Anonimização: Anonimizar dados sensíveis
- Minimização: Coletar apenas o mínimo necessário
- Transparência: Informar usuário sobre coleta de dados
- Deleção: Deletar dados quando usuário excluir conta
Versão: 1.0
Última Atualização: 2025-01-20
Autor: Sistema de Documentação Arah
📚 Referências Relacionadas
- Estratégia de Testes - Como testar métricas, logs e exceções
- Guia de Acessibilidade - Acessibilidade de componentes de logging/erro
- Planejamento do Frontend Flutter - Arquitetura completa do app