让我们一起写一个前端监控系统吧!(3)

2,071 阅读7分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情

  • 往期链接
  1. 让我们一起写一个前端监控系统吧!(1)
  2. 让我们一起写一个前端监控系统吧!(2)
  • 项目源码

序言

在前面两篇文章中,我们不仅了解了此前端监控的技术架构以及页面效果,还对完成“监控”这个核心功能的 npm 包有了深入的理解。

在封装完错误监控,获取错误数据之后,让我们走进可视化屏幕的世界!

没错,本章节将介绍我们中台可视化大屏的构建过程,其中主要使用到了 Echarts。

那么话不多说,和我一起来看看吧!

Echarts 大屏一览

首页

截屏2022-09-20 下午7.47.09.png

页面性能

截屏2022-09-20 下午7.48.07.png

JS错误概况

image

接口错误

image

看完了 Echarts 做出的效果之后,你有没有迫不及待的想要上手试一试呢?

让我们仔细的聊聊 Echarts~

Tips: 我会把某一个功能的代码分析完之后全部贴上去,大家如果需要可以直接取用看效果嗷!

关于echarts

安装

  • 下载
npm i echarts
  • main.js中导入echarts并注册别名
import * as echarts from 'echarts'
Vue.prototype.$echarts = echarts

使用

开发流程是在官网上找和自己需求相近的图然后对一些部分进行更改以满足自己的需求。

在下面我会给我的源代码以及几个主要用来更改图片效果的部分,不会列全,主要是常用,如果想要仔细的去看可以从这里看官方的文档Echarts

1. JS报错折线图

image.png

相关配置
  • 首先我设置的是dark模式,所以需要在初始化的时候单独操作

这样设置的原因是可以和UI整体的设计风格适配。

const myChart = this.$echarts.init(chartDom, 'dark')
  • 图片的标题可以通过option下的title来设置,字体也可以进行设置,诺~
title: {
text: 'JS报错趋势统计',
textStyle: {
fontFamily: "Alibaba",
   fontSize: 18,
   color: "#fff", 
}
},

但是我这里并没有使用title因为我这里还有需求,字体前面需要用到图片,所以我就单独写了个组件解决这个问题。

  • 背景颜色可以通过backgroundColor来设置
backgroundColor: '#000000',
  • 如果你想要设置hover出现相对应的特效,你可以加一个tooltip
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
  • xAxis|yAxis一般模版图里都有,这里就不赘述了。
  • 值得一题的是我这个折线图从上到下有一个渐变颜色,是怎么设置的呢?
areaStyle: {
color: {
type'linear',
x0,
y0,
x20,
y21,
 colorStops: [
 // 渐变颜色
  {
     offset0,
     color'#466AEB'
  },
  {
     offset: 1,
     color'#ffff'
  }
],
 global: false
}
}

在series下面设置一个areaStyle就可以了

