首页 > IT职业 > IT职场 > Sentry原理--收集错误、上报
我们平常收集错误,大部分人、公司都采用的Sentry来收集,当然也有好多公司也有自研的前端监控。趁着最近我们组刚好要接入Sentry,并且要根据自己的调研来做一次分享。所以就总结出了下面的内容。分享给各位大佬们。
自动收集
手动上报,手动上报又分为:
Sentry.captureException('captureException');
Sentry.captureMessage('captureMessage');
// 设置用户信息:
scope.setUser({ “email”: “xx@xx.cn”})
// 给事件定义标签:
scope.setTags({ ‘api’, ‘api/ list / get’})
// 设置事件的严重性:
scope.setLevel(‘error’)
// 设置附加数据:
scope.setExtra(‘data’, { request: { a: 1, b: 2 })
// 添加一个面包屑
Sentry.addBreadcrumb({
category: "auth",
message: "Authenticated user ",
level: Sentry.Severity.Info,
});
// 添加一个scope 标题??? 当前事务名称用于对Performance产品中的事务进行分组 ,并用错误点注释错误事件。
Sentry.configureScope(scope => scope.setTransactionName("UserListView"));
// 局部
Sentry.withScope(function (scope) {
scope.setTag("my-tag", "my value");
scope.setLevel("warning");
// will be tagged with my-tag="my value"
Sentry.captureException(new Error("my error"));
});
// 设置上下文
Sentry.setContext("character", {
name: "Mighty Fighter",
age: 19,
attack_type: "melee",
});
复制代码
而captureException和captureMessage的实现大概是这样的
// 手动触发throw一个报错,然后把报错的信息重置为用户传入的,然后调用callOnHub来触发一个上报
function captureException(exception, captureContext) {
var syntheticException;
try {
throw new Error('Sentry syntheticException');
}
catch (exception) {
syntheticException = exception;
}
return callOnHub('captureException', exception, {
captureContext: captureContext,
originalException: exception,
syntheticException: syntheticException,
});
}
function captureMessage(message, captureContext) {
var syntheticException;
try {
throw new Error(message);
}
catch (exception) {
syntheticException = exception;
}
return callOnHub('captureMessage', message, level, tslib_1.__assign({ originalException: message, syntheticException: syntheticException }, context));
}
复制代码
Sentry上传source map大概分为三种方式吧
下面我主要讲解一下webpack的实现方式,其他的方式请自行查看。其实sentry主要是使用@sentry/webpack-plugin这个插件进行source map上传的。
// 使用示例
yarn add --dev @sentry/webpack-plugin
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = {
// other configuration
configureWebpack: {
plugins: [
new SentryWebpackPlugin({
// sentry-cli configuration
authToken: process.env.SENTRY_AUTH_TOKEN,
org: "example-org",
project: "example-project",
release: process.env.SENTRY_RELEASE,
// webpack specific configuration
include: ".",
ignore: ["node_modules", "webpack.config.js"],
}),
],
},
};
复制代码
大概的一个过程其实就是在webpack的afterEmit钩子,获取到打包之后的文件。然后过滤得出文件类型是/.js$|.map$/结尾的就上传到sentry的服务器上。然后删除的时候只删除/.map$/结尾的文件。
// upload sourcemaps
apply(compiler) {
// afterEmit在生成文件到output目录之后执行
compiler.hooks.afterEmit.tapAsync(this.name, async (compilation, callback) => {
const files = this.getFiles(compilation);
try {
await this.createRelease();
await this.uploadFiles(files);
console.info('nu001b[32mUpload successfully.u001b[39mn');
} catch (error) {
// todo
}
callback(null);
});
}
// 获取需要上传的文件
getFiles(compilation) {
// 通过 compilation.assets 获取我们需要的文件信息,格式信息
// compilation.assets {
// 'bundle.js': SizeOnlySource { _size: 212 },
// 'bundle.js.map': SizeOnlySource { _size: 162 }
// }
return Object.keys(compilation.assets)
.map((name) => {
if (this.isIncludeOrExclude(name)) {
return { name, filePath: this.getAssetPath(compilation, name) };
}
return null;
})
.filter(Boolean);
}
// 获取文件的绝对路径
getAssetPath(compilation, name) {
return path.join(compilation.getPath(compilation.compiler.outputPath), name.split('?')[0]);
}
// 上传文件
async uploadFile({ filePath, name }) {
console.log(filePath);
try {
await request({
url: `${this.sentryReleaseUrl()}/${this.release}/files/`, // 上传的sentry路径
method: 'POST',
auth: {
bearer: this.apiKey,
},
headers: {},
formData: {
file: fs.createReadStream(filePath),
name: this.filenameTransform(name),
},
});
} catch (e) {
console.error(`uploadFile failed ${filePath}`);
}
}
// 删除 sourcemaps
sentryDel(compiler) {
compiler.hooks.done.tapAsync(this.name, async (stats, callback) => {
console.log('Whether to delete SourceMaps:', this.isDeleteSourceMaps);
if (this.isDeleteSourceMaps) {
await this.deleteFiles(stats);
console.info('nu001b[32mDelete SourceMaps done.u001b[39mn');
}
callback(null);
});
}
// 删除文件
async deleteFiles(stats) {
console.log();
console.info('u001b[33mStarting delete SourceMaps...u001b[39mn');
Object.keys(stats.compilation.assets)
.filter((name) => this.deleteRegex.test(name))
.forEach((name) => {
const filePath = this.getAssetPath(stats.compilation, name);
if (filePath) {
console.log(filePath);
fs.unlinkSync(filePath);
} else {
console.warn(`bss-plugin-sentry: 不能删除 '${name}', 文件不存在, 由于生成错误它可能没有创建`);
}
});
}
复制代码
但是@sentry/webpack-plugin上传完之后不支持删除source map,所以我们可以使用webpack-sentry-plugin这个插件,好像也是官方出的吧,是支持上传完之后删除source map的。使用的话可以参考文档。
export const SentryPluginConfig = {
deleteAfterCompile: true, // 上传完 source-map 文件后要不要删除当前目录下的source-map 文件
// Sentry options are required
organization: '', // 组名
project: '', // 当前项目名
baseSentryURL: 'https://xxx', // 默认是 https://sentry.io/api/0,也即是上传到 sentry 官网上去,如果是自己搭建的 sentry 系统,那把sentry.io替换成你自己的sentry系统域名就行。
apiKey: '',
// Release version name/hash is required
release: SentryRelease, // 版本
};
复制代码
前端捕获异常分为全局捕获和单点捕获。
window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等aixos中使用interceptor进行拦截,vue、react都有自己的错误采集接口setTimeout进行重写,在使用方法不变的情况下也可以异常捕获try…catchwindow.onerror window.onerror = function(message, source, lineno, colno, error) { ... }
window.addEventListener('error', function(event) { ... }, true)
// 函数参数:
// message:错误信息(字符串)。可用于HTML onerror=""处理程序中的event。
// source:发生错误的脚本URL(字符串)
// lineno:发生错误的行号(数字)
// colno:发生错误的列号(数字)
// error:Error对象(对象
大家可以看到 JS 错误监控里面有个 window.onEerror,
又用了 window.addEventLisenter('error'),
其实两者并不能互相代替。
window.onError 是一个标准的错误捕获接口,它可以拿到对应的这种 JS 错误;
window.addEventLisenter('error')也可以捕获到错误,
但是它拿到的 JS 报错堆栈往往是不完整的。
同时 window.onError 无法获取到资源加载失败的一个情况,
必须使用 window.addEventLisenter('error')来捕获资源加载失败的情况。
复制代码
Promise Promise的话主要是unhandledrejection事件,也就是未被catch的reject状态的promise
window.addEventListener("unhandledrejection", event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
复制代码
setTimeout、setInterval、requestAnimationFrame等 const prevSetTimeout = window.setTimeout;
window.setTimeout = function(callback, timeout) {
const self = this;
return prevSetTimeout(function() {
try {
callback.call(this);
} catch (e) {
// 捕获到详细的错误,在这里处理日志上报等了逻辑
// ...
throw e;
}
}, timeout);
}
复制代码
Vue的Vue.config.errorHandler // sentry中对Vue errorHandler的处理
function vuePlugin(Raven, Vue) {
var _oldOnError = Vue.config.errorHandler;
Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
// 上报
Raven.captureException(error, {
extra: metaData
});
if (typeof _oldOnError === 'function') {
// 为什么这么做?
_oldOnError.call(this, error, vm, info);
}
};
}
module.exports = vuePlugin;
复制代码
React的ErrorBoundary ErrorBoundary的定义:如果一个class组件中定义了static getDerivedStateFromError() 或componentDidCatch()这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用static getDerivedStateFromError()渲染备用 UI ,使用componentDidCatch()打印错误信息// ErrorBoundary的示例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
// 在这里可以做异常的上报
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return h1>Something went wrong./h1>;
}
return this.props.children;
}
ErrorBoundary>
MyWidget />
/ErrorBoundary>
复制代码
Sentry是怎么实现的呢?// ts声明的类型,可以看到sentry大概实现的方法
/**
* A ErrorBoundary component that logs errors to Sentry.
* Requires React >= 16
*/
declare class ErrorBoundary extends React.ComponentErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState;
componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void;
componentDidMount(): void;
componentWillUnmount(): void;
resetErrorBoundary: () => void;
render(): React.ReactNode;
}
// 真实上报的地方
ErrorBoundary.prototype.componentDidCatch = function (error, _a) {
var _this = this;
var componentStack = _a.componentStack;
// 获取到配置的props
var _b = this.props, beforeCapture = _b.beforeCapture, onError = _b.onError, showDialog = _b.showDialog, dialogOptions = _b.dialogOptions;
withScope(function (scope) {
// 上报之前做一些处理,相当于axios的请求拦截器
if (beforeCapture) {
beforeCapture(scope, error, componentStack);
}
// 上报
var eventId = captureException(error, { contexts: { react: { componentStack: componentStack } } });
// 开发者的回调
if (onError) {
onError(error, componentStack, eventId);
}
// 是否显示sentry的错误反馈组件(也是一种收集错误的方式)
if (showDialog) {
showReportDialog(__assign(__assign({}, dialogOptions), { eventId: eventId }));
}
// componentDidCatch is used over getDerivedStateFromError
// so that componentStack is accessible through state.
_this.setState({ error: error, componentStack: componentStack, eventId: eventId });
});
};
复制代码
请求
注意:sentry支持自动收集和手动收集两种错误收集方法,但是还不能捕捉到异步操作、接口请求中的错误,比如接口返回404、500等信息,此时我们可以通过Sentry.caputureException()进行主动上报。
xhr的实现function fill(source, name, replacementFactory) {
var original = source[name];
var wrapped = replacementFactory(original);
source[name] = wrapped;
}
// xhr
function instrumentXHR(): void {
// 保存真实的xhr的原型
const xhrproto = XMLHttpRequest.prototype;
// 拦截open方法
fill(xhrproto, 'open', function (originalOpen: () => void): () => void {
return function (this: SentryWrappedXMLHttpRequest, ...args: any[]): void {
const xhr = this;
const onreadystatechangeHandler = function (): void {
if (xhr.readyState === 4) {
if (xhr.__sentry_xhr__) {
xhr.__sentry_xhr__.status_code = xhr.status;
}
// // 上报sentry
triggerHandlers('xhr', {
args,
endTimestamp: Date.now(),
startTimestamp: Date.now(),
xhr,
});
}
};
if ('onreadystatechange' in xhr && typeof xhr.onreadystatechange === 'function') {
// 拦截onreadystatechange方法
fill(xhr, 'onreadystatechange', function (original: WrappedFunction): Function {
return function (...readyStateArgs: any[]): void {
onreadystatechangeHandler();
// 返回原来的方法
return original.apply(xhr, readyStateArgs);
};
});
} else {
xhr.addEventListener('readystatechange', onreadystatechangeHandler);
}
// 调用原来的方法
return originalOpen.apply(xhr, args);
};
});
// fill其实就是拦截的一个封装originalSend就是原来的send方法
fill(xhrproto, 'send', function (originalSend: () => void): () => void {
return function (this: SentryWrappedXMLHttpRequest, ...args: any[]): void {
// 上报sentry
triggerHandlers('xhr', {
args,
startTimestamp: Date.now(),
xhr: this,
});
// 返回原来方法
return originalSend.apply(this, args);
};
});
}
复制代码
Fetch// 重写fetch
function instrumentFetch() {
if (!supportsNativeFetch()) {
return;
}
fill(global$2, 'fetch', function (originalFetch) {
return function () {
var args = [];
for (var _i = 0; _i arguments.length; _i++) {
args[_i] = arguments[_i];
}
var handlerData = {
args: args,
fetchData: {
method: getFetchMethod(args),
url: getFetchUrl(args),
},
startTimestamp: Date.now(),
};
triggerHandlers('fetch', __assign({}, handlerData));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return originalFetch.apply(global$2, args).then(function (response) {
triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), response: response }));
return response;
}, function (error) {
triggerHandlers('fetch', __assign(__assign({}, handlerData), { endTimestamp: Date.now(), error: error }));
throw error;
});
};
});
}
复制代码
console.xxxfunction instrumentConsole() {
if (!('console' in global$2)) {
return;
}
['debug', 'info', 'warn', 'error', 'log', 'assert'].forEach(function (level) {
if (!(level in global$2.console)) {
return;
}
fill(global$2.console, level, function (originalConsoleLevel) {
return function () {
var args = [];
for (var _i = 0; _i arguments.length; _i++) {
args[_i] = arguments[_i];
}
// 上报sentry
triggerHandlers('console', { args: args, level: level });
// this fails for some browsers. :(
if (originalConsoleLevel) {
Function.prototype.apply.call(originalConsoleLevel, global$2.console, args);
}
};
});
});
}
复制代码


