Easiest way to print log message is with 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.

print function example output

print function example output

Alternatively, you might have been using debugPrint.2 Main difference is that 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 dart:developer package (part of 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 addition to text message. Importantly, error objects log passed to this 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 by log. In this case, origin name would be set as ‘log’.

import 'dart:developer';

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

// [log] Starting main

You can easily customize origin name by using 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 error parameter. Error will be highlighted with red color inside you 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

developer.log function example output

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

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,
  }) {
    dev.log(
      message,
      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 different name if you want to).

import 'log_mixin.dart';

class Screen extends StatelessWidget with LogMixin {
  @override
  Widget build(BuildContext context) {
    log('Building');
    return Scaffold(...);
  }
}

// [Screen] Building

  1. print function (Dart API) ↩︎

  2. debugPrint function (Flutter API) ↩︎

  3. log function (Flutter API) ↩︎