全部代码
// html
<div id="main" style="height: calc((100vw / 7 * 2 - 99px) * 0.67); width: calc(100vw / 7 * 2 - 99px)"></div>
// js
import titleComponentVue from './titleComponent.vue'
export default {
   name: 'firstGraph',
   components: {
       titleComponentVue
  },
   data() {
       return {
           time: ['7-24''7-25''7-26''7-27''7-28''7-29''7-30'] || [],
           jsErrorNumberTong: [12120430430450320120] || [],
           jsErrorNumberYi: [12225327330320220120] || [],
           na: 'JS报错趋势统计'
      }
  },
   mounted() {
       const chartDom = document.getElementById('main')
       const myChart = this.$echarts.init(chartDom, 'dark')
       let option = {
           tooltip: {
               trigger: 'axis',
               axisPointer: {
                   type: 'shadow'
              }
          },
           backgroundColor: '#000000',
           // title: {
           //     text: 'JS报错趋势统计',
           //     textStyle: {
           //         fontFamily: "Alibaba",
           //         fontSize: 18,
           //         color: "#fff", 
           //     }
           // },
           legend: {
               orient: 'vertical',
               data: ['同步错误''异步错误'],
               right: -5,
               top: 15
          },
           xAxis: {
               type: 'category',
               datathis.time, // 时间
               boundaryGap: false
          },
           yAxis: {
               type: 'value',
               splitLine: {
                   show: false
              }
          },
           series: [{
                   name: '同步错误',
                   type: 'line',
                   datathis.jsErrorNumberTong,
                   symbol: 'none',
                   lineStyle: {
                       // 设置线条的style等
                       normal: {
                           color: '#466AEB' // 折线线条颜色:红色
                      }
                  },
                   itemStyle: {
                       // 设置线条上点的颜色(和图例的颜色)
                       normal: {
                           color: '#466AEB'
                      }
                  },
                   areaStyle: {
                       color: {
                           type: 'linear',
                           x: 0,
                           y: 0,
                           x2: 0,
                           y2: 1,
                           colorStops: [
                               // 渐变颜色
                              {
                                   offset: 0,
                                   color: '#466AEB'
                              },
                              {
                                   offset: 1,
                                   color: '#ffff'
                              }
                          ],
                           global: false
                      }
                  }
              },
              {
                   name: '异步错误',
                   type: 'line',
                   symbol: 'none',
                   datathis.jsErrorNumberYi,
                   lineStyle: {
                       // 设置线条的style等
                       normal: {
                           color: '#6BE6C1' // 折线线条颜色:红色
                      }
                  },
                   itemStyle: {
                       // 设置线条上点的颜色(和图例的颜色)
                       normal: {
                           color: '#6BE6C1'
                      }
                  },
                   areaStyle: {
                       color: {
                           type: 'linear',
                           x: 0,
                           y: 0,
                           x2: 0,
                           y2: 1,
                           colorStops: [
                               // 渐变颜色
                              {
                                   offset: 0,
                                   color: '#6BE6C1'
                              },
                              {
                                   offset: 1,
                                   color: '#402D6B'
                              }
                          ],
                           global: false
                      }
                  }
              }
          ]
      };
       myChart.setOption(option)
  }
}

2. 页面访问速度分布

image.png

相关配置
  • 页面居中|居左|居右配置

可以使用grid来对你想要操作的部分进行配置,option

grid: {
 left'10%',
 // right: '30%',
 bottom: '0%',
 containLabel: true
}
  • boundaryGap属性

image.png

可以用来设置柱子是否在如下坐标下划线中间

image.png

在这里被我用来使我的柱状图更加紧凑,这样好看些。

  • axisLabel => formatter

想知道前面的<1s1-3s以及前面的圆点是怎么来的嘛,这里就需要使用formatter格式来进行约束了。

原图链接

image.png

可以对比一下上下两张图,你会发现我做出的改造是

图片 => 原点

文字 => 读秒

先贴一下这部分的代码

