本文字数:16030字
预计阅读时间:40分钟
01
前言
Sentry是一个日志记录、错误上报、性能监控的开源框架,支持众多平台:
其使用方式在本文不进行说明了,大家可参照官方文档:https://docs.sentry.io/platforms/android/?original_referrer=https%3A%2F%2Fsentry.io%2F
目前大部分免费的三方APM平台限制较多,好用的又收费。在降本增效的大环境下,免费开源是开发者们的目标。因此开源的Sentry平台,在基础能力上已经满足了绝大多数开发场景。而对于想深入定制,打造自己APM平台的同学们来说,Sentry也是个可以依托并以此为基础进行改造的捷径。
本文将对Sentry Android SDK(6.31.0)进行源码解析,给大家一些改造拓展的思路。
02
基础结构说明
在讲解Sentry之前,先介绍一些基础的结构,方便之后的理解。
2.1.SentryEvent和SentryTransaction
先介绍两个基本概念,即SentryEvent和SentryTransaction。它们俩就是Sentry支持的事件,继承自同一个父类:SentryBaseEvent。简单来说其中我们在后台看到的Issues就是SentryEvent,Performance就是SentryTransaction。一次发送一个事件,每个SentryBaseEvent都有唯一的eventId。
2.2.Scope
Scope是保存与event一起发送的有用信息。如context,breadCrumb等。当设置了Scope里的某个属性,那么在整个Scope中都会将此属性赋值给event。
2.3.SentryClient
SentryClient是客户端用来真正处理各种事件发送逻辑的。比方说我们调用captureEvent(),最终的实现就是在SentryClient里。
2.4.Hub
Hub是用来管理Scope和SentryClient的。在Sentry初始化时,会创建Hub对象,Hub创建Scope和SentryClient并进行管理。了解了这些之后,我们来看源码。
03
初始化
在详细梳理初始化流程之前,我们把关键步骤梳理成图方便大家理解:
接下来我们分析具体的实现。
3.1.SentryAndroid.init()
我们先从初始化开始分析。SentryAndroid.java位于sentry-android-core这个包内。
从类的结构来看我们发现,SentryAndroid实际上只做了初始化这个操作:
//SentryAndroid.java
//Sentry initialization with a configuration handler and custom logger
//Params:
//context – Application. context
//logger – your custom logger that implements ILogger
//configuration – Sentry.OptionsConfiguration configuration handler
public static synchronized void init(
@NotNull final Context context,
@NotNull ILogger logger,
@NotNull Sentry.OptionsConfiguration<SentryAndroidOptions> configuration) {
// if SentryPerformanceProvider was disabled or removed, we set the App Start when
// the SDK is called.
AppStartState.getInstance().setAppStartTime(appStart, appStartTime);
try {
Sentry.init(
OptionsContainer.create(SentryAndroidOptions.class),
options -> {
final LoadClass classLoader = new LoadClass();
final boolean isTimberUpstreamAvailable =
classLoader.isClassAvailable(TIMBER_CLASS_NAME, options);
final boolean isFragmentUpstreamAvailable =
classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options);
final boolean isFragmentAvailable =
(isFragmentUpstreamAvailable
&& classLoader.isClassAvailable(
SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options));
final boolean isTimberAvailable =
(isTimberUpstreamAvailable
&& classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
final LoadClass loadClass = new LoadClass();
final ActivityFramesTracker activityFramesTracker =
new ActivityFramesTracker(loadClass, options);
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
options, context, logger, buildInfoProvider);
// We install the default integrations before the option configuration, so that the user
// can remove any of them. Integrations will not evaluate the options immediately, but
// will use them later, after being configured.
AndroidOptionsInitializer.installDefaultIntegrations(
context,
options,
buildInfoProvider,
loadClass,
activityFramesTracker,
isFragmentAvailable,
isTimberAvailable);
configuration.configure(options);
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
options, context, buildInfoProvider, loadClass, activityFramesTracker);
deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
},
true);
final @NotNull IHub hub = Sentry.getCurrentHub();
if (hub.getOptions().isEnableAutoSessionTracking()
&& ContextUtils.isForegroundImportance(context)) {
hub.addBreadcrumb(BreadcrumbFactory.forSession("session.start"));
hub.startSession();
}
} catch (IllegalAccessException e) {
//..
} catch (InstantiationException e) {
//...
} catch (NoSuchMethodException e) {
//...
} catch (InvocationTargetException e) {
//...
}
}
我们看到在执行Sentry.init()之前先执行了:
AppStartState.getInstance().setAppStartTime(appStart, appStartTime);
synchronized void setAppStartTime(
final long appStartMillis, final @NotNull SentryDate appStartTime) {
// method is synchronized because the SDK may by init. on a background thread.
if (this.appStartTime != null && this.appStartMillis != null) {
return;
}
this.appStartTime = appStartTime;
this.appStartMillis = appStartMillis;
}
记录了appStartTime和appStartMillis。从类型上来看,一个是日期,一个是时间戳。我们看一下这两个变量的获取规则:
appStartMillis:
//SentryAndroid.java
// SystemClock.uptimeMillis() isn't affected by phone provider or clock changes.
private static final long appStart = SystemClock.uptimeMillis();
记录了自开机以来的运行时间(毫秒级)。
appStartTime:
//SentryAndroid.java
// static to rely on Class load init.
private static final @NotNull SentryDate appStartTime =
AndroidDateUtils.getCurrentSentryDateTime();
//AndroidDateUtils.java
public final class AndroidDateUtils {
private static final SentryDateProvider dateProvider = new SentryAndroidDateProvider();
public static @NotNull SentryDate getCurrentSentryDateTime() {
return dateProvider.now();
}
}
//SentryNanotimeDateProvider.java
public final class SentryNanotimeDateProvider implements SentryDateProvider {
@Override
public SentryDate now() {
return new SentryNanotimeDate();
}
}
//SentryNanotimeDate.java
private final @NotNull Date date;
private final long nanos;
public SentryNanotimeDate() {
this(DateUtils.getCurrentDateTime(), System.nanoTime());
}
//DateUtils.java
public static @NotNull Date getCurrentDateTime() {
final Calendar calendar = Calendar.getInstance(TIMEZONE_UTC);
return calendar.getTime();
}
到这里,我们可以看到:appStartTime记录了当前时区的日期,和当前的高精度时间戳(精确到纳秒级)。之后SentryAndroid主要执行了Sentry.init()方法。
我们继续分析Sentry.init()的实现。
3.2.Sentry.init()
Sentry.java位于sentry-6.31.0这个包下:
我们先来看看Sentry.init()的实现:
//Sentry.java
public static <T extends SentryOptions> void init(
final @NotNull OptionsContainer<T> clazz,
final @NotNull OptionsConfiguration<T> optionsConfiguration,
final boolean globalHubMode)
throws IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException {
final T options = clazz.createInstance();
applyOptionsConfiguration(optionsConfiguration, options);
init(options, globalHubMode);
}
首先三个参数,类型分别是OptionsContainer,OptionsConfiguration和boolean。其中最后一个参数globalHubMode传的是true。然后调用applyOptionsConfiguration(),最后再执行init()方法。我们再来看看头两个参数是如何定义的。
3.2.1 final @NotNull OptionsContaine<T>clazz:**
//SentryAndroid.java
OptionsContainer.create(SentryAndroidOptions.class)
//OptionsContainer.java
public final class OptionsContainer<T> {
public @NotNull static <T> OptionsContainer<T> create(final @NotNull Class<T> clazz) {
return new OptionsContainer<>(clazz);
}
private final @NotNull Class<T> clazz;
private OptionsContainer(final @NotNull Class<T> clazz) {
super();
this.clazz = clazz;
}
public @NotNull T createInstance()
throws InstantiationException, IllegalAccessException, NoSuchMethodException,
InvocationTargetException {
return clazz.getDeclaredConstructor().newInstance();
}
}
OptionsContainer.create()传的是SentryAndroidOptions这个class,返回的是OptionsContainer<SentryAndroidOptions>。在Sentry.java中调用clazz.createInstance()方法执行了SentryAndroidOptions的构造方法:
//SentryAndroidOptions.java
public SentryAndroidOptions() {
setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
setAttachServerName(false);
// enable scope sync for Android by default
setEnableScopeSync(true);
}
我们看做了一些Android相关的基础配置。SentryAndroidOptions的父类OptionsContainer的构造方法如下:
//SentryOptions.java
private SentryOptions(final boolean empty) {
if (!empty) {
// SentryExecutorService should be initialized before any
// SendCachedEventFireAndForgetIntegration
executorService = new SentryExecutorService();
// UncaughtExceptionHandlerIntegration should be inited before any other Integration.
// if there's an error on the setup, we are able to capture it
integrations.add(new UncaughtExceptionHandlerIntegration());
integrations.add(new ShutdownHookIntegration());
eventProcessors.add(new MainEventProcessor(this));
eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));
if (Platform.isJvm()) {
eventProcessors.add(new SentryRuntimeEventProcessor());
}
setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
addPackageInfo();
}
}
3.2.1.1.SentryExecutorService
首先初始化了一个SentryExecutorService:
//SentryExecutorService.java
SentryExecutorService() {
this(Executors.newSingleThreadScheduledExecutor(new SentryExecutorServiceThreadFactory()));
}
//SentryExecutorService.java
@Override
public @NotNull Future<?> submit(final @NotNull Runnable runnable) {
return executorService.submit(runnable);
}
这个service开启了一个新线程执行了submit()方法,我们追踪一下代码发现这个方法有多处调用,最主要的是SendCachedEnvelopeFireAndForgetIntegration调用了,而SendCachedEnvelopeFireAndForgetIntegration的作用是在App启动的时候发送在cache中的event用的。
我们继续看SentryOptions.java的构造方法,发现会为integrations列表添加各种Integration,我们看一个最常见的UncaughtExceptionHandlerIntegration来分析,从命名上来看这个Integration就是用来抓抛出来的异常用的。
3.2.1.2. Integration
//UncaughtExceptionHandlerIntegration.java
public UncaughtExceptionHandlerIntegration() {
this(UncaughtExceptionHandler.Adapter.getInstance());
}
UncaughtExceptionHandlerIntegration(final @NotNull UncaughtExceptionHandler threadAdapter) {
this.threadAdapter = Objects.requireNonNull(threadAdapter, "threadAdapter is required.");
}
//UncaughtExceptionHandler.java
interface UncaughtExceptionHandler {
Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler();
void setDefaultUncaughtExceptionHandler(@Nullable Thread.UncaughtExceptionHandler handler);
final class Adapter implements UncaughtExceptionHandler {
static UncaughtExceptionHandler getInstance() {
return Adapter.INSTANCE;
}
private static final Adapter INSTANCE = new Adapter();
private Adapter() {}
@Override
public Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {
return Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void setDefaultUncaughtExceptionHandler(
final @Nullable Thread.UncaughtExceptionHandler handler) {
Thread.setDefaultUncaughtExceptionHandler(handler);
}
}
}
其中Adapter实现了UncaughtExceptionHandler接口。我们回到UncaughtExceptionHandlerIntegration.java,它实现了Integration和Thread.UncaughtExceptionHandler接口,其中Integration的定义如下:
//Integration.java
public interface Integration extends IntegrationName {
/**
* Registers an integration
*
* @param hub the Hub
* @param options the options
*/
void register(@NotNull IHub hub, @NotNull SentryOptions options);
}
只有一个register()方法,我们回到UncaughtExceptionHandlerIntegration,看一下它的结构:
先看一下register()都做了什么:
//UncaughtExceptionHandlerIntegration.java
@Override
public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
if (registered) {
//...
return;
}
registered = true;
this.hub = Objects.requireNonNull(hub, "Hub is required");
this.options = Objects.requireNonNull(options, "SentryOptions is required");
//...
if (this.options.isEnableUncaughtExceptionHandler()) {
final Thread.UncaughtExceptionHandler currentHandler =
threadAdapter.getDefaultUncaughtExceptionHandler();
if (currentHandler != null) {
//...
defaultExceptionHandler = currentHandler;
}
threadAdapter.setDefaultUncaughtExceptionHandler(this);
//...
}
}
初始化了hub和options。最主要的逻辑就是注册了Thread.UncaughtExceptionHandler等待异常抛出时作处理。那么我们再来看uncaughtException()的实现:
//UncaughtExceptionHandlerIntegration.java
@Override
public void uncaughtException(Thread thread, Throwable thrown) {
if (options != null && hub != null) {
options.getLogger().log(SentryLevel.INFO, "Uncaught exception received.");
try {
final UncaughtExceptionHint exceptionHint =
new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger());
final Throwable throwable = getUnhandledThrowable(thread, thrown);
final SentryEvent event = new SentryEvent(throwable);
event.setLevel(SentryLevel.FATAL);
final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint);
final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint);
//...
} catch (Throwable e) {
//...
}
if (defaultExceptionHandler != null) {
options.getLogger().log(SentryLevel.INFO, "Invoking inner uncaught exception handler.");
defaultExceptionHandler.uncaughtException(thread, thrown);
} else {
if (options.isPrintUncaughtStackTrace()) {
thrown.printStackTrace();
}
}
}
}
主要逻辑是创建了一个SentryEvent并将Throwable包进去,然后调用hub.captureEvent(event, hint)(之后再讲),将event上报到Sentry。到此,我们知道了UncaughtExceptionHandlerIntegration的作用就是为了将异常上报给Sentry后台的,而它实现了Integration接口,会在合适的时候将自己注册给Sentry。其他实现了Integration接口的类,目的也是将自己注册给Sentry进行绑定,并提供相应的方法去hook
一些自己想要的逻辑。Integration的注册时机我们之后再讲,接下来看回到SentryOptions.java:
//SentryOptions.java
private SentryOptions(final boolean empty) {
if (!empty) {
// SentryExecutorService should be initialized before any
// SendCachedEventFireAndForgetIntegration
executorService = new SentryExecutorService();
// UncaughtExceptionHandlerIntegration should be inited before any other Integration.
// if there's an error on the setup, we are able to capture it
integrations.add(new UncaughtExceptionHandlerIntegration());
integrations.add(new ShutdownHookIntegration());
eventProcessors.add(new MainEventProcessor(this));
eventProcessors.add(new DuplicateEventDetectionEventProcessor(this));
if (Platform.isJvm()) {
eventProcessors.add(new SentryRuntimeEventProcessor());
}
setSentryClientName(BuildConfig.SENTRY_JAVA_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
addPackageInfo();
}
}
看eventProcessors.add()都干了什么,以MainEventProcessor为例。
3.2.1.3.EventProcessor
MainEventProcessor实现了EventProcessor接口,EventProcessor是为SentryEvent或SentryTransaction服务的,目的是在发送事件时插入一些附属信息://EventProcessor.java
public interface EventProcessor {
/**
* May mutate or drop a SentryEvent
*
* @param event the SentryEvent
* @param hint the Hint
* @return the event itself, a mutated SentryEvent or null
*/
@Nullable
default SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
return event;
}
/**
* May mutate or drop a SentryTransaction
*
* @param transaction the SentryTransaction
* @param hint the Hint
* @return the event itself, a mutated SentryTransaction or null
*/
@Nullable
default SentryTransaction process(@NotNull SentryTransaction transaction, @NotNull Hint hint) {
return transaction;
}
}
两个process()方法分别作用于SentryEvent或SentryTransaction。回到MainEventProcessor,看看process()的实现:
//MainEventProcessor.java
@Override
public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
setCommons(event);
setExceptions(event);
setDebugMeta(event);
setModules(event);
if (shouldApplyScopeData(event, hint)) {
processNonCachedEvent(event);
setThreads(event, hint);
}
return event;
}
@Override
public @NotNull SentryTransaction process(
final @NotNull SentryTransaction transaction, final @NotNull Hint hint) {
setCommons(transaction);
setDebugMeta(transaction);
if (shouldApplyScopeData(transaction, hint)) {
processNonCachedEvent(transaction);
}
return transaction;
}
逻辑很简单,就是做默认配置用的,通常如果自定义了一些信息就走自定义的,没有的话就配置默认信息。process()的调用时机是发送一个事件到Sentry后台时将基础信息进行配置,代码我们之后再来看。回到Sentry.init()方法:
//Sentry.java
public static <T extends SentryOptions> void init(
final @NotNull OptionsContainer<T> clazz,
final @NotNull OptionsConfiguration<T> optionsConfiguration,
final boolean globalHubMode)
throws IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException {
final T options = clazz.createInstance();
applyOptionsConfiguration(optionsConfiguration, options);
init(options, globalHubMode);
}
clazz怎么创建的,并且clazz.createInstance()都干了什么我们就清楚了。
总结一下:
1.初始化了SentryAndroidOptions做各种基础配置并返回;
2.定义了各种Integration和EventProcessor,hook需要的时机,获取基础参数,为之后发送事件作准备。接下来我们看一下optionsConfiguration。
3.2.2 @NotNullOptionsConfiguration<T>optionsConfiguration
OptionsConfiguration
是个接口,applyOptionsConfiguration()调用其configure()方法做一些基础的配置,所以回到SentryAndroid.java:
//SentryAndroid.java
options -> {
final LoadClass classLoader = new LoadClass();
final boolean isTimberUpstreamAvailable =
classLoader.isClassAvailable(TIMBER_CLASS_NAME, options);
final boolean isFragmentUpstreamAvailable =
classLoader.isClassAvailable(FRAGMENT_CLASS_NAME, options);
final boolean isFragmentAvailable =
(isFragmentUpstreamAvailable
&& classLoader.isClassAvailable(
SENTRY_FRAGMENT_INTEGRATION_CLASS_NAME, options));
final boolean isTimberAvailable =
(isTimberUpstreamAvailable
&& classLoader.isClassAvailable(SENTRY_TIMBER_INTEGRATION_CLASS_NAME, options));
final BuildInfoProvider buildInfoProvider = new BuildInfoProvider(logger);
final LoadClass loadClass = new LoadClass();
final ActivityFramesTracker activityFramesTracker =
new ActivityFramesTracker(loadClass, options);
AndroidOptionsInitializer.loadDefaultAndMetadataOptions(
options, context, logger, buildInfoProvider);
// We install the default integrations before the option configuration, so that the user
// can remove any of them. Integrations will not evaluate the options immediately, but
// will use them later, after being configured.
AndroidOptionsInitializer.installDefaultIntegrations(
context,
options,
buildInfoProvider,
loadClass,
activityFramesTracker,
isFragmentAvailable,
isTimberAvailable);
configuration.configure(options);
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
options, context, buildInfoProvider, loadClass, activityFramesTracker);
deduplicateIntegrations(options, isFragmentAvailable, isTimberAvailable);
},
前面的几个isClassAvailable()
方法就是检查是否能找到那几个类,正常情况下返回true
。BuildInfoProvider
是为了读取android.os.Build
下的信息,包括判断是否为模拟器:
ActivityFramesTracker
是利用FrameMetricsAggregator
来收集app
帧渲染的时间从而观察是否有掉帧的情况发生。我们继续看AndroidOptionsInitializer.loadDefaultAndMetadataOptions()
的实现:
//AndroidOptionsInitializer.java
static void loadDefaultAndMetadataOptions(
final @NotNull SentryAndroidOptions options,
@NotNull Context context,
final @NotNull ILogger logger,
final @NotNull BuildInfoProvider buildInfoProvider) {
Objects.requireNonNull(context, "The context is required.");
// it returns null if ContextImpl, so let's check for nullability
if (context.getApplicationContext() != null) {
context = context.getApplicationContext();
}
Objects.requireNonNull(options, "The options object is required.");
Objects.requireNonNull(logger, "The ILogger object is required.");
// Firstly set the logger, if `debug=true` configured, logging can start asap.
options.setLogger(logger);
options.setDateProvider(new SentryAndroidDateProvider());
ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
initializeCacheDirs(context, options);
readDefaultOptionValues(options, context, buildInfoProvider);
}
还是继续为SentryAndroidOptions做基础的配置。包括设置时间日期,读取Manifest里的配置信息,初始化cache目录和Android独有信息,如包名等。接着调用AndroidOptionsInitializer.installDefaultIntegrations()方法:
//AndroidOptionsInitializer.java
static void installDefaultIntegrations(
final @NotNull Context context,
final @NotNull SentryAndroidOptions options,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull LoadClass loadClass,
final @NotNull ActivityFramesTracker activityFramesTracker,
final boolean isFragmentAvailable,
final boolean isTimberAvailable) {
// Integration MUST NOT cache option values in ctor, as they will be configured later by the
// user
// read the startup crash marker here to avoid doing double-IO for the SendCachedEnvelope
// integrations below
LazyEvaluator<Boolean> startupCrashMarkerEvaluator =
new LazyEvaluator<>(() -> AndroidEnvelopeCache.hasStartupCrashMarker(options));
options.addIntegration(
new SendCachedEnvelopeIntegration(
new SendFireAndForgetEnvelopeSender(() -> options.getCacheDirPath()),
startupCrashMarkerEvaluator));
// Integrations are registered in the same order. NDK before adding Watch outbox,
// because sentry-native move files around and we don't want to watch that.
final Class<?> sentryNdkClass =
isNdkAvailable(buildInfoProvider)
? loadClass.loadClass(SENTRY_NDK_CLASS_NAME, options.getLogger())
: null;
options.addIntegration(new NdkIntegration(sentryNdkClass));
// this integration uses android.os.FileObserver, we can't move to sentry
// before creating a pure java impl.
options.addIntegration(EnvelopeFileObserverIntegration.getOutboxFileObserver());
// Send cached envelopes from outbox path
// this should be executed after NdkIntegration because sentry-native move files on init.
// and we'd like to send them right away
options.addIntegration(
new SendCachedEnvelopeIntegration(
new SendFireAndForgetOutboxSender(() -> options.getOutboxPath()),
startupCrashMarkerEvaluator));
// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
// relies on AppState set by it
options.addIntegration(new AppLifecycleIntegration());
options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider));
// registerActivityLifecycleCallbacks is only available if Context is an AppContext
if (context instanceof Application) {
options.addIntegration(
new ActivityLifecycleIntegration(
(Application) context, buildInfoProvider, activityFramesTracker));
options.addIntegration(new CurrentActivityIntegration((Application) context));
options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
if (isFragmentAvailable) {
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
}
} else {
options
.getLogger()
.log(
SentryLevel.WARNING,
"ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed.");
}
if (isTimberAvailable) {
options.addIntegration(new SentryTimberIntegration());
}
options.addIntegration(new AppComponentsBreadcrumbsIntegration(context));
options.addIntegration(new SystemEventsBreadcrumbsIntegration(context));
options.addIntegration(
new NetworkBreadcrumbsIntegration(context, buildInfoProvider, options.getLogger()));
options.addIntegration(new TempSensorBreadcrumbsIntegration(context));
options.addIntegration(new PhoneStateBreadcrumbsIntegration(context));
}
我们发现为options添加了一堆Integration。之前我们知道已经添加了一个UncaughtExceptionHandlerIntegration用来捕获Java异常,我们再一个个看看,弄清楚Sentry都给Android带来了哪些额外的能力。
(1)SendCachedEnvelopeIntegration:SendCachedEnvelopeIntegration有两处。一个是SendFireAndForgetEnvelopeSender,这个我们之前提到过,在App启动的时候将cache中的event发送出去。另一个是SendFireAndForgetOutboxSender,还在发件箱里未被发送的event。
(2)NdkIntegration:顾名思义,就是抓取NDK的异常。其中sentryNdkClass去的是上面定义的SENTRY_NDK_CLASS_NAME这个类,即io.sentry.android.ndk.SentryNdk。SentryNdk有个init()方法:
//SentryNdk.java
public static void init(@NotNull final SentryAndroidOptions options) {
SentryNdkUtil.addPackage(options.getSdkVersion());
initSentryNative(options);
// only add scope sync observer if the scope sync is enabled.
if (options.isEnableScopeSync()) {
options.addScopeObserver(new NdkScopeObserver(options));
}
options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader()));
}
将options传入。initSentryNative()是个native方法,用来做初始化。接着为options添加IScopeObserver用来为当前Scope设置参数。这个init()方法是在NdkIntegration的register()中执行的:
//NdkIntegration.java
@Override
public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
Objects.requireNonNull(hub, "Hub is required");
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");
final boolean enabled = this.options.isEnableNdk();
this.options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration enabled: %s", enabled);
// Note: `hub` isn't used here because the NDK integration writes files to disk which are picked
// up by another integration (EnvelopeFileObserverIntegration).
if (enabled && sentryNdkClass != null) {
//...
try {
final Method method = sentryNdkClass.getMethod("init", SentryAndroidOptions.class);
final Object[] args = new Object[1];
args[0] = this.options;
method.invoke(null, args);
//...
addIntegrationToSdkVersion();
} catch (NoSuchMethodException e) {
//...
} catch (Throwable e) {
//...
}
} else {
disableNdkIntegration(this.options);
}
}
我们可以看到,通过反射的方式,将options传给Sentryndk的init()方法。
(3)EnvelopeFileObserverIntegration:在发送Event到Sentry后台之前,会先把它保存到本地。这个Integration时用来监听文件读写完毕后,进行网络请求,具体流程就不进行分析了。
(4)AppLifecycleIntegration监听App前后台切换,并添加BreadCrumb给Sentry,register()的主要实现如下:
//AppLifecycleIntegration.java
@Override
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
Objects.requireNonNull(hub, "Hub is required");
//...
if (this.options.isEnableAutoSessionTracking()
|| this.options.isEnableAppLifecycleBreadcrumbs()) {
try {
Class.forName("androidx.lifecycle.DefaultLifecycleObserver");
Class.forName("androidx.lifecycle.ProcessLifecycleOwner");
if (AndroidMainThreadChecker.getInstance().isMainThread()) {
addObserver(hub);
} else {
//...
}
} catch (ClassNotFoundException e) {
//...
} catch (IllegalStateException e) {
//...
}
}
}
//AppLifecycleIntegration.java
private void addObserver(final @NotNull IHub hub) {
//...
watcher =
new LifecycleWatcher(
hub,
this.options.getSessionTrackingIntervalMillis(),
this.options.isEnableAutoSessionTracking(),
this.options.isEnableAppLifecycleBreadcrumbs());
try {
ProcessLifecycleOwner.get().getLifecycle().addObserver(watcher);
//...
addIntegrationToSdkVersion();
} catch (Throwable e) {
//...
}
}
//LifecycleWatcher.java
@Override
public void onStart(final @NotNull LifecycleOwner owner) {
startSession();
addAppBreadcrumb("foreground");
// Consider using owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED);
// in the future.
AppState.getInstance().setInBackground(false);
}
@Override
public void onStop(final @NotNull LifecycleOwner owner) {
if (enableSessionTracking) {
final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis();
this.lastUpdatedSession.set(currentTimeMillis);
scheduleEndSession();
}
AppState.getInstance().setInBackground(true);
addAppBreadcrumb("background");
}
(5)AnrIntegrationFactory.create():ANR在Android 11之前和之后的监测方式不同:
//AnrIntegrationFactory.java
public final class AnrIntegrationFactory {
@NotNull
public static Integration create(
final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) {
return new AnrV2Integration(context);
} else {
return new AnrIntegration(context);
}
}
}
先看Android 11及之后的处理方式:
//AnrV2Integration.java
@SuppressLint("NewApi") // we do the check in the AnrIntegrationFactory
@Override
public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
//...
if (this.options.isAnrEnabled()) {
try {
options
.getExecutorService()
.submit(new AnrProcessor(context, hub, this.options, dateProvider));
} catch (Throwable e) {
//...
}
options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed.");
addIntegrationToSdkVersion();
}
}
创建了一个AnrProcessor,实现了Runnable接口:
//AnrProcessor.java
@SuppressLint("NewApi") // we check this in AnrIntegrationFactory
@Override
public void run() {
final ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final List<ApplicationExitInfo> applicationExitInfoList =
activityManager.getHistoricalProcessExitReasons(null, 0, 0);
//...
final IEnvelopeCache cache = options.getEnvelopeDiskCache();
if (cache instanceof EnvelopeCache) {
if (options.isEnableAutoSessionTracking()
&& !((EnvelopeCache) cache).waitPreviousSessionFlush()) {
//...
((EnvelopeCache) cache).flushPreviousSession();
}
}
// making a deep copy as we're modifying the list
final List<ApplicationExitInfo> exitInfos = new ArrayList<>(applicationExitInfoList);
final @Nullable Long lastReportedAnrTimestamp = AndroidEnvelopeCache.lastReportedAnr(options);
ApplicationExitInfo latestAnr = null;
for (ApplicationExitInfo applicationExitInfo : exitInfos) {
if (applicationExitInfo.getReason() == ApplicationExitInfo.REASON_ANR) {
latestAnr = applicationExitInfo;
// remove it, so it's not reported twice
exitInfos.remove(applicationExitInfo);
break;
}
}
//...
if (options.isReportHistoricalAnrs()) {
reportNonEnrichedHistoricalAnrs(exitInfos, lastReportedAnrTimestamp);
}
reportAsSentryEvent(latestAnr, true);
}
不去深究代码的细节,只看方案,我们可以看到Android 11及以上是通过ActivityManager.getHistoricalProcessExitReasons()来得到Anr的相关信息,最终通过reportAsSentryEvent()来进行上报:
private void reportAsSentryEvent(
final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) {
final long anrTimestamp = exitInfo.getTimestamp();
final boolean isBackground =
exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
final ParseResult result = parseThreadDump(exitInfo, isBackground);
//...
final AnrV2Hint anrHint =
new AnrV2Hint(
options.getFlushTimeoutMillis(),
options.getLogger(),
anrTimestamp,
shouldEnrich,
isBackground);
final Hint hint = HintUtils.createWithTypeCheckHint(anrHint);
final SentryEvent event = new SentryEvent();
if (result.type == ParseResult.Type.ERROR) {
final Message sentryMessage = new Message();
sentryMessage.setFormatted(
"Sentry Android SDK failed to parse system thread dump for "
+ "this ANR. We recommend enabling [SentryOptions.isAttachAnrThreadDump] option "
+ "to attach the thread dump as plain text and report this issue on GitHub.");
event.setMessage(sentryMessage);
} else if (result.type == ParseResult.Type.DUMP) {
event.setThreads(result.threads);
}
event.setLevel(SentryLevel.FATAL);
event.setTimestamp(DateUtils.getDateTime(anrTimestamp));
if (options.isAttachAnrThreadDump()) {
if (result.dump != null) {
hint.setThreadDump(Attachment.fromThreadDump(result.dump));
}
}
final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
//...
}
这段代码我们先不去看具体的实现,它的作用就是将Anr信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。我们再来看看Android 11以下是如何处理的:
//AnrIntegrationFactory.java
public static Integration create(
final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) {
return new AnrV2Integration(context);
} else {
return new AnrIntegration(context);
}
}
}
//AnrIntegration.java
private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
//...
if (options.isAnrEnabled()) {
synchronized (watchDogLock) {
if (anrWatchDog == null) {
//...
anrWatchDog =
new ANRWatchDog(
options.getAnrTimeoutIntervalMillis(),
options.isAnrReportInDebug(),
error -> reportANR(hub, options, error),
options.getLogger(),
context);
anrWatchDog.start();
options.getLogger().log(SentryLevel.DEBUG, "AnrIntegration installed.");
addIntegrationToSdkVersion();
}
}
}
}
封装了一个ANRWatchDog继承了Thread,这个方案就是传统的Anr监测方案:启动一个异步线程,在while循环中,使用主线程的Handler发送一个消息,线程休眠指定的时间5s,当线程唤醒之后,如果发送的消息还没被主线程执行,即认为主线程发生了卡顿。具体流程不再描述了,最终也是将Anr
信息封装成Hint,再构造SentryEvent,通过hub.captureEvent(event, hint)进行上报。
(6)ActivityLifecycleIntegration:实现了Application.ActivityLifecycleCallbacks用来监测Activity生命周期。ActivityLifecycleIntegration主要干了三件事:
1.计算冷启动时间;
2.用来将这Activity的生命周期变化及信息添加到BreadCrumb中去;
3.计算Activity的启动时间。
先看一下register()方法的实现:
//ActivityLifecycleIntegration.java
@Override
public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");
this.hub = Objects.requireNonNull(hub, "Hub is required");
//...
performanceEnabled = isPerformanceEnabled(this.options);
fullyDisplayedReporter = this.options.getFullyDisplayedReporter();
timeToFullDisplaySpanEnabled = this.options.isEnableTimeToFullDisplayTracing();
application.registerActivityLifecycleCallbacks(this);
this.options.getLogger().log(SentryLevel.DEBUG, "ActivityLifecycleIntegration installed.");
addIntegrationToSdkVersion();
}
通过application.registerActivityLifecycleCallbacks(this)注册生命周期的监听,当执行了onActivityCreated():
//ActivityLifecycleIntegration.java
@Override
public synchronized void onActivityCreated(
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
setColdStart(savedInstanceState);
addBreadcrumb(activity, "created");
startTracing(activity);
final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
firstActivityCreated = true;
if (fullyDisplayedReporter != null) {
fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan));
}
}
我们看首先setColdStart()设置了是否为冷启动的标志位,addBreadcrumb()设置面包屑,startTracing()开始追踪这个Activity。具体代码实现不在此展开。除了加面包屑这件事,主要就是为了区分是否为冷启动,为了之后统计冷启动速度和页面加载速度作区分。而这两个都是以onActivityCreated()作为起始点(冷启动其实是以SentryPerformanceProvider作为起点的,但如果SentryPerformanceProvider被disable了,那就以第一个Activity走到onCreate()作为起点,在onActivityResumed()作为统计的终点:
//ActivityLifecycleIntegration.java
public synchronized void onActivityResumed(final @NotNull Activity activity) {
if (performanceEnabled) {
// app start span
@Nullable final SentryDate appStartStartTime = AppStartState.getInstance().getAppStartTime();
@Nullable final SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
if (appStartStartTime != null && appStartEndTime == null) {
AppStartState.getInstance().setAppStartEnd();
}
finishAppStartSpan();
final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity);
final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
final View rootView = activity.findViewById(android.R.id.content);
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.JELLY_BEAN
&& rootView != null) {
FirstDrawDoneListener.registerForNextDraw(
rootView, () -> onFirstFrameDrawn(ttfdSpan, ttidSpan), buildInfoProvider);
} else {
mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan));
}
}
addBreadcrumb(activity, "resumed");
}
其中finishAppStartSpan()方法最终会构造一个SentryTransaction,通过captureTransaction()将启动信息上报到Sentry后台。
(7)CurrentActivityIntegration:这个Integration也实现了Application.ActivityLifecycleCallbacks接口,目的是为CurrentActivityHolder添加目前活跃Activity的引用的。
(8)UserInteractionIntegration:为了记录用户的交互信息,依然是实现了Application.ActivityLifecycleCallbacks接口,仅对onActivityResumed()和onActivityPaused()做了实现:
//UserInteractionIntegration.java
@Override
public void onActivityResumed(@NotNull Activity activity) {
startTracking(activity);
}
@Override
public void onActivityPaused(@NotNull Activity activity) {
stopTracking(activity);
}
//UserInteractionIntegration.java
private void startTracking(final @NotNull Activity activity) {
final Window window = activity.getWindow();
if (window == null) {
//...
return;
}
if (hub != null && options != null) {
Window.Callback delegate = window.getCallback();
if (delegate == null) {
delegate = new NoOpWindowCallback();
}
final SentryGestureListener gestureListener =
new SentryGestureListener(activity, hub, options);
window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options));
}
}
在startTracking()方法中构建了SentryGestureListener和SentryWindowCallback,用来监听手势事件,从而记录用户行为。
(9)FragmentLifecycleIntegration:不知为什么看不到源码,但从命名来看就是监听Fragment生命周期的。
(10)SentryTimberIntegration:也看不到源码。
(11)AppComponentsBreadcrumbsIntegration:实现了ComponentCallbacks2接口监听内存不足的情况。
(12)SystemEventsBreadcrumbsIntegration:构造了一个SystemEventsBroadcastReceiver,监听了一系列系统相关的事件:
private static @NotNull List<String> getDefaultActions() {
final List<String> actions = new ArrayList<>();
actions.add(ACTION_APPWIDGET_DELETED);
actions.add(ACTION_APPWIDGET_DISABLED);
actions.add(ACTION_APPWIDGET_ENABLED);
actions.add("android.appwidget.action.APPWIDGET_HOST_RESTORED");
actions.add("android.appwidget.action.APPWIDGET_RESTORED");
actions.add(ACTION_APPWIDGET_UPDATE);
actions.add("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS");
actions.add(ACTION_POWER_CONNECTED);
actions.add(ACTION_POWER_DISCONNECTED);
actions.add(ACTION_SHUTDOWN);
actions.add(ACTION_AIRPLANE_MODE_CHANGED);
actions.add(ACTION_BATTERY_LOW);
actions.add(ACTION_BATTERY_OKAY);
actions.add(ACTION_BOOT_COMPLETED);
actions.add(ACTION_CAMERA_BUTTON);
actions.add(ACTION_CONFIGURATION_CHANGED);
actions.add("android.intent.action.CONTENT_CHANGED");
actions.add(ACTION_DATE_CHANGED);
actions.add(ACTION_DEVICE_STORAGE_LOW);
actions.add(ACTION_DEVICE_STORAGE_OK);
actions.add(ACTION_DOCK_EVENT);
actions.add("android.intent.action.DREAMING_STARTED");
actions.add("android.intent.action.DREAMING_STOPPED");
actions.add(ACTION_INPUT_METHOD_CHANGED);
actions.add(ACTION_LOCALE_CHANGED);
actions.add(ACTION_REBOOT);
actions.add(ACTION_SCREEN_OFF);
actions.add(ACTION_SCREEN_ON);
actions.add(ACTION_TIMEZONE_CHANGED);
actions.add(ACTION_TIME_CHANGED);
actions.add("android.os.action.DEVICE_IDLE_MODE_CHANGED");
actions.add("android.os.action.POWER_SAVE_MODE_CHANGED");
// The user pressed the "Report" button in the crash/ANR dialog.
actions.add(ACTION_APP_ERROR);
// Show activity for reporting a bug.
actions.add(ACTION_BUG_REPORT);
// consider if somebody mounted or ejected a sdcard
actions.add(ACTION_MEDIA_BAD_REMOVAL);
actions.add(ACTION_MEDIA_MOUNTED);
actions.add(ACTION_MEDIA_UNMOUNTABLE);
actions.add(ACTION_MEDIA_UNMOUNTED);
return actions;
}
收到相应的action将被添加至BreadCrumb。(13)NetworkBreadcrumbsIntegration:通过ConnectivityManager监听网络状态的变化并添加至BreadCrumb。
(14)TempSensorBreadcrumbsIntegration:实现了SensorEventListener接口,监听Sensor的状态变化。
(15)PhoneStateBreadcrumbsIntegration:通过TelephonyManager监听TelephonyManager.CALL_STATE_RINGING状态。好了, AndroidOptionsInitializer.installDefaultIntegrations()方法分析完了。这个方法就是为Android添加的Integration。
到此为止,这些Integration便是Sentry为Android添加的能力。我们再回到SentryAndroid.java继续来看Sentry.init()方法中applyOptionsConfiguration()针对options还干了什么,主要还剩最后一个方法:AndroidOptionsInitializer.initializeIntegrationsAndProcessors():
static void initializeIntegrationsAndProcessors(
final @NotNull SentryAndroidOptions options,
final @NotNull Context context,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull LoadClass loadClass,
final @NotNull ActivityFramesTracker activityFramesTracker) {
if (options.getCacheDirPath() != null
&& options.getEnvelopeDiskCache() instanceof NoOpEnvelopeCache) {
options.setEnvelopeDiskCache(new AndroidEnvelopeCache(options));
}
options.addEventProcessor(new DeduplicateMultithreadedEventProcessor(options));
options.addEventProcessor(
new DefaultAndroidEventProcessor(context, buildInfoProvider, options));
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));
options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider));
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
final SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));
final boolean isAndroidXScrollViewAvailable =
loadClass.isClassAvailable("androidx.core.view.ScrollingView", options);
final boolean isComposeUpstreamAvailable =
loadClass.isClassAvailable(COMPOSE_CLASS_NAME, options);
if (options.getGestureTargetLocators().isEmpty()) {
final List<GestureTargetLocator> gestureTargetLocators = new ArrayList<>(2);
gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable));
final boolean isComposeAvailable =
(isComposeUpstreamAvailable
&& loadClass.isClassAvailable(
SENTRY_COMPOSE_GESTURE_INTEGRATION_CLASS_NAME, options));
if (isComposeAvailable) {
gestureTargetLocators.add(new ComposeGestureTargetLocator(options.getLogger()));
}
options.setGestureTargetLocators(gestureTargetLocators);
}
if (options.getViewHierarchyExporters().isEmpty()
&& isComposeUpstreamAvailable
&& loadClass.isClassAvailable(
SENTRY_COMPOSE_VIEW_HIERARCHY_INTEGRATION_CLASS_NAME, options)) {
final List<ViewHierarchyExporter> viewHierarchyExporters = new ArrayList<>(1);
viewHierarchyExporters.add(new ComposeViewHierarchyExporter(options.getLogger()));
options.setViewHierarchyExporters(viewHierarchyExporters);
}
options.setMainThreadChecker(AndroidMainThreadChecker.getInstance());
if (options.getCollectors().isEmpty()) {
options.addCollector(new AndroidMemoryCollector());
options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
}
options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));
if (options.getCacheDirPath() != null) {
options.addScopeObserver(new PersistingScopeObserver(options));
options.addOptionsObserver(new PersistingOptionsObserver(options));
}
}
首先我们可以看到,添加了很多EventProcessor,在之前的章节我们介绍过,EventProcessor的目的是在发送事件时插入一些附属信息的,其中process()方法是具体的实现。我们选取ScreenshotEventProcessor看看都实现了什么,其他EventProcessor就不介绍了:
//ScreenshotEventProcessor.java
@Override
public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
//...
final byte[] screenshot =
takeScreenshot(
activity, options.getMainThreadChecker(), options.getLogger(), buildInfoProvider);
if (screenshot == null) {
return event;
}
hint.setScreenshot(Attachment.fromScreenshot(screenshot));
hint.set(ANDROID_ACTIVITY, activity);
return event;
}
主要实现是如果开启了ScreenShot模式,发送事件之前会截图并添加至hint中,待发送事件时将截图作为附件上传至服务端。除了EventProcessor,还有其他几项配置会添加到options里,我们来看看几个重要的配置:
(1)AndroidTransportGate用来判断设备的网络是否是connected的状态,如果是的话发送事件到Sentry后台,否则存在cache中。
(2)AndroidTransactionProfiler用来管理Transaction的,实现了ITransactionProfiler接口,重写了onTransactionStart()和onTransactionFinish()方法。设置了buffer,如果在跟踪的过程中到了buffer的阈值,那么新的记录将会被丢弃。默认每个traces会跟踪30s,总共给30MB的buffer。大概能记录3次的Transaction。其中onTransactionStart()设置了traceFilesDir。而onTransactionFinish()主要构件了一个ProfilingTraceData,
除了记录了常规的设备相关信息外,还有三个重要的参数:File traceFile,List<ProfilingTransactionData> transactions和measurementsMap。traceFile是在onTransactionStart()时调用系统的VMDebug.startMethodTracing()生成的trace文件。transactions记录了每一个Transaction相关的基础信息,其数据结构如下:
//ProfilingTransactionData.java
private @NotNull String id; // transaction event id (the current transaction_id)
private @NotNull String traceId; // trace id (the current trace_id)
private @NotNull String name; // transaction name
private @NotNull Long relativeStartNs; // timestamp in nanoseconds this transaction started
private @Nullable Long relativeEndNs; // timestamp in nanoseconds this transaction ended
private @NotNull Long relativeStartCpuMs; // cpu time in milliseconds this transaction started
private @Nullable Long relativeEndCpuMs; // cpu time in milliseconds this transaction ended
measurementsMap包含了screenFrameRateMeasurements,slowFrameRenderMeasurements和frozenFrameRenderMeasurements三个信息。
(3)AndroidMemoryCollector,AndroidCpuCollector和DefaultTransactionPerformanceCollector:AndroidMemoryCollector和AndroidCpuCollector实现了ICollector接口,重写了collect()方法用来记录一个Transaction里的内存和cpu使用情况。并交由DefaultTransactionPerformanceCollector进行处理。DefaultTransactionPerformanceCollector每100ms进行一次collect()操作总共持续30s,收集各个Collector的信息,在发送事件时一并发送到Sentry后来。
(4)PersistingScopeObserver:持久的Scope observer参数的实现。
(5)PersistingOptionsObserver:持久的SentryOptions observer的实现。到此为止,Sentry.init()中的applyOptionsConfiguration()方法的解析终于完成了。总结一下:applyOptionsConfiguration()最重要的事情就是初始化了开发者赋予Sentry的能力Intergration,和每个事件所需要的额外硬件/软件/环境等相关基础信息EventProcessor,及不同Intergration对应的信息和其他随着事件一起发送的额外数据。另外,进行了一些cache的初始化,做了一些硬件相关的检测等。总而言之,就是在我们真正产生事件之前做好一切基础准备,在上报各种事件时,相关的信息都会最终被封装在SentryOptions中。
我们再回到Sentry.init():
//Sentry.java
public static <T extends SentryOptions> void init(
final @NotNull OptionsContainer<T> clazz,
final @NotNull OptionsConfiguration<T> optionsConfiguration,
final boolean globalHubMode)
throws IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException {
final T options = clazz.createInstance();
applyOptionsConfiguration(optionsConfiguration, options);
init(options, globalHubMode);
}
还剩最后一行代码:init(options, globalHubMode)
3.2.3.init(options, globalHubMode)
通过上一章节的分析,我们知道Sentry
会先初始化好各种所需的能力,以及随事件需要上报的各种参数,并最终构建一个SentryOptions
对象。init()
这个方法就是根据我们上一步构建的SentryOptions
真正的去初始化SDK
:
//Sentry.java
private static synchronized void init(
final @NotNull SentryOptions options, final boolean globalHubMode) {
//...
if (!initConfigurations(options)) {
return;
}
Sentry.globalHubMode = globalHubMode;
final IHub hub = getCurrentHub();
mainHub = new Hub(options);
currentHub.set(mainHub);
hub.close();
final ISentryExecutorService sentryExecutorService = options.getExecutorService();
// If the passed executor service was previously called we set a new one
if (sentryExecutorService.isClosed()) {
options.setExecutorService(new SentryExecutorService());
}
for (final Integration integration : options.getIntegrations()) {
integration.register(HubAdapter.getInstance(), options);
}
notifyOptionsObservers(options);
finalizePreviousSession(options, HubAdapter.getInstance());
}
首先initConfigurations()
读取了Sentry
的配置信息,包括DNS
,Host
信息等,然后创建了一个Hub
对象,用来管理Scope
和SentryClient
:
//Hub.java
public Hub(final @NotNull SentryOptions options) {
this(options, createRootStackItem(options));
// Integrations are no longer registered on Hub ctor, but on Sentry.init
}
private static StackItem createRootStackItem(final @NotNull SentryOptions options) {
validateOptions(options);
final Scope scope = new Scope(options);
final ISentryClient client = new SentryClient(options);
return new StackItem(options, client, scope);
}
返回一个StackItem
对象。
//Hub.java
private Hub(final @NotNull SentryOptions options, final @NotNull StackItem rootStackItem) {
this(options, new Stack(options.getLogger(), rootStackItem));
}
private Hub(final @NotNull SentryOptions options, final @NotNull Stack stack) {
validateOptions(options);
this.options = options;
this.tracesSampler = new TracesSampler(options);
this.stack = stack;
this.lastEventId = SentryId.EMPTY_ID;
this.transactionPerformanceCollector = options.getTransactionPerformanceCollector();
// Integrations will use this Hub instance once registered.
// Make sure Hub ready to be used then.
this.isEnabled = true;
}
创建了Stack
对象。接着如果SentryExecutorService
是关闭的状态,那么创建一个SentryExecutorService
对象并交由options
。然后就是执行上一章节我们分析过的各种Integration
的注册方法,这样Sentry
就真正拥有了各种能力。notifyOptionsObservers()
方法是为了获得我们上一章节讲的为PersistingOptionsObserver
进行赋值。好了到此为止SentryAndroid.init()
方法就分析完了。现在Sentry
已经都准备好了,等待着我们发送各种事件了。接下来帮大家梳理一下事件发送的流程。
04
事件发送
在文章的最初介绍概念时,我们知道Sentry
的事件分为SentryEvent
和SentryTransaction
。它们俩其实继承自同一个父类:SentryBaseEvent
。我们最常用到的崩溃日志的上报是在UncaughtExceptionHandlerIntegration
中调用了hub.captureEvent(event, hint)
。Sentry
还为我们封装了个captureException(final @NotNull Throwable throwable)
的方法方便我们直接上报Throwable
。而captureException()
方法最终也是调到captureEvent()
里。实际上只要构建了一个SentryEvent
,最终都会调用到captureEvent()
里。我们以UncaughtExceptionHandlerIntegration
中的处理为例,看一下captureEvent()
的流程。
4.1.IHub.captureEvent()
在UncaughtExceptionHandlerIntegration
里,由于实现了UncaughtExceptionHandler
接口,当有exception
出现时,会回调至uncaughtException()
方法中进行处理:
//UncaughtExceptionHandlerIntegration.java
@Override
public void uncaughtException(Thread thread, Throwable thrown) {
if (options != null && hub != null) {
options.getLogger().log(SentryLevel.INFO, "Uncaught exception received.");
try {
final UncaughtExceptionHint exceptionHint =
new UncaughtExceptionHint(options.getFlushTimeoutMillis(), options.getLogger());
final Throwable throwable = getUnhandledThrowable(thread, thrown);
final SentryEvent event = new SentryEvent(throwable);
event.setLevel(SentryLevel.FATAL);
final Hint hint = HintUtils.createWithTypeCheckHint(exceptionHint);
final @NotNull SentryId sentryId = hub.captureEvent(event, hint);
final boolean isEventDropped = sentryId.equals(SentryId.EMPTY_ID);
final EventDropReason eventDropReason = HintUtils.getEventDropReason(hint);
//...
} catch (Throwable e) {
//...
}
if (defaultExceptionHandler != null) {
//...
defaultExceptionHandler.uncaughtException(thread, thrown);
} else {
if (options.isPrintUncaughtStackTrace()) {
thrown.printStackTrace();
}
}
}
}
先看SentryEvent
的创建,将Throwable
作为参数构造SentryEvent
:
//SentryEvent.java
public SentryEvent(final @Nullable Throwable throwable) {
this();
this.throwable = throwable;
}
public SentryEvent() {
this(new SentryId(), DateUtils.getCurrentDateTime());
}
SentryEvent(final @NotNull SentryId eventId, final @NotNull Date timestamp) {
super(eventId);
this.timestamp = timestamp;
}
//SentryId.java
public SentryId() {
this((UUID) null);
}
public SentryId(@Nullable UUID uuid) {
if (uuid == null) {
uuid = UUID.randomUUID();
}
this.uuid = uuid;
}
创建了一个uuid
作为SentryId
,记录上报的日期和Throwable
。通过event.setLevel(SentryLevel.FATAL)
将事件等级设置为FATAL
。然后创建hint
调用hub.captureEvent(event, hint)
:
//IHub.java
@NotNull
SentryId captureEvent(@NotNull SentryEvent event, @Nullable Hint hint);
//Hub.java
@Override
public @NotNull SentryId captureEvent(
final @NotNull SentryEvent event, final @Nullable Hint hint) {
return captureEventInternal(event, hint, null);
}
//Hub.java
private @NotNull SentryId captureEventInternal(
final @NotNull SentryEvent event,
final @Nullable Hint hint,
final @Nullable ScopeCallback scopeCallback) {
SentryId sentryId = SentryId.EMPTY_ID;
if (!isEnabled()) {
//...
} else if (event == null) {
//...
} else {
try {
assignTraceContext(event);
final StackItem item = stack.peek();
final Scope scope = buildLocalScope(item.getScope(), scopeCallback);
sentryId = item.getClient().captureEvent(event, scope, hint);
this.lastEventId = sentryId;
} catch (Throwable e) {
//...
}
}
return sentryId;
}
最终调到SentryClient
中的captureEvent()
方法,在看captureEvent()
的实现之前,我们先来看一下SentryClient
的构造方法:
//SentryClient.java
SentryClient(final @NotNull SentryOptions options) {
this.options = Objects.requireNonNull(options, "SentryOptions is required.");
this.enabled = true;
ITransportFactory transportFactory = options.getTransportFactory();
if (transportFactory instanceof NoOpTransportFactory) {
transportFactory = new AsyncHttpTransportFactory();
options.setTransportFactory(transportFactory);
}
final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options);
transport = transportFactory.create(options, requestDetailsResolver.resolve());
this.random = options.getSampleRate() == null ? null : new SecureRandom();
}
最重要的就是初始化了网络的部分,构造了一个AsyncHttpTransportFactory
,创建了AsyncHttpTransport
对象并赋值给transport
,而AsyncHttpTransport
负责缓存事件到本地和发送事件到服务端。接下来我们来看captureEvent()
的实现:
//SentryClient.java
@Override
public @NotNull SentryId captureEvent(
@NotNull SentryEvent event, final @Nullable Scope scope, @Nullable Hint hint) {
//...
event = processEvent(event, hint, options.getEventProcessors());
if (event != null) {
event = executeBeforeSend(event, hint);
//...
}
//...
@Nullable
Session sessionBeforeUpdate =
scope != null ? scope.withSession((@Nullable Session session) -> {}) : null;
@Nullable Session session = null;
if (event != null) {
// https://develop.sentry.dev/sdk/sessions/#terminal-session-states
if (sessionBeforeUpdate == null || !sessionBeforeUpdate.isTerminated()) {
session = updateSessionData(event, hint, scope);
}
//...
}
final boolean shouldSendSessionUpdate =
shouldSendSessionUpdateForDroppedEvent(sessionBeforeUpdate, session);
//...
SentryId sentryId = SentryId.EMPTY_ID;
if (event != null && event.getEventId() != null) {
sentryId = event.getEventId();
}
try {
@Nullable TraceContext traceContext = null;
if (HintUtils.hasType(hint, Backfillable.class)) {
// for backfillable hint we synthesize Baggage from event values
if (event != null) {
final Baggage baggage = Baggage.fromEvent(event, options);
traceContext = baggage.toTraceContext();
}
} else if (scope != null) {
final @Nullable ITransaction transaction = scope.getTransaction();
if (transaction != null) {
traceContext = transaction.traceContext();
} else {
final @NotNull PropagationContext propagationContext =
TracingUtils.maybeUpdateBaggage(scope, options);
traceContext = propagationContext.traceContext();
}
}
final boolean shouldSendAttachments = event != null;
List<Attachment> attachments = shouldSendAttachments ? getAttachments(hint) : null;
final SentryEnvelope envelope =
buildEnvelope(event, attachments, session, traceContext, null);
hint.clear();
if (envelope != null) {
transport.send(envelope, hint);
}
} catch (IOException | SentryEnvelopeException e) {
//...
}
//...
return sentryId;
}
这段代码非常长,我们截取了核心部分。首先调用了processEvent()
方法:
//SentryClient.java
@Nullable
private SentryEvent processEvent(
@NotNull SentryEvent event,
final @NotNull Hint hint,
final @NotNull List<EventProcessor> eventProcessors) {
for (final EventProcessor processor : eventProcessors) {
try {
// only wire backfillable events through the backfilling processors, skip from others, and
// the other way around
final boolean isBackfillingProcessor = processor instanceof BackfillingEventProcessor;
final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class);
if (isBackfillable && isBackfillingProcessor) {
event = processor.process(event, hint);
} else if (!isBackfillable && !isBackfillingProcessor) {
event = processor.process(event, hint);
}
} catch (Throwable e) {
//...
}
//...
}
return event;
}
我们可以看到传的参数:options.getEventProcessors()
就是在初始化阶段创建的EventProcessor
列表,用来在发送事件时添加一些信息的。在processEvent()
方法中主要是就是执行了各个EventProcessor
的process()
方法去添加额外信息。executeBeforeSend(event, hint)
实际上是个callback
,用来给用户提供一个发送事件之前的时机进行额外的处理。之后的代码实际上都是为了构建一个SentryEnvelope
对象envelope
交给transport
去处理。envelope
会把SentryEvent
,Attachment
(比如截图),session
,traceContext
进行封装,最终调用transport.send(envelope, hint)
方法。
我们来看transport.send()
方法的实现:
//AsyncHttpTransport.java
@Override
public void send(final @NotNull SentryEnvelope envelope, final @NotNull Hint hint)
throws IOException {
// For now no caching on envelopes
IEnvelopeCache currentEnvelopeCache = envelopeCache;
boolean cached = false;
if (HintUtils.hasType(hint, Cached.class)) {
currentEnvelopeCache = NoOpEnvelopeCache.getInstance();
cached = true;
options.getLogger().log(SentryLevel.DEBUG, "Captured Envelope is already cached");
}
final SentryEnvelope filteredEnvelope = rateLimiter.filter(envelope, hint);
if (filteredEnvelope == null) {
if (cached) {
envelopeCache.discard(envelope);
}
} else {
SentryEnvelope envelopeThatMayIncludeClientReport;
if (HintUtils.hasType(
hint, UncaughtExceptionHandlerIntegration.UncaughtExceptionHint.class)) {
envelopeThatMayIncludeClientReport =
options.getClientReportRecorder().attachReportToEnvelope(filteredEnvelope);
} else {
envelopeThatMayIncludeClientReport = filteredEnvelope;
}
final Future<?> future =
executor.submit(
new EnvelopeSender(envelopeThatMayIncludeClientReport, hint, currentEnvelopeCache));
if (future != null && future.isCancelled()) {
options
.getClientReportRecorder()
.recordLostEnvelope(DiscardReason.QUEUE_OVERFLOW, envelopeThatMayIncludeClientReport);
}
}
}
这段代码我们讲一下重点:先初始化currentEnvelopeCache
,再将envelope
封装成envelopeThatMayIncludeClientReport
,最终将envelopeThatMayIncludeClientReport
,hint
和currentEnvelopeCache
封装成EnvelopeSender
交给QueuedThreadPoolExecutor
处理。其中EnvelopeSender
是个Runnable
,我们看看其run()
方法的实现:
//AsyncHttpTransport.java
@Override
public void run() {
TransportResult result = this.failedResult;
try {
result = flush();
options.getLogger().log(SentryLevel.DEBUG, "Envelope flushed");
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, e, "Envelope submission failed");
throw e;
} finally {
final TransportResult finalResult = result;
HintUtils.runIfHasType(
hint,
SubmissionResult.class,
(submissionResult) -> {
//...
});
}
}
执行了flush()
方法:
//AsyncHttpTransport.java
private @NotNull TransportResult flush() {
TransportResult result = this.failedResult;
envelope.getHeader().setSentAt(null);
envelopeCache.store(envelope, hint);
HintUtils.runIfHasType(
hint,
DiskFlushNotification.class,
(diskFlushNotification) -> {
diskFlushNotification.markFlushed();
options.getLogger().log(SentryLevel.DEBUG, "Disk flush envelope fired");
});
if (transportGate.isConnected()) {
final SentryEnvelope envelopeWithClientReport =
options.getClientReportRecorder().attachReportToEnvelope(envelope);
try {
@NotNull SentryDate now = options.getDateProvider().now();
envelopeWithClientReport
.getHeader()
.setSentAt(DateUtils.nanosToDate(now.nanoTimestamp()));
result = connection.send(envelopeWithClientReport);
if (result.isSuccess()) {
envelopeCache.discard(envelope);
} else {
final String message =
"The transport failed to send the envelope with response code "
+ result.getResponseCode();
//...
if (result.getResponseCode() >= 400 && result.getResponseCode() != 429) {
HintUtils.runIfDoesNotHaveType(
hint,
Retryable.class,
(hint) -> {
//...
});
}
throw new IllegalStateException(message);
}
} catch (IOException e) {
//...
}
} else {
// If transportGate is blocking from sending, allowed to retry
HintUtils.runIfHasType(
hint,
Retryable.class,
(retryable) -> {
retryable.setRetry(true);
},
(hint, clazz) -> {
//...
});
}
return result;
}
我们可以看到,首先就是通过envelopeCache.store(envelope, hint)
将这个事件保存在本地。然后通过connection.send(envelopeWithClientReport)
将事件发送至服务端,如果事件发送成功的话,再调用envelopeCache.discard(envelope)
将保存在本地的事件删除。到此为止一个SentryEvent
的发送流程就分析完毕了。
简单地梳理一下发送的流程:
下面我们再来分析一下SentryTransaction
的发送流程。
4.2.IHub.captureTransaction()
之前我们在分析ActivityLifecycleIntegration
的实现时提到过,在onActivityCreated()
作为一个Transaction
起始点,在onActivityResumed()
时作为这个Transaction
统计的终点,通过调用finishAppStartSpan()
来进行Transaction
的上报。我们先来看看在onActivityCreated()
是如何创建一个SentryTransaction
的:
//ActivityLifecycleIntegration.java
@Override
public synchronized void onActivityCreated(
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
setColdStart(savedInstanceState);
addBreadcrumb(activity, "created");
startTracing(activity);
final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
firstActivityCreated = true;
if (fullyDisplayedReporter != null) {
fullyDisplayedReporter.registerFullyDrawnListener(() -> onFullFrameDrawn(ttfdSpan));
}
}
继续追踪startTracing()
方法:
//ActivityLifecycleIntegration.java
private void startTracing(final @NotNull Activity activity) {
WeakReference<Activity> weakActivity = new WeakReference<>(activity);
if (hub != null && !isRunningTransactionOrTrace(activity)) {
if (!performanceEnabled) {
activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance());
TracingUtils.startNewTrace(hub);
} else if (performanceEnabled) {
// as we allow a single transaction running on the bound Scope, we finish the previous ones
stopPreviousTransactions();
final String activityName = getActivityName(activity);
final SentryDate appStartTime =
foregroundImportance ? AppStartState.getInstance().getAppStartTime() : null;
final Boolean coldStart = AppStartState.getInstance().isColdStart();
final TransactionOptions transactionOptions = new TransactionOptions();
if (options.isEnableActivityLifecycleTracingAutoFinish()) {
transactionOptions.setIdleTimeout(options.getIdleTimeout());
transactionOptions.setTrimEnd(true);
}
transactionOptions.setWaitForChildren(true);
transactionOptions.setTransactionFinishedCallback(
(finishingTransaction) -> {
@Nullable Activity unwrappedActivity = weakActivity.get();
if (unwrappedActivity != null) {
activityFramesTracker.setMetrics(
unwrappedActivity, finishingTransaction.getEventId());
} else {
if (options != null) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"Unable to track activity frames as the Activity %s has been destroyed.",
activityName);
}
}
});
// This will be the start timestamp of the transaction, as well as the ttid/ttfd spans
final @NotNull SentryDate ttidStartTime;
if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
// The first activity ttid/ttfd spans should start at the app start time
ttidStartTime = appStartTime;
} else {
// The ttid/ttfd spans should start when the previous activity called its onPause method
ttidStartTime = lastPausedTime;
}
transactionOptions.setStartTimestamp(ttidStartTime);
// we can only bind to the scope if there's no running transaction
ITransaction transaction =
hub.startTransaction(
new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),
transactionOptions);
setSpanOrigin(transaction);
//...
}
}
}
截取重点部分,创建一个TransactionOptions
对象设置各种参数,再封装一个TransactionContext
对象记录activityName
,之后调用hub.startTransaction()
进行上报:
//Hub.java
@ApiStatus.Internal
@Override
public @NotNull ITransaction startTransaction(
final @NotNull TransactionContext transactionContext,
final @NotNull TransactionOptions transactionOptions) {
return createTransaction(transactionContext, transactionOptions);
}
//Hub.java
private @NotNull ITransaction createTransaction(
final @NotNull TransactionContext transactionContext,
final @NotNull TransactionOptions transactionOptions) {
Objects.requireNonNull(transactionContext, "transactionContext is required");
ITransaction transaction;
if (!isEnabled()) {
//...
} else if (!options.getInstrumenter().equals(transactionContext.getInstrumenter())) {
//...
} else if (!options.isTracingEnabled()) {
//...
} else {
final SamplingContext samplingContext =
new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext());
@NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext);
transactionContext.setSamplingDecision(samplingDecision);
transaction =
new SentryTracer(
transactionContext, this, transactionOptions, transactionPerformanceCollector);
// The listener is called only if the transaction exists, as the transaction is needed to
// stop it
if (samplingDecision.getSampled() && samplingDecision.getProfileSampled()) {
final ITransactionProfiler transactionProfiler = options.getTransactionProfiler();
transactionProfiler.onTransactionStart(transaction);
}
}
if (transactionOptions.isBindToScope()) {
configureScope(scope -> scope.setTransaction(transaction));
}
return transaction;
}
根据传入的transactionContext
和transactionOptions
创建一个SamplingContext
对象,调用tracesSampler.sample(samplingContext)
获取当前activity
的采样率samplingDecision
,再创建一个SentryTracer
对象transaction
。接着获取AndroidTransactionProfiler
对象transactionProfiler
,调用其onTransactionStart()
方法开始跟踪(前面的章节已经讲过了AndroidTransactionProfiler
)。onActivityCreated()
创建Transaction
的过程讲完了,我们再来看看在onActivityResumed()
时调用finishAppStartSpan()
进行上报的实现:
//ActivityLifecycleIntegration.java
private void finishAppStartSpan() {
final @Nullable SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
if (performanceEnabled && appStartEndTime != null) {
finishSpan(appStartSpan, appStartEndTime);
}
}
//ActivityLifecycleIntegration.java
private void finishSpan(
final @Nullable ISpan span,
final @NotNull SentryDate endTimestamp,
final @Nullable SpanStatus spanStatus) {
if (span != null && !span.isFinished()) {
final @NotNull SpanStatus status =
spanStatus != null
? spanStatus
: span.getStatus() != null ? span.getStatus() : SpanStatus.OK;
span.finish(status, endTimestamp);
}
}
跟踪到SentryTracer. finish()
方法:
//SentryTracer.java
@Override
@ApiStatus.Internal
public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) {
finish(status, finishDate, true);
}
//SentryTracer.java
@Override
public void finish(
@Nullable SpanStatus status, @Nullable SentryDate finishDate, boolean dropIfNoChildren) {
//...
ProfilingTraceData profilingTraceData = null;
if (Boolean.TRUE.equals(isSampled()) && Boolean.TRUE.equals(isProfileSampled())) {
profilingTraceData =
hub.getOptions()
.getTransactionProfiler()
.onTransactionFinish(this, performanceCollectionData);
}
//...
final SentryTransaction transaction = new SentryTransaction(this);
final TransactionFinishedCallback finishedCallback =
transactionOptions.getTransactionFinishedCallback();
if (finishedCallback != null) {
finishedCallback.execute(this);
}
//...
transaction.getMeasurements().putAll(measurements);
hub.captureTransaction(transaction, traceContext(), null, profilingTraceData);
}
}
先从options
拿到AndroidTransactionProfiler
对象再调用其onTransactionFinish()
方法封装成ProfilingTraceData
(在之前的章节已经介绍过ProfilingTraceData
了)。然后创建一个SentryTransaction
对象,最后调用 hub.captureTransaction(transaction, traceContext(), null, profilingTraceData)
上报事件:
//Hub.java
@ApiStatus.Internal
@Override
public @NotNull SentryId captureTransaction(
final @NotNull SentryTransaction transaction,
final @Nullable TraceContext traceContext,
final @Nullable Hint hint,
final @Nullable ProfilingTraceData profilingTraceData) {
Objects.requireNonNull(transaction, "transaction is required");
SentryId sentryId = SentryId.EMPTY_ID;
if (!isEnabled()) {
//...
} else {
if (!transaction.isFinished()) {
//...
} else {
if (!Boolean.TRUE.equals(transaction.isSampled())) {
//...
} else {
StackItem item = null;
try {
item = stack.peek();
sentryId =
item.getClient()
.captureTransaction(
transaction, traceContext, item.getScope(), hint, profilingTraceData);
} catch (Throwable e) {
//...
}
}
}
}
return sentryId;
}
调用了SentryClient
的captureTransaction()
方法。这个具体流程不讲了,跟captureEvent()
类似,只是封装的数据结构不太一样。最终都是调用到AsyncHttpTransport.send()
方法,流程一致。
到此为止,Sentry
提供的针对SentryEvent
和SentryTransaction
两种事件的上报已经分析完毕了。
05
定制化APM系统的初步想法
分析完整个Sentry
的实现后,我们意识到如果希望定制自己的APM
,完全可以以Sentry
为base
进行一些拓展和改造。比如说根据需求创建自己的Integration
为Sentry
增加新的能力;创建EventProcessor
随事件上报新的参数;重写AndroidTransactionProfiler
添加新的性能数据;结合其他三方APM
相关库的输出作为附件封装成事件上报等。
总而言之就是将我们希望监测的性能数据与Sentry
的基本能力和参数进行绑定,复用Sentry
的数据结构,上报到Sentry
后台或者其他后台。
06
总结
这篇文章相信分析了Sentry(Android端)
实现的具体流程,希望能给大家定制化APM
系统一些参考和想法。