Dart学习笔记(27):Logging日志管理

通常我们在调试代码的时候,如果程序不复杂,一般用print函数打印关键变量或提示信息,以判断程序是否正确运行。但如果代码量大、复杂,单纯的print控制起来就显得有些力不从心了,而Logging包就是为更好的输出调试信息而服务。

不同于一般的打桩法,Log最重要的特点是可以分级,如INFO、WARNING、SEVERE等等。只要控制好级别开关,就可以提供更准确的Log信息,正所谓“只要Log打得好,没有Bug解不了”。以下是Logging定义的级别,我们也可以自定义级别:

static const List<Level> LEVELS = const [
  ALL, //0
  FINEST, //300
  FINER, //400
  FINE, //500
  CONFIG, //700
  INFO, //800
  WARNING, //900
  SEVERE, //1000
  SHOUT, //1200
  OFF //2000
];

对于某个级别的Log,只有大于等于该级别的消息才会发布,否则被忽略。例如:对于INFO级别,WARNING消息可以发布,因为900>800,而ALL级别可以发布所有消息,因为其他的消息都是大于0。同理,OFF级别可以关闭所有消息。Logging包中用于日志管理的类是Logger,默认级别为INFO。

Logger中用于发布消息的函数有finestfinerfineconfiginfowarningsevereshout,参数均为(message, [Object error, StackTrace stackTrace]),虽然message没有定义类型,但“支持”的只是String。如果message是函数,会先执行该函数message = message(),接着判断message是否为字符串,如果不是,会将message保存到object中,然后执行message = message.toString()。对于自定义级别,调用log(Level logLevel, message, [Object error, StackTrace stackTrace, Zone zone])函数。

Logger中使用数据流Stream对消息记录对象LogRecord进行处理,例如:使用print打印记录的级别、时间、Message等信息,或存入log文件,保存到数据库、发送到服务器等等。而取得Stream的函数为Logger.onRecord

main.dart

import 'package:logging/logging.dart';

void main() {
  Logger log = new Logger(r"main");

  Logger.root.level = Level.WARNING;
  Logger.root.onRecord.listen((LogRecord rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
    if(rec.error != null &&
        rec.stackTrace != null) {
      print('${rec.error}: ${rec.stackTrace}');
    }
  });

  log.config("x=5");
  log.info("对x进行赋值");
  log.warning("x是double类型");
  log.severe("网络连接失败");

  try {
    var y = x/3;
  } catch(error, stackTrace) {
    //捕获Error及堆栈信息
    log.shout("Main.dart Line 35, var y = x/3", error, stackTrace);
  }
}

运行结果:

WARNING: 2016-06-07 01:11:27.970721: x是double类型
SEVERE: 2016-06-07 01:11:27.976721: 网络连接失败
SHOUT: 2016-06-07 01:11:27.978721: Main.dart Line 35, var y = x/3
No top-level getter 'x' declared.

NoSuchMethodError: method not found: 'x'
Receiver: top-level
Arguments: [...]: #0      NoSuchMethodError._throwNew (dart:core-patch/errors_patch.dart:163)
#1      main (file:///E:/DartProject/Note27/main.dart:24:13)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:261)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:148)

Logging中的各个Logger并不是孤立的,而是以节点树的形式存在。在我们new Logger的时候,名称不能以点号开头,并且每个节点以 . 分隔,而根节点是空字符串Logger(“”)。用节点来表示的话,路径(fullName)则是 root —> main —> warning —> 20160606。表达式的返回值为叶节点。当我们设置级别Level的时候,默认只能设置root,然后所有节点生效,例如:

Logger log = new Logger("main.warning.20160606");
Logger logX = new Logger("main.info.20160606");
//下面两个表达式作用一样,都是对根节点级别进行设置
new Logger("").level = Level.WARNING;
Logger.root.level = Level.WARNING;

assert(log.name == "20160606");
assert(log.level == Level.WARNING);
assert(logX.level == Level.WARNING);

Logging中的Logger使用了工厂模式,所有new的Logger在同一个树中,因此上面的log和logX可以用下图表示。

日志消息数据流使用了广播订阅数据流,进行监听的时候,可以多次调用listen注册回调函数,并且所有回调函数都会生效。并且,根节点的listen作用于所有节点。如果要清除某个listen,可以通过listen函数返回的StreamSubscription对象进行close关闭操作。如果要清除所有的listen,loger.clearListeners()可以实现。

默认设置非根节点级别的时候,会抛出异常。如果要对每个节点设置不同的级别,可以打开hierarchicalLoggingEnabled开关。这时候如果我们设置某节点Level,则该节点以及children的Level都会设置为同一值,但不影响父节点。例如:

Logger logA = new Logger("main");
Logger logB = new Logger("main.info");
Logger logC = new Logger("main.info.2016");

hierarchicalLoggingEnabled = true;

Logger.root.level = Level.ALL;
logB.level = Level.INFO;

按上面的设置,root —> logA —> logB —> logC的级别分别为ALL、ALL、INFO、INFO。

总的来说,Logging包的使用很简单,调试程序的过程中很实用。但是在处理日志消息的时候,我们需要自己在listen监听函数中实现,比如直接print输出、保存到.log文件或发送到Socket服务器等,这点略显不便。

本文出自“Dart语言中文社区”,允许转载,转载时请务必以超链接形式标明文章原始出处
本文地址:
http://www.cndartlang.com/870.html

发表评论

登录后才能评论