专栏文章 关于埋点测试和平台化处理方案

CC · 2018年11月24日 · 最后由 大鹏 回复于 2019年11月26日 · 4731 次阅读

最近忙在项目里面,在项目后期,有个埋点测试,服务端加客户端的点,差不多有 50 个,假设纯肉眼测试,那简直就是崩溃,因此前期先简单的做了数据解析及数据结构解析,后期准备平台化处理

以目前我们使用的神策打点举例,打点包含 事件,事件属性,预置属性
安卓和 ios 打同一个点的时候,若数据类型不一致,一旦一端入库之后,另一端肯定入不了库,因此需要检验数据类型

后续给大家补上关键代码处理

说下思路及目前做法:

1.由于我们这边等于在入库前做一次数据检查,但我们不需要存储数据,所以与客户端约定,在测试环境,客户端把打点数据发送到 nginx,由 nginx 转发到神策服务器
2.已经配置 nginx,把客服端的请求日志打印出来,所以只需解析 access.log 日志,
3.过滤出要处理的数据,urldecode 解码,base64 解密,gzip 解压缩,得到 json 数据
4.套用我之前文章中的格式解析,达到格式检验,其他就是自己写个方法解析数据。检验自己预期得到的数据

平台化思路:

1.打点时间按照指定数据格式文档上传服务器,自动生成模板事件格式,这个方案能解决客户端希望的格式检验问题
2.上传 access.log 日志到服务器
3.运行上述核心方法检验,输出测试结果

ps:服务端打点没有特意说到,我们这边服务端的打点处理更加简单,服务端代码默认是按照神策打点日志格式写到本地磁盘,把神策服务默认从该磁盘读取处理,因此只需把日志拉出来按照上述方式处理即可

Nginx 配置

