代码执行期间报告信息:最佳设计

时间:2009-04-12 00:37:07

标签: debugging logging

在设计正确的执行报告时,我总是怀疑。

说你有以下(愚蠢,简单)的情况。我将使用python。

def doStuff():
    doStep1()
    doStep2()
    doStep3()

现在,假设你想要报告各种步骤,如果出现问题等等。不是真的调试:只是应用程序的信息行为。

第一个简单的解决方案是放置印刷品

def doStuff():
    print "starting doing stuff"
    print "I am starting to do step 1"
    doStep1()
    print "I did step 1"
    print "I am starting to do step 2"
    doStep2()
    print "I did step 2"
    print "I am starting to do step 3"
    doStep3()
    print "I did step 3"

总的来说,这非常糟糕。假设这段代码最终会进入库中。我不希望我的图书馆打印出来的东西。我希望它能默默地完成这项工作。尽管如此,有时我想提供信息,不仅在调试情况下,而且还要让用户知道事情正在完成的过程中。打印也很糟糕,因为您无法控制邮件的处理。它只是去了stdout,除了重定向之外,你无能为力。

另一个解决方案是拥有一个用于记录的模块。

def doStuff():
    Logging.log("starting doing stuff")
    Logging.log("I am starting to do step 1")
    doStep1()
    Logging.log("I did step 1")
    Logging.log("I am starting to do step 2")
    doStep2()
    Logging.log("I did step 2")
    Logging.log("I am starting to do step 3")
    doStep3()
    Logging.log("I did step 3")

这样做的好处是,您可以为您的日志记录服务找到一个独特的位置,您可以根据需要修改此服务。您可以将其静音,将其重定向到文件,stdout,甚至是网络。缺点是您与Logging模块有很强的耦合。基本上,代码的每个部分都依赖于它,并且您可以在任何地方调用日志。

第三个选项是使报表对象具有清晰的界面,并将其传递给

def doStuff(reporter=NullReporter()):
    reporter.log("starting doing stuff")
    reporter.log("I am starting to do step 1")
    doStep1()
    reporter.log("I did step 1")
    reporter.log("I am starting to do step 2")
    doStep2()
    reporter.log("I did step 2")
    reporter.log("I am starting to do step 3")
    doStep3()
    reporter.log("I did step 3")

最后,如果有更多话要说,你也可以将报告对象传递给doStepX()。 优点:它减少了与模块的耦合,但它引入了与NullReporter对象的实例化的耦合。这可以通过使用None作为默认值并在调用log之前进行检查来解决,这是笨拙的,因为在python中你必须每次都写一个条件(在C中你可以定义一个宏)

def doStuff(reporter=None):
    if reporter is not None:
        reporter.log("starting doing stuff")
        # etc...

编辑: 另一种选择是使用类Qt,并有一个emit()信号策略。当您的代码执行时,它会使用正确的状态代码发出信息,任何感兴趣的人都可以订阅信号并提供信息。很好,干净,非常分离,但需要一些编码,因为我不认为这可以快速完成包括蟒蛇电池。

最后,您可以使用有意义的错误消息引发异常,但这当然只有在您退出错误条件时才能使用。它不适用于偶尔报告。

编辑:我想澄清这样一个事实,即情况更为笼统,而不仅仅局限于一系列被调用的步骤。它还可能涉及控制结构:

 if disconnected:
     print "Trying to connect"
     connect()
 else:
     print "obtaining list of files from remote host"
     getRemoteList()

报告也可以进入实际的例程,因此你可以在connect()和getRemoteList()例程中使用“print”作为第一个语句。

因此问题是:

  • 您认为某些代码(特别是在图书馆的情况下)的最佳设计是在噪声可能对客户端造成破坏的同时保持沉默,但是在有用的时候是冗长的?
  • 如何处理逻辑代码和报告代码之间的平衡混合?
  • 代码和错误检查之间的混合已经解决,但有例外。如何从代码逻辑中划分报告的“噪音”?

编辑:更多关于思​​想的想法

我认为这不仅仅是将记录代码与逻辑代码分离的问题。我认为这也是将信息生产与信息消费脱钩的问题。已经存在类似的技术,特别是处理UI事件,但我并没有真正看到应用于日志记录问题的相同模式。


编辑:我接受了马塞洛的回答,因为他指出了事实证据表明妥协是这种情况下的最佳解决方案,而且没有灵丹妙药。然而,所有其他人都是有趣的答案,我真的很高兴赞成所有这些。谢谢你的帮助!

