Global Observability & Error Reporting

Branches: feat/global-observability (commits merged directly to develop) Date: March 2026

Overview

Implemented a comprehensive, always-on error observability layer for the entire app. This includes a GlobalProviderObserver that intercepts Riverpod state errors, expanded service-level try-catch blocks that report errors to Supabase, and an opt-in model that lets users control whether automatic crash reports are sent.

This replaced ad-hoc print() statements and silent catch blocks with a unified reporting pipeline: every error flows through LogServiceErrorNotifier → Supabase app_issues table.

Why It Was Built

Without centralized observability, silent errors in services went unnoticed until users complained. The app_issues table existed but was only populated by the manual error dialog. Automatic background reporting was needed to surface issues in production without requiring user interaction on every error.

What Changed

core/observers/provider_observer.dart (new)

Added GlobalProviderObserver extends ProviderObserver:

class GlobalProviderObserver extends ProviderObserver {
  @override
  void didAddProvider(ProviderBase provider, Object? value, ProviderContainer container) { ... }
 
  @override
  void providerDidFail(ProviderBase provider, Object error, StackTrace stackTrace, ProviderContainer container) {
    LogService.instance.log('Provider failed: ${provider.name}', error, stackTrace);
    IssueReportService().reportIssue(
      errorMessage: '[Provider] ${provider.name}: $error',
      stackTrace: stackTrace.toString(),
    );
  }
}

Wired in main.dart:

final container = ProviderContainer(observers: [GlobalProviderObserver()]);

Any Riverpod provider that throws during its build or async computation is automatically reported.

Service-Level Reporting

The following services were updated to include LogService.instance.reportError(e, stack) in their catch blocks:

ServiceWhat gets reported
auth_service.dartLogin, registration, session refresh failures
gamification_service.dartXP update and badge award errors
crew_service.dartCrew creation, join, leave failures
feedback_service.dartFeedback submission errors
pr_service.dartPR dashboard data fetch failures
review_service.dartReview creation and fetch errors
ticket_service.dartTicket purchase and wallet sync failures

main.dart — Global Error Handlers

FlutterError.onError = (details) {
  container.read(errorNotifierProvider.notifier).reportError(
    details.exception, details.stack,
  );
};
 
PlatformDispatcher.instance.onError = (error, stack) {
  container.read(errorNotifierProvider.notifier).reportError(error, stack);
  return true;
};

Both handlers forward to ErrorNotifier, which checks the user’s crash reporting preference before sending to Supabase.

error_notifier.dart — Automatic Proactive Reporting

ErrorNotifier.reportError() now:

  1. Updates the UI state (shows the error dialog if visible)
  2. Calls IssueReportService().reportIssue() automatically in the background

The IssueReportService.reportIssue() checks SharedPreferences['feature_crashReporting'] before sending, respecting the user’s opt-in choice.

Opt-In Privacy Model (feat/privacy commits)

Changed crash reporting from always-on to opt-in:

  • Default: false (no reports sent without consent)
  • Exception: developer accounts (e.g., elia-c@outlook.it) default to true
  • Toggle in Settings → Privacy → Segnalazione Errori

Architecture Notes

The reporting pipeline has two paths:

User sees error dialog → taps "Invia Report"
  → IssueReportService.reportIssue(force: true)   ← always sends

Any error in FlutterError / PlatformDispatcher / Provider failure
  → ErrorNotifier.reportError()
  → IssueReportService.reportIssue()              ← checks preference first

The force: true flag on manual submissions bypasses the preference check, because the user explicitly clicked “send”. Automatic background reports are gated by the privacy setting.

Database

Reports are stored in Supabase:

ColumnTypeDescription
iduuidPrimary key
user_iduuidAuthenticated user (nullable for unauthenticated crashes)
error_messagetextCleaned error string
stack_tracetextFull stack trace
user_descriptiontextUser-provided context (manual reports only)
device_infojsonbPlatform, OS version, browser/device model
app_versiontext1.0.0+1 format
statustextnew, in_progress, resolved
created_attimestamptzAuto-set