【项目总结】android 日志收集

来新公司20天,完成了第一个任务,安卓端日志收集流程的开发,在这里总结一下。

 

1.场景介绍

    公司有多个产业,各产业产生若干app,现在需要收集app的日志信息,并做相关计算,例如流量统计、用户画像等。

    用户的数量级目前不易确定,因为有大半app还在开发中,并没有发布。因为我们是新成立的数据组,没有人熟悉安卓相关的东西,所以老板让我研究一下把这条线打通。

2.技术方案

    Android SDK(日志产生) -> Flume (日志收集) -> kafka(消息缓存) -> storm(日志解析) -> hbase (落地)

    每一个模块我之前都没有接触过,所以都要从头了解,好在flume之后的这些服务已经搭好,我只要摆正姿势使用就可以。

3.技术细节

3.1 Android SDK

    安卓日志的统计对象是用户的行为,即点击、滑动、翻页、跳转等事件;统计内容主要包括设备信息、用户信息、事件信息等。

3.1.1 接口设计

    SDK开发完后,最终会提供给app开发团队,让他们接入到app中,在需要产生日志的地方埋点;因此SDK的接口不能复杂,不能带来太多的接入开发工作。考虑之后,第一阶段只提供下面几个接口

(1) luanchApp() // app启动时调用,目的是初始化sdk
(2) onEvent()   // 各类按钮相应事件中调用,用来统计普通事件
(3) onPageStart() // 页面/activity的开始事件中调用,用来统计页面访问事件
(4) onPageEnd() // 页面/activity的结束事件中调用,用来统计页面访问事件

3.1.2 Android Activity 生命周期

    页面事件是比较重要的一部分,开始之前,对安卓activity的生命周期做了一下了解。下面是几个相关的方法,

(1) onCreate(), onDestroy()   // activity对象的创建和销毁
(2) onStart(), onStop() //activity的开始和停止事件
(3) onResume(), onPause() // activity的继续和暂停事件

    通俗来讲,当activity显示出来,就会调用一次onStart,变得看不见,就会调用一次 onStop;当可以在activity上进行操作时,则会调用一次onResume,变得不能操作时,就会调用一次onPause。onResume和onStart的区别的一个例子:当一个activity A的上面,出现一个透明activity B将A覆盖,那么会调用A的 onPause,而不会调用 onStop。因为A还看得见,但是不能操作了。

3.1.3 日志项

 

名称 例子 类型 说明
userid abc 字符串 用户id(由app提供)
appid 123 字符串 应用id(由app提供)
guid 787f7300-37e2-34d8-b101-c8ef415385ae 字符串 设备唯一id
imei 867831028457919 字符串 国际移动设备标识
ln zh 字符串 语言
density 3.0 浮点型 屏幕密度
tel 130128361936 11位整数 电话号码
mac f4:8b:32:af:22:e9 字符串 设备mac地址
iscrack 1 0或1 是否root
timezone Asia/Shanghai 字符串 时区
nettype lte 字符串 网络类型
longitude 39.001 浮点型 经度
os 4.4.4 字符串 os版本
platform android 字符串 os
module MI 4LTE 字符串 手机型号
sr 1080*1920 字符串 屏幕分辨率
sdkver 1.1.5 字符串 sdk版本
isp 46002 整型 运营商代号
appver 1 字符串 app版本
ip 10.0.2.15 字符串 ip地址
ismobile 1 0或1 是否为手机
requesttime 1451272912 整型 请求产生事件
netstatus 1 0或1 网络状态
sim 898600310115f0024716 字符串 sim卡id
channel 12 字符串 app渠道
latitude 120.123 浮点型 纬度
event.sessionid 112312341341341341 字符串 事件所属的sessionid
event.eventtime 1451028244 整型 事件产生时间
event.duration 12 整型 事件持续事件
event.pagedur 12 整型 页面停留时间
event.definedid abc 字符串 自定义事件id
event.prepageid red 字符串 前一个页面id
event.currevent page 字符串 事件类型
event.pageid blue 字符串 当前页面id

 

    这里面有几个项比较纠结,不易获取:

    (1) latitude 和 longitude,参考一篇帖子,http://stackoverflow.com/questions/20438627/getlastknownlocation-returns-null

    (2) IP,按照找到的方法总是取不到安卓的真实ip,索性不取了,在flume中的http请求头中得到

