BLoC State Logging: Debugging & Monitoring with BlocObserver
Last Sync: Today
flutter
BLoC State Logging: Debugging & Monitoring with BlocObserver
Introduction
Logging is essential for understanding the behavior of your BLoCs, debugging issues, and monitoring production apps. BLoC provides a built‑in BlocObserver that allows you to intercept events, transitions, and errors across all BLoCs and Cubits in your app. This guide covers how to implement comprehensive logging, integrate with Flutter DevTools, send logs to external services, and follow best practices for production‑ready logging.
Why Log BLoC State?
Debugging – Trace the flow of events and state changes to understand what happened before a bug.
Monitoring – Track performance, error rates, and user behavior in production.
Testing – Validate that events and transitions occur as expected during manual testing.
Documentation – Logs serve as a runtime record of app behavior.
Performance Analysis – Measure how long transitions take (with timestamps).
Setting Up a Custom BlocObserver
The simplest way to start logging is to create a custom BlocObserver that overrides onEvent, onTransition, and onError. Set it globally before running the app.
Use dart:developer's log function to send logs to the DevTools logging view. This allows filtering, searching, and seeing logs alongside other app events.
In production, you may want to send logs to a remote service (e.g., Firebase Crashlytics, Sentry). Use onError to capture unhandled errors and onTransition to log critical state changes.
DARTRead-only
1
classRemoteBlocObserverextendsBlocObserver{
@override
voidonError(BlocBase bloc, Object error, StackTrace stackTrace){// Send to crash reporting service
FirebaseCrashlytics.instance.recordError(error, stackTrace);super.onError(bloc, error, stackTrace);}
@override
voidonTransition(Bloc bloc, Transition transition){// Log important transitions (e.g., login, payment)if(transition.nextState is LoginSuccess || transition.nextState is PaymentComplete){
Analytics.logEvent(name:'state_change',parameters:{'bloc': bloc.runtimeType.toString(),'state': transition.nextState.runtimeType.toString(),},);}super.onTransition(bloc, transition);}}
Logging Specific BLoCs Only
If you want to log only certain BLoCs (e.g., during development of a feature), you can filter inside the observer.
Use BlocObserver to measure how long a transition takes, helping identify performance bottlenecks.
DARTRead-only
1
classPerformanceBlocObserverextendsBlocObserver{
final Stopwatch _stopwatch =Stopwatch();
@override
voidonEvent(Bloc bloc, Object? event){
_stopwatch.reset();
_stopwatch.start();super.onEvent(bloc, event);}
@override
voidonTransition(Bloc bloc, Transition transition){
_stopwatch.stop();print('${bloc.runtimeType} transition for ${transition.event} took ${_stopwatch.elapsedMilliseconds}ms');super.onTransition(bloc, transition);}}
Best Practices
Enable logging only in debug builds – Avoid performance hit and sensitive data leaks in production.
Use structured logging – JSON logs are easier to parse by external tools.
Log at appropriate levels – Use info for normal transitions, warning for recoverable errors, error for exceptions.
Avoid logging sensitive data – Do not log passwords, tokens, or personally identifiable information (PII).
Include timestamps – Essential for debugging race conditions and order of events.
Test observers – Ensure they don’t throw exceptions and handle malformed data gracefully.
Rotate logs or limit size – If writing to file, implement log rotation to avoid disk bloat.
Common Mistakes
❌ Logging everything in production – Can slow down the app and expose internal state.
✅ Use conditional logging or remote logging with sampling.
❌ Forgetting to call super methods – May break internal BLoC functionality.
✅ Always call super.onEvent/onTransition/onError.
❌ Logging huge state objects – Clutters logs and impacts performance.
✅ Log only essential fields or summary.
❌ Not handling errors in observers – Observers themselves can throw, crashing the app.
✅ Wrap logging in try‑catch if necessary.
❌ Using print in production – print is not suitable for production; use proper logging packages.
Conclusion
Effective logging of BLoC events and state transitions is a powerful tool for debugging and monitoring. By leveraging BlocObserver, you can gain full visibility into your app’s state flow. Combine with DevTools, structured logs, and remote services to create a robust observability layer that helps you ship with confidence.
Which class should you extend to globally observe BLoC events and transitions?
A
BlocObserver
B
BlocListener
C
BlocProvider
D
BlocBuilder
Q2
of 3
What method in BlocObserver is called when a state changes?
A
onEvent
B
onTransition
C
onStateChange
D
onEmit
Q3
of 3
How do you enable logging only in development builds?
A
Use conditional import
B
Check kDebugMode before setting Bloc.observer
C
Wrap logs with if (kDebugMode)
D
Both B and C
Frequently Asked Questions
Can I have multiple BlocObserver instances?
No, you can only set one Bloc.observer. If you need multiple observers, you can create a composite observer that delegates to multiple internal observers.
How do I log only specific events or states?
Inside onEvent or onTransition, you can check the type of event or state and conditionally log.
Does BlocObserver work with Cubit?
Yes, BlocObserver also observes Cubit transitions (events are not applicable for Cubits).
How can I send logs to Firebase Crashlytics?
Use FirebaseCrashlytics.instance.log() inside onEvent or onTransition to send custom logs, or recordError for errors.
What's the difference between `onTransition` and `onEvent`?
onEvent is called when an event is added to the BLoC. onTransition is called when the state changes as a result of processing an event (i.e., after mapEventToState yields a new state).