Bug writing is inevitable, and no one’s logic is correct all the time. A lot of times, it takes longer to fix a Bug than it takes to write code. Sometimes you wonder if you’re writing a Bug or a program

I would go so far as to say that a programmer’s ability to detect bugs determines his skill level to some extent. Usually we will print logs from the console to find the exact location of the program crash, and then break debugging, step by step to find the culprit. This article will teach readers how to use the log printing library Logger to greatly speed up your troubleshooting.

If you are still using print(), debugPrint() to print logs in Flutter, I think it is time to get to know logger as a logging component because it is really elegant.

Logger introduction

Logger is a platform-independent log printing library written in the pure DART language. It is very lightweight and scalable, the printed log is particularly beautiful, it perfectly realizes the stack of information to customize the printing, various printers, filters. You can print logs to the console, output them to a file, and temporarily save them to memory.

I’ve been using this log print component for a while now, and it feels very stable overall. I particularly like the way it prints the stack of methods, and as someone who is a bit of a stickler, I really like the log format and color it prints. The author of this component is a young man living in Munich, Germany. He said that this component was inspired by the Android platform logger.

Basic use and packaging of logger

First, take a look at the overall code structure of the Logger project, which consists of three major parts. The hierarchy is very clear, the author gives full play to the inheritance of classes and the combination of objects, and the class name makes people know what it means at a glance. Each class has achieved a single responsibility and abstracts the business into code, which shows the author’s code level is very high. In the filters directory are the filters, the outputs output specifies the log output location, and the printers printer specifies the log printing style and stack information, etc.

The basic use

Logger is easy to use, add the following dependencies to pubspec.yaml.

The logger: ^ 1.0.0Copy the code

Its log levels are divided into seven, as shown below. The default log level is verbose. That is, all logs whose level is >= verbose are displayed.

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

When you want to print logs, simply instantiate a Logger object and call different levels of print methods.

void main() {
  var _logger = Logger(
    printer: PrettyPrinter(
      methodCount: 0,),); _logger.v('verbose message');
  _logger.d('debug message');
  _logger.i('info message');
  _logger.w('warning message');
  _logger.e('error message');
  _logger.wtf('wft message');
}
Copy the code

Below is a printout of the code above.

In addition to being simple to use, logger outputs beautiful logs. In the Logger constructor, we can pass in the specific printer, filter, output position, and so on. Here is the Logger constructor.

Logger({
    LogFilter? filter,  // Filter can distinguish development environment from production environment
    LogPrinter? printer,  // Printer, control log output style and stack information, etc
    LogOutput? output,  // Control log output position. It can be console, file, memory
    Level? level,
  })  : _filter = filter ?? DevelopmentFilter(),
        _printer = printer ?? PrettyPrinter(),
        _output = output ?? ConsoleOutput() {
    _filter.init();
    _filter.level = level ?? Logger.level;
    _printer.init();
    _output.init();
  }
Copy the code

If no arguments are passed, the default filter is developer mode, the printer is a nice printer, and the output location is the console.

Simple packaging

The Logger only needs to be instantiated once, and can then be used anywhere in the project to invoke all levels of the print method. Here I use a PrefixPrinter to wrap the PrettyPrinter.

class Log {
  static Logger _logger = Logger(
    printer: PrefixPrinter(PrettyPrinter()),
  );
  
  static void v(dynamic message) {
    _logger.v(message);
  }

  static void d(dynamic message) {
    _logger.d(message);
  }

  static void i(dynamic message) {
    _logger.i(message);
  }

  static void w(dynamic message) {
    _logger.w(message);
  }

  static void e(dynamic message) {
    _logger.e(message);
  }

  static void wtf(dynamicmessage) { _logger.wtf(message); }}Copy the code

Logger printer

The printer of Logger is the most core function of Logger at present, and this paper will focus on the printer. Take the PrettyPrinter() as an example, first look at its constructor, as follows.

PrettyPrinter({
    this.stackTraceBeginIndex = 0.// Start index of method stack
    this.methodCount = 2.// Prints the number of method stacks
    this.errorMethodCount = 8.// This parameter is valid if you pass in the method stack object
    this.lineLength = 120.// Maximum number of characters to print per line
    this.colors = true.// Whether the log has a color
    this.printEmojis = true.// Whether to print emojis
    this.printTime = false.// Whether to print the time
  })