3.1.4 日志产生流程

    为了让后续实时分析避免对历史数据的关联,发送的日志数据,每条记录都带上全部字段信息。这样就导致每条记录至少在1K,如果实时发送,吃不消。因此,使用定时发送策略,暂定每60s发送一次,每次发送的数据中,设备相关的信息只保留一份,事件以数组的形式附在其后。这样,经过压缩之后,基本可以保证每分钟日志产生流程在 2K 以内。另外,页面事件的产生,是在 onPageEnd 中,也就是说,当离开这个页面的时候,才产生这个页面的对应事件,这样做的目的是为了统计页面停留时间。

 

    上图是日志产生的流程图。时钟响应每1分钟触发一次,期间产生的事件,放入sqlite数据库中。有一点需要注意:

    app如果退出,则会导致缓存的事件不能及时发出,因为我们平时从后台退出app的方法是会直接杀死进程的。为了避免这种情况,当app发生进入后台、屏幕锁定这两种动作时,不管时钟相应是否触发,直接发送一次缓存事件,因为这两种动作之后,app进程很有可能被杀掉。另外,如果因为各种原因,app退出后还是留下没有及时发出的事件,那么下次打开app时,第一件事就是把上次缓存的事件发送出去。

3.1.5 日志格式

    日志最终以json格式发送,例子如下。client部分是设备相关的信息,events是对应的事件信息。

{
  "client": {          
    "ln": "zh",        
    "density": "3.0",    
    "tel": "",            
    "userid": "",         
    "appid": "731224921",                
    "mac": "f4:8b:32:af:22:e9",         
    "iscrack": "0",                       
    "timezone": "Asia/Shanghai",         
    "nettype": "lte",                    
    "longitude": "",                     
    "os": "4.4.4",                       
    "platform": "android",               
    "module": "MI 4LTE",                
    "sr": "1080*1920",                   
    "sdkver": "1.1.5",                  
    "isp": "46002",                      
    "imei": "867831028457919",           
    "udid": "",                          
    "appver": "1.0",                     
    "ip": "10.0.2.15",                   
    "ismobile": "1",                     
    "guid": "787f7300-37e2-34d8-b101-c8ef415385ae",   
    "requesttime": "1451272912",                       
    "vendorid": "",                                    
    "netstatus": "1",                                  
    "advertid": "",                                    
    "sim": "898600310115f0024716",                     
    "latitude": "",                                    
    "channel": "12"                                    
  },
  "events": [         
    {
      "eventtime": "1451028244",
      "duration": "",
      "isfirst": "",
      "pagedur": "1",
      "defineid": "",
      "moduleid": "",
      "prepageid": "",
      "params": "",
      "modulecnt": "",
      "currevent": "page",
      "pageid": "MAIN"
    },
    {
      "eventtime": "1451028245",
      "duration": "",
      "isfirst": "",
      "pagedur": "1",
      "defineid": "",
      "moduleid": "",
      "prepageid": "MAIN",
      "params": "",
      "modulecnt": "",
      "currevent": "page",
      "pageid": "MAIN"
    }
  ]
}

