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 pub.dev.
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.
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,
}) {
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 a 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