分布式调试框架

187 views
Skip to first unread message

raymond

unread,
Nov 29, 2009, 5:06:52 AM11/29/09
to TopLanguage
分布式调试是一个颇为困难的话题。
hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
大家能否介绍一下对分布式调试框架的感受,比如,
开源/产品,分布式调适框架
很好的介绍分布式调试的文章
分布式调试网站
个人认为分布式调试的应该至少提供哪些功能

谢谢

Shuo Chen

unread,
Nov 29, 2009, 8:21:47 AM11/29/09
to TopLanguage
先说说你的想法。
你"认为分布式调试的应该至少提供哪些功能"?

Fuzhou Chen

unread,
Nov 29, 2009, 4:02:19 PM11/29/09
to pon...@googlegroups.com
在我的经验中合理编写的log能更有效地反映程序当时执行的次序。如果log写得比较好那么一般是犯不着麻烦调试器出马的。

多进程(包括单机和多机)难以调试的多数原因往往是开发人员不会写或者干脆就是懒得写log导致出问题时无法得到正确的执行和交互逻辑。而多进程程序中因为各种竞争条件之类引起的bug难以重现的问题要比单机程序严重得多。所以我在公司交接任何项目时第一个会问的问题永远是:log在哪里?

如果楼主认为还有比log更方便的东西,在下洗耳恭听。


2009/11/29 Shuo Chen <gian...@gmail.com>



--
《采莲》·江南

为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。

另抄自蒜头的评论:http://www.douban.com/review/1573456/

  且祭一束紫琳秋,为一段落花流水的传说
  且饮一杯青花酒,为一场几多擦肩的错过
  且焚一卷旖旎念,为一腔抛付虚无的惜怜
  且歌一曲罢箜篌,为一刻良辰春宵的寂寞

sagasw

unread,
Nov 29, 2009, 8:20:23 PM11/29/09
to pon...@googlegroups.com
多线程写log,会不会影响性能啊?

还是说你写的log是分线程输出的?如果是,如何判断前后顺序?

2009/11/30 Fuzhou Chen <cppo...@gmail.com>

Fuzhou Chen

unread,
Nov 30, 2009, 12:22:21 AM11/30/09
to pon...@googlegroups.com
首先我得说一句:多线程和多进程是两个概念,不能随便替换。可能我孤陋寡闻,但我从来没听说过什么分布式程序是以线程组织的。

Bingo!用性能做盾牌似乎是我认识的所有程序员试图偷懒时首选的通用借口,我们公司的一干人等也都喜欢用这个忽悠老板。

对此我的看法很简单:

a) 毫无疑问,写log必定会影响性能。
b) 恰恰是因为log对性能的影响不可忽略,所以更应该在产品开发阶段就开始计划,比如选择何种log机制,哪些代码段出于性能考虑不应输出log等等。
c) Log是一种设施,不能简单地理解为就是打开一个文件写输出。很多性能敏感的地方需要采用一些相当复杂的手法才能保证log的高效率和准确性,所以log必须作为软件设计的一部分考虑。

关于“多线程输出log”这个问题,我不想这么快从方向讨论掉落到技术细节里去。但我们确实有一些log框架本身就是为多进程环境设计的,比如UNIX的 syslog。另外关于分布式系统的log设施设计,也许sagasw可以参考一下这篇文章:http://www.devx.com/Java /Article/7999


我认为log重要的理由是,log实际上是唯一能满足如下三个条件的调试方法:a)程序员充分可控,b)部署成本相对较低,c)能真实反应程序流程。剩下的一些常用方法或者只能反映瞬态,比如出问题后调试dump;或者程序员无法控制,比如一直挂着调试器跑程序,又或者因为版权和硬盘开销不适合部署到生产环境,比如微软的iDNA。

当然我的观点基于这样的一个假设,就是多进程程序特别是多机环境下难以用简单步骤重现的bug比例很高,而很多多机环境的程序中性能瓶颈大部分情况下来自数据库或网络,而不是log。如果都是一些简单的bug的话,那么log这东西未必有多重要。


2009/11/29 sagasw <sag...@gmail.com>

Jianshi Huang

unread,
Nov 30, 2009, 1:20:40 AM11/30/09
to pon...@googlegroups.com
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:

> 在我的经验中合理编写的log能更有效地反映程序当时执行的次序。如果log写得比较好那么一般是犯不着麻烦调试器出马的。
>
> 多进程(包括单机和多机)难以调试的多数原因往往是开发人员不会写或者干脆就是懒得写log导致出问题时无法得到正确的执行和交互逻辑。而多进程程序中因为各种竞争条件之类引起的bug难以重现的问题要比单机程序严重得多。所以我在公司交接任何项目时第一个会问的问题永远是:log在哪里?
>
> 如果楼主认为还有比log更方便的东西,在下洗耳恭听。
>

Trace. User-space + Kernel-space. 本质上是 log,不过是编译器和 kernel 生成的。

有人写的 log 那更好,特别是输出一些全局变量(系统状态)。

>
> 2009/11/29 Shuo Chen <gian...@gmail.com>
>>
>> 先说说你的想法。
>> 你"认为分布式调试的应该至少提供哪些功能"?
>>
>> On Nov 29, 6:06 pm, raymond <shiqu...@gmail.com> wrote:
>> > 分布式调试是一个颇为困难的话题。
>> > hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
>> > 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
>> > 大家能否介绍一下对分布式调试框架的感受,比如,
>> > 开源/产品,分布式调适框架
>> > 很好的介绍分布式调试的文章
>> > 分布式调试网站
>> > 个人认为分布式调试的应该至少提供哪些功能
>> >
>> > 谢谢
>
>
> --
> 《采莲》·江南
>
> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> 另抄自蒜头的评论:http://www.douban.com/review/1573456/
>
>   且祭一束紫琳秋,为一段落花流水的传说
>   且饮一杯青花酒,为一场几多擦肩的错过
>   且焚一卷旖旎念,为一腔抛付虚无的惜怜
>   且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>

--
黄 澗石 (Jianshi Huang)
http://huangjs.net/

Tinyfool

unread,
Nov 30, 2009, 1:23:40 AM11/30/09
to pon...@googlegroups.com
多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
当然调试器也会有类似的影响。

2009/11/30 Fuzhou Chen <cppo...@gmail.com>



--
Tinyfool的开发日记 http://www.tinydust.net/dev/
代码中国网 http://www.codechina.org
myTwitter: http://twitter.com/tinyfool

Fuzhou Chen

unread,
Nov 30, 2009, 1:37:05 AM11/30/09
to pon...@googlegroups.com

2009/11/29 Tinyfool <tiny...@gmail.com>
多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
当然调试器也会有类似的影响。


正是因为这种影响的存在,所以我才说log不是想加就加想去掉就去掉的,而是必须在系统设计的时候作为设计的一部分进行考虑,什么地方可以用,什么地方不 能用,都必须有一个明确的设计决定。

从这个意义上来说,在我看来一些非常通用的编程惯例都是有问题的,比如编译一个带log的debug版本和一个不带log的release版本,然后把release版本发布给用户。我们怎么知道两个版本的行为一定是相同的?事实上我在工作中也确实见到很多因为这个原因最后不得不不了了之的bug报告。

如果非要较这个真的话,不妨想想看在一个多线程——好吧大家似乎都很喜欢把“分布式”偷偷换成这个概念,我只好跟了,幸亏对大部分讨论没有影响——加一个断点对时序的影响吧,整个消息队列的行为就可能被完全改变。

而且就如我说的,调试器本身还会带来一个部署的问题,很多情况下生产环境下并不允许随便装软件的,而且对于那种在复杂条件下才能触发的bug,难道我们能要求用户运行程序的时候永远开着调试器么?

 

Fuzhou Chen

unread,
Nov 30, 2009, 1:40:46 AM11/30/09
to pon...@googlegroups.com
既然trace本质上还是log,那么这不算是“比log更方便“的东西。

而另一方面我担心的是,既然trace需要依赖内核的支持,如果楼主设想的场景中一个分布式系统需要跑在不同的操作系统上,我怎么保证不同系统给出来的trace保持相同的格式?

2009/11/29 Jianshi Huang <jiansh...@gmail.com>

殷远超

unread,
Nov 30, 2009, 1:41:16 AM11/30/09
to pon...@googlegroups.com
对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。

2009/11/30 Tinyfool <tiny...@gmail.com>
多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
当然调试器也会有类似的影响。

Fuzhou Chen

unread,
Nov 30, 2009, 1:45:50 AM11/30/09
to pon...@googlegroups.com
恐怕殷兄的说法恰恰证明了只有log才能解决问题,而调试毫无办法。

这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?

2009/11/29 殷远超 <yinyu...@gmail.com>

Jianshi Huang

unread,
Nov 30, 2009, 1:49:45 AM11/30/09
to pon...@googlegroups.com
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:
> 既然trace本质上还是log,那么这不算是“比log更方便“的东西。
>

有本质区别,一个是人写,信息不全,一个是机器帮你生成,无遗漏,信息全(函数调用程度上)。

> 而另一方面我担心的是,既然trace需要依赖内核的支持,如果楼主设想的场景中一个分布式系统需要跑在不同的操作系统上,我怎么保证不同系统给出来的trace保持相同的格式?
>

不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。

Fuzhou Chen

unread,
Nov 30, 2009, 2:04:09 AM11/30/09
to pon...@googlegroups.com


2009/11/29 Jianshi Huang <jiansh...@gmail.com>

2009/11/30 Fuzhou Chen <cppo...@gmail.com>:
> 既然trace本质上还是log,那么这不算是“比log更方便“的东西。
>

有本质区别,一个是人写,信息不全,一个是机器帮你生成,无遗漏,信息全(函数调用程度上)。

恐怕我的看法恰恰相反:我认为机器生成的东西遗漏的信息太多所以不可信任。因为实际调试中我们需要的信息并不仅仅是函数调用程度,而是需要更细致的定制信息。比如精确到某个变量在进入某个if语句的if块和else块时的取值。而且程序员也需要输出一些其他的信息用来作为提示而用。

就我工作上所知,Windows自Vista开始的很多系统服务dll内部的确有这个trace,但首先它还远远没有精确到这个程度,而且就我接触的部分而言这些trace也一样是写那些DLL的程序员写在代码里的,只不过是用一个通用的log框架记录在系统某处,然后还需要靠一些专门的工具

莫非Linux下现在已经有能智能分析指定变量在所有分支上的当前值的工具了?我很多年没读Linux内核了,如果有的话,可否给个连接?
 
> 而另一方面我担心的是,既然trace需要依赖内核的支持,如果楼主设想的场景中一个分布式系统需要跑在不同的操作系统上,我怎么保证不同系统给出来的trace保持相同的格式?
>

不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。

唉,做应用开发的我研究syscall干什么?其实真的调试时候我们最关心的往往是我们自己的代码里哪个变量在某个位置的值是什么,它之前经过了几个while几个if,如果某个系统的trace能替我们自动做这个的话,我还真的抓住阁下非要问个明白了,这对我这种管维护的人来说可是福音。

 



--
黄 澗石 (Jianshi Huang)
http://huangjs.net/

Tinyfool

unread,
Nov 30, 2009, 2:05:42 AM11/30/09
to pon...@googlegroups.com
没有log就会产生的竞争条件,不解决往往是很危险的。因为在某机器上log的效率下降恰好把竞争条件掩盖了,而随着用户机器的升级,更快的cpu和io下,这些竞争条件可能还会重新出现。所以,debug有log没有问题,那就给release版本也加上log来解决竞争条件是非常不负责任的。而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。

2009/11/30 Fuzhou Chen <cppo...@gmail.com>

Fuzhou Chen

unread,
Nov 30, 2009, 2:12:28 AM11/30/09
to pon...@googlegroups.com
看来这是我没表述清楚,或者TinyFool有偷换概念的嫌疑,呵呵。

我不是说有了bug靠加上log解决就认为是fix掉了,这的确是混蛋而且不负责任的办法。我上面说的那番话的核心是:

>>>>>
    如果release版本有了log仍然能出现<Fuzhou: 对不起我漏写了,这里应该加“bug”>,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?
<<<<<

也就是说,我根本不同意只把log加在debug版本中,它应该也必须出现在release版本里,而且我们在内部开发时根本不应该存在debug版本。只有这样我们才能保证内部调试的版本和用户环境的版本是保持一致的,在出现问题的时候才能直接从release版本本身得到真实准确的信息。


2009/11/29 Tinyfool <tiny...@gmail.com>

Fuzhou Chen

unread,
Nov 30, 2009, 2:19:06 AM11/30/09
to pon...@googlegroups.com
关于这个问题我已经反复表明了我的立场:恰恰是因为性能下降无法忽略,所以log本身必须作为设计的一部分加以考虑。我同意不是什么地方都允许加log的,但我们也不能因为log有性能上的开销把它们全盘清除出我们的程序。

或者我们反过来想想:在我们说log的性能开销不能忽略的时候,有什么人能够用性能测试的数据证明某段程序中由于log的存在而导致性能达不到指标?恕我直言,在这个问题上我看到的更多地是一拍脑袋做的主。

2009/11/29 Tinyfool <tiny...@gmail.com>
而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。


Jianshi Huang

unread,
Nov 30, 2009, 2:20:22 AM11/30/09
to pon...@googlegroups.com
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:

> 恐怕我的看法恰恰相反:我认为机器生成的东西遗漏的信息太多所以不可信任。因为实际调试中我们需要的信息并不仅仅是函数调用程度,而是需要更细致的定制信息。比如精确到某个变量在进入某个if语句的if块和else块时的取值。而且程序员也需要输出一些其他的信息用来作为提示而用。
>

你说的局部变量的问题可以用 backtrace 显示,和有没有tracing 信息无关。如果你用到全局变量,可以用局部变量引用一下,照样可以有很全的信息。

信息是越多越好,不过我不认为哪些信息必须用人工才能输出,能否举个例子?


> 就我工作上所知,Windows自Vista开始的很多系统服务dll内部的确有这个trace,但首先它还远远没有精确到这个程度,而且就我接触的部分而言这些trace也一样是写那些DLL的程序员写在代码里的,只不过是用一个通用的log框架记录在系统某处,然后还需要靠一些专门的工具
>

跑到 kernel space 里,没kernel space 的调试工具你的程序自然啥也干不了。

> 莫非Linux下现在已经有能智能分析指定变量在所有分支上的当前值的工具了?我很多年没读Linux内核了,如果有的话,可否给个连接?

这就是 linux 的好处,你可以自己编译内核,在指定的地方加入你要的 probe 不就可以了?有 linux
内核的高手吗?能否说明一下。我个人暂时还没有项目需要我调试内核。不过对于 SA 来说,Kernel Tracing + Flight
Record 是很有用的技术。

>> 不是所有的 kernel tracing 都需要内核支持,如果你只想知道 syscall 的话,类似 strace
>> 就可以了。至于跨平台跑分布式。。。还是在编译器里搞吧,我没经验。
>
> 唉,做应用开发的我研究syscall干什么?其实真的调试时候我们最关心的往往是我们自己的代码里哪个变量在某个位置的值是什么,它之前经过了几个while几个if,如果某个系统的trace能替我们自动做这个的话,我还真的抓住阁下非要问个明白了,这对我这种管维护的人来说可是福音。
>

If you do network programming ... syscall tracing can be useful.
当然,不是说就没有其他方法,既然系统提供了那么方便的工具,干嘛不用?

你做管理的?建议你看看南京 Fujitsu 他们在做的 Flight Record (黑匣子)项目,就是说系统 crash 的时候把
kernel 的时序上一定时间内的log保存下来。

Fuzhou Chen

unread,
Nov 30, 2009, 2:37:21 AM11/30/09
to pon...@googlegroups.com


2009/11/29 Jianshi Huang <jiansh...@gmail.com>

2009/11/30 Fuzhou Chen <cppo...@gmail.com>:

> 恐怕我的看法恰恰相反:我认为机器生成的东西遗漏的信息太多所以不可信任。因为实际调试中我们需要的信息并不仅仅是函数调用程度,而是需要更细致的定制信息。比如精确到某个变量在进入某个if语句的if块和else块时的取值。而且程序员也需要输出一些其他的信息用来作为提示而用。
>

