前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货满满!如何做好前端日志和异常监控的思考

干货满满!如何做好前端日志和异常监控的思考

原创
作者头像
brzhang
发布2024-03-20 20:04:20
5191
发布2024-03-20 20:04:20
举报
文章被收录于专栏:玩转全栈玩转全栈

在研发过程中,日志是非常重要的一环,它可以帮助我们快速定位问题,解决问题。在前端开发中,日志也是非常重要的一环,它可以帮助我们快速定位问题,解决问题。本文将介绍前端日志的规范和最佳实践。但是我们经常看到一些项目日志打得满天飞,但是到了真正定位问题的时候,发现日志并没有什么卵用。这是因为日志打得不规范,不规范的日志是没有意义的。所以我们需要规范日志的打印,才能让日志发挥最大的作用。

那么,我们首先就要思考一下,打印什么样的日志才是有助于定位前端问题的,我想,可以从我们真是定位用户反馈问题的场景来思考。

通常,前端用户反馈的问题大概有以下几种:

  1. 页面加载慢
  2. 页面渲染错乱
  3. 页面白屏等交互异常
  4. 页面崩溃
  5. 页面卡顿

下面,我们可以基于这些场景,来思考一下,我们应该打印什么样的日志,才能帮助我们快速定位问题。

业务异常日志

页面加载慢

对于页面加载慢,这个问题,我们可以通过performance对象来获取页面加载的性能数据,然后打印出来,比如:

代码语言:javascript
复制
window.addEventListener('load', function() {
    setTimeout(function() {
        var timing = window.performance.timing;
        var loadTime = timing.loadEventEnd - timing.navigationStart;
        var pageUrl = window.location.href;
        console.log('Page load time for ' + pageUrl + ' is ' + loadTime + ' milliseconds.');
    }, 0);
});

这样,我们就可以在页面加载完成之后,打印出页面加载时间,这样我们就可以通过日志来定位页面加载慢的问题。

通常,日志里面可以穿插一些告警,比如,我们这里可以加上一个判断,如果页面加载时间超过了3秒,我们就打印一个告警,比如:

代码语言:javascript
复制
window.addEventListener('load', function() {
    setTimeout(function() {
        var timing = window.performance.timing;
        var loadTime = timing.loadEventEnd - timing.navigationStart;
        var pageUrl = window.location.href;
        console.log('Page load time for ' + pageUrl + ' is ' + loadTime + ' milliseconds.');
        if (loadTime > 3000) {
            console.warn('Page load time for ' + pageUrl + ' is ' + loadTime + ' milliseconds, it is too slow.');
            //todo 上报到监控系统
            reportToMonitor('Page load time for ' + pageUrl + ' is ' + loadTime + ' milliseconds, it is too slow.');
        }
    }, 0);
});

页面渲染错乱

对于页面渲染错乱,造成这个问题的原因有很多,比如网络问题、代码问题、浏览器兼容问题等等,这个问题比较复杂,我们可以通过一些手段来定位这个问题,比如:

这个问题,我们可以通过window.onerror来做,从里面区出渲染错误的问题,比如:

代码语言:javascript
复制
window.onerror = function(message, source, lineno, colno, error) {
    console.error('Error: ' + message + ' Script: ' + source + ' Line: ' + lineno + ' Column: ' + colno + ' StackTrace: ' + error.stack);

    //分析渲染错误
    parseAndLogRenderError(message, source, lineno, colno, error);
};

// 举例:分析渲染错误
function parseAndLogRenderError(message, source, lineno, colno, error) {
    if (message.indexOf('render error') > -1) {
        console.error('Render error: ' + message + ' Script: ' + source + ' Line: ' + lineno + ' Column: ' + colno + ' StackTrace: ' + error.stack);
        //todo 上报到监控系统
        reportToMonitor('Render error: ' + message + ' Script: ' + source + ' Line: ' + lineno + ' Column: ' + colno + ' StackTrace: ' + error.stack);
    }
}

当然,window.onerror 只能捕获到一部分错误,我们还需要结合 window.addEventListener('error', function(event) {}) 来捕获一些资源加载错误,比如:

