Log like a Pro in Flutter with the power of mixins
Logging is one of the ways to understand how users use your app. It allows you to track important events like navigation and errors, handled and unhandled. Let’s see how you can log using mixins in Dart.
What is logging?
Logs could be just text messages:
12:00:00 [I] App launch
12:00:00 [I] Home Screen opened
12:00:02 [I] Details Screen opened
12:00:02 [I] Receiving details for an item ...
12:00:03 [E] Unable to fetch the details of an item, here's why:
*Exception details + Stacktrace*
We can see two important things we achieve with logging:
- Reproduce users’ journey;
- Inspect errors that occured. We can see where an error has occured and what were the technical details. When investigating problems, it is more useful than a note from a user «I opened the screen and the app just crashed!».
Logger library
There are plenty of tools that we can use to log messages. If you search for a logger library on pub.dev you’ll find a lot of similar packages. prefer the logger library.
It gives us the Logger
class that we can use to write log messages:
// Create it first
final log = Logger();
// Use it!
log.v("Verbose log");
log.d("Debug log");
log.i("Info log");
log.w("Warning log");
log.e("Error log");
log.wtf("What a terrible failure log");
Here’s an example of what we’ll see in the console:
Neat, isn’t it? Colors, formatting, etc. Logger
gives you the power of configuring all of that by providing so called printers.
Personally I prefer simpler log messages. I’ll show it in a bit.
Mixins & Contextual logger
We won’t use the logger library directly. Instead, we’ll use contextual_logging.
Add it to your pubspec.yaml:
flutter pub add contextual_logging
contextual_logging
provides us with a mixin we can attach to our classes. The mixin will bring the logging functionality to our class. Let’s say you have a controller and you want to track what’s happening inside of it.
class MyController with ContextualLogging {
Future<void> init() async {
// Now you have access to the log object
log.i('Initializing ...');
await fetchSomeData();
log.i('Initialized!');
}
}
In the console, once you call init
, you’ll see these two messages:
10.03.00 [I] MyController : Initializing...
10.03.01 [I] MyController : Initialized!
Anatomy of the message
This message is formatted by the ContextualLogPrinter
of the contextual_logging
library. Checkout this readme section to learn how to configure it.
The anatomy is the following:
- Timestamp. It is the time when the message was written. Helpful when any logic in your app depends on time in any way;
- Level. The
Logger
object gives us different log levels. This is for better understanding of a message: if it’s an info message, then it is most likely about the app’s behaviour. If it’s an error message, then it is for sure about an error that has occured; - Context. Most of the times it is the name of a class that has logged the message. It is unique per class (if classes have different names of). But it can be configured by overriding the
logContext
getter of the mixin. Learn more here; - Message. It is the actual message that you pass to the log method. It is the behaviour: Screen opened, data fetched etc.
That’s it folks!
This is a very basic example. In fact, with logging, you can do more. For example, write them to files that users will be able to share with you! Or you can redirect logs to an external logging tool. I’ll cover it in upcoming articles.
For now, you can checkout the docs that show configuration of contextual_logging
in more details.
Reach out to me in Telegram or join my technical blog there. I will be happy to have you there.
Peace and light to your house.