解密

   @Test
    public void testDecode1() throws IOException{
        String value = "H4sIAAAAAAAAE%2B2Y7W8bNRzH%2FxenL5PK5%2FM9OO%2FyNIRgMCmb9mJClntxEqsX%2B%2BTztaqqSt1LYLANaUgIELA3IBDiDdLQpok%2FZqRZ%2FwvspCU5LS1dq0Vl9F6dfmf%2FHj%2F%2BysmdXWDEiIO6F2BEMPZ8GHphFVCjWbJJRQ%2FUoxBCLwgIrgK%2BxaUBdbDWyLKuYdoAu7KfFvmQlr0EXoxQFfREboRMzNQPCIJ2qwlxu0YaIaphHDdqpENatdhvRjBowmuYeNZhplXGtRE8B%2FVdsNbjWyLhMw8exp1mSEitEwakhjuwVWu22o1aFMYYkqjT9AiyHtZUTre4zoWSbhNah%2BvO8VrCtBZcW9tff%2Fw6%2Fvr55Mdn409%2Bmm2wRvFh172LnPaFzg3tsR1Q77M059aaJ5pzSYdcDIa2BbGH5sZt0TNDUPejwNpGqsdT5%2B3GUEnuwarvnEputpXepGYns20C%2BB1nTMXGPCzLsoWkXc7QmUdMFn2WmEJPE7eNT7mzb4u%2BmGdnPS1WvB6vI69Uy2w8x8s1z4sRp32tRnTDznmgVSFtg40u%2BF4VHCU5RcB6maa5OwvS44YJV16Xy1zpvCFZumNEknfb71UqtoZUJMzYNNqi1%2BSJGvFGYsQWr1fsA07J9Cy9cHtH3AyVYyGxfQZ7e9VXACZhgEoA%2B5j4gYUYzQHmNGMDTo8rvIQM36AfNK537PprQg64Pvzi8eTB7%2FTF%2FoPxd08nP%2By%2F2H84%2BerZ%2BPmjyaN7h98%2BvjD0S%2FE%2B9SScAfrSTJeegItA7%2FbmqtCJc9efdolmgkl1ImZnRPvmzY7DpGVR3rzOpAVFVyrG0Ck8tK%2F0jP1%2F0L8tzLDOjNFiozA8Xx3qGCLsl1DHMIQ%2BjpyCL2r1tJQVcM5TPnI9SpQ0s%2BgHH3968M3T8Z%2B%2FHHz2ZAasq27lmv4fxPs4EcncsCyUt3nq9LRle6tVmtqt1XnHj8LeerdZGGOjXfgIvBKtUlkh1zGKSlx7oSUEBYj4b5eGHx2P%2Bwtn4%2F%2FG%2BZWMY2LpLuFOIncLj2F8WWT85edPxve%2FPPz53svf7k6%2Bv3ul4efS8Gnk99VAyJWI%2BLJwK1TxwENxWcXjCBJoP%2BHLgvXVjeSK5jPTHJHyXdtDIY4j%2B8tygeauGMhb2WuxjKBHMIqhfd7onx%2F%2FCt%2FJ3J4TwDI8S3E8ibtTJrWMH6WF5YKlr6UOi9DR3A6uyM53lUgdlG%2FsuvDR3wNnZBqjEwAA";
        String value1 = URLDecoder.decode(value);
        System.out.println(value1);
        System.out.println(new String(uncompress(Base64Coder.decode(value1))));
    }


    public static byte[] uncompress(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        try {
            GZIPInputStream ungzip = new GZIPInputStream(in);
            byte[] buffer = new byte[256];
            int n;
            while ((n = ungzip.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (IOException e) {
//            ApiLogger.error("gzip uncompress error.", e);
            e.printStackTrace();
        }

        return out.toByteArray();
    }

输出结果:

数据校验

    /**
     * 检查原始数据是否包含基础checkList,失败则添加到结果
     * @param sourceInfo
     * @param checkList
     * @param event
     * @return
     */
    public JSONObject checkResult(JSONArray sourceInfo,Map<String,String> checkList,String event){
        JSONObject result = new JSONObject();
        Set<String> eventSet = new HashSet<String>();

        JSONObject formatResult = new JSONObject();
        for(int i = 0 ;i < sourceInfo.size(); i++){
            JSONObject tempObj = JSONObject.fromObject(sourceInfo.get(i));
            String eventValue = tempObj.getString("event");

            if(eventValue.startsWith("$")){
                continue;
            }

            if(StringUtils.isNotEmpty(eventValue)&& event.equals(eventValue)){
                JSONObject propertiesObj = tempObj.getJSONObject("properties");

                String eventInfo = "";
                if(propertiesObj.containsKey("$os")) {
                    //拼接 Event事件 名称: os+event
                    eventInfo = propertiesObj.getString("$os") + "_" + event;
                    eventSet.add(eventInfo);
//                    //比对JSON
//                    formatResult.put(eventInfo,DiffMethod.diffFormatJson(propertiesObj,DEFAULT_PROPERTIES_JSON));
                }

                StringBuffer resultInfo = new StringBuffer();
                StringBuffer formatResultInfo = new StringBuffer();
                for (String key:checkList.keySet()){
                    if(!propertiesObj.containsKey(key)){
                        if(propertiesObj.containsKey("$os")) {
                            resultInfo.append("操作系统:[" + propertiesObj.getString("$os") + "]");
                        }
                        resultInfo.append("预期应有基础属性值:" + key + ",而实际不包含这个值");

                    }

                    if(propertiesObj.containsKey(key)){
                        String expectType = checkList.get(key);
                        String resultType = DiffMethod.getTypeValue(propertiesObj.get(key));
                        System.out.println(eventInfo + "--实际值:" + key + ":::" + propertiesObj.get(key));
                        if(!resultType.equalsIgnoreCase(expectType)){
                            System.out.println("预期:" + expectType);
                            System.out.println("实际:" + resultType);
                            formatResultInfo.append(key + "预期类型:" + expectType + ",实际类型:" + resultType);
                        }
                    }

                }
                result.put(eventInfo,resultInfo.toString());
                result.put(eventInfo + "Format",formatResultInfo.toString());

            }
        }

        return result;
    }

方法调用

/**
     * 测试指定事件
     */
    @Test
    public void testPointEvent(){
        String filePath = "/nglog/dd.txt";
        JSONArray sourceLog = scLogAnalyseApi.readLogAsList(filePath);
        //测试自定义属性
        Map<String,String> customPropertiesList = Maps.newHashMap();
        String eventName = "PIANO_STUDENTS_ATTEND_CLASS";
        customPropertiesList.put("app_source","String");
        customPropertiesList.put("HANDLE_OBJECT","String");
        customPropertiesList.put("TRAIN_GOODS_NAME","String");
        customPropertiesList.put("COURSE_ITEM_TEACHER_USER_NICK","String");
        JSONObject analyseResult = checkResult(sourceLog,customPropertiesList,eventName);
        System.out.println(analyseResult);
}
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 11 条回复 时间 点赞
CC 关闭了讨论 11月24日 14:00
CC 重新开启了讨论 11月24日 14:00

已经 mark,加油哦!

还好 我们的埋点都是开发自测的。。。

CC #5 · 2018年11月24日 Author
恒温 回复

其实埋点我想丢给产品测试,都是产品跟运营在看😀

CC 回复

我们因为要自己洗数据,所以还要关心下,其实大部分应该是数据来做

我们都是产品测试的,测试没人力,产品去大数据那边拿埋点数据。

CC #8 · 2018年11月24日 Author
恒温 回复

大公司分工比较明确,我这边小公司,很多时候一旦出问题都是直接找测试,所有还是要经过测试这一关

CC #9 · 2018年11月24日 Author
magicyang 回复

我们没大数据部门😄

我们做了一个实时上报的工具,可以实时查看终端的上报。验证终端是否上报并上报字段是否正确,如果是 OK 的,然后再到大数据查询是否入库成功。

qiangqing2018 回复

你好,实时上报是什么意思?如何验证终端是否上报并检查上报字段是否正确呢?因为打点事件非常多。

CC #12 · 2019年03月07日 Author
不二家 回复

猜测只是打印出神策相关日志。根据打点要求,肉眼比对

CC 回复

请问,过滤出要处理的数据,是按照您文章里说的 urldecode 解码,base64 解密,gzip 解压缩,得到 json 数据这个顺序来解码吗?我试了一下遇到来问题呢,urldecode 是转成中文吗?然后在 base64 解码?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册