你说的局部变量的问题可以用 backtrace 显示,和有没有tracing 信息无关。如果你用到全局变量,可以用局部变量引用一下,照样可以有很全的信息。

信息是越多越好,不过我不认为哪些信息必须用人工才能输出,能否举个例子?


好,这回遇上高人了,呵呵。

敢问Jianshi说的可是GNU glibc的backtrace()函数?如果是这个的话,那问题就没有解决:程序员仍然要自己决定在程序的哪里加这个调用,需要多频繁。

另外我们说了这么就trace,jianshi可否给个列表说一下你说的这个东西是需要什么工具的?跑在哪个平台上?
 

> 就我工作上所知,Windows自Vista开始的很多系统服务dll内部的确有这个trace,但首先它还远远没有精确到这个程度,而且就我接触的部分而言这些trace也一样是写那些DLL的程序员写在代码里的,只不过是用一个通用的log框架记录在系统某处,然后还需要靠一些专门的工具
>

跑到 kernel space 里,没kernel space 的调试工具你的程序自然啥也干不了。


跑题一下,系统服务不等于kernel啊,比如Windows DNS和Active Directory就有这个trace,只是一个daemon级别的service而已。但是它写的log是留在系统里看不见的,需要一些工具dump出来并且转成文本格式。这个月调这个东西,里面给的trace信息太少,痛苦得要死。

 

> 莫非Linux下现在已经有能智能分析指定变量在所有分支上的当前值的工具了?我很多年没读Linux内核了,如果有的话,可否给个连接?

这就是 linux 的好处,你可以自己编译内核,在指定的地方加入你要的 probe 不就可以了?有 linux
内核的高手吗?能否说明一下。我个人暂时还没有项目需要我调试内核。不过对于 SA 来说,Kernel Tracing + Flight
Record 是很有用的技术。


根据公司雇佣协议要求,我不能阅读Linux内核,三年前就out了。现在我的工作平台是Windows,所以Linux那套对我来说恐怕参考意义不大。

其实自己编译内核也没解决我说的问题。商业软件开发的典型模式是自己内部测好了就交给用户用,我们总不能要求用户帮我编译一个内核吧?而且如果说是出了问题重新编译一个内核再加上一些probe,那和Debug/Release双版本模式有什么区别呢?我怎么知道问题不会因为我加了几个probe而无法重现?

Fuzhou Chen

unread,
Nov 30, 2009, 2:39:11 AM11/30/09
to pon...@googlegroups.com
Faint, SCIM在Netbook机器上老出问题,少了几个字:

敢问Jianshi说的可是GNU glibc的backtrace()函数?如果是这个的话,
那问题就没有解决:程序员仍然要自己决定在程序的哪里加这个调用,需要多频繁的调用。而这说白了就不是机器来生成了。


2009/11/29 Fuzhou Chen <cppo...@gmail.com>

Jianshi Huang

unread,
Nov 30, 2009, 3:25:34 AM11/30/09
to pon...@googlegroups.com
2009/11/30 Fuzhou Chen <cppo...@gmail.com>:

> 敢问Jianshi说的可是GNU
> glibc的backtrace()函数?如果是这个的话,那问题就没有解决:程序员仍然要自己决定在程序的哪里加这个调用,需要多频繁。
>

No, 我的开发语言是 Common Lisp,编译器也不是 gcc。但是做法是类似的。

至于何时调用 backtrace? 那当然是程序出错的时候。比如某个函数的 precondition
不满足,资源耗尽,等。这个时候我一般输出资源使用情况(这个其实定期会输出) + backtrace。问题是,backtrace (或者
dump)只包含当前的状态,有些情况下比如多线程有racing情况下你是无法只用它来找出原因的。

所以 tracing 就很有用了。tracing 对理解程序的行为也是很有帮助的。


> 另外我们说了这么就trace,jianshi可否给个列表说一下你说的这个东西是需要什么工具的?跑在哪个平台上?
>

我自己其实在做一个 Common Lisp (Linux) 平台下的 flight record。暂时用到的工具是:

- Common Lisp 的 trace (implementation dependent)
- Strace
- SystemTap

有专门的进程负责收集 log,保存到 ring-buffer。现在的问题是 overhead 太大,不过能用是关键。

也在看 LTTng,不过没什么进展。

我所有的开发都在 Linux 下。

>
> 跑题一下,系统服务不等于kernel啊,比如Windows DNS和Active
> Directory就有这个trace,只是一个daemon级别的service而已。但是它写的log是留在系统里看不见的,需要一些工具dump出来并且转成文本格式。这个月调这个东西,里面给的trace信息太少,痛苦得要死。
>

原来如此,closed source 就没办法了,你问问微软看看有没有 log 版本。

> 其实自己编译内核也没解决我说的问题。商业软件开发的典型模式是自己内部测好了就交给用户用,我们总不能要求用户帮我编译一个内核吧?而且如果说是出了问题重新编译一个内核再加上一些probe,那和Debug/Release双版本模式有什么区别呢?我怎么知道问题不会因为我加了几个probe而无法重现?
>

说实在的,尚未用到需要重新编译内核才能开启的功能。tracing 和 log 一样,目的是帮你 locate
问题代码片段。有了输入输出再加一点时序,开始读代码吧。就如 log 一样,trace 什么内容对性能有很大影响,自己看适合不适合吧。

>
>
> --
> 《采莲》·江南
>
> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> 另抄自蒜头的评论:http://www.douban.com/review/1573456/
>
>   且祭一束紫琳秋,为一段落花流水的传说
>   且饮一杯青花酒,为一场几多擦肩的错过
>   且焚一卷旖旎念,为一腔抛付虚无的惜怜
>   且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>

--

Fuzhou Chen

unread,
Nov 30, 2009, 4:37:54 AM11/30/09
to pon...@googlegroups.com


2009/11/30 Jianshi Huang <jiansh...@gmail.com>

2009/11/30 Fuzhou Chen <cppo...@gmail.com>:
> 敢问Jianshi说的可是GNU
> glibc的backtrace()函数?如果是这个的话,那问题就没有解决:程序员仍然要自己决定在程序的哪里加这个调用,需要多频繁。
>

No, 我的开发语言是 Common Lisp,编译器也不是 gcc。但是做法是类似的。

至于何时调用 backtrace? 那当然是程序出错的时候。比如某个函数的 precondition
不满足,资源耗尽,等。这个时候我一般输出资源使用情况(这个其实定期会输出) + backtrace。问题是,backtrace (或者
dump)只包含当前的状态,有些情况下比如多线程有racing情况下你是无法只用它来找出原因的。

所以 tracing 就很有用了。tracing 对理解程序的行为也是很有帮助的。


这可是第一回看到Common Lisper出马了,失敬失敬。

不过这个回答就麻烦了:如此说来,这个backtrace是用在程序出错的时候,用来获得系统一部分信息的了。

现在的问题是,在C/C++或者任何命令式程序中,我们需要的不仅仅是出错的时候某个值的是什么,我们还希望知道它在还没出错的时候取值是怎么变化的,以及是怎么一步一步地走到一个错误的值来的。当bug难以简单重现的时候,如果程序log能够包含这些信息,则可以大大方便维护人员定位错误。
  ——所以我特别讨厌release版本里把log去掉的做法,偏偏这却是很多项目开发的“共识”。

我能理解这个问题在函数式编程中不容易出现,因为你可以要求程序员不用 set!, let* 和 begin (抱歉我只知道scheme的关键字,不过道理是一样的),这样所有的数据都只能作为常量对待,而调用次序没有依赖关系,相对来说调试起来能容易很多。



> 另外我们说了这么就trace,jianshi可否给个列表说一下你说的这个东西是需要什么工具的?跑在哪个平台上?
>

我自己其实在做一个 Common Lisp (Linux) 平台下的 flight record。暂时用到的工具是:

- Common Lisp 的 trace (implementation dependent)
- Strace
- SystemTap

有专门的进程负责收集 log,保存到 ring-buffer。现在的问题是 overhead 太大,不过能用是关键。

也在看 LTTng,不过没什么进展。

我所有的开发都在 Linux 下。

说得好,能用是关键。

我认为一个合理的开发模式应该是:一开始把log都加在Release版本里,然后实际运行起来,如果发现性能上有问题,再根据比如gprof之类的工具做性能分析。如果真的确定瓶颈出现在log上,再根据设计指标在对应的代码区中去掉一部分log。根据80/20准则,我不相信所有的log都应该被去除掉才能满足性能要求。

可是事实上我见到的*所有*项目几乎都是另外一套:一开始就说我们要一个Release和Debug版本,只有Debug版本有log,Release版本不能加,而且理由千篇一律都是“为了性能”。然后一个典型的测试方案是:

a) 照着Debug版本的log设计一堆test case。
b) 在Debug版本和Release版本上都跑一遍。
c) 验证表明Debug版本的log输出信息没有显示问题,Release版本跑完也全PASS。
d) 产品组非常Happy地宣布没有问题,然后就把Release版本发给用户了。

可是测试是一个永远完不了的事情,内部做得再完善也总有一些很难覆盖的项目被漏掉,比如因为成本和时间不允许所以太复杂的测试不能做(我们曾接到过必须安装上千台Windows域控制器环境才能重现的bug,而我们整个组的机器也不过就几百台),或者需要和某个第三方软件交互才能触发特定的错误。而用户的环境千奇百怪,他们往往能上报一些内部测试中无法发现的bug。

这个时候维护人员就被迫处在非常尴尬的境地,因为Release版本什么信息也没有,直接分析非常困难;而如果直接把Debug版本发给用户,其实我们也不止一次地发现Debug版本无法重现问题。虽然我们内部口口相传了一整套歪门邪道用来在这种清况下获取需要的程序信息,但是这个时候大把的时间就花掉了,而且在一些极端情况下也不一定管用。客户和我们都不满意,但我们什么也做不了。

扯远了,呵呵。

 

>
> 跑题一下,系统服务不等于kernel啊,比如Windows DNS和Active
> Directory就有这个trace,只是一个daemon级别的service而已。但是它写的log是留在系统里看不见的,需要一些工具dump出来并且转成文本格式。这个月调这个东西,里面给的trace信息太少,痛苦得要死。
>

原来如此,closed source 就没办法了,你问问微软看看有没有 log 版本。

要求他们加log也没有用。首先这不是bug,其次产品已经卖出来了,他们不会为了方便某个无聊的已经进入维护期的项目专门给我build一个版本。我真的发信求助过,完全没有回音。
 


> 其实自己编译内核也没解决我说的问题。商业软件开发的典型模式是自己内部测好了就交给用户用,我们总不能要求用户帮我编译一个内核吧?而且如果说是出了问题重新编译一个内核再加上一些probe,那和Debug/Release双版本模式有什么区别呢?我怎么知道问题不会因为我加了几个probe而无法重现?
>

说实在的,尚未用到需要重新编译内核才能开启的功能。tracing 和 log 一样,目的是帮你 locate
问题代码片段。有了输入输出再加一点时序,开始读代码吧。就如 log 一样,trace 什么内容对性能有很大影响,自己看适合不适合吧。


这个我不反对,呵呵。事实上我也有类似的经验,一般能大概定位到问题的大概片段之后就得开始读代码了,经验之谈。

raymond

unread,
Nov 30, 2009, 10:50:57 AM11/30/09
to TopLanguage
看了回帖两个感受:
1. 不幸的是,我的问题描述的不清楚。
2. 幸运的是,看到一场关于log的精彩讨论。

几位兄弟在讨论log的时候主要还是在讨论单线程或者单击多线程问题,讨论的重点也是经典问题:
1. log要不要?
2. log如何产生?系统来做还是用户来做?
3. 执行顺序和变量状态哪个更有利于debug?这恰好反应两位的不同工作场景

关于上面分布式是多线程还是多进程的问题
分布式显然是多进程的,但是当我们在单机上进行分布式调试的时候,是用多线程模拟的,我们总不可能人人配备集群来debug程序。

我假定的分布式模型是MAP-REDUCE,假设就在hadoop上跑程序。
工作场景如下:
100台机器,1000个process,数据将流过task A,然后流过task B,然后是task C,最后结束。A,B,C相当于是三类进
程,不是特定的进程。
现在B的某个进程出现了问题

1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?

2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
可能。。。,

问题2跟问题1颇为类似,但是不同。问题1是说如何构建分布式debug框架,属于宏观问题。问题2是说,即使有了分布式调试框架,应该记录哪些信息,
是微观问题。

我知道某些general的解决方案,写输出,读输出,这肯定能解决,而且无论多么高效优雅的解决方案,其底层也一定是这么干的,但是,那个比较高效优
雅的方案在哪里?

On 11月30日, 下午5时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/30 Jianshi Huang <jianshi.hu...@gmail.com>
>
> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>:

Fuzhou Chen

unread,
Nov 30, 2009, 2:58:10 PM11/30/09
to pon...@googlegroups.com
呵呵怎么着我也是把问题带跑的那个人,既然楼主都发话了我们还是回到正题吧。
 
 
首先我得说我没有做MapReduce类型应用的经验,我现在的工作集中在Windows活动目录这一块,特征是低平均通信强度下的服务器LDAP数据库信息同步。所以我的观点基本上基于我的背景,可能和楼主期望的场景有所不同,如果有不同意见欢迎指出。
 
2009/11/30 raymond <shiq...@gmail.com>


1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
我的观点很简单:
 
a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试 + Code Coverage + Fuzzying。
c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
 

2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
可能。。。,

我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
 
没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug——道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
 
关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
 
我自己有时候用一个山寨版的简化方案:比如每个程序还是在本机写log,但测试最后我会通过文件共享把这些log收集到单独的一台机器上,然后通过一些script分析时间戳并组合到单独的一个文件里。只要测试不要求修改系统时间(比如NTP协议的测试,这个我也做过),这个方案基本上还是可行的。
 
 
至于调试能够稳定重现的bug,不知道在这里的同学们有没有机会用过微软的iDNA,http://blogs.msdn.com/cse/attachment/1077668.ashx。它可以很方便地抓取程序执行中每一个汇编指令执行时的程序瞬态的内存信息,然后存成一个dump。利用这个dump我们就可以在Windbg里虚拟地对进程的执行情况做调试,甚至能做到单步后退,算是我见过的最好的产品化的调试工具。但这个工具最大的限制是硬盘空间,因为它生成的dump文件大小会随着程序执行时间快速膨胀,所以它不适用于需要长时间运行的程序,也很难用于一些硬盘空间有限制的生产环境。
 
 
我不知道Linux下有没有类似的技术,有高手可以说明一下否?

redsea

unread,
Nov 30, 2009, 9:16:00 PM11/30/09
to TopLanguage
> > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?

我现在做的服务器有类似的功能, 我们管它叫作 replay, 需要保存的东西
1. 所有 os platform api, 包括 文件, ioctl, 等等
这就包括了, 和其他进程的通信, 现在我们用的是 tcp socket
2. 未必能够重复的 c library function 例如, rand
3. 所有时间相关的东西, 包括所有时间相关的系统调用, 以及所有被封装的 rdtsc 调用, 需要专门处理.
这个需要专门的处理, 包括获取时间(当前时刻时间值, 时间片长度), 以及sleep
4. 我们现在的程序没有, 但是如果其他程序有的话, 也需要处理的: 和其他进程进行同步(例如 mutex 等)

另外, 程序还不能是多线程的, 因为 replay 过程中, 你无法控制多线程的调度次序.

最麻烦的不是保存数据, 而是 程序的时间控制/主调度框架, 因为这和正常执行毕竟是不一样的, 只要有一点点区别, 由于蝴蝶效应, 后面就
会完全不一样了.

这个方法还有一个麻烦的问题在于 (大概只是对于 C/C++), linux clib 每次跑, malloc 分配的地址空间是不同的
(大概是堆扰乱防止容易被攻击? 但是我没有看到取消点), 某些非法访问的操作, replay 过程中, 未必会在同一个地方出现, 如果提早
出现了还好, 如果晚出现了, 这次 replay 可能就不出错了. ( 这个问题我还没有深入分析, 如果是 c library 造
成, 可以用替换 malloc 库的方法解决, 如果是 linux kernel 造成, 可能就需要想更深入的方法了.)

> 我的观点很简单:
>
> a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> + Code Coverage + Fuzzying。
> c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。

确实如此.


> > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。

如果面向大众的服务器, 呵呵, 这也是不能做到的.

>
> 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。

说得好 :)
我也是因为好几个 log 在开发环境无法重现, 而在生产环境时不时冒出来, 才费力去开发一套 replay 机制, replay 也是
挺费时间的, desktop cpu + overclock 比生产环境快不到 50%, 生产环境平均 cpu 利用率 50%, 那么生产环境
跑6天出的错误, replay 环境需要跑完第二天才能到, 挺费力的一件事情.


> 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。

log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
( printf or io stream).

至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊. 如果是要加上错误
情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的. ( gcc 学习 linux kernel 的
likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).


Fuzhou Chen

unread,
Nov 30, 2009, 10:37:07 PM11/30/09
to pon...@googlegroups.com

2009/11/30 redsea red...@gmail.com


 
  log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
( printf or io stream).

  至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊.   如果是要加上错误
情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的.  ( gcc 学习 linux kernel 的
likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).

这也是我想说的。我们不是为了log而写log,它只是用于保证代码质量的众多工具中的一种,如果确实性能指标不允许,我们当然可以选择换一种方式。
 
在之前的争论中,我一直强调的是这种方式的选择应当是以确实的测试数据为依据,而不是动不动就拿“降低性能”作借口一笔带过。因为相对于后期花在反复调试上的时间而言,这些额外的工作量我认为是完全值得的。
 
当然如果有兄弟说:我们公司的开发组从不需要承担任何维护责任,好吧那当我什么也没说——别笑,我知道的一家非常知名的公司的拳头产品就是这么组织团队的,名字恕我不便公开。
 
多谢Redsea的例子,这样我的论点就不太像是胡说八道了。:) 而且你的replay框架应该很适合Raymond参考。
 
 

phpxer

unread,
Nov 30, 2009, 11:40:44 AM11/30/09
to TopLanguage
根据我的经验,也来回答一下 raymond 的三个问题:
1. log要不要?
log是必须的,不然在出现问题的时候你哭死,但无可奈何。 尤其是在产品分发给很多客户使用的场合。在产品环境下是不能debug的,只有log或者
专门的控制台等。
2. log如何产生?系统来做还是用户来做?
log产生基本上都是写文件。 log应该有一个稳妥的方法,在产品环境下可以通过配置或者控制台打开,并根据不同的级别和模块来控制日志的量和大小。
将log细致到不同的模块是由意义的,大部分情况下不需要所有的log都记录,不同的模块需要的日志的详细程度也不一样。在默认情况下,也需要一些日志
来监控系统的负载,主要状态,对重要的错误做记录。
3. 执行顺序和变量状态哪个更有利于debug?这恰好反应两位的不同工作场景
两种方式都很重要。这根本不是问题,debug时我们经常要注意这两种,而在设计良好的日志里,完全应该放映出执行顺序。一个请求从接收到结束处理的过
程中,通常都有唯一性的标识的,如果没有,也应该设计一个。

> > ----所以我特别讨厌release版本里把log去掉的做法,偏偏这却是很多项目开发的"共识"。

Linker

unread,
Dec 1, 2009, 3:06:59 AM12/1/09
to pon...@googlegroups.com
调试是windows 程序员的想法

On 11/29/09, raymond <shiq...@gmail.com> wrote:
> 分布式调试是一个颇为困难的话题。
> hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
> 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
> 大家能否介绍一下对分布式调试框架的感受,比如,
> 开源/产品,分布式调适框架
> 很好的介绍分布式调试的文章
> 分布式调试网站
> 个人认为分布式调试的应该至少提供哪些功能
>
> 谢谢
>

--
Sent from my mobile device

Regards,
Linker Lin
linker...@gmail.com

Fuzhou Chen

unread,
Dec 1, 2009, 4:06:04 AM12/1/09
to pon...@googlegroups.com
水一下,Linker这话打击面太大了啊……

2009/12/1 Linker <linker...@gmail.com>

jun lin

unread,
Dec 1, 2009, 4:24:47 AM12/1/09
to pon...@googlegroups.com
有问题当然要调试的说。。。
unit test,log统统加上。
那种gdb之类断点调试。。。有的时候还是要用。

2009/12/1 Fuzhou Chen <cppo...@gmail.com>

stefan

unread,
Dec 1, 2009, 9:13:42 AM12/1/09
to TopLanguage
非常赞同这个观点,我也有个人体会。
1、调试器往往在解决小点的项目时比较有用,当程序规模增大时候,可能效果就不那么明显了
2、对于大型系统,特别是分布式的,当部署到正式环境的时候,出了问题基本只有靠log和代码review了

On 11月30日, 下午2时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/29 Tinyfool <tinyf...@gmail.com>


>
> > 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> > 当然调试器也会有类似的影响。
>
> 正是因为这种影响的存在,所以我才说log不是想加就加想去掉就去掉的,而是必须在系统设计的时候作为设计的一部分进行考虑,什么地方可以用,什么地方不
> 能用,都必须有一个明确的设计决定。
>
> 从这个意义上来说,在我看来一些非常通用的编程惯例都是有问题的,比如编译一个带log的debug版本和一个不带log的release版本,然后把release版本发布给用户。我们怎么知道两个版本的行为一定是相同的?事实上我在工作中也确实见到很多因为这个原因最后不得不不了了之的bug报告。
>
> 如果非要较这个真的话,不妨想想看在一个多线程——好吧大家似乎都很喜欢把“分布式”偷偷换成这个概念,我只好跟了,幸亏对大部分讨论没有影响——加一个断点对时序的影响吧,整个消息队列的行为就可能被完全改变。
>
> 而且就如我说的,调试器本身还会带来一个部署的问题,很多情况下生产环境下并不允许随便装软件的,而且对于那种在复杂条件下才能触发的bug,难道我们能要求用户运行程序的时候永远开着调试器么?
>
>
>

> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>


>
> >> 在我的经验中合理编写的log能更有效地反映程序当时执行的次序。如果log写得比较好那么一般是犯不着麻烦调试器出马的。
>
> >> 多进程(包括单机和多机)难以调试的多数原因往往是开发人员不会写或者干脆就是懒得写log导致出问题时无法得到正确的执行和交互逻辑。而多进程程序中因为各种竞争条件之类引起的bug难以重现的问题要比单机程序严重得多。所以我在公司交接任何项目时第一个会问的问题永远是:log在哪里?
>
> >> 如果楼主认为还有比log更方便的东西,在下洗耳恭听。
>

> >> 2009/11/29 Shuo Chen <giantc...@gmail.com>


>
> >> 先说说你的想法。
> >>> 你"认为分布式调试的应该至少提供哪些功能"?
>
> >>> On Nov 29, 6:06 pm, raymond <shiqu...@gmail.com> wrote:
> >>> > 分布式调试是一个颇为困难的话题。
> >>> > hadoop是一款开源的分布式系统,其上面提供了一点点分布式调试方面的用法,但是我感觉多集中在细节层面的处理,比如方便查看log文件,重新启动
> >>> > 一个失败的instance等等,但是缺乏统一的系统层面的调试框架。
> >>> > 大家能否介绍一下对分布式调试框架的感受,比如,
> >>> > 开源/产品,分布式调适框架
> >>> > 很好的介绍分布式调试的文章
> >>> > 分布式调试网站
> >>> > 个人认为分布式调试的应该至少提供哪些功能
>
> >>> > 谢谢
>
> >> --
> >> 《采莲》·江南
>
> >> 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> >> 另抄自蒜头的评论:http://www.douban.com/review/1573456/
>
> >> 且祭一束紫琳秋,为一段落花流水的传说
> >> 且饮一杯青花酒,为一场几多擦肩的错过
> >> 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> >> 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> > --
> > Tinyfool的开发日记http://www.tinydust.net/dev/

> > 代码中国网http://www.codechina.org

stefan

unread,
Dec 1, 2009, 9:21:32 AM12/1/09
to TopLanguage
那我想请问:如果release版本没有log信息,长程序在用户那里出现问题时,程序员应该如何进行定位,难度还要让用户进行现场恢复,
程序员则去取个debu版本过来吗?
或者在用户那里开启调试工具,我想,这些场景似乎都不应该出现吧。

正如你说的,当用户机器慢时没有问题(暂且认为正常),当用户机器升级后暴露了问题,如果有日志,正好可以帮助进行定位,为什么不呢?

在这样大系统中,合理设计大log系统并不会成为系统的瓶颈吧。


On 11月30日, 下午3时05分, Tinyfool <tinyf...@gmail.com> wrote:
> 没有log就会产生的竞争条件,不解决往往是很危险的。因为在某机器上log的效率下降恰好把竞争条件掩盖了,而随着用户机器的升级,更快的cpu和io下,这些竞争条件可能还会重新出现。所以,debug有log没有问题,那就给release版本也加上log来解决竞争条件是非常不负责任的。而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。
>

> 2009/11/30 Fuzhou Chen <cppof...@gmail.com>


>
>
>
> > 恐怕殷兄的说法恰恰证明了只有log才能解决问题,而调试毫无办法。
>
> > 这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?
>

> > 2009/11/29 殷远超 <yinyuanc...@gmail.com>


>
> >> 对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。
>

> >> 2009/11/30 Tinyfool <tinyf...@gmail.com>


>
> >>> 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> >>> 当然调试器也会有类似的影响。
>
> > --
> > 《采莲》·江南
>
> > 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> > 另抄自蒜头的评论:http://www.douban.com/review/1573456/
>
> > 且祭一束紫琳秋,为一段落花流水的传说
> > 且饮一杯青花酒,为一场几多擦肩的错过
> > 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> > 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> --

stefan

unread,
Dec 1, 2009, 9:26:16 AM12/1/09
to TopLanguage
公司还规定不能阅读linux内核,这么BT啊??

On 11月30日, 下午3时37分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 2009/11/29 Jianshi Huang <jianshi.hu...@gmail.com>
>
> > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>:
>

raymond

unread,
Dec 1, 2009, 12:52:10 PM12/1/09
to TopLanguage
redsea兄这套方法非常切合我的想法,再问几个细节:

1. 你是把所有的重要调用过程全部记录下来,然后replay的时候,按照顺序重新调用吗?比如,之前发送了一个消息,现在也要发送一个一样的;之前
做了排序,现在也做一遍?如果是这样,这个replay框架是不是非常大和复杂?是不是要支持很多类似反射的东西?是不是针对每个功能点还要写支持
replay附加操作?

2. 需要保存的东西包括:
平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?

还有一个问题想问,我们现在的log格式多是
时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
议。


能不能举个贯通的例子说明一下。十分感谢。

raymond

unread,
Dec 1, 2009, 12:58:03 PM12/1/09
to TopLanguage
说到code coverage,我之前一直觉得code coveage最多是代码优雅方面的问题(写的不好的代码当然code coverage
差,bug多),难道这个跟debug还有很大关系?兄弟明说一下。

看了一下介绍,iDNA基本是调试单机程序的,而且应该适合调试那种逻辑多,数据少的程序,而分布式调试一般处理的是逻辑不多,数据量大的程序。

On 12月1日, 上午3时58分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 呵呵怎么着我也是把问题带跑的那个人,既然楼主都发话了我们还是回到正题吧。
>
> 首先我得说我没有做MapReduce类型应用的经验,我现在的工作集中在Windows活动目录这一块,特征是低平均通信强度下的服务器LDAP数据库信息同步。所以我的观点基本上基于我的背景,可能和楼主期望的场景有所不同,如果有不同意见欢迎指出。
>

> 2009/11/30 raymond <shiqu...@gmail.com>


>
>
>
> > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>
> 我的观点很简单:
>
> a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> + Code Coverage + Fuzzying。
> c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>
>
>
> > 2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
> > 可能。。。,
>
> > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>

> 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。

Fuzhou Chen

unread,
Dec 1, 2009, 2:27:05 PM12/1/09
to pon...@googlegroups.com
我想我们对Code coverage的定义似乎有偏差。在我看来code coverage和优雅与否毫无关系。
 
(顺便说一句,我个人不喜欢拿“优雅”这种过于主观的概念作为评判程序好坏的标准)
 
我所说的code coverage是通过运行测试计算在本次测试中覆盖了多少产品代码。我们公司内部有一些专门的工具用来做这个,通过在编译器和函数库级别注入一些代码,得到一个专门的code coverage版本。只要在这个版本上运行测试就可以自动生成一些数据,记录在本次测试中有多少代码被覆盖到,一般可以精确到代码块级别。也就是说我可以知道一个if语句的then块和else块究竟哪一个被覆盖到。
 
Linux也有gcov,不过我没怎么用过。如果有熟悉的同学不妨介绍一下。
 
在我的工作中,Code coverage主要的作用有两个:
 
a) 衡量某个测试方案是否存在漏洞。
 
    在我看来Code coverage高不等于测试的质量一定高,但Code Coverage低则必然说明测试质量低下。因为覆盖率低表明测试中遗漏了功能点,换句话说很多代码根本没经过测试。
 
b) 管理回归测试。
 
    我们公司内部有一个数据库用来保存每个测试和产品代码的对应关系,以及产品代码之间的调用关系。这样当一个新的fix被引入时,我们可以通过数据库来查询究竟有多少相关的其他产品代码可能被这个fix影响,从而确定这一次fix需要增加哪些测试。
 
对于个人开发而言,b) 可能已经超出我们力所能及的范围了。不过用gcov验证一下测试的质量还是有好处的,呵呵。

 
2009/12/1 raymond <shiq...@gmail.com>

Fuzhou Chen

unread,
Dec 1, 2009, 2:53:30 PM12/1/09
to pon...@googlegroups.com
这话题是越发沉重了啊。以下可能有些离题,Raymond别见怪。
 
毋庸讳言,写出真正能对调试有帮助的log的工作量实际上是很大的。而从我的工作经验上看,我只能很遗憾地说不写log很大程度上出于两个原因:
 
a) 开发组把写log看作是一种纯粹的负担,而不是为了保证产品的长期可维护性必须引入的步骤。
 
    我知道的为数不少的公司,开发组写完代码只负责函数级别的Unit test,剩下的功能/集成测试和维护完全靠测试组完成。很多时候测试功夫不到家,只能发现简单的bug,这时开发组凭着自己对产品的了解往往可以快速解决。既然测试组抓不出必须有log才能解决的bug,开发组就可以堂而皇之地拒绝测试组对log的需求。
 
    造成这种问题的另一个原因是开发组不理解测试的难度所在,他们把测试当作是一种可以像添加功能一样进度可知的东西,或者把unit test等同于功能测试。我所知道的很多开发人员很不理解为什么即使测试通过了的产品,发布出去之后仍然能发现bug。他们喜欢说:如果出了问题就说明你们这些测试是吃干饭的。——很无奈,但他们中的很多人自己甚至不测试自己的代码。
 
b) 有时候不写log纯粹是因为某个自以为是的老板认为这样可以不暴露商业机密——我知道这种政治问题本不该由我们操心,但人在江湖,没办法。

2009/12/1 stefan <pengl...@gmail.com>

redsea

unread,
Dec 1, 2009, 8:55:21 PM12/1/09
to TopLanguage
On Dec 2, 1:52 am, raymond <shiqu...@gmail.com> wrote:
> redsea兄这套方法非常切合我的想法,再问几个细节:
>
> 1. 你是把所有的重要调用过程全部记录下来,然后replay的时候,按照顺序重新调用吗?比如,之前发送了一个消息,现在也要发送一个一样的;之前
> 做了排序,现在也做一遍?如果是这样,这个replay框架是不是非常大和复杂?是不是要支持很多类似反射的东西?是不是针对每个功能点还要写支持
> replay附加操作?

你想想, 软件无非是一个状态机(有限或者无限状态就不知道了), 我每次给出同样的输入内容, 他应该给出同样的输出.
所以, 记录初次运行你给他的所有输入, 以及它给出的输出; replay 的时候, 再次给它同样的输入, 看看输出是否有变就知道了.

"之前发送了一个消息" 被测软件的发送? replay 的时候, 这样的东西, 不是用来调用的, 是用来检查和初次运行的结果是否相同的.

排序? 这种东西不是程序边界的东西, 你管它干什么? 除非你告诉我, 你是通过 com/xpcom 调用一个外部程序来排序.