Copy the code

Use PrettyPrinter without specifying any parameters, the default is as above, then use the code we just encapsulated above. Print it and see what it looks like.

// LogTest.dart
void main(){
  Log.w("PrettyPrinter warning message");
}  
Copy the code

Dart: a warning log is printed in the main method of logtest. dart. Since no arguments to PrettyPrinter are specified, the default stack methods printed are #0 and #1. The reader should know that calling a method is constantly pushing the system, and the last method to be called must be at the top of the stack, which is obviously #0. So what method is called at the bottom of the stack? You can see this if you specify a large enough stack of methods to print.

I don’t know if readers have noticed a problem, but after encapsulation, each printed log carries a #0 method stack log. Most of the time we don’t care about the method call inside the wrapper, just where the log is printed from (#1 above) so we can quickly locate the corresponding code.

Now, how do I get rid of #0? In fact, it is very simple to view the source code. We can control the output by specifying stackTraceBeginIndex and methodCount values. Now specify the values of these two parameters, 5 and 1, for PrettyPrinter.

static Logger _logger = Logger(
    printer: PrefixPrinter(PrettyPrinter(
      stackTraceBeginIndex: 5,
      methodCount: 1)));Copy the code

Why stackTraceBeginIndex is 5. The reader can look at the formatStackTrace method in the PrettyPrinter class. Breakpoint debugging can look at the method stack information to get the specific value.

Then I print the log again, just the last one# 1The stack method will now be printed.

Logger filter

Logger currently has two filters, DevelopmentFilter and ProductionFilter. Logs are printed when using the DevelopmentFilter debug mode. If you Release the APK package, none of the logs will be printed.

With ProductionFilter, logs will be printed either by the Debug mode or by putting the APK Release package into production.

How does Logger do this? By viewing its source code, it is also very simple and clever. Here is the DevelopmentFilter implementation, and since assert assertions are only called in Debug mode, shouldLog = true should print the log. Assert assertions are not executed in a production environment, thus masking all log output.

class DevelopmentFilter extends LogFilter {
  @override
  bool shouldLog(LogEvent event) {
    var shouldLog = false;
    assert(() {  // Assert assertions are called only in debug mode.
      if(event.level.index >= level! .index) { shouldLog =true;
      }
      return true; } ());returnshouldLog; }}Copy the code

Logger output

Logger fully considers user scenarios and supports log printing on the console, file, and memory. You can even use a MultiOutput to output logs simultaneously in multiple locations. I won’t go into the details of how to use these apis, so you can try them out for yourself.

The realization principle of color log

One of the most attractive things about this project is that the logs printed out are really nice! The color is clear and looks very comfortable. Are you also curious about how the console outputs these color logs?

This must refer to ANSI escape sequences, which allow you to control text cursor position, color, and other options on the terminal. A standard ANSI escape sequence consists of an ASCII code value of 31 with a left square bracket. Since the hexadecimal representation of 31 is x1B, the escape ends up looking like this: For example, if you want the word helloworld output to be red, the entire string sequence looks like this. 31m specifies the output to the console to be red.

"\x1B[31m helloworld"
Copy the code

For more information on the output patterns and usage of ANSI escape sequences, check out the resources. In the Logger component, the AnsiColor class implements different colors for different levels of logs.

Write in the last

This paper introduces the detailed usage method of logger log component. The printer, filter and output of Logger are introduced to the reader. The parameters and possible doubts are explained in detail. Finally, the principle of how to print color log is revealed. This is the basic composition of a logging component. Because logger components are very extensible, we can inherit logger base classes to implement our own printer, filter, and output.

If you are interested in me, please go to blogss.cn or follow programmer Xiabei to learn more.

  • If this article has helped you, please feel free to like it and follow it. This is what keeps me writing ❤️
  • Due to the author’s limited level, if there are any mistakes in this article, please correct them in the comments section ✔️
  • This article was first published in nuggets. Reprint is prohibited without permission ©️