Mastering Dart & Flutter DevTools — Part 5: Logging View

Flutter Gems
10 min readDec 28, 2022

by Ashita Prasad (LinkedIn, Twitter), fluttergems.dev

This is the fifth article in the “Mastering Dart & Flutter DevTools” series of in-depth articles. In this article, you will learn about logging in Flutter apps and how you can effectively utilize the DevTools’ Logging View. In case you want to check out any other article in this series, just click on the link provided below:

Installation and setup of DevTools is a pre-requisite for this part. In case you missed it, the installation and setup details are provided in detail here.

While developing an app, you might often come across a situation that requires “behind-the-scene” inspection of the code while it is being executed. This is where logging comes to our rescue and helps us keep track of app events, visualize code logic flows, debug unexpected errors and discover bugs. Logging helps us understand the behaviour of an application by providing us a way to monitor how the code works with the actual data.

In this article, we will explore the different methods of logging in Flutter and utilize the DevTools’ Logging View to monitor the logs generated in a Flutter app and understand its use via a real-world example.

Different Methods of Logging

print() & debugPrint()

The most basic way to generate any log is by using the print() function that outputs the object passed as an argument to the function on the standard output (terminal).

For example:

void main() {
var message = "Hello logger!";
print(message);
}

Displays the below output in the terminal.

Hello logger!

If this message is too large then some log lines may get discarded or truncated due to the printing limit of the OS (such as Android).

To circumvent this, we can use debugPrint() function from Flutter’s foundation library which can be modified via assigning it to a custom DebugPrintCallback that can throttle the rate at which messages are sent to avoid data loss in Android. You can also provide a wrapWidth argument that word-wraps the message to the given width so as to avoid any truncation of the output.

import 'package:flutter/foundation.dart' show debugPrintThrottled;

// if no wrapWidth is provided then automatically set it to 70 characters
final debugPrint = (String? message, {int? wrapWidth}) {
debugPrintThrottled(message, wrapWidth: wrapWidth ?? 70);
};

void main() {
var message = "Hello logger!";
debugPrint(message);

var longMessage = "A very long line that is automatically wrapped by the function so that it does not get truncated or dropped.";
debugPrint(longMessage);
}

Displays the below output in the terminal.

Hello logger!
A very long line that is automatically wrapped by the function so that it does
not get truncated or dropped.

Although both the above functions can be used for logging, the output of print() and debugPrint() functions can be viewed easily as they are displayed in the terminal even if the app is in release mode. You can actually go ahead and run the flutter logs or or adb logcat | grep flutter command in the terminal and observe the logs generated using these functions while running the app. This can lead to security issues if any sensitive information or any authorization process related information is being logged in your app due to the usage of these functions.

debugPrint() does have the option to prevent this as it can be customized to work only in dev mode by giving and empty callback as shown below.

void main() {
if (kReleaseMode) {
debugPrint = (String? message, {int? wrapWidth}) {};
}
}

Still, there is a possibility of error in case this method is not being properly used.

log()

Apart from security concerns, the print() and debugPrint() functions are also not very helpful for log analysis as they lack features to add granularity, perform customization and display additional information while logging.

This is where the log() function available in the dart:developer library comes to our rescue. Apart from displaying the message that represents the log message, there are some other useful parameters provided in this function that can help us add granularity and data to our logs, such as:

  • name (String) — A short string that can quickly help you locate the source of the error. In the Logging View, it becomes the value of the Kind field and can be directly used for filtering logs. If this value is not specified then the default value of the column is log.
  • error (Object?) — An error object is usually the application data at that point in the workflow (event) that we might want to investigate. This data is passed and can be viewed in the Logging View.
  • level (int) — Denotes the severity level (a value between 0 and 2000). You can use the package:logging Level class to get an overview of the possible values. If the value is equal to or above 1000 (Level.SEVERE.value), the log’s Kind field in the Logging View is highlighted in Red for the log to stand out.

The logs generated using log() are not displayed on the terminal which makes it secure. We can view these logs using the DevTools’ Logging View as described in the next section.

Logging View

Logging View in the DevTools suite can be used to view the following events:

  • Dart runtime events, like garbage collection.
  • Flutter framework events, like frame creation.
  • Standard Output events (stdout, stderr) from the application that is generated using functions like print().
  • Custom logging events generated using the log() function in an application.

To better understand the usage of log() and view the corresponding logs in the DevTools’ Logging View, let us go through an example app where we will log various types of data and view it.

Step 1: Getting the Source Code

Create a local copy of the below repository 👇

Open the repo in GitHub as shown below.

Click on the green button <> Code, then visit the Local tab and click Download ZIP to download the source code for this exercise.

Extract the file using any unzip tool.

We now have a local copy of the source in the folder logging_view_demo-master.

Step 2: Opening the Project in VS Code

Launch VS Code, open the project folder in VS Code and go to pubspec.yaml file.

Click on Get Packages to fetch all the packages required for the project.

Click Get Packages

Step 3: Launching Logging View in VS Code

Before we launch the Logging View, we must run the app.

Click on No Device in the status bar and select a target device. If a default device is already selected, you can click on it and change the target device.