你只找出程序和程序之外的所有边界api, 看看哪些边界api 调用, 结果是依赖于被测软件之外的因素, 不可以重复的, 控制这些api.

例如我们认为 c lib 是外部, 那么 snprintf 是可以重复的, 每次给同样的参数, 它会给同样的输出, 这个api 就不用控制,
gettimeofday/timeGetTime 这种是不能够重复的, 你给它同样的参数, 它不保证同样的输出, 你就需要在初次运行的时候保
存 参数, 结果, errno, replay 的时候, 给出同样的结果, 设置同样的 errno 不用调用这个 api.

被控制的函数, 调用被 redirect 到 normal run OR run & save OR load and return 的
代码, 这样, 一套代码就可以在几个模式下运行了. 我用 C++, 没有反射可以用, 也用不着, 将被控制的函数改成 函数指针 OR
interface 的一个成员就 ok 了.

这已经很细节了, 再详细就到代码了, 说来就没有意思了.

>
> 2. 需要保存的东西包括:
> 平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?
>
> 还有一个问题想问,我们现在的log格式多是
> 时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
> 议。

我们的软件中, replay log 和 log 是两套东西, 一套给机器用, 一套给人看. 当然, 也可以从 replay
log 中进行业务分析.

replay log 是直接存struct 的, 避免解析字符串. 同时 replay log 很大, 每512M 就开新文件, 同时后
台用7zip压缩旧文件 (就这样, 一天还会有二十多G 压缩结果).

save 的时候, 对我们的系统, 这套机制大概要消耗 10% 的 cpu 处理能力, 还能够接受.

> 能不能举个贯通的例子说明一下。十分感谢。

我觉得上面已经很清楚了.

redsea

unread,
Dec 1, 2009, 9:01:39 PM12/1/09
to TopLanguage
On Dec 1, 10:13 pm, stefan <pengli.h...@gmail.com> wrote:
> 非常赞同这个观点,我也有个人体会。
> 1、调试器往往在解决小点的项目时比较有用,当程序规模增大时候,可能效果就不那么明显了
> 2、对于大型系统,特别是分布式的,当部署到正式环境的时候,出了问题基本只有靠log和代码review了


调试器对解决有时序要求的问题, 都没有什么作用, 除非时间粒度非常大. 对于调试竞态问题, 也没有用.

对于稍复杂的系统, 特别是分布式的, 时序相关的问题肯定是存在的, 这部分问题, 调试器自然没有用.

而时序, 竞态问题, 正属于难处理的问题, 调试器都发挥不出用处.

对于调试器可以发挥作用的问题, 每次在调试器上单步, watch 消耗的精力, 都是一次性的, 没有积累的, 曾经花费的努力, 对于以后的质量
保证没有任何帮助.

而如果将这些精力里面的大部分, 转移到单元测试, 覆盖测试, 自动测试, 自查错, log 代码上, 就是有积累的, 以后程序越来越容易保证质
量.

Fuzhou Chen

unread,
Dec 1, 2009, 9:56:45 PM12/1/09
to pon...@googlegroups.com
从Redsea的描述上看,他的框架应该是很接近于dmalloc一类的工具,用一批自己写的函数替换真正的函数(源代码替换还是修改ELF入口我暂时看不出来),然后准备跑两次:
 
第一次:读入参数并转调真正的函数,然后记录输入和返回值。
第二次:接受参数并转调真正的函数,然后将返回的结果和对应的第一套函数当初记录的值对比,验证是否相同。
 
其中第二次运行时替换函数似乎也可以作为一个空架子,不调真正的函数而是直接返回期望的值,如此就可以做replay使用。
 
 
从Raymond老兄问的问题上看,我猜他希望的是一个零侵入的框架。最好原来的代码都别动,然后找个工具外部监控一下,就能输出log做debug了。但如果是这样的话请恕我泼一盆冷水,我认为零侵入是不可能的,而且这个框架不可能很简单。如果要做到我上面说的功能,对调用函数的代码做一定的修正恐怕无法避免。
 
早年很多C函数库也用这个思路,把调用的函数用自己的宏做一个包装,根据需要修改宏定义,就可以把实际调用的函数换掉。既然Raymond立即提到反射,那么说明他常用的环境应该是C#或Java。这些语言里没有能像C里那样起作用的宏,所以估计反射是唯一有效的方法。
 
2009/12/1 redsea <red...@gmail.com>

up duan

unread,
Dec 1, 2009, 11:20:01 PM12/1/09
to pon...@googlegroups.com
看到大家讨论的热火朝天的,不免心痒。看来大家对Log的作用开始认识统一起来了,趁机推销一下我写的一个Log工具:)【附件就是】

使用时如下:
std::vector<int> logIds_;

    //设置log设施
    Util::Log::Sink* fileSink = new Util::Log::Sink();
    Util::Log::Destination* fileDst = new Util::Log::FileDestination("log.log"); //normal file
    fileSink->SetDestination(fileDst);
    Util::Log::Formatter* fileFmt = new Util::Log::Formatter();
    fileSink->SetFormatter(fileFmt);
    int fileSinkId = Util::Log::AddProcess(fileSink, -1);
    logIds_.push_back(fileSinkId);

    Util::Log::Sink* consoleSink = new Util::Log::Sink();
    Util::Log::Destination* consoleDst = new Util::Log::FileDestination(); //console stdout
    consoleSink->SetDestination(consoleDst);
    Util::Log::Formatter* consoleFmt = new Util::Log::Formatter();
    consoleSink->SetFormatter(consoleFmt);
    int consoleSinkId = Util::Log::AddProcess(consoleSink, fileSinkId);
    logIds_.push_back(consoleSinkId);

    Util::Log::Sink* traceSink = new Util::Log::Sink();
    Util::Log::Destination* traceDst = new Util::Log::TraceDestination(); //TRACE
    traceSink->SetDestination(traceDst);
    Util::Log::Formatter* traceFmt = new Util::Log::Formatter();
    traceSink->SetFormatter(traceFmt);
    int traceSinkId = Util::Log::AddProcess(traceSink, consoleSinkId);
    logIds_.push_back(traceSinkId);

    Util::Log::Sink* uiSink = new Util::Log::Sink();
    Util::Log::Destination* uiDst = new Util::Log::UIDestination(); //MessageBox
    uiSink->SetDestination(uiDst);
    Util::Log::Formatter* uiFmt = new Util::Log::Formatter();
    uiSink->SetFormatter(uiFmt);
    int uiSinkId = Util::Log::AddProcess(uiSink, traceSinkId);
    logIds_.push_back(uiSinkId);

...
    Util::Log::Log(...);
...


最后,销毁log设施
    for (size_t i = 0; i < logIds_.size(); ++i) {
        Util::Log::RemoveProcess(logIds_[i]);
    }


2009/12/2 Fuzhou Chen <cppo...@gmail.com>
Log.zip

woo

unread,
Dec 1, 2009, 9:53:12 PM12/1/09
to pon...@googlegroups.com
说实话我觉得这个说法很扯蛋
尤其是在跟人协作的时候,简单的举个例子
有一次,我发现下面有库总是异常的崩溃,看代码死活看不出问题,日志也完全ok,但是似乎在某一个时刻,数据突然被改变了
没办法,那就gdb吧,单步跟踪的时候就发现数据的确被其他线程改变了,通过设置watch point才发现,原来是其他模块的函数导致的数据写越界
象这种问题,调试是最快的解决方案,如果考自己通读代码,查出问题来的时候,黄花菜都凉了。

我见过有些程序员超级喜欢打日志,但是实际上这些日志做的就是调试器做的工作----看变量,然后看到日志刷得到处都是,真想找点有用的信息的时候,
很长的"!!!!!!!!!!!"或者'****************'满天飞,有时候还得grep来grep去,麻烦的很。

另外我觉得windows程序的调试很方便,尤其是运行时设置断点的功能

Fuzhou Chen

unread,
Dec 2, 2009, 5:16:00 AM12/2/09
to pon...@googlegroups.com
说句笑话,任何认为某种说法很扯淡的说法我都认为很扯淡。

我很难反驳woo的例子,因为我去年负责UI测试的时候也曾经通过单步跟踪查出过一些多线程相关的bug(基本上是因为double free引起的),但我绝对不会因此认为单步跟踪和断点是最有效的调试多进程/多线程协作的手段,因为同样在我手上的反面的例子实在太多。

唯一能指出的是,woo的例子中两个线程似乎并不存在真正意义上的“协作”,因为你所提到的唯一协作是一个错误的,非预期的行为,而且从你的描述上看,你设置断点的时候并没有影响时序关系。相反地,Redsea和我试图描述的是多进程环境下各个进程之间存在大量交互行为,并且时序要求非常严格的情况下,我们如何找到其中的某个错误的交互行为。

所以,恐怕我们谈的根本是两个不同的领域。



2009/12/1 woo <woos...@gmail.com>

Fuzhou Chen

unread,
Dec 2, 2009, 6:04:25 AM12/2/09
to pon...@googlegroups.com
嘿嘿,UP 之前和我吵过一次架,上次是我说话他抬杠,不过这一次看来我必须得抬一次杠了。

接下来我说话会很重,但请相信我绝无人身攻击或打击报复的意思。


首先我表明立场,如果我是team lead,我不会选择这样设计的log。我甚至不会允许这样的代码进入我的代码树。


理由 1:框架华丽,但充满了用不上的功能和几乎不可能用得到的扩展点。

    在我看来这个框架中至少以下几个类和方法没有存在的意义:

    Formatter
    看来UP是希望将来需要的时候修改log格式。我想问的是,我们真的
    需要修改log格式么?Log的主要读者是软件管理员,维护人员和开发者。也就是说
    Log的读者都是计算机技能相对熟练的人,对他们来说,Log格式优美与否并不是
    重点。现实世界的例子我也可以举,Windows NTP系统时间服务器w32time和活
    动目录服务的log格式自NT4到现在的Win7,完全没有改变过,因为不需要。

    我想UP会立即跳起来拍砖,说这个Formatter类是为了将来给别的软件使用时方便
    他们自己定义格式的。——可正如我之前所说,格式不重要。另外,Log这东西并
    不是程序设计的重点,我必须得说,我从未见过一个严肃的产品开发组把自己工作
    正常的的Log代码换成另一个而保持原有输入格式不变,因为这没有产出效益。

    Processor
    按我的理解,UP在试图通过Processor多播支持多数据源和数据输出。
    但我想问的是,为什么我需要把log同时输出到文件、数据库和网络上去?对程序员
    而言,只要有一份准确的log就足够分析了,连续增加三份一模一样的log不会给
    调试带来任何帮助,而且这样进一步加重了Log本身的开销。

    另一个牢骚发生在这里:由于Processor这个公共基类的存在,Source和Sink
    被强迫使用同一个Process(Entry) 方法,而偏偏Source的参数实际上不需要
    Entry,所以代码里使用了一个dummyEntry。

    如果大家还能回想起当初我曾和UP争论过的话题,这就是一个例子:为形式而
    形式的框架会对后来的人员理解造成困难,因为当他们先看到
    Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
    图理解为什么这个输入的Entry似乎被神秘地忽略了。

        Entry const Source::Process(Entry const& dummyEntry) {
            Entry entry = parser_->Parse(receiver_->Read());
            return Processor::Process(entry);
        }

    Sink
    说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
    会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
    没有存在的理由,直接用Destination就够了。


理由 2:基本功能缺失

    简单地说是Entry不支持可变长参数。

    从代码里我们可以看到每次调用Util::Log::Log()时程序员只能输入
    一个entry,而entry实际上就是一个字符串的只读包装。可程序员
    怎么输出一个整型或构体变量的值呢?是不是程序员得一次次地调
    CString.format()或stringstream?如果是这样的话我估计没有什
    么人会喜欢这个东西。

理由 3:基本功能设计错误。

    我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
    类。如果照这样写法,那么这个log类在写log的时候别的进程是不
    能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
    的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
    理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。

    可移植的做法应当是写一行开关一次文件。这也就是为什么很多程序
    员不喜欢log的原因,因为确实效率不高。但没办法,这是唯一可移植
    也是唯一可靠的方法。

    另一个设计错误是Log::Update()。UP的代码里没有这个例子,那么
    我就得问是谁负责调用Log::Update()?是程序员在Log写入一段时间
    后主动调用么?如果是这样,那么请问这个Log框架能否在程序由于
    crash而无法调用Update()时保证将尚未写入的信息写入数据库?如果
    不能,那么我只能说这个log无法满足起码的健壮性需要。



总结陈词:

以一个天天要和各式各样的Log打交道的测试人员的眼光看,一个实用的Log
实现应该是:

a) 需要明确的功能实现,比如如何管理网络连接,数据库,文件等等。
b) 不需要复杂的扩展。

而这个Log框架正好相反:

a) 除了文件写入(带有严重bug)之外没有提供任何其他实际功能
b) 提供了远远超出预期的扩展性,而且这个扩展性还以一定程度上的
    代码阅读困难为代价。

所以它仅仅是一个Design Pattern风格的完美框架展示。但很遗憾,我们
需要的从来都不是框架。




2009/12/1 up duan <fix...@gmail.com>

Shuo Chen

unread,
Dec 2, 2009, 6:20:32 AM12/2/09
to TopLanguage
基本同意。
另,请评判 google-glog。

up duan

unread,
Dec 2, 2009, 6:39:37 AM12/2/09
to pon...@googlegroups.com


2009/12/2 Fuzhou Chen <cppo...@gmail.com>

嘿嘿,UP 之前和我吵过一次架,上次是我说话他抬杠,不过这一次看来我必须得抬一次杠了。

接下来我说话会很重,但请相信我绝无人身攻击或打击报复的意思。


首先我表明立场,如果我是team lead,我不会选择这样设计的log。我甚至不会允许这样的代码进入我的代码树。


理由 1:框架华丽,但充满了用不上的功能和几乎不可能用得到的扩展点。

    在我看来这个框架中至少以下几个类和方法没有存在的意义:

    Formatter
    看来UP是希望将来需要的时候修改log格式。我想问的是,我们真的
    需要修改log格式么?Log的主要读者是软件管理员,维护人员和开发者。也就是说
    Log的读者都是计算机技能相对熟练的人,对他们来说,Log格式优美与否并不是
    重点。现实世界的例子我也可以举,Windows NTP系统时间服务器w32time和活
    动目录服务的log格式自NT4到现在的Win7,完全没有改变过,因为不需要。

    我想UP会立即跳起来拍砖,说这个Formatter类是为了将来给别的软件使用时方便
    他们自己定义格式的。——可正如我之前所说,格式不重要。另外,Log这东西并
    不是程序设计的重点,我必须得说,我从未见过一个严肃的产品开发组把自己工作
    正常的的Log代码换成另一个而保持原有输入格式不变,因为这没有产出效益。

    Processor
    按我的理解,UP在试图通过Processor多播支持多数据源和数据输出。
    但我想问的是,为什么我需要把log同时输出到文件、数据库和网络上去?对程序员
    而言,只要有一份准确的log就足够分析了,连续增加三份一模一样的log不会给
    调试带来任何帮助,而且这样进一步加重了Log本身的开销。

sorry,我设想的目标场景跟你不一样。我的参照物是现有市场上的log系统,比如:java的log系统或者c++的log系统,他们都有大量的扩展点,而且侵入性比较强。我只是想展示一下其实可以很少侵入的提供更强的扩展性的log体系而已:)。我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,你可以只用一个而不用为任何没有用的付出任何代价,不是么?log是不是一模一样完全依赖于formatter,是吧。
 

    另一个牢骚发生在这里:由于Processor这个公共基类的存在,Source和Sink
    被强迫使用同一个Process(Entry) 方法,而偏偏Source的参数实际上不需要
    Entry,所以代码里使用了一个dummyEntry。

我知道你的问题,这儿也是我的一个问题,其实最初我设想的只是没有Source的体系,后来有人提议说能做log服务器么?我就又加入了一个Source,当然,难免有割裂之感。
ps:说实话,我真的没有设想到log服务器的意义和作用:)。
 
    如果大家还能回想起当初我曾和UP争论过的话题,这就是一个例子:为形式而
    形式的框架会对后来的人员理解造成困难,因为当他们先看到
    Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
    图理解为什么这个输入的Entry似乎被神秘地忽略了。

        Entry const Source::Process(Entry const& dummyEntry) {
            Entry entry = parser_->Parse(receiver_->Read());
            return Processor::Process(entry);
        }


