Observabilidade no Flutter: um pacote unificado para logs, erros e performance
Observabilidade não é mais um luxo, é um requisito básico para apps Flutter que precisam escalar, diagnosticar problemas rapidamente e entender o comportamento real do usuário. Pensando nisso, criei o Observability Flutter, um pacote que centraliza logs, erros, transações e breadcrumbs em uma API simples e extensível.
👉 Quer testar agora? O pacote já está disponível no pub.dev e pode ser usado em qualquer projeto Flutter:
🔗 https://pub.dev/packages/observability_flutter
Esse é um pacote que eu mesmo desenvolvi e já utilizo em projetos reais. Ele está em constante evolução, e fico totalmente aberto a dicas, sugestões de melhoria, novos providers ou feedbacks de uso no dia a dia
A ideia é clara: escrever observabilidade uma única vez e enviar para quantos providers você quiser.
O problema da observabilidade fragmentada
Em projetos reais, é comum acabar com:
printespalhado pelo código- Logs em um provider
- Erros em outro (Sentry, Firebase, etc.)
- Métricas de performance em um terceiro
Isso gera acoplamento, duplicação de código e dificulta trocar ou adicionar ferramentas no futuro.
O Observability Flutter resolve isso criando uma camada de abstração única, desacoplando sua aplicação das ferramentas de observabilidade.
O que o Observability Flutter entrega
Principais features do pacote:
- 📝 Logs com níveis (
debug,info,warning,error) - 🔴 Captura de erros e exceções
- 📊 Transações para medir performance
- 🍞 Breadcrumbs para rastrear ações do usuário
- 🔌 Suporte a múltiplos providers simultâneos
- 🏭 Registry para auto-discovery de providers
Tudo isso exposto por uma única interface.
Instalação
Adicione ao seu pubspec.yaml:
flutter pub add observability_flutterInicialização básica
Você pode inicializar o manager passando diretamente os providers desejados:
import 'package:observability_flutter/observability_flutter.dart';
await ObservabilityManager.instance.initialize(
providers: [
DebugObservabilityService(),
MyCustomObservabilityService(),
],
);
ObservabilityManager.instance.logMessage('Hello!');
ObservabilityManager.instance.recordError(error, stackTrace: stackTrace);Criando um provider customizado (exemplo real com Sentry)
A seguir, um exemplo real de provider customizado usando Sentry, implementando a interface ObservabilityService.
import 'package:flutter/foundation.dart';
import 'package:observability_flutter/observability_flutter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Implementação de Observabilidade para Sentry
class SentryObservabilityService implements ObservabilityService {
final String dns;
final Map<String, dynamic>? config;
SentryObservabilityService({required this.dns, this.config});
@override
Future<void> initialize() async {
await SentryFlutter.init((options) {
options.dsn = dns;
options.tracesSampleRate = (config?['tracesSampleRate'] ?? 1.0) as double;
options.environment = config?['environment'] as String?;
options.release = config?['release'] as String?;
});
}
@override
void logMessage(
String message, {
LogLevel level = LogLevel.info,
Map<String, dynamic>? data,
String? category,
}) {
Sentry.addBreadcrumb(
Breadcrumb(
message: message,
category: category ?? 'log',
level: _mapLevel(level),
data: data,
),
);
}
@override
void recordError(
dynamic error, {
StackTrace? stackTrace,
Map<String, dynamic>? data,
String? reason,
}) {
Sentry.captureException(
error,
stackTrace: stackTrace,
withScope: (scope) {
if (data != null) {
data.forEach(scope.setExtra);
}
if (reason != null) {
scope.setTag('reason', reason);
}
},
);
}
@override
Future<T> startTransaction<T>(
String name,
Future<T> Function() operation, {
Map<String, dynamic>? data,
}) async {
final transaction = Sentry.startTransaction(name, 'task');
try {
final result = await operation();
transaction.finish(status: const SpanStatus.ok());
return result;
} catch (e, s) {
transaction.throwable = e;
transaction.finish(status: const SpanStatus.internalError());
rethrow;
}
}
@override
void addBreadcrumb(
String message, {
Map<String, dynamic>? data,
String? category,
}) {
Sentry.addBreadcrumb(
Breadcrumb(
message: message,
category: category ?? 'breadcrumb',
data: data,
),
);
}
SentryLevel _mapLevel(LogLevel level) {
switch (level) {
case LogLevel.debug:
return SentryLevel.debug;
case LogLevel.info:
return SentryLevel.info;
case LogLevel.warning:
return SentryLevel.warning;
case LogLevel.error:
return SentryLevel.error;
}
}
}Esse provider pode ser usado isoladamente ou em conjunto com outros (ex: Debug, Firebase, Datadog, etc.).
Benefícios dessa abordagem
- 🔁 Troca de provider sem refatorar a aplicação
- 🧪 Facilidade para mockar observabilidade em testes
- 📦 Providers reutilizáveis entre projetos
- 🧱 Arquitetura limpa e desacoplada
- 🚀 Escala natural conforme o app cresce
Quando usar esse padrão
Esse pacote é ideal para:
- Apps de médio e grande porte
- Projetos white-label
- Apps corporativos
- Times que valorizam Clean Architecture
- Projetos que precisam trocar ferramentas sem dor