Select Target Device

We will go ahead and select an android emulator as the target device.

android emulator selected as target device

Now, click Run to run the app (main function) as shown in the image below.

Run the app

The Logging View can be launched as shown below using the following instructions:

Click Dart DevTools in status bar > 
Click Open DevTools in Web Browser > Click Logging tab
Launch App and Logging View

On running the app, you can already see some logs in the Logging View. These are the Flutter framework events.

App along-side the Logging View

In the App Home Screen, there are various buttons for different types of logs that we will generate in this exercise.

Example #1: String

Click Short String button on the app screen. This activity will trigger a number of events (flutter.frame Flutter frame event, gc garbage collection) including the log event as shown in the image below.

Example #1

When the button is pressed, it triggers the following log event:

log(
"Log Event: Short String",
name: "buttonLog",
error: kShortString,
);

The event can be seen in the Logging View as shown below. buttonLog passed as the value of parameter name becomes the value of field Kind in the log event row. Click on the log to view its details. The log message Log Event: Short String is the first row in the details, followed by the application data kShortString, the string Hello logger!, in the next line.

Filtering Required Logs

Events like Flutter frame events (flutter.frame) and garbage collection events (gc) captured in the Logging View that are not useful for our investigation can be filtered out using the filter option as shown below.

Just click the Filter button and enter -k:flutter.frame,gc to hide all logs of kind flutter.frame and gc. In case you want to view logs of only a certain kind that you specified (like buttonLog events), just enter k:buttonLog in the filter text field and it will display only the logs of that kind.

This is a powerful feature of Logging View that can selectively show only the relevant logs and help us analyze them.

Applying Log Filter

Example #2: Long String (Severe)

Clicking Long String (Severe Event) button triggers the following log event:

log(
"Log Event: Long String",
name: "buttonLog",
error: kString,
level: 1000,
);

In this example, we have set the value of parameter level as 1000. As this value is >=1000 (equivalent to Level.SEVERE.value in package:logging), the log’s Kind field buttonLog gets highlighted in Red background color in the Logging View as shown below. Also, it can be observed that as compared to the normal terminal logs that are truncated after a certain length, in this case, the entire data (long string) gets logged for further inspection.

Example #2

Example #3: Large List

Now, let us go ahead and press Large List button to trigger the following log event:

log(
"Log Event: Large List",
name: "buttonLog",
error: kList,
);

In this case, kList is a large list containing more than 350 items. Again, the entire list is shown in the log which would have been truncated if displayed on the terminal.

Example #3

Example #4: Map

Similar to the List, let us now go ahead and log a Map using the following call:

log(
"Log Event: Map",
name: "buttonLog",
error: kMap,
);

The corresponding log result is shown in the image below:

Example #4

Example #5: Custom Class Object

One of the major advantages of using the log() function and the Logging View is that they can be used to analyze complex data (available as a Dart Class Object) that might be causing a runtime error.

For example, let us take a class MenuModel, that can be used to store the data of the options available in the menu bar of a software application.

class MenuModel {
Menu? menu;

MenuModel({this.menu});

MenuModel.fromJson(Map<String, dynamic> json) {
menu = json['menu'] != null ? Menu.fromJson(json['menu']) : null;
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = {};
if (menu != null) {
data['menu'] = menu!.toJson();
}
return data;
}
}

Now, we can create an object of this class with some initial data provided in kModelData.

final model = MenuModel.fromJson(kModelData);

On pressing the Dart Object (Custom Class) button, the following log event is triggered. As the data is stored using a custom model (Dart Object), we have to use thejsonEncode() function of library dart:convert to encode the object into a JSON string and then pass it as the error argument.

import 'dart:convert';

log(
"Log Event: Dart Object (Custom Class)",
name: "buttonLog",
error: jsonEncode(model),
);

By utilizing the toJson() method of class MenuModel, the jsonEncode() function converts the object into a readable JSON String which is rendered for further analysis in the details view for the log entry as shown below.

Example #5

In this exercise, we generated a wide variety of logs capturing different types of data and error severity. These logs were then seamlessly monitored using the feature-rich DevTools’ Logging View tool.

In this article, we took a deep dive into the various methods available to generate logs while debugging a Flutter application. Using the example application, we triggered different types of log events and analyzed them using the Logging View. Using examples, we got a first hand experience of using the various features of Logging View and saw how it can help us debug our apps faster by helping us analyze the logs more efficiently.

We would love to hear your experience with logging in Flutter and the DevTools’ Logging View. In case you faced any issues while going through this exercise or while running the tool for your project, please feel free to mention it in the comments and we can definitely take a look into it. Also, in case you have any other suggestion, do add it in the comments below.

In the remaining articles of this series, we have discussed other tools available in the DevTools suite that can help you build high-performance Flutter apps. Don’t forget to check out the links below to navigate to the tool you want to learn next:

Source Code(s) used in this article:

Special thanks to Kamal Shree (GDE — Dart & Flutter) for reviewing this article.

--

--

Flutter Gems

Maintained by Ashita Prasad, Flutter Gems is a curated package guide for Flutter ecosystem. Visit https://fluttergems.dev