sentry-javascript为语言包,在该语言包下包含各项平台包
其中:
- 诸如react、vue、next等框架包为平台包,主要是引用browser功能并增添平台相关特性
- browser包为浏览器侧的统一内容封装,降低各平台间差异,其主要部分来自于core及utils
- core封装如hub、client、integration等核心概念,其hub部分由hub等于单独封装
- utils封装平台、模块无关的公用方法
当调用init时:
- 平台相关参数集成与默认参数处理
- 将客户端相关参数层层解析初始化最终传递进hub中
- 获取主载体环境(global)
- 在该载体上获取或创建hub
- 将客户端环境与该hub绑定
- 初始化设置客户端环境相关integration
- 如果不传递参数默认该客户端环境绑定默认integration
以下以react平台为例:
sentry/react
index.js
- 暴露基础功能
- 暴露react相关集成功能
export * from '@sentry/browser';
export { init } from './sdk';
export { Profiler, withProfiler, useProfiler } from './profiler';
export type { ErrorBoundaryProps, FallbackRender } from './errorboundary';
export { ErrorBoundary, withErrorBoundary } from './errorboundary';
export { createReduxEnhancer } from './redux';
export { reactRouterV3Instrumentation } from './reactrouterv3';
export { reactRouterV4Instrumentation, reactRouterV5Instrumentation, withSentryRouting } from './reactrouter';
export { reactRouterV6Instrumentation, withSentryReactRouterV6Routing, wrapUseRoutes } from './reactrouterv6';
sdk
- 增加包相关元信息作为参数
- 调用browserInit进行初始化
import { BrowserOptions, init as browserInit, SDK_VERSION } from '@sentry/browser';
/**
* Inits the React SDK
*/
export function init(options: BrowserOptions): void {
options._metadata = options._metadata || {};
options._metadata.sdk = options._metadata.sdk || {
name: 'sentry.javascript.react',
packages: [
{
name: 'npm:@sentry/react',
version: SDK_VERSION,
},
],
version: SDK_VERSION,
};
// 传递参数实例化一个browser客户端实例
// 将该客户端实例绑定到当前环境集成中心
browserInit(options);
}
sentry/browser
index.js
- 暴露基础功能
- 暴露集成配置
export * from './exports';
import { Integrations as CoreIntegrations } from '@sentry/core';
import { getGlobalObject } from '@sentry/utils';
import * as BrowserIntegrations from './integrations';
let windowIntegrations = {};
// This block is needed to add compatibility with the integrations packages when used with a CDN
const _window = getGlobalObject<Window>();
if (_window.Sentry && _window.Sentry.Integrations) {
windowIntegrations = _window.Sentry.Integrations;
}
const INTEGRATIONS = {
...windowIntegrations,
...CoreIntegrations,
...BrowserIntegrations,
};
export { INTEGRATIONS as Integrations };
integration/index.js
// 监控各项错误信息
// error、unhandledrejection、错误信息、日志打印等
// 暴露GlobalHandlers类
export { GlobalHandlers } from './globalhandlers';
// 对全局方法进行包装
// 进行信息注入
export { TryCatch } from './trycatch';
// 添加事件面包屑信息
export { Breadcrumbs } from './breadcrumbs';
// 错误信息
export { LinkedErrors } from './linkederrors';
// 请求类型信息附加
export { HttpContext } from './httpcontext';
// 重复信息处理
export { Dedupe } from './dedupe';
sdk.ts
- 参数初始化
- 合并设备参数
export function init(options: BrowserOptions = {}): void {
if (options.defaultIntegrations === undefined) {
options.defaultIntegrations = defaultIntegrations;
}
if (options.release === undefined) {
const window = getGlobalObject<Window>();
// This supports the variable that sentry-webpack-plugin injects
if (window.SENTRY_RELEASE && window.SENTRY_RELEASE.id) {
options.release = window.SENTRY_RELEASE.id;
}
}
if (options.autoSessionTracking === undefined) {
options.autoSessionTracking = true;
}
if (options.sendClientReports === undefined) {
options.sendClientReports = true;
}
const clientOptions: BrowserClientOptions = {
...options,
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
integrations: getIntegrationsToSetup(options),
transport: options.transport || (supportsFetch() ? makeFetchTransport : makeXHRTransport),
};
// 用参数实例化一个客户端
// 将该客户端绑定集成中心
initAndBind(BrowserClient, clientOptions);
if (options.autoSessionTracking) {
startSessionTracking();
}
}
sentry/core
index.js
export type { ClientClass } from './sdk';
export {
addBreadcrumb,
captureException,
captureEvent,
captureMessage,
configureScope,
startTransaction,
setContext,
setExtra,
setExtras,
setTag,
setTags,
setUser,
withScope,
addGlobalEventProcessor,
getCurrentHub,
getHubFromCarrier,
Hub,
makeMain,
Scope,
} from '@sentry/hub';
export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api';
export { BaseClient } from './baseclient';
export { initAndBind } from './sdk';
export { createTransport } from './transports/base';
export { SDK_VERSION } from './version';
export { getIntegrationsToSetup } from './integration';
export { FunctionToString, InboundFilters } from './integrations';
import * as Integrations from './integrations';
export { Integrations };
sdk.ts
- debug功能及环境判断
- 集成中心绑定客户端参数
import { getCurrentHub } from '@sentry/hub';
import { Client, ClientOptions } from '@sentry/types';
import { logger } from '@sentry/utils';
/** A class object that can instantiate Client objects. */
export type ClientClass<F extends Client, O extends ClientOptions> = new (options: O) => F;
/**
* Internal function to create a new SDK client instance. The client is
* installed and then bound to the current scope.
*
* @param clientClass The client class to instantiate.
* @param options Options to pass to the client.
*/
// 获取主载体,如果该载体上不存在__SENTRY__,则创建
// 如果该载体的__SENTRY__上不存在集成中心或存在低版本集成中心则创建一个集成中心挂载在载体的对象上
// 如果是在node环境上,则从公ActiveDomain获取集成中心
// 将客户端参数传递给集成中心用于初始化监控集成绑定
export function initAndBind<F extends Client, O extends ClientOptions>(
clientClass: ClientClass<F, O>,
options: O,
): void {
if (options.debug === true) {
if (__DEBUG_BUILD__) {
logger.enable();
} else {
// use `console.warn` rather than `logger.warn` since by non-debug bundles have all `logger.x` statements stripped
// eslint-disable-next-line no-console
console.warn('[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.');
}
}
const hub = getCurrentHub();
const scope = hub.getScope();
if (scope) {
scope.update(options.initialScope);
}
const client = new clientClass(options);
hub.bindClient(client);
}
integration.ts
import { addGlobalEventProcessor, getCurrentHub } from '@sentry/hub';
import { Integration, Options } from '@sentry/types';
import { arrayify, logger } from '@sentry/utils';
declare module '@sentry/types' {
interface Integration {
isDefaultInstance?: boolean;
}
}
export const installedIntegrations: string[] = [];
/** Map of integrations assigned to a client */
export type IntegrationIndex = {
[key: string]: Integration;
};
/**
* Remove duplicates from the given array, preferring the last instance of any duplicate. Not guaranteed to
* preseve the order of integrations in the array.
*
* @private
*/
function filterDuplicates(integrations: Integration[]): Integration[] {
const integrationsByName: { [key: string]: Integration } = {};
integrations.forEach(currentInstance => {
const { name } = currentInstance;
const existingInstance = integrationsByName[name];
// We want integrations later in the array to overwrite earlier ones of the same type, except that we never want a
// default instance to overwrite an existing user instance
if (existingInstance && !existingInstance.isDefaultInstance && currentInstance.isDefaultInstance) {
return;
}
integrationsByName[name] = currentInstance;
});
return Object.values(integrationsByName);
}
/** Gets integrations to install */
export function getIntegrationsToSetup(options: Options): Integration[] {
const defaultIntegrations = options.defaultIntegrations || [];
const userIntegrations = options.integrations;
// We flag default instances, so that later we can tell them apart from any user-created instances of the same class
defaultIntegrations.forEach(integration => {
integration.isDefaultInstance = true;
});
let integrations: Integration[];
if (Array.isArray(userIntegrations)) {
integrations = [...defaultIntegrations, ...userIntegrations];
} else if (typeof userIntegrations === 'function') {
integrations = arrayify(userIntegrations(defaultIntegrations));
} else {
integrations = defaultIntegrations;
}
const finalIntegrations = filterDuplicates(integrations);
// The `Debug` integration prints copies of the `event` and `hint` which will be passed to `beforeSend`. It therefore
// has to run after all other integrations, so that the changes of all event processors will be reflected in the
// printed values. For lack of a more elegant way to guarantee that, we therefore locate it and, assuming it exists,
// pop it out of its current spot and shove it onto the end of the array.
const debugIndex = finalIntegrations.findIndex(integration => integration.name === 'Debug');
if (debugIndex !== -1) {
const [debugInstance] = finalIntegrations.splice(debugIndex, 1);
finalIntegrations.push(debugInstance);
}
return finalIntegrations;
}
/**
* Given a list of integration instances this installs them all. When `withDefaults` is set to `true` then all default
* integrations are added unless they were already provided before.
* @param integrations array of integration instances
* @param withDefault should enable default integrations
*/
export function setupIntegrations(integrations: Integration[]): IntegrationIndex {
const integrationIndex: IntegrationIndex = {};
integrations.forEach(integration => {
integrationIndex[integration.name] = integration;
if (installedIntegrations.indexOf(integration.name) === -1) {
integration.setupOnce(addGlobalEventProcessor, getCurrentHub);
installedIntegrations.push(integration.name);
__DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`);
}
});
return integrationIndex;
}
sentry/hub
- 使用client参数生成hub
- 初始化创建integration进行监控
hub.ts
export class Hub implements HubInterface {
/** Is a {@link Layer}[] containing the client and scope */
private readonly _stack: Layer[] = [{}];
/** Contains the last event id of a captured event. */
private _lastEventId?: string;
/**
* Creates a new instance of the hub, will push one {@link Layer} into the
* internal stack on creation.
*
* @param client bound to the hub.
* @param scope bound to the hub.
* @param version number, higher number means higher priority.
*/
public constructor(client?: Client, scope: Scope = new Scope(), private readonly _version: number = API_VERSION) {
this.getStackTop().scope = scope;
if (client) {
this.bindClient(client);
}
}
public bindClient(client?: Client): void {
const top = this.getStackTop();
top.client = client;
if (client && client.setupIntegrations) {
client.setupIntegrations();
}
}
// ...
}