再说一次Source是为log服务器而生的。
 
    Sink
    说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
    会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
    没有存在的理由,直接用Destination就够了。

呵呵,或许Formatter能够把log转换为一条SQL语句?而Destination可以直接执行它?谁知道呢?并且,我这个框架设计的时候同事的反应并不是理解困难【实际上理解起来并不是太困难,不是么?你这么快就理解了:)】,而是有没有xx功能?能不能加上xx功能?
 

理由 2:基本功能缺失

    简单地说是Entry不支持可变长参数。

    从代码里我们可以看到每次调用Util::Log::Log()时程序员只能输入
    一个entry,而entry实际上就是一个字符串的只读包装。可程序员
    怎么输出一个整型或构体变量的值呢?是不是程序员得一次次地调
    CString.format()或stringstream?如果是这样的话我估计没有什
    么人会喜欢这个东西。


我觉得没有任何理由不能为Entry提供构造函数吧。甚至变长参数的构造函数也是可以的啊。我只是暂时没有这方面的需求而已。其实,Entry本身都应该是抽象的,才能为各种场景下的特异性提供适应性。
 
理由 3:基本功能设计错误。

    我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
    类。如果照这样写法,那么这个log类在写log的时候别的进程是不
    能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
    的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
    理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。

我不清楚你说的“写log的时候“是指什么时候?是写的那一刹那呢?还是从打开文件到关闭文件?我不知道是不是那一刹那不能打开log文件,但是我自己可以在任何时候能打开它。不需要把进程关掉。

    可移植的做法应当是写一行开关一次文件。这也就是为什么很多程序
    员不喜欢log的原因,因为确实效率不高。但没办法,这是唯一可移植
    也是唯一可靠的方法。

或许我的那个方法不可移植,但是我在WinCE上用的时候没有你说的那个xian限制。
 
    另一个设计错误是Log::Update()。UP的代码里没有这个例子,那么
    我就得问是谁负责调用Log::Update()?是程序员在Log写入一段时间
    后主动调用么?如果是这样,那么请问这个Log框架能否在程序由于
    crash而无法调用Update()时保证将尚未写入的信息写入数据库?如果
    不能,那么我只能说这个log无法满足起码的健壮性需要。


我得好好想想,Update?嗯,没有这个函数,最初log是一个纯粹的写入数据库的一个东西,有严格的结构。后来发现其实不实用,所以已经完全废弃了。
 

总结陈词:

以一个天天要和各式各样的Log打交道的测试人员的眼光看,一个实用的Log
实现应该是:

a) 需要明确的功能实现,比如如何管理网络连接,数据库,文件等等。
log不应该跟任何业务有关系,所以,我不认为log系统需要关心: 如何管理网络连接,数据库,文件等等

b) 不需要复杂的扩展。
在特定的场景下,扩展是无用的。但是,作为一个log库的提供者,我不能假定任何场景。It’s so that。


而这个Log框架正好相反:

a) 除了文件写入(带有严重bug)之外没有提供任何其他实际功能
bug或许需要更细致的测试,无论是你还是我。我只提供一个你看起来不实际的功能,那就是记录。
 
b) 提供了远远超出预期的扩展性,而且这个扩展性还以一定程度上的
    代码阅读困难为代价。

或许扩展性超出了你的预期,但是未必超出我的预期。
代码阅读有多困难?至少你粗略的扫描就已经搞定了。
 
所以它仅仅是一个Design Pattern风格的完美框架展示。但很遗憾,我们
需要的从来都不是框架。


我不知道它符合什么模板。但是,我们确实需要。

woo

unread,
Dec 2, 2009, 6:05:05 AM12/2/09
to pon...@googlegroups.com
本来说的就是不同的事情,我说的是调试在某些程序上解决问题的很好方式,跟操作系统无关.

Serenade

unread,
Dec 2, 2009, 5:38:41 AM12/2/09
to pon...@googlegroups.com
log是个挺必要的东西,但是可以视具体产品做不同处理,比如,客户端程序,那么记录error就可以了,程序崩溃的时候还可以借助dump来分析;服务器系统,那么尽可能记录得详细一点,并且能保存数据库就保存数据库,除非资金成问题或者性能实在太重要,那么才关闭日志,反正把日志系统做成可以配置的就行了

Serenade

unread,
Dec 2, 2009, 5:41:00 AM12/2/09
to pon...@googlegroups.com
补充一下,正常情况下,具体使用环境有调试环境的不多

Tinyfool

unread,
Dec 2, 2009, 10:27:55 AM12/2/09
to pon...@googlegroups.com
我之前的回复只针对有人说多线程情况下,log是最好的手段的说法。

事实上,我并不偏向任何一种方法,复杂的问题,log,调试器,profile,memdump什么都用上才能解决的情况我也遇到过。

调试问题上,本质上也没有什么银弹,把各种工具都掌握好,在恰当的时候用吧。

简单的说优劣:
log-劣:
1、对多线程来说有副作用,可能会掩盖某些竞争条件(减慢了速度造成程序不出错,但是遇到快速的机器和io还是要出问题)。
2、很多程序员不知道该输出什么不输出什么,经常看到无数不知所云的log,反而增加调试的复杂度
3、加错位置(忘了关闭,等等)可能大幅降低效率(我们分词器内部实现里面有个调试log,程序员发布到真实环境去了,查询效率下降到惊人的程度)。
4、只能事后去看,不能即使查看
优:
1、在debug/开发/实际环境中都可以用。
2、不用停止程序
3、可以做统计分析

调试器-劣:
1、不能安装在生产服务器和客户的机器上
2、多线程下对时序有影响
3、必须把程序停下来
优:
1、可以把程序停在某个点上,所有的内部状态都可以查询


2009/12/2 Serenade <seren...@gmail.com>
补充一下,正常情况下,具体使用环境有调试环境的不多



--
Tinyfool的开发日记 http://www.tinydust.net/dev/
代码中国网 http://www.codechina.org
myTwitter: http://twitter.com/tinyfool

Fuzhou Chen

unread,
Dec 2, 2009, 3:11:52 PM12/2/09
to pon...@googlegroups.com
首先说一句:如下内容和Raymond的原帖关系已经不大了,但我实在觉得有公开讨
论的必要,如果不喜欢的同学请忽略此帖子。
 
 
 
忧喜参半,喜是很高兴能得到正面的回应,忧是看来咱们俩的分歧不是代码细节上的。

 
2009/12/2 up duan fix...@gmail.com



sorry,我设想的目标场景跟你不一样。我的参照物是现有市场上的log系统,比如:java的log系统或者c++的log系统,他们都有大量的扩展点,而且侵入性比较强。我只是想展示一下其实可以很少侵入的提供更强的扩展性的log体系而已:)。我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,你可以只用一个而不用为任何没有用的付出任何代价,不是么?log是不是一模一样完全依赖于formatter,是吧。
 
 
没有付出代价?我不同意,对你而言确实如此,但你让你的用户付出了惨重的代价:
 
a) 效率:本来一个函数操作完成的任务现在变成了遍历链表 +
    两次虚函数调用。本来log就不是一个高效的操作,这一下效率更低。
 
b) 后来人的阅读开销——这是我反复强调的。如果一个东西没有用,它就
    ***绝对不应该***出现在代码里。这东西会对维护和将来扩展造成持续的
    负面作用。
 
至于市场上的log系统,我不敢说自己非常熟悉Log4J,我承认它也提供了很
多诸如XXXAppender的类负责把信息输出到文件、Socket等等等等。但是
Log4J的每一个Appender都是一个明确定义的类,而不是个抽象的interface,
所以它才是一个实用的工具。详情可参阅http://logging.apache.org/log4j/1.2/apidocs/index.html
 
而你的代码里所有的各种Destination只有文件是有实现的,剩下的你打算
怎么办?让用你的Log框架的人去实现?如果是我的话,我宁可直接用已有的。
 
 
再提一句:对于Log这种特定目的的应用,扩展性不重要。因为Log这种东西
的定位非常特殊,它本身是产品代码不可或缺的一部分,但却对产品的功能
只有***反贡献***(主要是效率),所以它必须避免在编写、阅读和部署的时
候付出太大的精力,因为程序员不会有兴趣花大把的时间去考虑怎么实现一
堆逻辑来适应log的接口。
 
作为一个Log库,其必须做到接口简单,并且功能完备。我不能要求你同意我的
看法,但我确实是这么认为。
 
 
 

ps:说实话,我真的没有设想到log服务器的意义和作用:)。
 
……因为当他们先看到

    Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
    图理解为什么这个输入的Entry似乎被神秘地忽略了。

        Entry const Source::Process(Entry const& dummyEntry) {
            Entry entry = parser_->Parse(receiver_->Read());
            return Processor::Process(entry);
        }

再说一次Source是为log服务器而生的。
 
 
我的看法是如果这是一个不同的概念,那么就应该有不同的方法来表示。
 
我不太理解你说的服务器是什么,但只要是接受Log信息的东西,根据你的
框架定义,它就应当接受那个参数并处理。而你的代码行为并不是这样的。
最要命的是你的代码里还没有文档,如果你不说是为了服务器考虑,我看了
半天也不知道**为什么**这个dummyEntry不会被处理。我相信每一个人看代
码的人都会遇到像我一样的问题。
 
>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。
 
如果设计者都没有明确地理解log服务器的作用,那么我不敢信任这个设计的可行性。
 
 
 
    Sink
    说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
    会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
    没有存在的理由,直接用Destination就够了。

呵呵,或许Formatter能够把log转换为一条SQL语句?而Destination可以直接执行它?谁知道呢?并且,我这个框架设计的时候同事的反应并不是理解困难【实际上理解起来并不是太困难,不是么?你这么快就理解了:)】,而是有没有xx功能?能不能加上xx功能?
 
 
 
 也许,又是也许。为什么要让他转换?我不敢想象某个用了你的log的
软件在运行时写上文档说:为了得到正确的log输出,使用该软件时请
安装SQLServer服务器。这太荒谬了。
 
我和你对框架的理解不一样。我的观点是框架的设计者必须牢牢地控制
用户场景,理解每一个扩展点的用途和可能的变化。对自己不理解的场景
随便加一个接口不是负责的态度。
 
Sink本身理解上并不困难,而这也就是为什么能这么快断言这东西没有
存在的必要性的原因。
 
 
 

我觉得没有任何理由不能为Entry提供构造函数吧。甚至变长参数的构造函数也是可以的啊。我只是暂时没有这方面的需求而已。其实,Entry本身都应该是抽象的,才能为各种场景下的特异性提供适应性。
 
 
 
问题不在于能不能加入,而是在于**你现在没有实现**。如果
是要做一个通用的产品,那么这东西应当能保证一个完整的基
本用户场景,而不是到时候再扩展。
 
我妄言一句:如果你说你暂时没有这方面的需求,那么我可以
立即断言这个框架还没有用在实际的生产环境里。因为我也曾
经写过一个Log框架,一开始也没有提供变长参数,而第一次
用到生产环境中的时候就遭到了同事的抱怨,因为他们厌倦了
到处写stringstream。我不相信任何一个需要写log的程序员会
没有这个要求。
 
 
 
理由 3:基本功能设计错误。

    我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
    类。如果照这样写法,那么这个log类在写log的时候别的进程是不
    能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
    的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
    理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。

我不清楚你说的“写log的时候“是指什么时候?是写的那一刹那呢?还是从打开文件到关闭文件?我不知道是不是那一刹那不能打开log文件,但是我自己可以在任何时候能打开它。不需要把进程关掉。
或许我的那个方法不可移植,但是我在WinCE上用的时候没有你说的那个xian限制。
 
 
 
 
这是我说错了,我得道歉。
 
我重新查了一下我的测试代码,出错的原因是我自己写的验证代码中
忽略了那个fflush(),就是缺少了fflush()导致log信息被存在buffer里而别的进程
打开文件时看不到。
  
Windows下的自动文件锁来自CreateFile()。默认情况下在没有明确指定sharing mode
时Windows确实会自动锁住这个文件,导致别的进程试图读取文件时出现拒绝访问错
误,这符合我的经验。看来我这个错误印象是从这里来的。
 
没有调查就没有发言权啊,也是给我一个教训,呵呵。
 
 
 
我得好好想想,Update?嗯,没有这个函数,最初log是一个纯粹的写入数据库的一个东西,有严格的结构。后来发现其实不实用,所以已经完全废弃了。
 
 
再次强调,既然没有这个函数,就得尽早清除。我们不能假设每一个后来
人都能像你这个设计者一样知道哪些API已经过时。
 

 
在特定的场景下,扩展是无用的。但是,作为一个log库的提供者,我不能假定任何场景。It’s so that。
我不知道它符合什么模板。但是,我们确实需要。
 
 
 
以下几句话都是从你的原文摘出来的:
 
>>> 我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,
>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。
>>> 作为一个log库的提供者,我不能假定任何场景。
 
看来这就是我们两个的根本分歧。
 
我认为定义一个软件的用户场景是设计师的责任,而不是用户的。设计师
有责任去定义用户场景,甄别用户提出的需求,并选择性地说Yes或者No,
否则这个软件的膨胀速度就会失控。所以架构师在处理用户要求的时候必
须完整地考察并理解每一个要求的适用性和可行性,而不是不加选择地把
所有可能的功能都加到框架里来。
 
 
你说“我们确实需要”,但从你之前的回复上看你并没有真的去理解你的同
事提出要求的原因和可行性,只是简单地加了一个新的interface了账,而且
恕我直言,在我看来这个interface加得不算高明。
 
 
我一直拿这句话提醒自己:每增加一个抽象都意味着总得有一些人必须为
此增加多个业务逻辑,而业务逻辑代码永远比写一个interface困难许多。
所以代码的设计者必须抵制随意增加接口的诱惑,因为“现在没有代价”
不等于以后永远没有,而我不能要求将来接手我的工作的同事为我的一时
兴起而承担不必要的代价。

Fuzhou Chen

unread,
Dec 2, 2009, 4:06:53 PM12/2/09
to pon...@googlegroups.com


2009/12/2 Tinyfool tiny...@gmail.com
log-劣:
1、对多线程来说有副作用,可能会掩盖某些竞争条件(减慢了速度造成程序不出错,但是遇到快速的机器和io还是要出问题)。
 
还是那句话:在您看来Log就不该是产品代码的一部分,
所以才有此结论。但在我看来Log就必须是Release版本
的一部分。也许某些用户在速度较慢的机器上因为log而
不能重现错误,而最终必定有用户拥有相对较快的机器
从而发现你的bug。
 
问题的关键是,你希望当他们告诉你出问题的时候把log
文件交给您,还是仅仅告诉你:你的程序crash了,你自
己想办法吧?
 
别忘了,很多生产环境中并不允许现场调试,即便允许,
也不等于您拿着调试器就一定能重现。
 
2、很多程序员不知道该输出什么不输出什么,经常看到无数不知所云的log,反而增加调试的复杂度
 
这是个问题,我同意。但这恰恰说明了这样的程序员
不是有经验的程序员,他们并没有把使用Log当作技能
的一部分而仅仅是随意地使用。他们需要进一步学习。
 
按照这个逻辑我不妨说句笑话:我是不是可以因为C++
的模板造成大量编译时难以理解的错误而要求取消模板?
要是这样的话我估计这个group里一般以上的人都要来
找我拼命吧。
 
 
3、加错位置(忘了关闭,等等)可能大幅降低效率(我们分词器内部实现里面有个调试log,程序员发布到真实环境去了,查询效率下降到惊人的程度)。
 
如果这个问题能够重现,那么这说明这个软件发布前没
有经过合理的性能测试,开发和测试组都需要反省。如
果不能稳定重现,我只能说,Log是人加的,如果整个开
发组已经穷尽了人力物力都无法发现这个问题,说明大
家都尽力了,但我们怎么能拿Log做替罪羊?
 
我们都知道内存泄漏是个问题,但我们没有人要求C语言
取消指针,对不对?因为它的设计必然带来这个问题,Log
也一样,它确实有拖慢效率的一面,但所以我们得小心地
使用,而不是用Debug/Release之类的手段把它彻底清除出
代码。
 
