Better Logs In Flutter

The easiest way to print a log message is with the print() function.1 print function takes a string input and outputs it to standard output which is usually terminal. Every print is prefixed with its origin, which defaults to I/flutter ...: for every call.

You can get the following log utilities with my package logx on

example output of print function

Alternatively, you might have been using the debugPrint().2 Main difference is that the debugPrint() tries to throttle the output rate so no log gets lost. It still prints the same output which becomes hard to read when you log many things.

Fortunately, Flutter has a better way of doing things. Inside the dart:developer package (part of the standard library which you can easily import) is log function.3

log function allows much better customization. You can pass origin name, error objects, or stack traces as an addition to the text message. Importantly, error objects passed to the log function will be printed in red color to your terminal output.

Lets start with a print example so we can compare them.

void main() {
    print('Starting main);

// I/Flutter ( 6726): Starting main

Pretty straightforward right? Well, log can do the same. All you need to do is replace print with log. In this case, the origin name would be set as 'log'.

import 'dart:developer';

void main() {
    log('Starting main');

// [log] Starting main

You can easily customize the origin name by using the name parameter.

log('Starting main', name: 'Main');
// [Main] Starting main

log is also very handy inside try/catch statements. Pass your caught error object e and provide it to the error parameter. The error will be highlighted with red color inside your terminal.

try {
    throw Exception('Failed to fetch data.', name: 'Main');
} catch (e) {
    log('Error', error: e);

// [Root] Error
//        Exception: Failed to fetch data.

developer.log function example output

Specifying name each time gets boring. To make this easier I have created a custom LogMixin mixin which 'overrides' log call and sets the name based on the current class. Because everything in Flutter is Widget—therefore a class—this makes debugging a lot easier.

UPDATE 2022-25-06: Do NOT use runtimeType.toString(). This call impacts your performance. Also type names can be minified in production builds therefore you get invalid values. See lints no_runtimeType_toString and avoid_type_to_string. Prefer to instantiate log class and hardcode class name with plain String. Do NOT use the example below.

import 'dart:async';
import 'dart:developer' as dev;

mixin LogMixin on Object {
  String get _className => this.runtimeType.toString();

  void log(
    String message, {
    DateTime time,
    int sequenceNumber,
    int level: 0,
    String name,
    Zone zone,
    Object error,
    StackTrace stackTrace,
  }) {
      name: name ?? _className,
      time: time,
      sequenceNumber: sequenceNumber,
      level: level,
      zone: zone,
      error: error,
      stackTrace: stackTrace,

When you need to log inside your widget, add this mixin and use log instead of print. You don't have to worry about name anymore (well, you can always provide a different name if you want to).

import 'log_mixin.dart';

class Screen extends StatelessWidget with LogMixin {
  Widget build(BuildContext context) {
    return Scaffold(...);

// [Screen] Building


  1. print function (Dart API)

  2. debugPrint function (Flutter API)

  3. log function (Flutter API)