3.1.6 日志压缩方法

    为了节省流程,日志在传输之前需要进行压缩。压缩使用 gzip 方法。直接对 json 字符串进行 gzip,然后输出的数据进行 base64,最后为了用get方法请求,需要再进行一次 UrlEncode。压缩后的数据形式如下。经测试,在包含50个左右的event时,压缩后大小在2k以内。

 data=H4sIAAAAAAAAAM1STW%2FUMBD9KyhH1A3%2BSuLkxgHQSgUOIHGgHBx7sms1cYLtVGpX%2Fe%2BMk91qKwFSqx6qXObNeGbee5NDpnsLLmbNIetd1mR3%2B%2BwiM%2BCCjbcIeU4QR%2BgxxmAO4K1ZYzVNS1hxypioGcXcoDRmOtHItuGsUV3DWAM1VmzQXulrrC4D7QB3owOE74NVV%2B%2B%2B7ZXb7ZXFmoMYb6dU%2BrH9uMVEP7qdjbNJKUp5zhmticTCGDAjcvwQTL2K3egHTCln%2FIjckM9o5j71fd6%2BEZffP2Aq%2BDSGSPKW1ixxCeb6BpZkTvNioTqluSUhLKEBLEJZVpJTwqQoqpomRbM5c%2BI0IQ20qZ1WLKdVzoTMGV0NGMbWLmSSU7t5NU9WXcUJ2fAK2IYLIzctJXSjJXSCFlwWCvC1h98zhJhsS%2F2iYKREHmnbDTgzPhwFzQtRxTkc1yiDzOKpGmyyR9YStaEYSosORYqKlslmFU8uM55TVvMipTVexi33pyy7v8gAF0Yc%2F%2FOwho858SJpNbPHYaNbt9rQWR%2FiCia1Aywf6RnorIMTvfVaJzR5SI8foPJqCOcPtTvO1LP3CxeEqeW4Zum8%2FPpp%2BwV5%2F5VttfxG%2F2JLHtF9PtsAIeD4pcQ1iEK3oCtetoSrVtNO1x2pO1Hymsv%2Fi8PT%2Bniu7nnCXuoMLyfsaVeTr%2FMf%2B3X%2FB5yw2A5OBQAA

3.1.7 接入flume测试

   flume是一个常用的日志收集工具,下一节做具体说明。flume提供了很多日志的接入方式,http就是其中一种。只需要在app中向flume指定的服务器端口发送http请求,即可完成日志的收集。

3.1.8 部分参考资料

    http://stackoverflow.com/questions/5586197/android-user-agent              --- 安卓获取 user agent

    http://wingjang.blog.163.com/blog/static/479134422013107111424348/      --- base64 编码的换行问题

    http://blog.csdn.net/liuhe688/article/details/6733407                             ---  activity 的生命周期

    http://blog.csdn.net/gouguofei/article/details/7775752                            --- 安卓监听程序进入后台

    http://blog.csdn.net/m_changgong/article/details/7608911                      ---  安卓监听手机锁屏

 

 

3.2 Flume

      flume 的简介参考这篇帖子,http://shiyanjun.cn/archives/915.html。

      在这个项目中,配置了一个http source,两个memory channel,两个 sink;一个是 file roll sink,用来把日志写到本地文件,另一个是 kafka sink,用来将日志推到 kafka 上,以便后续处理。

3.3 Kafka

3.4 Storm

     storm 的简介参考,http://www.searchtb.com/2012/09/introduction-to-storm.html。

     在这个项目中,topology 中一共有一个spout 和 三个 bolt;spout 是从kafka中取数据,然后emit;第一个 bolt 是日志的解析,通过 UrlDecode -> 反base64 -> gunzip,可以恢复出日志json字符串;再将json拆成若干个events数据行,然后以list的形式发送;第二个bolt是将数据写入hbase,每个event对应一行数据,每行数据都带有全部采集项信息;第三个bolt是创建hbase表,日志表目前按照日期每天新建一个。当第二个bolt插入时发现没有建表时,才会执行第三个bolt。

3.5 HBase

 

posted on 2016-01-14 12:04  sparrowjack  阅读(4301)  评论(0编辑  收藏  举报

导航