4、只能事后去看,不能即使查看
 
Log最大的价值在于能够提供一些无法重现的bug发生时的信
息,如果确实需要即时察看而且程序允许,用debugger才是正道。
 
这个时候再奢谈用Log,未免就有拿着锤子把一切当钉子的
毛病了。

Fuzhou Chen

unread,
Dec 2, 2009, 4:26:14 PM12/2/09
to pon...@googlegroups.com
不好意思,这个软件我真的是第一次听说,估计完整看一遍时间不会少。可否留待以后解决?

2009/12/2 Shuo Chen <gian...@gmail.com>
基本同意。
另,请评判 google-glog。

Tinyfool

unread,
Dec 2, 2009, 8:33:39 PM12/2/09
to pon...@googlegroups.com
你的理解能力真是有问题,唉

2009/12/3 Fuzhou Chen <cppo...@gmail.com>

Fuzhou Chen

unread,
Dec 2, 2009, 10:02:47 PM12/2/09
to pon...@googlegroups.com
OK,我自认才疏学浅及不上阁下的高瞻远瞩。我的大部分回答更多地是根据阁下之前的这句话:
 
>>> 对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。
 
以及
 
>>> 对多线程来说有副作用,可能会掩盖某些竞争条件(减慢了速度造成程序不出错,但是遇到快速的机器和io还是要出问题)。
 
所以我仍然认为您对log的理解是“只能存在于Debug build当中”。晚生愚昧无知,恰恰认为这一点我不能认同。从后面阁下的回复而言,您既没有同意我的看法,也没有给出任何理由证明您的看法,而只是一味地重复您的结论而已。
 
 
人身攻击就到此为止吧,本贴之内我不会再回复阁下的任何一句话。
 
Raymond也请见谅,心里憋着火了。
 

 
2009/12/2 Tinyfool <tiny...@gmail.com>

Tinyfool

unread,
Dec 2, 2009, 10:09:39 PM12/2/09
to pon...@googlegroups.com
调试log这个词跟log是一个意思么?我写的程序大多数服务器程序,都有大量的log,但是那不是调试log。另外我说的是仅在debug的时候存在,我说了仅在debug build里面存在么?

在TL这个地方说话,难道也需要附上一个字典么?
我说的够严格了吧?你所指摘的东西一句话都对不上,我不指责你的理解能力我怎么说?

2009/12/3 Fuzhou Chen <cppo...@gmail.com>

redsea

unread,
Dec 2, 2009, 10:31:18 PM12/2/09
to TopLanguage
我的log 模块中, 输出信息分为 debug 信息和 log 信息.
log 信息release 版本呢里面照样有的. debug 信息release 版本就没有了.

Tinyfool

unread,
Dec 2, 2009, 10:34:40 PM12/2/09
to pon...@googlegroups.com
调试log,我们在release版本中一般也留着,但是默认是关掉的,但是如果客户不发现问题,我们就建议他不要打开。他发现了问题,我们才要求他打开,然后复现错误,然后把log发过来我们进行分析。

另外,写mac和iphone的程序,如果崩溃了,操作系统会收集详细的调试信息,提示给用户,要不要发给开发者或者苹果。
2009/12/3 redsea <red...@gmail.com>

Fuzhou Chen

unread,
Dec 2, 2009, 10:37:06 PM12/2/09
to pon...@googlegroups.com
好吧,我食言。
 
第一,我的确没注意到“调试log”和“log”这两个用词的差异。尽管我看不出这里有任何差别。在我看来Log之所以存在就是为了调试的。所以我认为两者是同义语。如果“调试log ”和“log”的确是业界公认的不同术语,请证明,我愿意为自己的不学无术道歉。
 
 
第二,我的确没有注意到“仅应当在Debug时存在”和“仅存在于Debug Build当中”的差异,我道歉。的确,Debug和Debug版本是两回事。
 
不过阁下似乎也没有注意到我和当初殷兄的讨论(下面已经摘出)。我们可以看到两段话中我们都在讲Debug版本和Release版本。而当我开始对阁下的说法提出质疑的时候,您也这么顺着说下去了,既然您知道两者用词上的差异,那么一开始为什么就用您的显然不同的定义插入我们的讨论,而且没有任何说明?
 
 
第三,我不认为在TL上随便质疑别人的理解能力可以被认为一种不带侮辱意味的形容,哪怕是一位前辈对后辈说话。我不认为自己说的每一句话都是对的,所以我会认真地对每一个质疑我观点的同学们进行回复,哪怕为此通宵达旦。如果说质疑别人理解能力有问题是TL讨论应有的风气,我很遗憾。
 
 
再说一次,我希望人身攻击到此为止。
 
 
    >>>>
    这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?

    2009/11/29 殷远超 <yinyu...@gmail.com>

    对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。
    <<<<<
 


 
2009/12/2 Tinyfool <tiny...@gmail.com>

Tinyfool

unread,
Dec 2, 2009, 10:45:54 PM12/2/09
to pon...@googlegroups.com
第一,调试log和log没有区别?如果这是你的观点,那么你需要多了解些基础知识。web服务器的access log是让你调试什么的?error log倒是可以调试你的配置对错。那么如果你真的想调试web服务器代码,这些够么?

第二,如果你的程序在正常情况下跑大量的调试log出来,我认为你的程序是不合格的。当然如果你的程序特别简单,效率要求特别低,调试log特别少,那还可以接受。

剩下的话,我不纠缠你的理解能力了,但是我要明确一点,我只对我的话负责,不对你对我的话的以为负责。

2009/12/3 Fuzhou Chen <cppo...@gmail.com>

up duan

unread,
Dec 3, 2009, 2:22:17 AM12/3/09
to pon...@googlegroups.com


2009/12/3 Fuzhou Chen <cppo...@gmail.com>

首先说一句:如下内容和Raymond的原帖关系已经不大了,但我实在觉得有公开讨
论的必要,如果不喜欢的同学请忽略此帖子。
 
 
 
忧喜参半,喜是很高兴能得到正面的回应,忧是看来咱们俩的分歧不是代码细节上的。

 
2009/12/2 up duan fix...@gmail.com



sorry,我设想的目标场景跟你不一样。我的参照物是现有市场上的log系统,比如:java的log系统或者c++的log系统,他们都有大量的扩展点,而且侵入性比较强。我只是想展示一下其实可以很少侵入的提供更强的扩展性的log体系而已:)。我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,你可以只用一个而不用为任何没有用的付出任何代价,不是么?log是不是一模一样完全依赖于formatter,是吧。
 
 
没有付出代价?我不同意,对你而言确实如此,但你让你的用户付出了惨重的代价:
 
a) 效率:本来一个函数操作完成的任务现在变成了遍历链表 +
    两次虚函数调用。本来log就不是一个高效的操作,这一下效率更低。
 
嗯?我不清楚你fopen,fwrite,fclose调用跟我的这个调用相比效率能有多高?
虚函数确实调用效率低,但是究竟有多低,不过就是多了一个指针间接而已。是,需要遍历链表,可是你忘了,链表的长度是1的时候的遍历似乎也不很慢吧:)
 
b) 后来人的阅读开销——这是我反复强调的。如果一个东西没有用,它就
    ***绝对不应该***出现在代码里。这东西会对维护和将来扩展造成持续的
    负面作用。
 
问题是什么叫有用?我觉得MFC里面的永久化机制对我完全没有用,它是不是就不应该出现在MFC框架里面?
 
至于市场上的log系统,我不敢说自己非常熟悉Log4J,我承认它也提供了很
多诸如XXXAppender的类负责把信息输出到文件、Socket等等等等。但是
Log4J的每一个Appender都是一个明确定义的类,而不是个抽象的interface,
所以它才是一个实用的工具。详情可参阅http://logging.apache.org/log4j/1.2/apidocs/index.html
 
哦,你觉得我的Destination是抽象的接口么?
 
而你的代码里所有的各种Destination只有文件是有实现的,剩下的你打算
怎么办?让用你的Log框架的人去实现?如果是我的话,我宁可直接用已有的。
 
 
不只是文件,还有MFC的Trace,还有Console(当然,也是文件),还有MessageBox啊……,确实我的数据库和socket没有实现,但是,实现它们跟实现文件Destination没有本质的区别。 文件时fopen,socket是创建一个到log server的连接,而db是创建一个到具体数据库的连接。文件时fwrite,socket是send,db是insert。我前面说过,我不知道log server有什么用,这是我同事的要求,当然,后来他也没有搭建log server,所以socket没有实现。db也没有实现。

再提一句:对于Log这种特定目的的应用,扩展性不重要。因为Log这种东西
的定位非常特殊,它本身是产品代码不可或缺的一部分,但却对产品的功能
只有***反贡献***(主要是效率),所以它必须避免在编写、阅读和部署的时
候付出太大的精力,因为程序员不会有兴趣花大把的时间去考虑怎么实现一
堆逻辑来适应log的接口。
 
我不太清楚你的立场,站在log实现者的立场看,我不应该化太大精力去编写log库,站在log使用者的立场看,我的log应该不花大量的时间来适应以便使用?这两个似乎都是你的猜测吧。我觉得对于一个稍微具有规模的项目【2-4个人参见】,log基础设施的配置根本就可以忽略不计【整个工程就配置一次,而且想修改配置也就是这一个地方】,而log调用跟printf相比究竟复杂在那儿?
 
作为一个Log库,其必须做到接口简单,并且功能完备。我不能要求你同意我的
看法,但我确实是这么认为。
 
我觉得我的log接口简单。我们可以探讨出一个共同认可的标准来界定什么是简单才能探讨这个问题。
log库的功能完备是什么意思?我觉得我的功能是完备的。我也确实不知道你关于log功能完备的界定标准是什么。
 
 

ps:说实话,我真的没有设想到log服务器的意义和作用:)。
 
……因为当他们先看到

    Log::AddProcess()而没有来得及看到Source的定义时,他们会用很长时间试
    图理解为什么这个输入的Entry似乎被神秘地忽略了。

        Entry const Source::Process(Entry const& dummyEntry) {
            Entry entry = parser_->Parse(receiver_->Read());
            return Processor::Process(entry);
        }

再说一次Source是为log服务器而生的。
 
 
我的看法是如果这是一个不同的概念,那么就应该有不同的方法来表示。
 
我不太理解你说的服务器是什么,但只要是接受Log信息的东西,根据你的
框架定义,它就应当接受那个参数并处理。而你的代码行为并不是这样的。
最要命的是你的代码里还没有文档,如果你不说是为了服务器考虑,我看了
半天也不知道**为什么**这个dummyEntry不会被处理。我相信每一个人看代
码的人都会遇到像我一样的问题。
 

看来我得仔细的说一下log服务器,那是这样一个计算机节点,它从网络上别的地方接收log entry,记录下来。这意味着它自己并不产生log,那个dummyEntry只是为了哄过死心眼的静态强类型语言的一个塑料树。没人需要产生它,也没人需要消耗它。
对于log server,它需要通过add source去装配log,然后调用startLog就ok了。代码中没有看到source去监听并记录,那是因为我们根本没有搭建log server。

>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。
 
如果设计者都没有明确地理解log服务器的作用,那么我不敢信任这个设计的可行性。
 
 
这是我们同事的要求,后来没有用。设计可行性是什么意思?我不知道实现出来算不算设计可行性的一个明确的答案?
 
 
    Sink
    说白了,Sink的作用仅仅是绑定Destination和Formatter。我不知道大家
    会不会同意我对Formatter的评价。但如果我的评论能成立,那么Sink确实
    没有存在的理由,直接用Destination就够了。

呵呵,或许Formatter能够把log转换为一条SQL语句?而Destination可以直接执行它?谁知道呢?并且,我这个框架设计的时候同事的反应并不是理解困难【实际上理解起来并不是太困难,不是么?你这么快就理解了:)】,而是有没有xx功能?能不能加上xx功能?
 
 
 
 也许,又是也许。为什么要让他转换?我不敢想象某个用了你的log的
软件在运行时写上文档说:为了得到正确的log输出,使用该软件时请
安装SQLServer服务器。这太荒谬了。
 
你或许没有说清楚你心里想的事情?我怎么感觉你这儿说的有点不知所云?你如果想让你的log写入SQLServer,好,没有问题,但是显然,你需要自己配置好SQLServer,设计好表格,然后按照你自己的想法把log结构化的存入SQLServer。这很过分么?你是希望我的Log库提供一个数据库服务器?而且还设计好log在数据库表格中的字段和结构?抱歉,我的Log库并不卖钱。

我和你对框架的理解不一样。我的观点是框架的设计者必须牢牢地控制
用户场景,理解每一个扩展点的用途和可能的变化。对自己不理解的场景
随便加一个接口不是负责的态度。
 
嗯,你说的或许有道理,但是我的理解是如果架构不能扩展到自己不能理解的场景,那么这个架构就有问题。
 
Sink本身理解上并不困难,而这也就是为什么能这么快断言这东西没有
存在的必要性的原因。
 
 
嗯,理解简单就是存在无必要。我可以这样理解你这句话么?
 

我觉得没有任何理由不能为Entry提供构造函数吧。甚至变长参数的构造函数也是可以的啊。我只是暂时没有这方面的需求而已。其实,Entry本身都应该是抽象的,才能为各种场景下的特异性提供适应性。
 
 
 
问题不在于能不能加入,而是在于**你现在没有实现**。如果
是要做一个通用的产品,那么这东西应当能保证一个完整的基
本用户场景,而不是到时候再扩展。
 
什么是“完整的基本用户场景”,我看来,我的用户【也就是我的同事们】能用它替代自己fopen,fprintf ,fclose就是完整的用户场景了。

我妄言一句:如果你说你暂时没有这方面的需求,那么我可以
立即断言这个框架还没有用在实际的生产环境里。因为我也曾
经写过一个Log框架,一开始也没有提供变长参数,而第一次
用到生产环境中的时候就遭到了同事的抱怨,因为他们厌倦了
到处写stringstream。我不相信任何一个需要写log的程序员会
没有这个要求。
 
嗯,你说是你的经验,在WinCE下,std::stream是不能用的。我们有一套自定义的string<->int的库。
 
 
理由 3:基本功能设计错误。

    我得说这个log类在Windows上没有什么实用性。理由可见FileDestination
    类。如果照这样写法,那么这个log类在写log的时候别的进程是不
    能打开log文件的,因为这个进程独占了那个文件。但对于长期运行
    的系统,系统管理员往往需要定期地检查log,要是这么设计的话管
    理员每次查log就得把进程关掉。这个对服务器来说是不可接受的。

我不清楚你说的“写log的时候“是指什么时候?是写的那一刹那呢?还是从打开文件到关闭文件?我不知道是不是那一刹那不能打开log文件,但是我自己可以在任何时候能打开它。不需要把进程关掉。
或许我的那个方法不可移植,但是我在WinCE上用的时候没有你说的那个xian限制。
 
 
 
 
这是我说错了,我得道歉。
 
我重新查了一下我的测试代码,出错的原因是我自己写的验证代码中
忽略了那个fflush(),就是缺少了fflush()导致log信息被存在buffer里而别的进程
打开文件时看不到。
  
Windows下的自动文件锁来自CreateFile()。默认情况下在没有明确指定sharing mode
时Windows确实会自动锁住这个文件,导致别的进程试图读取文件时出现拒绝访问错
误,这符合我的经验。看来我这个错误印象是从这里来的。
 
没有调查就没有发言权啊,也是给我一个教训,呵呵。
 
 
 
我得好好想想,Update?嗯,没有这个函数,最初log是一个纯粹的写入数据库的一个东西,有严格的结构。后来发现其实不实用,所以已经完全废弃了。
 
 
再次强调,既然没有这个函数,就得尽早清除。我们不能假设每一个后来
人都能像你这个设计者一样知道哪些API已经过时。
 

我不是用#if 0 #endif给discard了么?

 
在特定的场景下,扩展是无用的。但是,作为一个log库的提供者,我不能假定任何场景。It’s so that。
我不知道它符合什么模板。但是,我们确实需要。
 
 
 
以下几句话都是从你的原文摘出来的:
 
>>> 我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,
>>> ps:说实话,我真的没有设想到log服务器的意义和作用:)。
>>> 作为一个log库的提供者,我不能假定任何场景。
 