9 个答案:

答案 0 :(得分:4)

我认为库的最佳解决方案是添加例如

Log.Write(...)

从周围环境中拾取Log的行为(例如app.config或环境变量)。

(我也认为这是一个已经多次接近和解决的问题,虽然设计领域中有一些“甜点”,但上面的答案对于你描述的情况来说是最好的IMO。)

我认为没有任何好方法可以将代码的“正常”部分与“日志记录”部分“解耦”。记录往往是相对非侵入性的;我没有发现偶尔的Log.Write(...)会分散现实世界的代码。

答案 1 :(得分:2)

另一种选择是在没有记录的情况下编写代码,然后在执行代码之前应用一些转换来插入适当的日志语句。执行此操作的实际技术将高度依赖于语言,但与编写调试器的过程非常相似。

虽然可能不值得增加复杂性......

答案 2 :(得分:2)

我在为python搜索面向方面编程时找到了this。我同意其他海报的观点,即这些问题不应与核心逻辑相混淆。

从本质上讲,您希望放置日志记录的点可能并不总是任意的,可能是通过切入点识别“错误之前的所有点”之类的概念。可以使用简单的记录技术捕获其他完全任意点。

答案 3 :(得分:2)

我经常使用DTrace。在OS X上,python和ruby都已经设置了DTrace钩子。在其他平台上,您可能必须自己做。但是,能够将调试跟踪附加到正在运行的进程是非常棒的。

但是,对于库代码(假设您正在编写http客户端库),最好的选择是将可选的记录器作为参数传递,如您所述。 DTrace适用于在生产中(有时在其他地方)出错的情况下添加日志记录,但如果其他人可能需要访问日志来调试其随后调用您的代码,那么可选的记录器作为参数绝对是方式去吧。

答案 4 :(得分:1)

应该有工具允许样板日志消息“输入方法A带参数(1,2,3)”,“从方法B返回值X,花费10毫秒”自动(和选择性地)生成(在运行或部署时控制)。手工编写这些东西太无聊/重复/容易出错。

不确定是否有。

如果您要编写手动日志消息,请务必包含一些有用的上下文信息(用户ID,正在查看的URL,搜索查询等),以便在出现问题时获取更多信息而不仅仅是方法名称。

答案 5 :(得分:1)

我会使用自Python 2.3以来一直是标准库一部分的标准logging模块。

这样,查看代码的人很可能已经知道logging模块的工作原理。如果他们必须学习,那么至少它已被充分记录,并且他们的知识可以转移到也使用logging的其他图书馆。

是否有您想要但在标准logging模块中找不到的功能?

答案 6 :(得分:1)

我认为最简单的解决方案是最好的。这取决于语言,但只使用一个非常短的,全局可访问的标识符 - 在PHP中我使用自定义函数trace($msg) - 然后只需实现并重新实现您认为适合特定项目或阶段的代码。

自动编译器版本是标准调试器。如果你想看到有意义的标签,不幸的是你需要自己写这些标签:)

或者您可以尝试暂时将内联注释转换为函数调用,但我不确定它是否可行。

答案 7 :(得分:1)

回应您对信息生产/消费的编辑:这是一般案例的有效问题,但日志记录不是一般情况。特别是,您不应该依赖于程序某一部分的日志记录输出来影响另一部分的执行。这确实会将消费者与生产者的实施紧密联系在一起。

记录应视为主要执行的附带事项。您的系统不应该知道或关心此类日志文件的存在或内容(可能的监视工具除外)。在这种情况下,将原木“消耗”与其生产脱钩的概念是无关紧要的。由于您没有使用日志来执行任何有意义的操作,因此耦合不是问题。

答案 8 :(得分:1)

我认为有一点你必须画一条线并做出妥协。 我认为没有办法完全从系统中解除日志记录,因为你必须以某种方式发送这些消息并以某人理解的方式发送。

我会使用默认的日志记录模块,因为......它是默认模块。它有很好的文档,并附带默认库,所以这里没有依赖问题。此外,您可以避免重新发明轮子。

也就是说,如果你真的想做一些新事物,你可以成为一个全球记者对象。您可以在进程开始时进行实例化和配置(记录,不记录,重定向流等。即使在每个进程/函数/步骤的基础上)并从任何地方调用它,不需要传递它(可能在多线程环境,但这将是最小的)。

您也可以将它放在另一个线程中并捕获日志事件。

相关问题