从爬网时候遇到的一个问题看如何找到错误的根源

前一阵遇到的一个爬网错误,从爬网日志中看到的,结果就是这个网站集几乎所有内容都没有被爬下来。

后来经分析发现这应该算是一个SharePoint的Bug……

当然本身不是什么大Bug,知道了之后也很容易规避。这个Bug我后来也发到微博上了,这里主要介绍一下到底如何从一个错误找出具体的原因,也算是授人以渔。

(如果是你自己的代码出的错,直接附加进程调试就好了,这个不在本文所涉及的范围内)

涉及到的工具有两种,一个是日志查看工具(我用的是最土的记事本……比较专业一点的可以用微软出的SharePoint Admin Toolkit中的日志查看器),另一个就是反编译工具(我用的JustDecompile,一个免费的反编译工具,Slogan很有意思,叫May the source be with you)。

 

我找到这个Bug的具体过程如下:

表现现象:完全爬网之后搜不到某个网站集的内容(这个网站集是用代码创建的)

 

第1步:这种搜不到东西的问题,能直接想到的就是去检查管理中心搜索服务中的爬网日志,果然看到在爬这个网站集的时候遇到了一个错误:

image

 

第2步:这个时候你可以尝试去搜一下这个问题(是的,不要从一开始就尝试自己找到错误根源,除非你能确定这个错误一定是你自己的程序造成的问题,或者你心里有一点模模糊糊感觉到问题的所在,否则合理的利用搜索引擎绝对是个高效率的办法),当然,建议是用英文搜。这个时候用英文版SharePoint的好处就体现出来了,不过我们一般用的都是中文版,这个时候可以先尝试自己把中文给翻译回去,只要翻译得差不太多的、并且这个错误在网上提及比较多的时候,都是能通过搜索找到准确的翻译的(另一种方法是在SharePoint的资源文件里面搜报错的字符串,不过这个比较费时费力,还不一定能找到……)。对于这个问题来说,很遗憾,我木有搜到……

 

第3步:查SharePoint日志,一般我都偷懒先找事件查看器,有些问题可能会记录到事件查看器里,可惜这个问题没有。然后就是去找14\LOGS中的日志文件,之所以没一开始就找这个地方,是因为日志是分散在很多个文本文件中的,找起来不是太方便,格式也不是很容易看(微软那个日志查看工具,说实话还是挺慢的……)。

如果不用现成工具的话,就要先定位到到底是哪个日志文件,方法也比较简单,如果是刚刚发生的错误,那就去找最新的那个日志文件,否则的话根据文件的修改时间间隔,也很容易定位到错误可能会被记录到的那个文件里。

 

第4步:找到具体错误。用记事本或者别的什么文本查看工具打开日志文件(好歹这个日志还是纯文本的),找到具体错误位置的方法无非就那么几种:

最笨的,按照出错时间找,不过如果日志级别设置的比较高的话,一分钟之内会有上百条日志,找起来也不太方便;

最精确的,按照Correlation ID(中文翻译叫互联ID)搜索,这个玩意儿一般会出现在下面这种SharePoint典型出错页面中:

image

如果像本文涉及的错误那样,没有互联ID的话,可以直接搜索错误信息,比如“数据为空。不能对空值调用此方法或属性。”。因为日志中如果涉及到Exception的话,会把完整的错误信息和Call Stack都记录下来,因此也可以找到问题,对于这次的这个问题,就能找到如下信息:

image

 

第5步:再搜索一次(是的,你没看错),因为这个时候我们可以看到更具体的错误信息了。一般我的做法,就是直接搜索那个出错的方法名字,如果错误比较常见的话,也能比较容易找到(也有可能你只能找到有人遇到了同样的问题,但是没人解答,哈哈)。不过,这次搜索依然没找到原因(我不记得我这一步到底干没干了,就暂且假设没找到吧……)

 

第6步:定位错误的根源。从上面这个日志中,可以看到整个CallStack里面都没有自己的代码,都是SharePoint自身代码造成的问题。具体再结合同一个Correlation Id(就是日志最后的那个GUID)往前找的话,可以看到错误的起点:

image

可以看到,是调用sitedata这个Web Service的时候抛出的一个异常。

那么到底怎么知道为什么会发生这个异常呢?既然日志中已经给出了完整的Call Stack,那么我们只需要找到出错那个方法,也许就能大概猜测到错误的原因了。

