Deven Joshi Deven is a Google Developer Expert for Flutter and an avid developer who loves all things mobile. Tinkering with Flutter since its early days, he started out with writing content to make Flutter even easier to understand and experiment with. When not programming, you will find him playing chess or reading something new.

Flutter logging best practices

6 min read 1866

Flutter Logging Best Practices

Developing a large-scale application is no easy feat. It usually involves multiple modules working together coherently and is often written by different developers. So, when issues pop up in development, a single person has to go through an app flow created by multiple developers to identify the root cause. Misidentifying what went wrong or adding temporary fixes can break other parts of the code and lead to more issues in the long run.

If you want to speed-run creating an app, you may use these in your code:

print('reached here');

// Some code

print('now here');

// Some other code

print('method called');

The print() or debugPrint() statements are convenient ways to log errors to the console or to check where the code is. Initially, having a few print statements around the app isn’t cause for concern. However, it isn’t a sustainable way to build an app in the long run.

One great way to ensure all modules and functionalities of your project run smoothly is to use logging. A well-defined logging system can help reduce headaches when building apps and provide concise information to the user and developer while the app runs.

This article will explore using the Logger package to create easily interpreted Flutter logs, consider log levels, and cover how to use Crashlytics to get ongoing logs.

Jump ahead:

What does a good Flutter logging system look like?

Before we discuss the best practices for logging in Flutter, let’s look at the logs themselves.

Logs with uneven structures and messaging make it difficult to decipher the contents. For example, HTTP requests can contain several parameters or have significant results that need to be inspected. Doing this can be difficult and time-consuming when the logs are not structured well.

Let’s think about what you need to build an app. To start, you must ensure all calls to the server are successfully going through. Later, you’ll need to check that certain parts of the UI build appropriately, and you’ll need verbose information about the database.

Logging systems with even structures and messaging will save you from ruining your weekend when you monitor for significant errors and fix them. Logging systems must suit the developer’s needs rather than be uniformly set.

Now, let’s look at logging levels.

The importance of logging levels in Flutter projects

Flutter projects can have many logs, including networks, databases, and errors. Often, developers only need a moderate amount of logs and can safely ignore the more verbose ones. However, you may need to inspect the more detailed events if things aren’t working.

When an app is released, you may only need to record errors and other important events. Having levels in each log is essential for recording these errors because the levels assign the importance or type to each log.



You may find log types such as verbose, warning, and error, which filter out unnecessary logs. Now that we understand the importance of a solid logging system and log levels of logs are set, let’s look at adding them to an app.

Best practices for adding logs to Flutter projects

Here, we will discuss the basic rules of adding logs to a project. By following these best practices, you can better understand your app’s flow and tackle any unexpected issues more quickly than an app written without a coherent logging system.

1) Log appropriate information

Logging too much information is overwhelming when trying to solve an error, while having too little information does not provide enough to work with to address errors. As with any error, errors can be traced down to the foundation of Flutter itself. If the entire stack track is provided to the developer, it becomes like a needle in a haystack rather than helpful information. To avoid this, log appropriate information to determine the root cause of the error in developer code without extending the same chain down to the base Dart errors.

2) Make sure all events are covered

Several systems work in unison when an app runs, including the UI, network calls, databases, and more. With so many systems working simultaneously, it’s easy to overlook covering critical events. These missing logs obscure the inner workings of processes and the causes of errors. To save yourself the headache, make sure that all events are covered.

3) Don’t use logs as tests

Logs are often used instead of tests to ensure certain parts of the code are reached. While having a log in those parts of the code isn’t always a bad idea, avoiding the test can be harmful.

4) Log uniformly

Each logged event needs to be inspected for its importance, so assign one level for each type of event. For example, you can set all network calls as verbose. This allows developers to separate logs effectively to prevent dealing with many of them at a higher log level.

5) Switch off logs when they are not needed

When in development, you’ll need to inspect more logs than in production – so don’t log unnecessary information when in production. The production app will likely run on many more devices than the debug app. Logging all events from these app runs will add unnecessary costs to operations; hence, production logging is usually limited to warnings and errors.


More great articles from LogRocket:


Now that the best practices are set, let’s look at a package that can implement the various aspects of logging for us.

Using the Logger package to log in Flutter

While creating different components for logging in-house is possible, it’s time-consuming and offers little benefit since logging systems across apps are rarely customized or differ. There are several logging packages, such as FLogs, loggy, and simple_logger.

In this article, we’ll explore the Logger package. It is one of the most popular Flutter logging solutions because it has out-of-the-box support for logging and creates concisely formatted logs. You can find the entire GitHub repository here.

Creating basic logs

To start logging, create a Logger class instance using the log() method.

Next, supply a level and a message with the command below:

var logger = Logger();

logger.log(Level.verbose, "Demo log");

You can supply an error and a stackTrace associated with a particular log:

class StringStackTrace implements StackTrace {

 final String _stackTrace;

 const StringStackTrace(this._stackTrace);

 String toString() => _stackTrace;

}

var logger = Logger();

logger.log(Level.verbose, "Demo log", "An error", StringStackTrace("Your stacktrace here"));

The error can be any object other than a String. Therefore, you can supply your error along with the log, as shown below:

var error = Error();

var logger = Logger();

logger.log(Level.verbose, "Demo log", error, error.stackTrace);

In the example, we can pass the error and the stackTrace of the error while logging. This allows the user to quickly extract more details from the log and tackle errors.

Using the Logger class to log levels

Now that we’ve created our basic logs, it’s time to log levels. Luckily, Logger has several levels that you can use to log events, as shown below:

enum Level {
 verbose,
 debug,
 info,
 warning,
 error,
 wtf,
 nothing,
}

To log without having to describe levels every time, Logger comes with multiple methods to log at various levels:

var logger = Logger();

logger.log(Level.verbose, "Demo log", error, error.stackTrace);

//OR

logger.v("Demo log", error, error.stackTrace);

// SIMILARLY

logger.w("Demo log", error, error.stackTrace);

logger.i("Demo log", error, error.stackTrace);

// AND MORE...

Building a log filter

Log filters help determine which events should be logged and which should not. This is useful when deciding what kinds of logs need to be shown in release mode.

To create a LogFilter, extend the LogFilter class, and implement shouldLog(). Next, pass in the filter when instantiating Logger:

class DemoFilter extends LogFilter {
 @override
 bool shouldLog(LogEvent event) {

   if(event.level == Level.error || event.level == Level.warning) {
     return true;
   }

   return false;
 }
}

var logger = Logger(filter: DemoFilter());

logger.w("This will be accepted", error, error.stackTrace);

logger.v("This will not", error, error.stackTrace);

The LogFilter lets you decide if the received event should be logged or ignored. For example, you can have different filters depending on the severity level of the log.

Designing log printers

The Logger package supports printing well-structured and aesthetically pleasing logs. By default, they are printed in a standard way with a stackTrace and a message similar to this:

Flutter Logging With Logger Package

However, you can use the in-built PrettyPrinter class to add more flair to your logs using the command below:

var logger = Logger(
 printer: PrettyPrinter(),
);

To make your printer, you can extend the LogPrinter using this:

class DemoPrinter extends LogPrinter {
 @override
 List<String> log(LogEvent event) {
   switch(event.level) {
     case Level.verbose:
       break;
     case Level.debug:
       break;
     case Level.info:
       break;
     case Level.warning:
       break;
     case Level.error:
       break;
     case Level.wtf:
       break;
     case Level.nothing:
       break;
   }
 }
}

You can now print messages in your customized format for every log level.

Connecting to Crashlytics

The Firebase’s Crashlytics service allows developers to analyze crashes and peculiar events in the app. Although crashes are extreme events, Crashlytics also supports sending custom logs in the app to the Firebase Crashlytics console. This helps Crashlytics become a general-purpose logging tool rather than just one that helps when something goes wrong in the app.

To start, add Firebase Crashlytics to your app using these steps:

1. Add the firebase_crashlytics dependency

Run this command to add the dependency to your project:

flutter pub add firebase_crashlytics

2. Add configuration for Android

Add these lines to your android/build.gradle file:

dependencies {
 // ... 
 classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
}

Additionally, add these to android/app/build.gradle:

android {
 // ... your android config
}


dependencies {
 // ... your dependencies
}


// This must appear at the bottom of the file
apply plugin: 'com.google.firebase.crashlytics'

To get more information on the Crashlytics integration, find the docs here.

To record an error in your app, use the recordError() method that Crashlytics offers:

FirebaseCrashlytics.instance.recordError(
 error,
 stackTrace,
 reason: 'Your error reason',
 fatal: true
);

If there isn’t an error, and you want to see a log, use the log() method:

FirebaseCrashlytics.instance.log("Your log event");

Additionally, there are Flutter-specific error functions, such as recordFlutterError():

FirebaseCrashlytics.instance.recordFlutterError(
 FlutterErrorDetails(
   exception: YourException(),
   stack: stackTrace,
 ),
 fatal: false,
);

The exception value has a type of Object, so this value can be anything you want to pass along.

Conclusion

This article discussed the best practices for logging in a Flutter project. We also learned how to use a package to create easily interpreted logs, considered log levels, and covered how to use Crashlytics and similar tools to get ongoing logs.

While this was a summary of logging in Flutter, your implementation will probably be a little different every time, depending on the project you are developing.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Deven Joshi Deven is a Google Developer Expert for Flutter and an avid developer who loves all things mobile. Tinkering with Flutter since its early days, he started out with writing content to make Flutter even easier to understand and experiment with. When not programming, you will find him playing chess or reading something new.

Leave a Reply