data: ['0''1''2''3'],
axisLabel: {
interval0,
formatterfunction (value) {
 if (parseInt(value) == 0) {
return '{' + value + '| } {value|' + '< 1秒   ' + '}';
} else if (parseInt(value) == 1) {
return '{' + value + '| } {value|' + '1-3秒   ' + '}';
} else if (parseInt(value) == 2) {
return '{' + value + '| } {value|' + '3-5秒 ' + '}';
} else {
   return '{' + value + '| } {value|' + '5秒以上' + '}';
}
},

data里面拿值,在formatter里面逐个的进行判断,然后写成文字。

  • 那么,前面的小圆球怎么出现的呢?

当然是通过rich属性来设置的!

rich: {
value: {
lineHeight: 30,
align: 'center'
},
 0: {
   height8,
   width8,
   align'center',
   backgroundColor'#8ED6FA ',
   borderRadius10
},
 1: {
   height8,
   width8,
   align'center',
   backgroundColor'#6BE6C1 ',
   borderRadius10
  },
  2: {
  height8,
  width8,
  align'center',
  backgroundColor'#466AEB ',
  borderRadius10
  },
  3: {
  height8,
  width8,
  align'center',
  backgroundColor'#466AEB ',
  borderRadius10
  }
}

通过和css类似的写法,完成了小圆球的绘制

  • 还有一个很酷的操作,看原图会有y轴,而我的需求图中没有y轴,我试过把y轴去掉,但是这样操作的话,前面的label都会被消除掉。

我的team成员给我出了一个主意,不用删除它,只要我们看不见它就可以了 ———— 线条颜色与背景颜色同色!

yAxis下方

axisLine: {
lineStyle: {
   color'#000',
   width: 1, //这里是为了突出显示加上的  
},
},
完整代码
// html
<div id="main1" style="width: calc((100vw / 7 * 2 - 64px) * 5 / 8 );"></div>
// js
export default {
   name'thirdGraph',
   components: {
       titleComponent,
       fourGraph
  },
   data() {
       return {
           na'页面访问速度分布'
      }
  },
   mounted() {
       const chartDom1 = document.getElementById('main1')
       const myChart1 = this.$echarts.init(chartDom1, 'dark')
       var option1;
​
       const seriesLabel = {
           showtrue,
           position'right'
      };
       option1 = {
           grid: {
               left'10%',
               // right: '30%',
               bottom'0%',
               top'0%',
               containLabeltrue
          },
           backgroundColor'#000000',
           // title: {
           //     text: '页面访问速度分布',
           //     textStyle: {
           //         fontFamily: "Alibaba",
           //         fontSize: 18,
           //         color: "#fff",
           //     }
           // },
           tooltip: {
               trigger'axis',
               axisPointer: {
                   type'shadow'
              }
          },
           // grid: {
           //     left: 100
           // },
           // toolbox: {
           //   show: true,
           //   feature: {
           //     saveAsImage: {}
           //   }
           // },
           xAxis: {
               type'value',
               showfalse,
               axisLabel: {
                   formatter'{value}'
              },
               splitLine: {
                   showfalse
              },
               color'black',
          },
           yAxis: {
​
               axisLine: {
                   lineStyle: {
                       color'#000',
                       width1//这里是为了突出显示加上的  
                  },
              },
               splitLine: {
                   showfalse
              },
               boundaryGaptrue,
               type'category',
               inversetrue,
               data: ['0''1''2''3'],
               axisLabel: {
                   interval0,
                   formatterfunction (value) {
                       if (parseInt(value) == 0) {
                           return '{' + value + '| } {value|' + '< 1秒   ' + '}';
                      } else if (parseInt(value) == 1) {
                           return '{' + value + '| } {value|' + '1-3秒   ' + '}';
                      } else if (parseInt(value) == 2) {
                           return '{' + value + '| } {value|' + '3-5秒 ' + '}';
                      } else {
                           return '{' + value + '| } {value|' + '5秒以上' + '}';
                      }
                  },
                   margin20,
                   rich: {
                       value: {
                           lineHeight30,
                           align'center'
                      },
                       0: {
                           height8,
                           width8,
                           align'center',
                           backgroundColor'#8ED6FA ',
                           borderRadius10
                      },
                       1: {
                           height8,
                           width8,
                           align'center',
                           backgroundColor'#6BE6C1 ',
                           borderRadius10
                      },
                       2: {
                           height8,
                           width8,
                           align'center',
                           backgroundColor'#466AEB ',
                           borderRadius10
                      },
                       3: {
                           height8,
                           width8,
                           align'center',
                           backgroundColor'#466AEB ',
                           borderRadius10
                      }
                  }
              }
          },
           series: [{
               name'City Beta',
               type'bar',
               label: seriesLabel,
               data: [150105110178],
               barWidth13,
               color: {
                   type'linear',
                   x0,
                   y0,
                   x21,
                   y21,
                   colorStops: [{
                           offset0,
                           color'#1A70F3' // 0% 处的颜色
                      },
                      {
                           offset1,
                           color'#6BE6C1 ' // 100% 处的颜色
                      }
                  ],
                   globalfalse // 缺省为 false
              }
               // barCategoryGap: '100%',
               // boundaryGap: ['10%','10%','10%','20%']
          }]
      };
       myChart1.setOption(option1);
  }
}

3. 竖着的柱状图

相关配置

image.png

  • 通过 option => xAxis => axisTick => alignWithLabel来设置是否去掉向下的钩

alignWithLabel: false

xAxis: [{
type: 'category',
data: this.jiekouTime,
axisTick: {
alignWithLabel: true
}
}],

image.png

alignWithLabel: true

image.png

  • 通过 option => yAxis => splitLine 来设置去掉背景分割线
yAxis: [{
type: 'value',
splitLine: {
show: false
}
}],
完整代码
// html
<div id="main8" style="height: calc((100vw / 7 * 2 - 99px) * 0.67); width: calc(100vw / 7 * 2 - 99px)"></div>
// js
export default {
 components: { titleComponent },
   name: 'eightGraph',
   comments: {
       titleComponent
  },
   data() {
       return {
           jiekouTime: ['7-24''7-25''7-26''7-27''7-28''7-29''7-30']|| [],
           jiekouErrorNumber: [1052200334390330220] || [],
           na: '接口错误趋势',
      }
  },
   mounted() {
       var chartDom4 = document.getElementById('main8');
       var myChart4 = this.$echarts.init(chartDom4, 'dark');
       var option4;
​
       option4 = {
           backgroundColor: '#000000',
           // title: {
           //     text: '接口错误趋势'
           // },
           tooltip: {
               trigger: 'axis',
               axisPointer: {
                   type: 'shadow'
              }
          },
           grid: {
               left: '3%',
               right: '4%',
               bottom: '3%',
               containLabel: true
          },
           xAxis: [{
               type: 'category',
               datathis.jiekouTime,
               axisTick: {
                   alignWithLabel: true
              }
          }],
           yAxis: [{
               type: 'value',
               splitLine: {
                   show: false
              }
          }],
           series: [{
               name: 'Direct',
               type: 'bar',
               barWidth: '60%',
               datathis.jiekouErrorNumber,
               color: {
                   type: 'linear',
                   x: 0,
                   y: 0,
                   x2: 1,
                   y2: 1,
                   colorStops: [{
                           offset: 0,
                           color: '#6BE6C1' // 0% 处的颜色
                      },
                      {
                           offset: 1,
                           color: '#1A70F3' // 100% 处的颜色
                      }
                  ],
                   global: false // 缺省为 false
              }
          }]
      };
​
       myChart4.setOption(option4);
​
  }
}

4. 3D 地球的绘制

  • 3D地球主要采用了 echarts-gl 的技术,并非原创,是我们团队在曾经浏览的掘金文章中学到的。

源码如下~

import 'echarts-gl'
import titleComponent from './titleComponent.vue'
export default {
  name: 'SevenGraph',
  components: {
    titleComponent
  },
  data() {
    return {
      myChart: null,
      chartDom: null,
      na: '用户地区分布图'
    }
  },
  mounted() {
    this.chartDom = document.getElementById('globe')
    this.myChart = this.$echarts.init(this.chartDom)
    this.mapInit()
  },
  methods: {
    mapInit() {
      console.log('xxx')
      const url = 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dbf199cb27ad46839fbab926b8f94e66~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp?'
      this.myChart.setOption({
        backgroundColor: '#000', //背景颜色
        globe: {
          baseTexture: url, //地球的纹理。支持图片路径的字符串,图片或者 Canvas 的对象
          heightTexture: url, //地球的高度纹理
          shading: 'lambert', //地球中三维图形的着色效果
          light: {
            ambient: {
              intensity: 0.2, //环境光源强度
            }, //环境光
            main: {
              intensity: 0.8, //光源强度
            }, //主光源
          }, //光照设置
        },
        series: {
          type: 'lines3D',
          coordinateSystem: 'globe',
          blendMode: 'source-over',
          effect: {
            show: true,
          },
          lineStyle: {
            width: 1,
            color: 'rgb(255, 255,255)',
            opacity: 0.5,
            trailWidth: 4,
            trailLength: 0.01,
          }, //3D飞线图
          data: [
            [
              [112, 40, 2], // 终点的经纬度和海拔坐标
              [120, 20, 1], // 起点的经纬度和海拔坐标
            ],
            [
              [112, 40, 2],
              [20, -40, 1],
            ],
            [
              [112, 40, 2],
              [-60, 60, 1],
            ],
            [
              [112, 40, 2],
              [40, 0, 1],
            ],
            [
              [112, 40, 2],
              [-20, 20, 1],
            ],
            [
              [112, 40, 2],
              [-39, -40, 1],
            ],
            [
              [112, 40, 2],
              [67, 43, 1],
            ],
            [
              [112, 40, 2],
              [160, -18, 1],
            ],
            [
              [112, 40, 2],
              [145, 66, 1],
            ],
            [
              [112, 40, 2],
              [1790, 42, 1],
            ],
          ],
        },
      })
    },
  },
}

本章节我们主要介绍了首页的 Echarts 大屏如何绘制,相比你已经有了初步的理解。

那么我们前端监控系统的系列文章也即将走入尾声,其实前端监控系统的中台部分并不复杂,主要是 CSS 与 Echarts的运用,如何画出好看的页面,大概是值得我们一生努力探索的问题。

最后的最后,我们的反思~

项目的优点

可扩展性强

同时支持全局应用和组件级别应用

// 被监控网站的main.js
import revue, { httpError } from 'revue-monitor'
// 我们可以在mian.js中全局引入sdk
// 更灵活的,我们还可以再某个组件中,引入某个监控功能,这都是被允许的
Vue.use(revue.immediate)

个性化定制插件

// 利用发布订阅和单利模式存储一个全局的消息队列,且具有注册功能
// 我们在不同的插件处触发对应的生命周期钩子函数并给予在此处生命周期可以获取到的信息
// 当用户使用函数注册了某一个生命周期后,这个函数就成为了一个watcher
// 在某一个监控触发时(如js报错,js监控被触发),会自动执行对应watcher函数
class Topic {
  eventList = {}
  on(callback, event) {
    ;(this.eventList[event] || (this.eventList[event] = [])).push(callback)
  }
  emit(event, ...arr) {
    this.eventList[event] && this.eventList[event].forEach(e => {
      e(...arr)
    });
  }
}

const topic = new Topic()

export default topic

//注册函数绑定在revue上
function registLifecallBack(key, callBack) {
    topic.on(callBack, key)
}

let errorIds= [] 
export default  function myRecord(id) {
  errorIds.push(id)
}

function registLifecallBack(key, callBack) {
    topic.on(callBack, key)
}
    
//被监控网站的main.js    
revue.registLifecallBack("onJsError", myRecord);

项目的问题

  • 中台网站打开的渲染时间略长,仍需优化

  • 插件的可扩展性和个性化定制功能不够强,仍需加强插件的扩展性与定制化

架构演进的可能性

对于当前的框架来说,在监控工具上,我们的最大特点便是专注于vue项目的监控。一方面,我们可以扩展更多的流行前端开发框架如React,svelte等项目,为这些主流的前端项目设计符合其特点的监控SDK。另一方面,我们可以利用我们的监测工具易拓展的特性,形成plugin的生态(建立在线的扩展插件仓库),集结更多的开发者完善监测工具。

在监控中台上,我们的可以将监控中台分为两个方向,一是用户自我落地的本地监控中台,我们提供更友好的使用文档和更加渐进式的中台框架(不用的模块就不必配置,框架根据配置动态加载);另外对于在线的监控中台,我们通过提供更优质和专业的服务,小团队免费试用,在线问题解答,数据存储和管理按量付费等,不断演进在线的监控中台框架。

  • 源码链接在开头,欢迎大家与我们共建,也欢迎大家提出宝贵的意见!

那么前端监控系统文章就到此结束啦(完结撒花)~