看来这就是我们两个的根本分歧。
 
我认为定义一个软件的用户场景是设计师的责任,而不是用户的。设计师
有责任去定义用户场景,甄别用户提出的需求,并选择性地说Yes或者No,
否则这个软件的膨胀速度就会失控。所以架构师在处理用户要求的时候必
须完整地考察并理解每一个要求的适用性和可行性,而不是不加选择地把
所有可能的功能都加到框架里来。
 
嗯,你跟我在这儿差别很大。我认为用户场景是用户自己的的选择,而不是设计师指导用户怎么做。对于用户场景,设计师能做的只能是能不能做到,能不能实现,而不是说:应该如此这般,如此那般。给用户选择权用户才会选择你。可行或不可行你做主,如何走业务流程你说了不算。
另外,我不清楚你概念体系中的框架和我的有多大差别,框架对我来说其实一般情况下并不带有功能,或者说并不是血肉丰满的系统,它只是规定了组成系统的组件之间的关系和结构,所以,并不存在“把功能加到框架中”的情形,因为,框架没有功能,它只是为功能实现提供支撑性结构。
 
 
你说“我们确实需要”,但从你之前的回复上看你并没有真的去理解你的同
事提出要求的原因和可行性,只是简单地加了一个新的interface了账,而且
恕我直言,在我看来这个interface加得不算高明。
 
嗯,我也觉得Source加的过于草率。同意你的观点。但是我确实没有磨砺和精化这个接口的可能性,因为同事并不使用这个功能。
 
 
我一直拿这句话提醒自己:每增加一个抽象都意味着总得有一些人必须为
此增加多个业务逻辑,而业务逻辑代码永远比写一个interface困难许多。
所以代码的设计者必须抵制随意增加接口的诱惑,因为“现在没有代价”
不等于以后永远没有,而我不能要求将来接手我的工作的同事为我的一时
兴起而承担不必要的代价。

 
我觉得你这儿的跳跃如此之大以至于我都理解不了了。抽象、业务逻辑这两个概念各是什么?它们互相纠缠么?它们有关系么?为什么增加一个抽象就要增加多个业务逻辑?
 

Fuzhou Chen

unread,
Dec 3, 2009, 2:24:35 AM12/3/09
to pon...@googlegroups.com
呵呵没问题,既然您已经确认了你说话犯不着顾及别人的感受,我下次注意便是。


回到正题。您所说access log,莫非说的是Apache?

这是我从apache网站上找回来的一个例子:http://httpd.apache.org/docs/2.0/logs.html

127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)"

我需要首先澄清一个概念,在我的工作环境中debug和诊断是没有明确界限的,
因为工作需要中我经常要控管整个网络,而不是某个产品的代码。所以有时候我
和我的同事们会不自觉地将其混为一谈。如果仅仅从上面这行log来看,我认为
这里提供的信息足以让我看到很多东西。至少如果存在这样一条信息,我就有自信
不再怀疑网络是否出出现短暂的连接不上。假定我们知道某个产品总是在Get操作
结束时输出信息,那么我至少可以肯定Get操作已经完成。


当然这些东西显然还不足以进行代码级别的debug,所以如果某个产品的设计师
告诉我他的web服务器只能输出这些东西,那么我也会告诉他这不可接受。

如果我的表述还是不够清楚,确实抱歉,因为我好像也没带字典,呵呵。


>>>
>>> 如果你的程序在正常情况下跑大量的调试log出来,我认为你的程序是不合格的。
>>>

关于这一点,我只能说我们触及了一个历史上一直没有结果的争论。就以微软产品为例
吧,我常常接触的几个产品中各种做法都有。一个简单的清单如下:

a) w32time
Windows NTP时间服务器
通过w32tm /debug /entries 控制log输出的详细程度。

b) DCPromo.exe
活动目录升级程序。用于将Windows Server升级为域控制器。
永远输出所有log。

c) LSA
Windows域服务器认证服务。
通过注册表启动或停止log,但不分级别,一旦启动则输出所有log。


从上面的列表上看,即使是同一个公司的不同产品对log的处理方法也是
不一样的。至于这些软件是不是不合格,我想我还没有资格评论。



2009/12/2 Tinyfool <tiny...@gmail.com>

Fuzhou Chen

unread,
Dec 3, 2009, 3:54:20 AM12/3/09
to pon...@googlegroups.com
回得越发地长了啊,不过理不辩不明,这样挺好。完全读完这一段,
我想我和UP老兄的互相抬杠也就可以告一段落了。:)

UP啊,我推荐你倒着看我的评论,这样你应该能更容易理解我的看法。

2009/12/2 up duan <fix...@gmail.com>

2009/12/3 Fuzhou Chen <cppo...@gmail.com>


a) 效率:本来一个函数操作完成的任务现在变成了遍历链表 +
    两次虚函数调用。本来log就不是一个高效的操作,这一下效率更低。
 
嗯?我不清楚你fopen,fwrite,fclose调用跟我的这个调用相比效率能有多高?
虚函数确实调用效率低,但是究竟有多低,不过就是多了一个指针间接而已。是,需要遍历链表,可是你忘了,链表的长度是1的时候的遍历似乎也不很慢吧:)

这倒是,可能是我的效率至上症间歇性发作了。我认为对产品功能没有贡献的
开销,能小则小,嘿嘿。
 
 
b) 后来人的阅读开销——这是我反复强调的。如果一个东西没有用,它就
    ***绝对不应该***出现在代码里。这东西会对维护和将来扩展造成持续的
    负面作用。
 
问题是什么叫有用?我觉得MFC里面的永久化机制对我完全没有用,它是不是就不应该出现在MFC框架里面?


MFC的例子显然不合适,因为我们都是知道这东西有人用的。我反对你的设计的理由我
前面已经解释过,是因为我认为多次输出log是一个无用的功能。而最糟糕的是,原本
只是我个人的怀疑,却被你的回复证实了:


>>> 我也不知道为什么需要有多个log目标,但是,似乎有多个也不很坏,毕竟,
>>> 你可以只用一个而不用为任何没有用的付出任何代价,不是么?lo

我不知道你是否同意我前面的观点,即架构师添加一个功能的时候需要对这个功能的
可行性和场景都能进行明确的定义。但是你的回复给我的感觉是,在多输出的这个功
能上你考察了可行性(所以你实现了),可是你对它是否应当出现在用户场景中却没有
肯定的答复,更像是一个跟着感觉走出来的决定。站在我的立场上,我更习惯于把它
归类于一种相对糟糕的设计。

如果我的分析不正确,请指出。


 
至于市场上的log系统,我不敢说自己非常熟悉Log4J,我承认它也提供了很
多诸如XXXAppender的类负责把信息输出到文件、Socket等等等等。但是
Log4J的每一个Appender都是一个明确定义的类,而不是个抽象的interface,
所以它才是一个实用的工具。详情可参阅http://logging.apache.org/log4j/1.2/apidocs/index.html
 
哦,你觉得我的Destination是抽象的接口么?


不,恰恰是因为我发现他们都不是抽象接口,所以我才提出来。我不理解一个发布出
来的函数库怎么可以留一个空白函数在里面。
 
 
而你的代码里所有的各种Destination只有文件是有实现的,剩下的你打算
怎么办?让用你的Log框架的人去实现?如果是我的话,我宁可直接用已有的。
 
 
不只是文件,还有MFC的Trace,还有Console(当然,也是文件),还有MessageBox啊……,确实我的数据库和socket没有实现,但是,实现它们跟实现文件Destination没有本质的区别。 文件时fopen,socket是创建一个到log server的连接,而db是创建一个到具体数据库的连接。文件时fwrite,socket是send,db是insert。我前面说过,我不知道log server有什么用,这是我同事的要求,当然,后来他也没有搭建log server,所以socket没有实现。db也没有实现。


不,他们当然有区别。你已经实现的东西都是对现有的东西做一个简单的包装,
所以每个函数几乎就一行实现,轻松愉快;而如果你的log框架真的需要支持数据库
或远程主机作为输出,你要考虑的东西会多出很多:比如效率,因为数据库操作和
网络通信都要比本机操作慢许多,所以为了保证性能、健壮性等很多东西,你的设计
绝对不是像现在实现的那样方便的。


有一点我想也许你也已经注意到了,为什么每每你在和我讲抽象讲设计的时候我却
总是和你扯实现。因为我有这么一个观点,只有正确理解实现才能得到合理的抽
象,或者它的逆否命题,即我无法在避免对实现建模的前提下而获得合理的抽象。


你说的是对的,就是因为上面这个理由,我认为一个合格的产品必须是框架+功能。
如果我来做你的log,我至少会保证实现一个TCPDestination,至于别人是不是愿意
继续在TCP的基础上添加更细化的HTTPDestination, LDAPDestination,那是他们
的自由,因为这些协议都是基于TCP的,跑不出我定义的场景,所以我会有自信认为
我的框架能够大体上适应这些扩展。


>>> 我前面说过,我不知道log server有什么用,这是我同事的要求。

我可以这么认为么?你在不知道这个功能的可行性的情况下给出了一个接口,并
假定它大概应该工作正常。

所以我就担心:当将来你的某个雄心勃勃的同事接过你的工作并试图将其扩展到支
持log服务器的时候,作为最初的设计者,你能向他保证你设计的接口能够适应它
的需求么?

如果我是你,我是不敢做这个保证的。可糟糕的是无论我是否能够保证,那位同事
在默认情况下都会首先尝试按现有的接口做实现,否则使用你的接口就没有意义了。
那么假如他最终发现他的要求无法通过你的接口实现,怎么办?

 
我不太清楚你的立场,站在log实现者的立场看,我不应该化太大精力去编写log库,站在log使用者的立场看,我的log应该不花大量的时间来适应以便使用?这两个似乎都是你的猜测吧。我觉得对于一个稍微具有规模的项目【2-4个人参见】,log基础设施的配置根本就可以忽略不计【整个工程就配置一次,而且想修改配置也就是这一个地方】,而log调用跟printf相比究竟复杂在那儿?
 

我想你肯定已经知道我的答案了。从log实现者的角度,我会把所有的精力花在
实现各种实际的功能上:既然我说了要实现TCPDestination,我就肯定会去实
现它,这是我说的“功能完备”;如果我不确定数据库写入的基本流程和功能要
求,我根本不会定义这么一个类,以免给用户造成误会。这就是我说的“接口简单”。
 
 
如果设计者都没有明确地理解log服务器的作用,那么我不敢信任这个设计的可行性。
 
这是我们同事的要求,后来没有用。设计可行性是什么意思?我不知道实现出来算不算设计可行性的一个明确的答案?

怪我,我用词不当。这里说的可行性指的是你设计的接口在将来继续扩展的时候是否确实
能符合网络Log的需要。

具体请参阅我之前的回答。


 
我和你对框架的理解不一样。我的观点是框架的设计者必须牢牢地控制
用户场景,理解每一个扩展点的用途和可能的变化。对自己不理解的场景
随便加一个接口不是负责的态度。
 
嗯,你说的或许有道理,但是我的理解是如果架构不能扩展到自己不能理解的场景,那么这个架构就有问题。


所以我们已经找到共识了,尽管我们都不能说服对方,不过这样更好。:)

我的看法与你确实刚好相反。我认为架构决不能扩展到设计师不能理解的场景,
否则我们会遇到两个麻烦:

a) 当用户抱怨某个功能不好用的时候,设计者无法回答这该算一个设计错误,
    还是一个潜在的新功能要求。
b) 当用户提出需要添加一个新功能时,设计者很难评估这个功能对其他用户场
    景的影响。

一旦我们陷入上面的局面,最直接的后果就是架构师将丧失接受或拒绝用户要
求的自信,进而丧失对项目后续发展的控制能力。——而用户的要求不可能永
远无条件满足,我想这一点上我们还是有共识的吧。


诚然,设计者不能未卜先知地预测用户将如何使用他们的软件,但是至少自己
心里得有一批基本的用户场景,当别人问起时,设计师应当有能力解释自己代
码中提供的每一个类和接口应该被如何使用。

这就是为什么当你说“可能”“大概”之类的话时我的反应会如此激烈的原因。从
你的回复上看,我发现你似乎在没有搞明白至少两个功能的应用场景时就急着
加入新的类和接口,前面我已经说明了,这在我看来是非常危险的。


后面的话我就不再回复了,凌晨三点(太平洋时间)写东西思路确实有点跳跃,
我承认我没写好。我想上面的说明结合你前一篇回复,我们对双方的分歧和共
同点应该都非常了解了,剩下的,还是让实现来说明吧。


 
问题不在于能不能加入,而是在于**你现在没有实现**。如果
是要做一个通用的产品,那么这东西应当能保证一个完整的基
本用户场景,而不是到时候再扩展。
 
什么是“完整的基本用户场景”,我看来,我的用户【也就是我的同事们】能用它替代自己fopen,fprintf ,fclose就是完整的用户场景了。

其实我今天回想了一下,这个指责有把问题简单化的错误。我的同事当时抱怨很多,
不等于你那边也一样如此。非常抱歉。

missdeer

unread,
Dec 3, 2009, 5:30:18 AM12/3/09
to TopLanguage

On Dec 3, 11:34 am, Tinyfool <tinyf...@gmail.com> wrote:
> 调试log,我们在release版本中一般也留着,但是默认是关掉的,但是如果客户不发现问题,我们就建议他不要打开。他发现了问题,我们才要求他打开,然后复现错误,然后把log发过来我们进行分析。
>
> 另外,写mac和iphone的程序,如果崩溃了,操作系统会收集详细的调试信息,提示给用户,要不要发给开发者或者苹果。

不光是mac和iphone可以这样。Windows和linux也可以,google-breakpad就是做这些事的。

raymond

unread,
Dec 3, 2009, 12:18:11 PM12/3/09
to TopLanguage
我们现在就是用gcov,用起来还比较方便,生成结果也直观。你说的这两点我赞同,但是我想第二点我们还没有做到,需要考虑。

On 12月2日, 上午3时27分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 我想我们对Code coverage的定义似乎有偏差。在我看来code coverage和优雅与否毫无关系。
>
> (顺便说一句,我个人不喜欢拿"优雅"这种过于主观的概念作为评判程序好坏的标准)
>
> 我所说的code
> coverage是通过运行测试计算在本次测试中覆盖了多少产品代码。我们公司内部有一些专门的工具用来做这个,通过在编译器和函数库级别注入一些代码,得到一个专门的code
> coverage版本。只要在这个版本上运行测试就可以自动生成一些数据,记录在本次测试中有多少代码被覆盖到,一般可以精确到代码块级别。也就是说我可以知道一个if语句的then块和else块究竟哪一个被覆盖到。
>
> Linux也有gcov,不过我没怎么用过。如果有熟悉的同学不妨介绍一下。
>
> 在我的工作中,Code coverage主要的作用有两个:
>
> a) 衡量某个测试方案是否存在漏洞。
>
> 在我看来Code coverage高不等于测试的质量一定高,但Code
> Coverage低则必然说明测试质量低下。因为覆盖率低表明测试中遗漏了功能点,换句话说很多代码根本没经过测试。
>
> b) 管理回归测试。
>
> 我们公司内部有一个数据库用来保存每个测试和产品代码的对应关系,以及产品代码之间的调用关系。这样当一个新的fix被引入时,我们可以通过数据库来查询究竟有多少相关的其他产品代码可能被这个fix影响,从而确定这一次fix需要增加哪些测试。
>
> 对于个人开发而言,b) 可能已经超出我们力所能及的范围了。不过用gcov验证一下测试的质量还是有好处的,呵呵。
>
> 2009/12/1 raymond <shiqu...@gmail.com>
>
>
>
> > 说到code coverage,我之前一直觉得code coveage最多是代码优雅方面的问题(写的不好的代码当然code coverage
> > 差,bug多),难道这个跟debug还有很大关系?兄弟明说一下。
>
> > 看了一下介绍,iDNA基本是调试单机程序的,而且应该适合调试那种逻辑多,数据少的程序,而分布式调试一般处理的是逻辑不多,数据量大的程序。
>
> > On 12月1日, 上午3时58分, Fuzhou Chen <cppof...@gmail.com> wrote:
> > > 呵呵怎么着我也是把问题带跑的那个人,既然楼主都发话了我们还是回到正题吧。
>
> > 首先我得说我没有做MapReduce类型应用的经验,我现在的工作集中在Windows活动目录这一块,特征是低平均通信强度下的服务器LDAP数据库信息同步。所以我的观点基本上基于我的背景,可能和楼主期望的场景有所不同,如果有不同意见欢迎指出。
>
> > > 2009/11/30 raymond <shiqu...@gmail.com>
>
> > > > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > > > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>
> > > 我的观点很简单:
>
> > > a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> > > b)
> > 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> > > + Code Coverage + Fuzzying。
> > > c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>
> > > > 2.如何方便的重现bug。B的进程之所以失败,可能是A的输出错误,可能是B的逻辑错误,可能是那个失败的B进程当前所在的机器上运行了其他的进程,
> > > > 可能。。。,
>
> > > > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>
> > 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
>
> > 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
>
> > 我自己有时候用一个山寨版的简化方案:比如每个程序还是在本机写log,但测试最后我会通过文件共享把这些log收集到单独的一台机器上,然后通过一些script分析时间戳并组合到单独的一个文件里。只要测试不要求修改系统时间(比如NTP协议的测试,这个我也做过),这个方案基本上还是可行的。
>
> > > 至于调试能够稳定重现的bug,不知道在这里的同学们有没有机会用过微软的iDNA,
> >http://blogs.msdn.com/cse/attachment/1077668.ashx
>
> > 。它可以很方便地抓取程序执行中每一个汇编指令执行时的程序瞬态的内存信息,然后存成一个dump。利用这个dump我们就可以在Windbg里虚拟地对进程的执行情况做调试,甚至能做到单步后退,算是我见过的最好的产品化的调试工具。但这个工具最大的限制是硬盘空间,因为它生成的dump文件大小会随着程序执行时间快速膨胀,所以它不适用于需要长时间运行的程序,也很难用于一些硬盘空间有限制的生产环境。
>
> > > 我不知道Linux下有没有类似的技术,有高手可以说明一下否?

