美文网首页
内忧外患:埋点的优化

内忧外患:埋点的优化

作者: 凌霄光 | 来源:发表于2018-09-19 12:08 被阅读94次

    为了感知用户的行为,并基于用户使用产品的各种行为,进行产品的迭代优化等。项目代码中有大量的埋点,主要是页面浏览元素点击两种。

    按理说,这也是一个业务无关的逻辑,不应该出现在业务代码中,就算必须嵌入业务代码,也应该占据很少的一部分,但事实上,埋点不但侵入了业务代码,而且占据了非常多的代码量。

    现场是这样的,感受一下。

    点击事件埋点:

       timer(x = this) {
          this.$logger.log({
            type: this.$logger.eventType.action,
            event_id: Date.now().toString(),
            payload: [
              {
                key: 'type',
                value: 'time'
              }
            ],
            event: 'quick_tool_click',
            event_name: this.$logger.getEventName('quick_tool_click')
          })
          //使用默认值,把this方便传给vuex,方便埋点
          this.showTimer(x)
        },
        redflag() {
          this.$logger.log({
            type: this.$logger.eventType.action,
            event_id: Date.now().toString(),
            payload: [
              {
                key: 'type',
                value: 'redflag'
              }
            ],
            event: 'quick_tool_click',
            event_name: this.$logger.getEventName('quick_tool_click')
          })
    
          if (this.selectedStudentList.length == 0) {
            return this.toast2()
          } else {
            this.$router.push({
              name: coverPageNameToLowerCase(PageName.GROUPING_GRANT.ROUTE),
              query: {
                from: PageName.LECTURE.ROUTE
              }
            })
          }
        },
    

    页面浏览埋点:

      beforeRouteEnter(to, from, next) {
        next(vm => {
          vm.pv_id = Date.now().toString()
          vm.$logger.log({
            type: vm.$logger.eventType.pv,
            event_id: vm.pv_id,
            page_name: PageName.CLASS_ENJOY.LOG,
            event: 'page_entry',
            event_name: vm.$logger.getEventName('page_entry')
          })
        })
      },
      beforeRouteLeave(to, from, next) {
        this.$logger.log({
          type: this.$logger.eventType.pv,
          event_id: this.pv_id,
          page_name: PageName.CLASS_ENJOY.LOG,
          event: 'page_leave',
          event_name: this.$logger.getEventName('page_leave')
        })
        next()
      }
    

    这两段代码,第一段是点击事件埋点,第二段是页面浏览埋点。很常见的一个需求,却也很常见的大段代码,这显然是不合理的。

    经过分析,我发现了几个问题:

    1. 很多参数是没必要传的,完全可以封装到log内部,比如自动生成的event_id,比如从map中取的event_name。
    2. 像页面浏览,每个页面都要加一个beforeRouteEnter,beforeRouteLeave,太繁琐了,而且一旦有改动,那需要改动的文件数量也特多。

    这两点问题是两个方面,一个是内部的封装(内忧),一个是外部的使用(外患),一个是部分参数应该封装到内部,一个是外部的使用方式应该简化。

    我分别对这两点进行了改进,第一点可以把内部暴露出的方法简化,第二点可以用mixin或指令来解决,这里指令的方式更加优雅。

    之前暴露出的接口:

    const _logger = function (Vue, options) {
      if (_logger.installed) {
        return;
      }
    
      const tracker = logger.getLogger('tracker');
    
      Object.defineProperties(Vue.prototype, {
        $logger: {
          get() {
            return {
              log: function (data) {
                var _log = configContext(tracker, options);
                _log.info(data)
              },
              getEventName: function (key) {
                return _eventmap.get(key);
              },
              eventType: {
                pv: 'page_view',
                action: 'action',
                profile: 'performance'
              }
            }
          }
        }
      })
    }
    

    我把log改成了私有方法,暴露出了trackPv,trackAction和trackPerformance三个方法。参数也精简成了:pageName、eventName、trackData,简洁却又足够。

      const tracker= logger.getLogger('tracker');
    
      const eventType = {
        pv: 'page_view',
        action: 'action',
        performance: 'performance'
      }
      //这里叫eventName是不对的,应该是eventDesc,是描述信息
      const getEventDesc = function (key) {
        return _eventmap.get(key);
      }
      const log = function (data) {
        var _log = configContext(tracker, options);
        _log.info(data)
      }
    
      const idGenerator = {
        timeId() {
          return Date.now().toString()
        }
      }
    
      const track = (eventType, pageName, eventName, eventData) => {
        log({
          event_id: idGenerator.timeId(),
          type: eventType,
          page_name: pageName,
          event: eventName,
          event_name: getEventDesc(eventName),
          payload: eventData
        });
      }
    
      const eventTracker = {
        trackPv(pageName, eventName, eventData) {
          track(eventType.pv, pageName, eventName, eventData);
        },
        trackAction(pageName, eventName, eventData) {
          track(eventType.action, pageName, eventName, eventData);
        },
        trackPerformance(pageName, eventName, eventData) {
          track(eventType.performance, pageName, eventName, eventData);
        }
      };
    
      Object.defineProperties(Vue.prototype, {
        $tracker: {
          get() {
            return eventTracker;
          }
        }
      })
    

    现在组件内的调用方式:

    this.$tracker.trackPv('login', 'login_btn_click', {
        a: 'aaa',
        b: 'bbb'
    });
    

    这样还不够,比如页面浏览,埋点还是得写在组件里的路由切换的钩子里。于是我又封装了v-trakPv,v-trackClickAction两个指令,前者加在页面组件上,后者用在具体元素上。

    trackPv:

    /**
     * trackPv指令
     * 绑定元素在组件创建销毁时会进行 page_entry和page_leave埋点
     *
     */
    export default (tracker) => {
    
        const handleTrackInfo = (value) => {
            const trackInfo = value || {};
            if(!trackInfo.pageName) {
                throw new Error('trackPv指令: pv埋点必须传入pageName');
            }
            return trackInfo;
        }
    
        return {
            bind(el, binding) {
                const {pageName, eventData} = handleTrackInfo(binding.value);
                tracker.trackPv(pageName, 'page_entry', eventData);
            },
            unbind(el, binding) {
                const {pageName, eventData} = handleTrackInfo(binding.value);
                tracker.trackPv(pageName, 'page_leave', eventData);
            }
        }
    }
    

    trackClickAction:

    /**
     * trackClickAction指令
     * 绑定元素点击时会埋点
     * 
     */
    export default (tracker) =>{
    
        let clickHandler = () => {}
    
        return {
            bind(el, binding) {
                const {pageName, eventName, eventData} = binding.value || {};
                clickHandler = () => {
                    tracker.trackAction(pageName, eventName, eventData);
                }
                el.addEventListener('click', clickHandler);
            },
            unbind(el, binding) {
                el.removeEventListener('click', clickHandler);
            }
        }
    }
    

    这样,就把pageview埋点的所有代码都封装到了指令内部,组件里只需要:

      <div v-trackPv="{pageName: 'aaa', eventData: {}}"></div>
    

    而元素点击的埋点也可以简化成:

     <button  v-trackClickAction="{pageName:'login', eventName: 'login_btn_click',eventData:{}}">登陆</button>
    

    这两种情况之外,与业务有关的埋点,或者其他事件的埋点,可以手动调用api

    this.$tracker.trackAction('pageName', 'event_name ', {data: 'data'});
    

    组件中的业务无关代码量,得到了非常大的减少。都转移到了模板中的指令里。

    总结

    埋点是很常见的需求,有些是业务无关、有的是业务相关。业务无关的埋点不应该出现在组件代码里,业务相关的埋点调用方式也应该简单。

    所以,我首先部分解决了内忧,把暴露的api进行了精简,同时也针对外患,也就是调用方式的繁杂,通过指令进行了封装。

    相关文章

      网友评论

          本文标题:内忧外患:埋点的优化

          本文链接:https://www.haomeiwen.com/subject/lqzbnftx.html