代码语言:javascript
复制
window.addEventListener('error', function(event) {
    console.error('Error: ' + event.message + ' Script: ' + event.filename + ' Line: ' + event.lineno + ' Column: ' + event.colno + ' StackTrace: ' + event error.stack);

    parseAndLogRenderError(event.message, event.filename, event.lineno, event.colno, event.error);
});

页面交互异常

这里的页面交互异常,通常是指用户在页面上进行一些操作的时候,出现了一些异常,比如点击按钮无反应、输入框无法输入等等,这个问题,我们可以通过一些手段来定位。

先说一说点击按钮无反应的问题。

假如说,我们有一个这样的场景,用户点击一个按钮,理论上点击按钮会发送一个请求,成功失败可能都会有一个界面上的反馈,但是如何点击之后,界面没有任何的反馈,这个时候就,我们基本上可以判定,这种时候就是页面交互异常了。那么,我们该如何捕捉这种异常呢?

我们可以通过window.addEventListener('click', function(event) {})来捕捉用户的点击事件,然后在里面做一些判断,比如:

代码语言:javascript
复制
// 设置一个标志位,当按钮被点击时,将标志位设置为 true。
var isButtonClicked = false;

window.addEventListener('click', function(event) {
    var target = event.target;
    if (target.tagName === 'BUTTON') {
        console.log('User clicked button: ' + target.innerText);
        // 设置标志位
        isButtonClicked = true;
    }
});

// 创建一个 observer 实例
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        // 如果页面有变化并且按钮被点击过,重置标志位
        // 这里的mutations 可能需要根据实际情况来判断,具体要结合自己业务逻辑来判断
        if(mutation.type === 'childList' || mutation.type === 'attributes') {
            if (isButtonClicked) {
                console.log('Page change detected after button click.');
                isButtonClicked = false;
            }
        }
    });
});

// 配置观察选项:
var config = { attributes: true, childList: true, characterData: true, subtree: true };

// 传入目标节点和观察选项
observer.observe(document.body, config);

// 启动定时器
setInterval(function() {
    if (isButtonClicked) {
        console.log('Button click exception detected.');
        // 上报到监控系统
        reportToMonitor('Button click exception detected');
        // 重置标志位
        isButtonClicked = false;
    }
}, 5000);  // 5秒后检查标志位

页面白屏的问题

页面白屏的问题,通常是指用户打开页面之后,页面长时间没有任何的反应,这个问题,我们可以通过一些手段来定位。

我们可以通过window.addEventListener('DOMContentLoaded', function() {})来捕捉页面的加载事件,然后在里面做一些判断,比如:

代码语言:javascript
复制
window.addEventListener('DOMContentLoaded', function() {
    setTimeout(function() {
        if (document.body.innerHTML === '') {
            console.error('Page is blank.');
            // 上报到监控系统
            reportToMonitor('Page is blank.');
        }
    }, 3000);  // 3秒后检查页面是否为空
});

或者,我们可以通过window.addEventListener('load', function() {})来捕捉页面的加载事件,然后在里面做一些判断,比如:通过oberver监听页面变化,如果页面变化了,就说明页面没有白屏.

代码语言:javascript
复制
window.addEventListener('load', function() {
    var observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            console.log('Page change detected.');
            // 上报到监控系统
            reportToMonitor('Page change detected.');
        });
    });

    var config = { attributes: true, childList: true, characterData: true, subtree: true };
    observer.observe(document.body, config);
});

页面卡顿

页面卡顿的问题,通常是指用户在页面上进行一些操作的时候,页面出现了卡顿的现象,我们先来分析一下,页面卡顿的原因。

页面卡顿的原因,通常有以下几种:

  1. 页面渲染性能问题
  2. 页面交互性能问题
  3. 页面资源加载性能问题
  4. 页面网络性能问题 等等,不排除可能还有其他的原因,但是这里我们只列举了一些常见的原因。
  5. 内存泄漏,这通常是元凶

那么,我们该如何捕捉页面卡顿的问题呢?

我们可以通过window.requestAnimationFrame来捕捉页面的渲染性能问题,比如:

代码语言:javascript
复制
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function(callback, element) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
        var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    };
}

if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    };
}

var lastFrameTime = 0;
var frameCount = 0;
var frameRate = 0;
var frameRateThreshold = 60;

