Flutter logging proved inadequate for all our requirements as we started using Flutter more extensively in larger projects at Infinum.
Flutter had a great year in 2020 with over half a million developers using it daily. It has surpassed react-native, linux, and vscode by the number of stars on github.
Is a logger really necessary?
Logging is a really important tool for any developer and any application. It can make debugging time significantly shorter and help developers figure out sooner what’s going on in the app.
Using the Flutter logger properly can be highly valuable when an app is in the production environment. It allows developers to analyze user actions and understand the cause of a problem.
Problems with Flutter logging
As logging in Flutter is very bland by default, developers mostly rely on print. The main problem with print is that all messages are treated the same way. There is no distinction between info and critical log messages. Critical messages are easily overlooked.
There is also no ability to turn logging off when building a production version. That can be a big security concern since all prints would be visible and easily accessible.
Possible solutions to Flutter logging
We noticed this problem when we were developing an app with complex navigation with socket communication. Often, socket messages would overflood our console, and then removing them would take a while, and debugging if something went wrong in communication would take even longer.
We wanted a way to toggle logs depending on the feature we were working on, so we set up the following criteria for the logger. Firstly, we wanted to have multiple logger levels and types, because filtering just by levels is not sufficient. In addition to that, we would need filtering and the ability to easily toggle logging.
We would also try to find a logger that has as little setup as possible in order to get as much data as possible.
The hunt for libraries
We tried almost every logging library that existed at the time. Even though all of them had implemented our criteria on multiple levels, none of them fitted all the criteria.
Filter by type was the hardest requirement to fulfil and it was either impossible or very difficult to implement. But we persisted on fulfilling that criteria as it would allow us to turn loggers on and off for a single feature, without affecting other loggers.
Filtering only by level would again lead us to the same path, and further down in app development more and more messages would become error or warning levels in order to become visible.
Some of the libraries had nice extra features which we wanted to include, but since none of them met all of our requirements, the only reasonable decision was to make our own Flutter logging library. Cue Flutter Loggy!
Mixins to the rescue
We decided to use mixins from dart to achieve quick setup, while keeping different types separated. Using mixins has a lot of practical usages, so we wanted to see if they could help us with our logging problems.
Since mixins can get calling class name, we used that to generate tags for logs, and we could make more types by making different mixins for different layers. Having multiple types would allow us to separate loggers for different layers of the application, which can be easily filtered.
We built the logger with mixins, and filtering them worked as expected – making new types was simple, and Loggy would come with 2 types –
NetworkLoggy. They are a great starting point and good examples for creating custom types later.
To use Loggy, simply add
runApp(), and add
with UiLoggy in any class where you need Loggy. That being done, it’s now time to log the messages
The library can be configured with
initLoggy, from where it’s possible to add filters, levels and printer for logger.
With different types of logging and a habit of using networking in pretty much every app, we made an interceptor for the most popular networking library Dio. Dio interceptor can attach to Dio instance and log requests and responses.
This interceptor is useful because of ║, ╔ and ╚. Using these symbols, it is possible to set up the console to collapse all lines that contain them. This way, we will see the request and the response code, but the request body will be collapsed and can be expanded to show more details.
Problems solved with Flutter Loggy
With Loggy, all of the problems we had before were gone. Now we can easily set up different printers, eg. instead of sending events to console the printer can send events to crashlytics for production builds or just disable logging completely.
When it comes to types, it’s simple to set up different ones, allowing for easy filtering by type in order to decide which to include in production builds.
Finally, using Loggy inside the app is convenient – simply by adding
with UiLoggy and logging something a lot more info becomes available, and it’s possible to control priority or easily disable it without actually removing it.
We also managed to clear out our console while actually logging more stuff about the request, since they show up collapsed they take just 1 line of space, but they can be expanded to show whole request details.