raymond

unread,
Dec 3, 2009, 12:23:17 PM12/3/09
to TopLanguage
没有见怪,我很喜欢你回复的方式。我这两天一直在思考关于log的种种讨论,现在感觉确实如你所言:写出真正有用的log是系统级别的事情,绝非一个功
能点,我现在还很难想清楚这么一个分布式的框架应该如何组织log才能尽可能达到出现bug快速定位。我很希望以后想清楚了和大家分享。

On 12月2日, 上午3时53分, Fuzhou Chen <cppof...@gmail.com> wrote:
> 这话题是越发沉重了啊。以下可能有些离题,Raymond别见怪。
>
> 毋庸讳言,写出真正能对调试有帮助的log的工作量实际上是很大的。而从我的工作经验上看,我只能很遗憾地说不写log很大程度上出于两个原因:
>
> a) 开发组把写log看作是一种纯粹的负担,而不是为了保证产品的长期可维护性必须引入的步骤。
>
> 我知道的为数不少的公司,开发组写完代码只负责函数级别的Unit
> test,剩下的功能/集成测试和维护完全靠测试组完成。很多时候测试功夫不到家,只能发现简单的bug,这时开发组凭着自己对产品的了解往往可以快速解决。既然测试组抓不出必须有log才能解决的bug,开发组就可以堂而皇之地拒绝测试组对log的需求。
>
> 造成这种问题的另一个原因是开发组不理解测试的难度所在,他们把测试当作是一种可以像添加功能一样进度可知的东西,或者把unit
> test等同于功能测试。我所知道的很多开发人员很不理解为什么即使测试通过了的产品,发布出去之后仍然能发现bug。他们喜欢说:如果出了问题就说明你们这些测试是吃干饭的。----很无奈,但他们中的很多人自己甚至不测试自己的代码。
>
> b) 有时候不写log纯粹是因为某个自以为是的老板认为这样可以不暴露商业机密----我知道这种政治问题本不该由我们操心,但人在江湖,没办法。
>
> 2009/12/1 stefan <pengli.h...@gmail.com>
>
>
>
> > 那我想请问:如果release版本没有log信息,长程序在用户那里出现问题时,程序员应该如何进行定位,难度还要让用户进行现场恢复,
> > 程序员则去取个debu版本过来吗?
> > 或者在用户那里开启调试工具,我想,这些场景似乎都不应该出现吧。
>
> > 正如你说的,当用户机器慢时没有问题(暂且认为正常),当用户机器升级后暴露了问题,如果有日志,正好可以帮助进行定位,为什么不呢?
>
> > 在这样大系统中,合理设计大log系统并不会成为系统的瓶颈吧。
>
> > On 11月30日, 下午3时05分, Tinyfool <tinyf...@gmail.com> wrote:
>
> > 没有log就会产生的竞争条件,不解决往往是很危险的。因为在某机器上log的效率下降恰好把竞争条件掩盖了,而随着用户机器的升级,更快的cpu和io下,这些竞争条件可能还会重新出现。所以,debug有log没有问题,那就给release版本也加上log来解决竞争条件是非常不负责任的。而且,对复杂的程序来说,调试log带来的性能下降是不能忽略的,这就是为什么调试log仅应该在debug的时候存在。
>
> > > 2009/11/30 Fuzhou Chen <cppof...@gmail.com>
>
> > > > 恐怕殷兄的说法恰恰证明了只有log才能解决问题,而调试毫无办法。


>
> > 这里最大的问题是:为什么release版本就不输出log?如果release版本因为有了log而导致问题不再产生,那么这个bug报告就根本不会出现;如果release版本有了log仍然能出现,那么我们从release版本的log就能看到我们需要的信息,我们为什么还需要debug版本?
>

> > > > 2009/11/29 殷远超 <yinyuanc...@gmail.com>


>
> > 对头,我这曾经有个问题,就是release版有问题,debug版始终没有,调试无法重现。后来发现log降低效率之后,完全不产生竞争条件,对于共享内存的模型,线程/j进程安全绝对不是log能解决的。
>

> > > >> 2009/11/30 Tinyfool <tinyf...@gmail.com>
>
> > > >>> 多线程log会对时序有影响,另外某些级别的效率下降可能会降低产生竞争条件的几率,甚至完全不产生任何竞争条件。
> > > >>> 当然调试器也会有类似的影响。


>
> > > > --
> > > > 《采莲》·江南
>
> > > > 为卿采莲兮涉水,为卿夺旗兮长战。为卿遥望兮辞宫阙,为卿白发兮缓缓歌。
>
> > > > 另抄自蒜头的评论:http://www.douban.com/review/1573456/
>
> > > > 且祭一束紫琳秋,为一段落花流水的传说
> > > > 且饮一杯青花酒,为一场几多擦肩的错过
> > > > 且焚一卷旖旎念,为一腔抛付虚无的惜怜
> > > > 且歌一曲罢箜篌,为一刻良辰春宵的寂寞
>
> > > --

> > > Tinyfool的开发日记http://www.tinydust.net/dev/
> > > 代码中国网http://www.codechina.org
> > > myTwitter:http://twitter.com/tinyfool

raymond

unread,
Dec 3, 2009, 12:31:44 PM12/3/09
to TopLanguage
谢谢,是我想复杂了。我以为replay就像数据库的回复机制,要按照log进行完全的处理。现在看来,replay的核心思想是:划定程序边界,记录
向外的输出和外界的响应,然后replay的时候mock一下外部对象就行。好主意。我用google搜类似这样的技术文档我都不知道该用什么关键词
(replay with log, simulate program behavior, 不行),对这样的一类技术现在有统一的说法吗?还是你们
公司自己搞出来的东东?

redsea

unread,
Dec 3, 2009, 9:19:34 PM12/3/09
to TopLanguage
其实这种技术也很普通啊, 我知道的, 有开发电信软件的团队, 开发游戏服务器的团队, 用类似的技术.

redsea

unread,
Dec 3, 2009, 9:24:16 PM12/3/09
to TopLanguage
我不知道有什么统一的说法, 我的经历中, 这种技术有几个作用:

1. 压力测试和查找性能热点

2. 重构支持 ( 验证重构的工作是否正确 )

3. 查找 bug.

特别是逻辑复杂, 很难重复 bug, 不用这种的技术, 基本上无法解决.


On 12月4日, 上午1时31分, raymond <shiqu...@gmail.com> wrote:

EchoTiro

unread,
Dec 5, 2009, 7:43:23 AM12/5/09
to TopLanguage

无意中在网上搜索到了一个实现了record/replay的分布式开源调试框架,名字叫Jockey。下面是它的简介:

Jockey is a user-space library for recording and replaying an
execution of generic GNU/Linux programs. It is a debugging tool
especially for long-running networked servers that suffer from bugs
that are difficult to reproduce.

在它的网址上有对应的源代码和论文,请参考:

http://home.gna.org/jockey/


On 12月2日, 上午1时52分, raymond <shiqu...@gmail.com> wrote:
> redsea兄这套方法非常切合我的想法,再问几个细节:
>
> 1. 你是把所有的重要调用过程全部记录下来,然后replay的时候,按照顺序重新调用吗?比如,之前发送了一个消息,现在也要发送一个一样的;之前
> 做了排序,现在也做一遍?如果是这样,这个replay框架是不是非常大和复杂?是不是要支持很多类似反射的东西?是不是针对每个功能点还要写支持
> replay附加操作?
>

> 2. 需要保存的东西包括:
> 平台api,输入输出文件,消息,方法参数,时间序列。保存这些元素的格式方面有什么需要注意的吗?
>
> 还有一个问题想问,我们现在的log格式多是
> 时间,级别,调用方法,代码行,线程id,二进制文件。我感觉要想达到replay的功能还差的很远,那么,在log格式的设计上,还请兄弟给点建
> 议。
>

> 能不能举个贯通的例子说明一下。十分感谢。


>
> On 12月1日, 上午10时16分, redsea <red...@gmail.com> wrote:
>
> > > > 1. 如何debug?因为B的输入是A的输出,这个输出可能来自N个不同的进程,所以为了调试,我们要把A的每个进程的输出保存起来,然后要调试B的
> > > > 时候,启动相应的A类进程将数据送给B,这样就可以模拟了。可是,这不是很麻烦吗?能不能方便一点?
>

> > 我现在做的服务器有类似的功能, 我们管它叫作 replay, 需要保存的东西
> > 1. 所有 os platform api, 包括 文件, ioctl, 等等
> > 这就包括了, 和其他进程的通信, 现在我们用的是 tcp socket
> > 2. 未必能够重复的 c library function 例如, rand
> > 3. 所有时间相关的东西, 包括所有时间相关的系统调用, 以及所有被封装的 rdtsc 调用, 需要专门处理.
> > 这个需要专门的处理, 包括获取时间(当前时刻时间值, 时间片长度), 以及sleep
> > 4. 我们现在的程序没有, 但是如果其他程序有的话, 也需要处理的: 和其他进程进行同步(例如 mutex 等)
>
> > 另外, 程序还不能是多线程的, 因为 replay 过程中, 你无法控制多线程的调度次序.
>
> > 最麻烦的不是保存数据, 而是 程序的时间控制/主调度框架, 因为这和正常执行毕竟是不一样的, 只要有一点点区别, 由于蝴蝶效应, 后面就
> > 会完全不一样了.
>
> > 这个方法还有一个麻烦的问题在于 (大概只是对于 C/C++), linux clib 每次跑, malloc 分配的地址空间是不同的
> > (大概是堆扰乱防止容易被攻击? 但是我没有看到取消点), 某些非法访问的操作, replay 过程中, 未必会在同一个地方出现, 如果提早
> > 出现了还好, 如果晚出现了, 这次 replay 可能就不出错了. ( 这个问题我还没有深入分析, 如果是 c library 造
> > 成, 可以用替换 malloc 库的方法解决, 如果是 linux kernel 造成, 可能就需要想更深入的方法了.)


>
> > > 我的观点很简单:
>
> > > a) 由于会影响时序和实际操作上的困难,现有的常用debugger(WinDBG, GDB, etc)调试多机通信应用基本不可行。
> > > b) 我一般不推荐模拟,因为我们组的经验中模拟往往只能反映很少的一部分时序,如果权衡测试代码的工作量和代码覆盖率我认为得不偿失。相比之下我推荐压力测试
> > > + Code Coverage + Fuzzying。
> > > c) 多机通信的应用中,log信息越多,相对地调试越容易,不支持log == 无法做支持 == 软件不合格。
>

> > 确实如此.
>
> > > > 我的回答可能很多同学马上要排砖:一般情况下除非客户明确地说明可以稳定重现,否则我不会一开始就把希望寄托于自己能够稳定重现问题。
>
> > 如果面向大众的服务器, 呵呵, 这也是不能做到的.


>
> > > 没有做过专职维护的程序员可能无法想像,由于受限于资源,设备和时间,在维护过程中遇到的很难或根本无法重现的bug所占的比例在我的工作中一般会高于能够重现的bug----道理很简单:开发组的人也不是猪头,容易重现的bug在开发阶段就都发现并解决了,而留给我们的往往就是穷尽他们人力物力而无法发现的问题。再加上维护人员对产品的了解程度往往比不上开发组,解决这些问题时难度当然高出很多。
>

> > 说得好 :)
> > 我也是因为好几个 log 在开发环境无法重现, 而在生产环境时不时冒出来, 才费力去开发一套 replay 机制, replay 也是
> > 挺费时间的, desktop cpu + overclock 比生产环境快不到 50%, 生产环境平均 cpu 利用率 50%, 那么生产环境
> > 跑6天出的错误, replay 环境需要跑完第二天才能到, 挺费力的一件事情.


>
> > > 关于如何设计多机调试框架的问题,我知道的一些项目基本上还是依赖于log,而他们的设计则比我们常用的log复杂很多。我曾经听一个同事说起过某个杀毒软件公司(似乎是瑞星)的内部log框架,他们的log不是直接写在本机上的,而是每个进程有一个独立的线程来收取log,然后各个log线程会把信息发送给一个中央的服务器,由中央服务器根据时间戳把所有的log组合成一个单独的文件。那个框架用了很多buffer机制来避免log输出对功能模块造成额外的性能开销。不过我没有自己用过,效果如何难以评价。
>

> > log 的性能问题, 我觉得只要不是在hotspot 处, 都不应该是问题, 如果检查到已经造成拖累了, 那么可以开发一个 log
> > process, 和主进程通过 lock free ring buffer 通信, 这样的话, 主要的开销其实只是主进程里面的格式化而已
> > ( printf or io stream).
>
> > 至于 hotspot 哪里, 代码量至少不会很大, 再review 几次 code, 去掉 log 问题也不大啊. 如果是要加上错误
> > 情况检查, 那么写一个为 cpu 分支预测考虑的检查代码, 开销应该也不会很大的. ( gcc 学习 linux kernel 的
> > likely, unlikely, vc 没有这个, 就能将常见情况写作肯定分支了).

redsea

unread,
Dec 5, 2009, 10:25:51 AM12/5/09
to TopLanguage
嗯, 我准备试试看, 大概以后最核心的进程 可以自己实现 record 模块, 以便深入控制, 其他一般性的, 用这个就可以了.

wangl...@gmail.com

unread,
Dec 6, 2009, 4:50:11 AM12/6/09
to TopLanguage
前两天听过一个报告,好像和你的话题有关。网上可以搜索到有关的资料。

Mumak -- Using Simulation for Large-scale Distributed System
Verification and Debugging (Hong Tang@Yahoo!)

这是一个调试hadoop的平台。

我个人没有hadoop实战经验,不乱说。在MPI上的调试,一般还是依靠LOG。先搞清楚崩溃现场是哪个节点,然后打开一个宏开关,重跑一次,但这一
次其他节点实际都不跑,只有问题节点上在真正运行程序,并输出比较详细的跟踪日志。某些时候,还得指望最基本的断点printf大法。

raymond

unread,
Dec 7, 2009, 10:56:43 AM12/7/09
to TopLanguage
兄弟在CMU?我记得前段时间受到过一封邮件,YAHOO的工程师在CMU做了这个报告。
分布式调试依靠log是肯定的,如果有更好的框架将log组织起来会很方便。

On 12月6日, 下午5时50分, "wangleh...@gmail.com" <wangleh...@gmail.com>
wrote:

Reply all
Reply to author
Forward
0 new messages