function onAnimationFrame() {
    var now = Date.now();
    var elapsed = now - lastFrameTime;
    lastFrameTime = now;
    frameCount++;
    if (elapsed > 1000) {
        frameRate = frameCount;
        frameCount = 0;
        if (frameRate < frameRateThreshold) {
            console.warn('Frame rate is ' + frameRate + 'fps, it is too low.');
            // 上报到监控系统
            reportToMonitor('Frame rate is ' + frameRate + 'fps, it is too low.');
        }
    }
    window.requestAnimationFrame(onAnimationFrame);
}

window.requestAnimationFrame(onAnimationFrame);

这样,我们就可以通过window.requestAnimationFrame来捕捉页面的渲染性能问题。

对于内存泄漏,我们可以监听页面卸载事件,然后检查所有的事件处理器和定时器是否都已经被清除,比如:

代码语言:javascript
复制
// 存储所有的事件处理器和定时器
var eventHandlers = [];
var timers = [];

// 覆盖 addEventListener 和 removeEventListener 方法
var originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(eventName, eventHandler) {
    eventHandlers.push({target: this, eventName: eventName, eventHandler: eventHandler});
    originalAddEventListener.call(this, eventName, eventHandler);
};
var originalRemoveEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function(eventName, eventHandler) {
    eventHandlers = eventHandlers.filter(function(handler) {
        return handler.target !== this || handler.eventName !== eventName || handler.eventHandler !== eventHandler;
    });
    originalRemoveEventListener.call(this, eventName, eventHandler);
};

// 覆盖 setTimeout 和 clearTimeout 方法
var originalSetTimeout = window.setTimeout;
window.setTimeout = function(callback, delay) {
    var id = originalSetTimeout(callback, delay);
    timers.push(id);
    return id;
};
var originalClearTimeout = window.clearTimeout;
window.clearTimeout = function(id) {
    timers = timers.filter(function(timer) {
        return timer !== id;
    });
    originalClearTimeout(id);
};

// 在页面卸载时检查所有的事件处理器和定时器是否都已经被清除
window.addEventListener('unload', function() {
    if (eventHandlers.length > 0) {
        console.error('Memory leak detected: Not all event handlers were removed.');
        // 上报到监控系统
        reportToMonitor('Memory leak detected: Not all event handlers were removed.');
    }
    if (timers.length > 0) {
        console.error('Memory leak detected: Not all timers were cleared.');
        // 上报到监控系统
        reportToMonitor('Memory leak detected: Not all timers were cleared.');
    }
});

用户行为日志

用户行为日志是指用户在页面上的一些操作,比如点击按钮、输入框输入等等,这些操作都是用户行为日志,这些日志是非常重要的,它可以帮助我们快速定位问题。比如,用户反馈说,我点击了一个按钮,但是没有反应,这个时候,我们就可以通过用户行为日志来定位问题。但是通常用户将的可能是问题出现的时间点,而不是问题出现的原因,所以我们需要在用户行为日志中加入一些额外的信息。用户经历过哪些路由页面,做过哪些交互,各交互步骤的数据是否正常,这些都是我们需要记录的。

对于页面的路由,我们可以通过window.addEventListener('hashchange', function() {})来捕捉页面的路由变化,然后在里面打印一些日志,比如:

代码语言:javascript
复制
window.addEventListener('hashchange', function() {
    console.log('User navigated to ' + window.location.hash);
});

对于用户的点击事件,我们可以通过window.addEventListener('click', function(event) {})来捕捉用户的点击事件,然后在里面打印一些日志,比如:

代码语言:javascript
复制
window.addEventListener('click', function(event) {
    var target = event.target;
    if (target.tagName === 'BUTTON') {
        console.log('User clicked button: ' + target.innerText);
    }
});

对于用户的输入事件,我们可以通过window.addEventListener('input', function(event) {})来捕捉用户的输入事件,然后在里面打印一些日志,比如:

代码语言:javascript
复制
window.addEventListener('input', function(event) {
    var target = event.target;
    if (target.tagName === 'INPUT') {
        console.log('User input: ' + target.value);
    }
});

对于一些用户的交互事件,我们可以通过window.addEventListener('customEvent', function(event) {})来捕捉用户的交互事件,然后在里面打印一些日志,比如:

代码语言:javascript
复制
// 触发自定义事件
window.dispatchEvent(new CustomEvent('customEvent', {detail: 'User interaction detected.'}));

// 监听自定义事件
window.addEventListener('customEvent', function(event) {
    console.log('User custom event: ' + event.detail);
});

// 触发自定义事件,我们可以做一个工具函数,比如:这阿姨给你方便在任何地方触发自定义事件
function triggerCustomEvent(eventName, detail) {
    window.dispatchEvent(new CustomEvent(eventName, {detail: detail}));
}

一些情况下,前端页面的交互是通过后端返回的数据来触发的,这个时候,数据的正确性也是非常重要的,我们可以通过window.addEventListener('ajaxSuccess', function(event) {})来捕捉用户的交互事件,然后在里面打印一些日志,比如:

代码语言:javascript
复制
// 添加一个响应拦截器
axios.interceptors.response.use(function (response) {
    // 请求成功
   // 请求成功
    if (response.data.code !== 0) {
        console.error('Request success, but response data is not as expected.');
        triggerCustomEvent('request abnormal', 'Request success, but response data is not as expected.');
    }
    triggerCustomEvent('request Success', response.data);
    return response;
}, function (error) {
    // 请求失败
    console.error(error);
    triggerCustomEvent('request Error', 'Ajax request error.');
    return Promise.reject(error);
});

日志规范

我们还差一些什么,我们怎么知道打一些日志,但是怎么评估这些日志是否非常容易帮助我们定位问题呢?换句话说,当用户反馈问题的时候,我们怎么知道我们的日志是否能帮助我们快速定位问题呢?

一个场景:用户A,通过反馈说,你们页面加载好慢啊。然后就没有其他信息了。这个时候,我们需要去从日志中发现一些问题。

我们会问用户一个问题,比如用户的uid,然后我们就可以通过uid去查找用户的日志,然后我们就可以通过用户的日志来定位问题。

那么,我们就需要在日志中加入一些用户的信息,比如用户的uid,用户的设备信息,用户的网络信息等等,这样我们就可以通过用户的日志来定位问题。因此,我们需要在日志中加入一些用户的信息,比如:

代码语言:javascript
复制
// 获取用户的uid

var uid = getUid();

// 获取用户的设备信息

var deviceInfo = getDeviceInfo();

// 获取用户的网络信息

var networkInfo = getNetworkInfo();

// 打印环境信息

console.log('User: ' + uid + ' Device: ' + deviceInfo + ' Network: ' + networkInfo + ' Page load time for ' + pageUrl + ' is ' + loadTime + ' milliseconds.');

日志上报

有了收集的日志之后,我们上报到日志系统,那么这个日志系统应该是怎么样的呢?现在,我们可以使用 mermaid 来绘制一下整个日志系统的架构图:

代码语言:mermaid
复制
graph TD
A[前端日志] -->|上报| B[日志系统]
B -->|存储| C[日志存储]
B -->|分析| D[日志分析]
B -->|告警| E[日志告警]

这块后端可能有一些开源的日志系统,比如 ELK、Logstash、Kibana、Prometheus、Grafana 等等,这些都是比较常见的日志系统,我们可以根据自己的需求来选择。

比如,我们可以上报到ELK,然后通过Kibana来分析日志,通过Prometheus、Grafana来做一些监控。这些的搭建和使用,这里就不展开了。这里最终是需要提供前端日志的上报接口,然后后端来接收这些日志。

然后前端的日志上报,我们可以通过一些手段来做,比如:

代码语言:javascript
复制
function reportToMonitor(log) {
    // 上报到监控系统
    var img = new Image();
    img.src = 'http://monitor.com/report?log=' + log;
}

这里为什么要用img标签来上报日志呢?因为img标签是不会阻塞页面的,而且可以跨域,这样我们就可以通过img标签来上报日志。

另外,我们还可以通过navigator.sendBeacon来上报日志,这个方法是异步的,不会阻塞页面,比如:

代码语言:javascript
复制
function reportToMonitor(log) {
    // 上报到监控系统
    navigator.sendBeacon('http://monitor.com/report', log);
}

然后,为了保证日志的可靠性,我们还可以通过localStorage来存储日志,然后在下一次用户访问的时候,再上报日志,比如:

代码语言:javascript
复制
function reportToMonitor(log) {
    // 上报到监控系统
    if (navigator.sendBeacon) {
        navigator.sendBeacon('http://monitor.com/report', log);
    } else {
        localStorage.setItem('log', log);
    }
}

有些人可能会说,我还需要做一些日志的压缩和加密,这个时候,我们可以通过pako来做日志的压缩,通过CryptoJS来做日志的加密,比如:

代码语言:javascript
复制
function reportToMonitor(log) {
    // 上报到监控系统
    var compressedLog = pako.deflate(log, { to: 'string' });
    var encryptedLog = CryptoJS.AES.encrypt(compressedLog, 'secret key').toString();
    var img = new Image();
    img.src = 'http://monitor.com/report?log=' + encryptedLog;
}

有人可能会讲,频繁的发送请求,可能会导致一些性能问题,能不能积攒一批日志,然后再发送呢?这个时候,我们可以通过setTimeout来做,比如:

代码语言:javascript
复制
var logs = []; // 存储日志
function reportToMonitor(log) {
    // 存储日志
    logs.push(log);
    // 延迟发送日志
    setTimeout(function() {
        var img = new Image();
        img.src = 'http://monitor.com/report?log=' + logs.join(',');
        logs = [];
    }, 5000);  // 5秒后发送日志
}

结构化日志

我们有了上述日志之后就,针对一个具体用户进行搜索,看到可能是这样的一些个日志:

  • User: 123456 Device: iPhone 6s Network: 4G enter page /home 2024-02-12 12:00:00
  • User: 123456 Device: iPhone 6s Network: 4G Page load time for /home is 3000 milliseconds 2024-02-12 12:00:03
  • User: 123456 Device: iPhone 6s Network: 4G User clicked button: submit 2024-02-12 12:00:05
  • User: 123456 Device: iPhone 6s Network: 4G User inputid=userName : zhangsan 2024-02-12 12:00:06
  • User: 123456 Device: iPhone 6s Network: 4G User inputid=password : 123456 2024-02-12 12:00:07
  • User: 123456 Device: iPhone 6s Network: 4G User clicked button: login 2024-02-12 12:00:08
  • User: 123456 Device: iPhone 6s Network: 4G Request success, url: /login. 2024-02-12 12:00:12
  • User: 123456 Device: iPhone 6s Network: 4G User navigated to /dashboard 2024-02-12 12:00:13
  • User: 123456 Device: iPhone 6s Network: 4G Page load time for /dashboard is 3000 milliseconds 2024-02-12 12:00:16
  • User: 123456 Device: iPhone 6s Network: 4G Request Failed, url: /dashboard. {errMessage: 'Internal Server Error'} 2024-02-12 12:00:20

这样,我们就可以通过用户的日志来定位问题。

日志图形化

比如,我们可以将用户的日志,使用 mermaid 来画一下这个流程图:

代码语言:mermaid
复制
graph TD
A[enter page /home] -->|3000ms| B[User clicked button: submit]
B --> C[User input username: zhangsan]
C --> D[User input password: 123456]
D --> E[User clicked button: login]
E --> F[Request success, url: /login]
F --> G[User navigated to /dashboard]
G -->|3000ms| H[Request Failed, url: /dashboard]

篇幅有限,顺着思路,本来想在写一些监控相关的内容,但是感觉篇幅有点长了,就到这里吧。后续在继续写一些监控相关的。

关注我的公众号,第一时间获取更新!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 业务异常日志
    • 页面加载慢
      • 页面渲染错乱
        • 页面交互异常
          • 页面白屏的问题
            • 页面卡顿
              • 用户行为日志
              • 日志规范
              • 日志上报
                • 结构化日志
                  • 日志图形化
                  相关产品与服务
                  Prometheus 监控服务
                  Prometheus 监控服务(TencentCloud Managed Service for Prometheus,TMP)是基于开源 Prometheus 构建的高可用、全托管的服务,与腾讯云容器服务(TKE)高度集成,兼容开源生态丰富多样的应用组件,结合腾讯云可观测平台-告警管理和 Prometheus Alertmanager 能力,为您提供免搭建的高效运维能力,减少开发及运维成本。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                  http://www.vxiaotou.com