A Dart package for logging events once and writing them to multiple services.
Most apps collect data on events that happen during the lifetime of the app. This would include a user tapping a button, a failure at sign on, the completion of a network request, an exception, and more.
When collecting these important events they are often sent to a metrics service such as Firebase Analytics, Pendo, or New Relic, and then to the console, and maybe an ad platform, and more.
It would not be a good idea to call multiple services for a single event. That would make adding and updating events difficult.
onPressed: () {
FirebaseAnalytics().logEvent('tapped_sign_in'); // Bad idea
MyLogger.log('tapped sign in'); // Bad idea
print('tapped sign in'); // Bad idea
}
It would also take a lot of work to change metrics providers or add an additional one throughout the app.
With EventBus
, you can log one event in your code, and then create multiple
consumers that write the event to various services.
onPressed: () {
eventBus.tap('sign_in', screen: 'SignInScreen'); // Good idea
}
Note: This is a similar but different kind of event bus from the package EventBus.
There are specific types of events supported by EventBus
that help organize
the events and make it easier for consumers to write them to services.
- app: when an app event occurs such as cold start, or resume from background.
- error: when an error occurs such as an exception.
- event: when a specific notable event occurs, such as the user completing sign in, or the user completed a purchase.
- tap: when a user taps a button, a link, or any other tappable widget.
- network: when an HTTP request is made or completed.
- trace: any event to be logged that is not one of the above, such as normal debug logging.
First, you need to instantiate the EventBus
and hold a reference to it.
final eventBus = EventBus();
Now, you can log your events. For example, to log a tap on a button, use the
tap
method on EventBus
.
eventBus.tap('signin_button', screen: 'SignInScreen');
You can also log any specific event with the event
method on EventBus
.
eventBus.event('signin_completed');
You can easily log app level events consistently, such as cold start, resume, and version.
eventBus.app(EventBusAppEvent.coldStart);
All of the methods can take a additional parameters to enhance the logging. These parameters are unlimited and take the form of a [Map].
eventBus.event('signin_completed', params: {'username': username});
There will likely be multiple consumers needed, one to support each service.
These consumers will listen to the stream of events from the EventBus
, and
decides which ones to send to their service.
Here is a simple consumer that just prints events to the console:
eventBus.events.listen((event) {
print(event);
});
There is a consumer (EventBusLogger
) included in this package that logs events to a [Logger].
It is very easy to use.
final eventBus = EventBus();
final logger = Logger(
level: Level.debug,
output: ConsoleOutput(),
filter: ProductionFilter(),
printer: PrefixPrinter(SimplePrinter(printTime: true, colors: false)),
);
EventBusLogger(eventBus, logger);
After EventBusLogger
is setup, it will log every event to the logger with a format
that looks like this:
INFO [I] TIME: 2023-07-02T18:29:34.613347 @ app.coldstart
INFO [I] TIME: 2023-07-02T18:29:34.621539 @ tap=>signin_button - {screen: SignInScreen}
DEBUG [D] TIME: 2023-07-02T18:29:34.621638 @ ⚡︎ SignInBloc: start signing
DEBUG [D] TIME: 2023-07-02T18:29:34.621705 @ SignInScreen.build: started - {name: foobar}
INFO [I] TIME: 2023-07-02T18:29:34.621751 @ sign_in - {username: foobar}
ERROR [E] TIME: 2023-07-02T18:29:34.621784 @ SignInScreen._signIn: failed
DEBUG [D] TIME: 2023-07-02T18:29:34.621828 @ SignInScreen._signIn: completed
If you like using EventBusLogger
but would rather have a different output
format, just copy the code inside EventBusLogger
to a new class, customize it
to your needs, and use that class instead.
There is also an included consumer ('EventBusEventCollector) that collects events and keeps them in memory for things like displaying them on a debug screen.
Here is a convenient example of a consumer written for Firebase Analytics. It logs all events to FB but changes the names of the error and tap events.
class EventBusFirebaseAnayltics {
EventBusFirebaseAnayltics(this.eventBus) {
eventBus.events.listen((event) {
_handleEvent(event);
});
}
final EventBus eventBus;
Future<void> _handleEvent(EventBusEvent event) async {
if (event.group == EventBusGroup.error) {
final name = event.isValidName ? 'error_${event.name}' : 'error_detected';
FirebaseAnalytics.instance.logEvent(name: name, parameters: event.params);
} else if (event.group == EventBusGroup.tap) {
final name = 'tap_${event.name}';
FirebaseAnalytics.instance.logEvent(name: name, parameters: event.params);
} else {
FirebaseAnalytics.instance
.logEvent(name: event.name, parameters: event.params);
}
}
}
Note: This class is not included in this package to avoid requiring FirebaseAnalytics as a dependency. Feel free to copy this class to your app for your own use.
All comments and pull requests are welcome.
Please sponsor or donate to the creator of event_bus_basics
on Flattr or Patreon.