responseEnd - navigationStart
loadEventEnd - navigationStart
domainLookupEnd - domainLookupStart
connectEnd - connectStart
responseEnd - responseStart
domComplete - domInteractive
domContentLoadedEventEnd - navigationStart
注意: 由于window.preformance.timing是一个在不同阶段,被不停修正的一个参数对象,所以,建议在window.onload中进行性能数据读取和上报。
// 收集性能信息
export const getPerformance = () => {
if (!window.performance) return null;
const {timing} = window.performance
if ((getPerformance as any).__performance__) {
return (getPerformance as any).__performance__;
}
const performance = {
// 重定向耗时
redirect: timing.redirectEnd - timing.redirectStart,
// 白屏时间 html head script 执行开始时间
whiteScreen: window.__STARTTIME__ - timing.navigationStart,
// DOM 渲染耗时
dom: timing.domComplete - timing.domLoading,
// 页面加载耗时
load: timing.loadEventEnd - timing.navigationStart,
// 页面卸载耗时
unload: timing.unloadEventEnd - timing.unloadEventStart,
// 请求耗时
request: timing.responseEnd - timing.requestStart,
// 获取性能信息时当前时间
time: new Date().getTime(),
};
(getPerformance as any).__performance__ = performance;
return performance;
};
复制代码
sentry的性能监控其实主要依靠web-vitals这个包,主要依靠PerformanceObserver()实现
xhr上报(Sentry采用的方式) BrowserBackend.prototype._setupTransport = function () {
if (!this._options.dsn) {
// We return the noop transport here in case there is no Dsn.
// 没有设置dsn,调用BaseBackend.prototype._setupTransport 返回空函数
return _super.prototype._setupTransport.call(this);
}
var transportOptions = __assign({}, this._options.transportOptions, { dsn: this._options.dsn });
if (this._options.transport) {
return new this._options.transport(transportOptions);
}
// 支持Fetch则返回 FetchTransport 实例,否则返回 XHRTransport实例,
// 这两个构造函数具体代码在开头已有提到。
if (supportsFetch()) {
return new FetchTransport(transportOptions);
}
return new XHRTransport(transportOptions);
};
复制代码
FetchTransport.prototype.sendEvent = function (event) {
var defaultOptions = {
body: JSON.stringify(event),
method: 'POST',
referrerPolicy: (supportsReferrerPolicy() ? 'origin' : ''),
};
return this._buffer.add(global$2.fetch(this.url, defaultOptions).then(function (response) { return ({
status: exports.Status.fromHttpCode(response.status),
}); }));
};
复制代码
class XHRTransport extends BaseTransport {
sendEvent(event) {
return this._sendRequest(eventToSentryRequest(event, this._api), event);
}
_sendRequest(sentryRequest, originalPayload) {
// 如果超过限制的数量%就不发送
if (this._isRateLimited(sentryRequest.type)) {
return Promise.reject({
event: originalPayload,
type: sentryRequest.type,
reason: `Transport locked till ${this._disabledUntil(sentryRequest.type)} due to too many requests.`,
status: 429,
});
}
return this._buffer.add(new SyncPromise((resolve, reject) => {
const request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState === 4) {
const headers = {
'x-sentry-rate-limits': request.getResponseHeader('X-Sentry-Rate-Limits'),
'retry-after': request.getResponseHeader('Retry-After'),
};
this._handleResponse({ requestType: sentryRequest.type, response: request, headers, resolve, reject });
}
};
request.open('POST', sentryRequest.url);
for (const header in this.options.headers) {
if (this.options.headers.hasOwnProperty(header)) {
request.setRequestHeader(header, this.options.headers[header]);
}
}
request.send(sentryRequest.body);
}));
}
}
this._buffer.add(task) {
if (!this.isReady()) {
return SyncPromise.reject(new SentryError('Not adding Promise due to buffer limit reached.'));
}
if (this._buffer.indexOf(task) === -1) {
this._buffer.push(task);
}
task
.then(() => this.remove(task))
.then(null, () => this.remove(task).then(null, () => {
// We have to add this catch here otherwise we have an unhandledPromiseReject
// because it's a new Promise chain
})
);
return task;
}
复制代码
new Image()上报var REPORT_URL = 'xxx' //数据上报接口
var img = new Image; //创建img标签
img.onload = img.onerror = function(){ //img加载完成或加载src失败时的处理
img = null; //img置空,不会循环触发onload/onerror
};
img.src = REPORT_URL + Build._format(params); //数据上报接口地址拼接上报参数作为img的src
// 或者
img src="images/bg.png" onerror="javascript:this.src='images/bgError.png';">
复制代码
比如我们的一些打点之类的需求,我们有些数据上报都是采用new Image()的方式去上报的,那么真的就只是随便一个资源就可以吗?
为什么不能用请求其他的文件资源(js/css/ttf)的方式进行上报?
同样都是图片,上报时选用了1x1的透明GIF,而不是其他的PNG/JEPG/BMP文件?
总结: 使用img元素发送不会影响页面的展示、性能和体验这些,就是不会带来副作用,还有一个使用GIF的格式是因为体积小。
sentry获取上报的url的代码给精简逻辑拼接起来了,这样也方便大家理解。// 匹配DSN的正则
const DSN_REGEX = /^(?:(w+):)//(?:(w+)(?::(w+))?@)([w.-]+)(?::(d+))?/(.+)/;
const dsn = 'https://3543756743567437634345@sentry.com/430'; // 假的dsn
// 获取到正则匹配到的分组信息
const match = DSN_REGEX.exec(dsn);
const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1);
const obj = {
'protocol': protocol,
'publicKey': publicKey,
'pass': pass,
'host': host,
'port': port,
'lastPath': lastPath,
'projectId': lastPath.split('/')[0],
}
// 打印信息
console.log('obj', JSON.stringify(obj));
// {
// protocol: "https",
// publicKey: "3543756743567437634345",
// pass: "",
// host: "sentry.com",
// port: "",
// lastPath: "430",
// projectId: "430",
// }
function getBaseApiEndpoint() {
const dsn = obj;
const protocol = dsn.protocol ? `${dsn.protocol}:` : '';
const port = dsn.port ? `:${dsn.port}` : '';
return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`;
}
console.log('getBaseApiEndpoint', getBaseApiEndpoint());
// https://sentry.com/api/
// 不同的事件类型发送到不同的域名
function getIngestEndpoint(target) {
const eventType = event.type || 'event';
const useEnvelope = eventType === 'transaction';
target = useEnvelope ? 'envelope' : 'store';
const base = this.getBaseApiEndpoint();
const dsn = obj;
return `${base}${dsn.projectId}/${target}/`;
}
console.log('getIngestEndpoint', getIngestEndpoint());
// https://sentry.com/api/430/store/
function getStoreOrEnvelopeEndpointWithUrlEncodedAuth() {
return `${this.getIngestEndpoint()}?${this.encodedAuth()}`;
}
// 获取到认证的信息,可以理解为一个凭证
function encodedAuth() {
const dsn = obj;
const auth = {
// We send only the minimum set of required information. See
// https://github.com/getsentry/sentry-javascript/issues/2572.
sentry_key: dsn.publicKey,
sentry_version: SENTRY_API_VERSION = 7, // 写死的7,不同的版本不一样
};
return urlEncode(auth);
}
// 格式化url
function urlEncode(object) {
return Object.keys(object)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(object[key])}`)
.join('&');
}
// 最后获取到的完整的url
console.log('getStoreOrEnvelopeEndpointWithUrlEncodedAuth', getStoreOrEnvelopeEndpointWithUrlEncodedAuth());
// https://sentry.com/api/430/store/?sentry_key=3543756743567437634345&sentry_version=7
复制代码
Vue2.x中对errorHandle的处理src/core/util/error.js
/* @flow */
import config from '../config'
import { warn } from './debug'
import { inBrowser, inWeex } from './env'
import { isPromise } from 'shared/util'
import { pushTarget, popTarget } from '../observer/dep'
// 初始化
function initGlobalAPI (Vue) {
// config
var configDef = {};
const config = {
errorHandler: null,
warnHandler: null,
};
configDef.get = function () { return config; };
{
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
// 定义全局的Vue.prototype.config的初始值
Object.defineProperty(Vue, 'config', configDef);
}
export function handleError (err: Error, vm: any, info: string) {
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget()
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
// 获取全局以及所有子级的errorCaptured hook
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i hooks.length; i++) {
try {
// 调用errorCaptured事件
// 钩子可以返回 false 以阻止该错误继续向上传播
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
// 如果这个hook发生了错误也会上报
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
// errorHandle上报
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
// if the user intentionally throws the original error in the handler,
// do not log it twice
// 防止两次上报
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)
}
// 判断是否输出到控制台
function logError (err, vm, info) {
if (process.env.NODE_ENV !== 'production') {
warn(`Error in ${info}: "${err.toString()}"`, vm)
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console !== 'undefined') {
console.error(err)
} else {
throw err
}
}
复制代码
// 初始化数据的时候 src/core/instance/state.js
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
}
export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
// 执行nextTick回调的时候 src/core/util/next-tick.js
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
}
// 执行指令的一些声明周期的时候 src/core/vdom/modules/directives.js
callHook(dir, 'bind', vnode, oldVnode)
callHook(dir, 'update', vnode, oldVnode)
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
function callHook (dir, hook, vnode, oldVnode, isDestroy) {
const fn = dir.def && dir.def[hook]
if (fn) {
try {
fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
} catch (e) {
handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
}
}
}
// watch中通过配置 src/core/observer/watcher.js
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
// 在这个条件下才会上报
// 这个我没有在代码中和api中有看到配置user,有了解的可以在评论区告诉我哦~
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
// 从这里我们也可以看出vue的deep watch是怎么实现的
// 其实就是监听这个对象的所有元素
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
复制代码
sentry中检测是否支持fetch // 是否支持fetch 通过在window中能不能找到fetch来判断
function supportsFetch() {
if (!('fetch' in getGlobalObject())) {
return false;
}
try {
// 如果支持这些属性,就说明支持fetch
// MDN https://developer.mozilla.org/zh-CN/docs/Web/API/Headers
new Headers(); // Fetch API 的 Headers 接口呈现了对一次请求的响应数据。
new Request(''); // Fetch API 的 Request 接口呈现了对一次请求的响应数据
new Response(); // Fetch API 的 Response 接口呈现了对一次请求的响应数据
return true;
}
catch (e) {
return false;
}
}
// 是否是原生的就是浏览器的fetch
function isNativeFetch(func) {
return func && /^function fetch()s+{s+[native code]s+}$/.test(func.toString());
}
// 是否支持原生的fetch而不是被代理的或者prolyFill对的
function supportsNativeFetch() {
if (!supportsFetch()) {
return false;
}
const global = getGlobalObject();
// Fast path to avoid DOM I/O
// eslint-disable-next-line @typescript-eslint/unbound-method
if (isNativeFetch(global.fetch)) {
return true;
}
// window.fetch is implemented, but is polyfilled or already wrapped (e.g: by a chrome extension)
// so create a "pure" iframe to see if that has native fetch
let result = false;
const doc = global.document;
// eslint-disable-next-line deprecation/deprecation
// 如果当前的fetch不是原生的,那么就通过创建一个iframe来判断支不支持fetch
if (doc && typeof doc.createElement === `function`) {
try {
const sandbox = doc.createElement('iframe');
sandbox.hidden = true;
doc.head.appendChild(sandbox);
// 查看window上有没有fetch方法
if (sandbox.contentWindow && sandbox.contentWindow.fetch) {
// eslint-disable-next-line @typescript-eslint/unbound-method
result = isNativeFetch(sandbox.contentWindow.fetch);
}
doc.head.removeChild(sandbox);
}
catch (err) {
logger.warn('Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ', err);
}
}
return result;
}
复制代码
import { matchPath } from 'react-router-dom';
const routes = [{ path: '/' }, { path: '/report' }];
// https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/react-router/
Sentry.init({
dsn: 'xxx',
integrations: [
new Integrations.BrowserTracing({
// Can also use reactRouterV4Instrumentation
// 上报的时候可以上报具体的路由地址
routingInstrumentation: Sentry.reactRouterV5Instrumentation(HashHistory, routes, matchPath),
}),
],
})
// https://docs.sentry.io/platforms/javascript/guides/react/components/profiler/
// withProfiler高阶组件用于检测App组件
class App extends React.Component {
render() {
return (
NestedComponent someProp={2} />
);
}
}
export default Sentry.withProfiler(App);
复制代码

作者:codeXiu
链接:https://juejin.cn/post/6957475955858210823
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2872/viewspace-2797645/ ,如需转载,请注明出处,否则将追究法律责任。