Dart:Scalable Application Development
上QQ阅读APP看书,第一时间看更新

Logging

I reached many points of difficulty in my early software projects where the only thing left to do was to resort to logging, to figure out what was going on. Soon I grew to enjoy logging and made it a key point when starting a new project. It is an extra pair of eyes to help you test—especially when you are not looking, such as when running a data collector continuously.

The Dart package logging provides facilities for logging, however it does not do anything useful with the output, or to put it another way, the handling of the storage of the logging is entirely in the hands of the application.

A simple example of logging

Open up this chapter's Logging folder, which is a standalone example containing a range of logging:

  Logger log = new Logger('DataMonitor');

  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((LogRecord rec) {
    String logMessage = '${rec.level.name}\t${rec.time}\t\t${rec.message}';
    String exceptionMessage = '';
    if (rec.error != null) {
      print("$logMessage \t${rec.error.message}");
      print(exceptionMessage);
      print(rec.stackTrace);
    } else {
      print("$logMessage");
    }
  });

The Logger object is initialized and the level property set to Level.ALL. Setting this property gives control over the detail of logging occurring. The custom handler for an incoming log message simply prints it to standard output. Other possibilities are writing to disk, database, or a network socket:

  log.info("This is my first logging program in Dart");
  log.fine("Level 1 detail.");
  log.finer("Level 2 detail.");
  log.finest("Level 3 detail.");
  log.shout("Something bad.");

The Logger class has numerous methods for logging out at different levels, and this also helps produce readable code. By having levels of logging, this can assist with analysis of the logs file. For example, if an application's logs are being looked at to get a general overview of what is going on, the info level of logging would be examined. If a low-level bug is occurring, the approach would be to check the logging down to the finest level. Each method also allows the passing of an Exception and a stack trace:

try {
  throw new Exception("We have a problem!");
} catch (exception, stackTrace) {
  log.severe("Something really bad.", exception, stackTrace);
}

The name passed to the Logger constructor, in this case DataMonitor, identifies a unique object. Any other scope can declare a Logger object with the same name, and the same instance will be returned.

Data monitor logging

For the QuakeMonitorFS, the application launch and the calls to the web service will be recorded. Network connections are not perfect, so we will want to know if the program has not been able to access the web service. Lets have a look at the following code snippet:

Logger log;

setupLogging() {
  log = new Logger('DataMonitor');
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((LogRecord rec) {
    var entry = '${rec.level.name}\t${rec.time}\t\t${rec.message}\n';
    File logStore = new File("datamonlog.txt");
    logStore.writeAsStringSync(entry, mode: FileMode.APPEND);
  });
  log.info('Earthquake Monitor - Data fetcher. Starting...');
}

The logging is set up in bin/main.dart and the output appended to a text file. Note that this is not saved to the data sub-folder, as it may get lost with all the data files.

In the fetchData method of the DataMonitor class, the Logger object is set up in the constructor, and key events will be noted. The different levels of logging are used (info, fine, severe, and so on), which would allow an operator of the program to adjust the level of logging to the desired level.