一个简易的debug库设计与实现

背景

最近项目上线了广告Offer按照ecpm等排序策略功能,上线之后经常被pm骚扰,因为他经常想查看为什么一个offer没有展示等问题,每次都要帮他查看线上日志,过程很痛苦,占用了大把时间。必须要改变这种现状。

debug的用途

便于线上case追踪用,分析程序执行的每个环节。

设计要点

  • debug信息的层级关系

为了很好地阅读debug信息,必须将debug信息很好地组织起来,比如一个请求来了,在后台执行的时候需要经过好几步,stage1, stage2,state3,…,其中stage1中又有好几步,我们可以把这些信息按照树的结构组织起来:

{
    "request": {
        "ip": "180.92.201.3",
        "uri": "/api/offer",
        "network": "wifi",
        "debugid": "8782399662"
    },
    "process": {
        "stage_readOffers": [
            {
                "offer_id": "3142",
                "type": "aio",
                "flags": {
                    "d": 1,
                    "x": false,
                    "ne": -1
                }
            },
            {
                "offer_id": "3142",
                "type": "aio",
                "flags": {
                    "d": 1,
                    "x": false,
                    "ne": -1
                }
            },
            ...
        ],
        "stage_filterOffers": [
            {
                "offer_id": "3142",
                "type": "aio",
                "flags": {
                    "d": 1,
                    "x": false,
                    "ne": -1
                }
            },
            ...
        ],
        ...
    },
    "response": {
        "offer": {
            "offer_id": "3142",
            "type": "aio",
            "flags": {
                "d": 1,
                "x": false,
                "ne": -1
            }
        }
    }
}

这样我们就知道每一步发生了什么,为什么有些offer信息没有展示,有些展示了,整个请求走了哪些逻辑分支。

  • 如何动态地添加debug信息

打印debug信息是分散在代码的各个地方,如何方便地将这些分散的debug信息组织起来?如果是动态语言,这种操作很方便地,比如上面的json格式和python的dict和php的array是对应起来的。如果是C/C++这种静态语言,需要单独开发,公司的bsl::IVar就是做这样的事,有时间研究一下。

那具体怎么添加呢?比如我要给上面的process的stage2添加一个offer,可以这样做:

addDebug('process.stage2.offer', $offer);
    public function addDebug($key, $info, $flag = false) {
        $offset = 0;
        $keyLen = strlen($key);
        $key = trim($key, Mj_Debug::SEP_KEY_CHAR);
        $cur = $this->value;
        while ($found = strpos($key, Mj_Debug::SEP_KEY_CHAR, $offset)) {
           $tmp = substr($key, $offset, $found - $offset);
           if (!isset($cur[$tmp])) {
               $cur[$tmp] = array();
           }
           $cur = $cur[$tmp];
           $offset = $found + 1;
        }
        $lastKey = substr($key, $offset);
        if (!isset($cur[$lastKey])) {
            $cur[$lastKey] = array();
        }
        if ($flag) {
            $cur[$lastKey] = $info;
        } else {
            $cur[$lastKey][] = $info;
        }
    }

这样在debug库的内部自动解析成数组的维度。

  • 如何避免影响对主要业务逻辑的性能影响

因为一个请求来了之后可能打印很多的debug信息,而这些debug信息落地或者网络传输到cache中,由于数据量大,有两个弊端,一个是占用业务逻辑的大量cache空间,一个是影响主要逻辑的性能。为了避免这两点,可以引入旁路cache,这个cache和业务逻辑的cache不是同一个,这样就避免了影响业务逻辑。旁路cache的写入,往往需要单独的后台线程进行写入,这样就需要一个任务队列了,业务代码产生debug信息放到任务队列,后台线程读这个任务队列,将debug信息写入cache。

但是,对应php这种语言,没有线程这一说,就不存在旁路写入了,但是php使用的场景是nginx的一个请求来了,启动一个子进程,也就是说php处理请求地时候是在一个单独的进程中,这样对其他的请求不会影响。 而且实际使用中debug的请求只是站所有请求的很小一部分。而且将将debug信息输出,往往只是内存地操作,速度很快。

  • 如何避免cache中的debug信息过多

这个好解决。首先并不是所有的请求都需要写debug信息,可以采用随机抽样的方式,比如只有5%的请求需要打debug信息,而且写入到cache中的debug信息是会失效的,可以设置超时。

代码实现

参见debuglib