从上面的日志中可以看到,错误中最后一个和SharePoint相关的方法是Microsoft.SharePoint.SoapServer.SiteDataImpl.GetSiteGroupsXml这个方法,结合前面的起点,可以确定这个就是SiteData那个Web Service中调用的方法。

 

第7步:进一步定位。这个时候就要祭出大杀器:反编译器了。为了能够看到具体的原因,我们需要深入到SharePoint的源代码中,一个好消息是,SharePoint 2010的几乎所有dll都是未经混淆的,很容易解读。

但是,应该反编译哪个文件呢?

这个时候就需要你对SharePoint稍微低一点的层次有一定了解了,你需要知道Web Service的那个目录“_vti_bin”是一个虚拟目录(如果你不知道的话,你能想起来去IIS里看一眼也行),从IIS里可以很容易看到这个sitedata.asmx的具体路径,是在14\ISAPI目录中。asmx文件中一般都是只记录了一个程序集的信息:

image

这个信息虽然很简短,但是也足够我们知道,这个程序集的信息了,程序集的名字叫stssoap。一般情况下,程序集的名称和dll的名称是一致性的,因此我们的目的就变成了找到一个名字叫stssoap.dll的文件。

到这一步的时候,就你对SharePoint有一些更深入的了解——你需要知道SharePoint的那些dll都分布在哪些地方:大多数dll可以在GAC里找到,当然也有一些其他地方,比如这个stssoap.dll,是在IIS路径(也就是所谓的“80”目录中的_app_bin里面,对了,这个目录中还有一个我经常会看的dll,就是Microsoft.SharePoint.ApplicationPages.dll,也就是layouts中那些配置页面的后台文件)。

当然,如果你没有这个知识的话,还有一个笨办法:按照文件名做全盘搜索……

 

第8步:反编译。用任何一种反编译工具(我用的是JustDecompile)打开这个stssoap.dll,可以很容易看到那个Web Service的后台类,以及出错的那个类:

image

然后就看一下具体的出错方法的代码,也就是那个GetSiteGroupsXml:

image

方法有点长(也不算太长),我没有贴全,结合日志中具体的错误信息(一个数据库操作的xxx.get_String()方法),我们可以有目的的注意到两个地方:首先,就是调用的那个存储过程和参数(我们很幸运,这个方法是直接用ADO.NET操作的数据库,SharePoint很多底层的方法追踪到最后都是COM+的非托管方法,我们就无能为力了);其次,就是可能出错的那两句话,sqlDataReader.GetString()。

如果你有经验的话,到这一步已经可以八九不离十地猜到原因了:在GetString的时候,数据库里的那个值是NULL,转换到C#中就是DBNull类型,当你直接GetString的时候,就会发生之前的那个错误。这里面GetString的有两个(当然方法后面还有几个GetString没有贴出来),一个是Name,一个是Description。首先从方法名字和属性名字我们可以猜出来这个方法是获取SharePoint用户组的,用户组的Name和Description有可能是null么?对于我这个情况来说,还真是(因为这个用户组是我用代码创建的,创建的时候,那个Description属性我还真用的是null……)

谜底揭开!!

当然,一个好的技术人员都是有好奇心的,既然已经走到这里了,何不接着走下去呢?

 

第9步:定位到数据库级别。在内容数据库中,找到那个proc_SecGetAllGroupsAndMembershipInfo存储过程,先来执行一下看看……

等等,这里面有个参数SiteID,怎么填?回到最早的那个爬网日志,记得那个出错的网站集么……你问我怎么得到网站集的ID?方法太多了……你可以在管理中心跟网站集相关的各种设置页面的Url参数中找到;可以用PowerShell找到;可以写个Console程序找到;可以直接从数据库里找到……随你选择。

存储过程执行之后的结果:

image

啊哈!

 

第10步:再深入一点。看看这个存储过程是怎么写的?

image

看到了一个叫TVF_Groups_Site的函数,继续看这个函数:

image

来自Groups这个表,而这个表:

image

嗯,就没什么神秘的了……就是SharePoint存储用户组的表了。

到这个时候,你就可以摇摇尾巴,考虑要不要把这个像解密一样的过程写成一篇Blog共享出来了。

posted on 2012-09-15 14:17  Erucy  阅读(1563)  评论(4编辑  收藏  举报

导航