Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose a module loader for Android thru sentry-native #1043

Merged
merged 23 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Bump: AGP 4.1.1 (#1040)
* Fix: use neutral Locale for String operations #1033
* Update to sentry-native 0.4.4 and fix shared library builds (#1039)
* Added java doc to protocol classes based on sentry-data-schemes project (#1045)
* Feat: Expose a Module (Debug images) Loader for Android thru sentry-native #1043
* Enhancement: Added java doc to protocol classes based on sentry-data-schemes project (#1045)
* Enhancement: Make SentryExceptionResolver Order configurable to not send handled web exceptions (#1008)
* Enhancement: Resolve HTTP Proxy parameters from the external configuration (#1028)
* Enhancement: Sentry NDK integration is compiled against default NDK version based on AGP's version #1048
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.sentry.android.core;

import io.sentry.protocol.DebugImage;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;

/** Used for loading the list of debug images from sentry-native. */
@ApiStatus.Internal
public interface IDebugImagesLoader {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved
List<DebugImage> loadDebugImages();

void clearDebugImages();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,44 @@ public NdkIntegration(final @Nullable Class<?> sentryNdkClass) {
@Override
public final void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
Objects.requireNonNull(hub, "Hub is required");
Objects.requireNonNull(options, "SentryOptions is required");
final SentryAndroidOptions androidOptions =
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
"SentryAndroidOptions is required");

final boolean enabled = options.isEnableNdk();
options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration enabled: %s", enabled);
final boolean enabled = androidOptions.isEnableNdk();
androidOptions.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) {
final String cachedDir = options.getCacheDirPath();
final String cachedDir = androidOptions.getCacheDirPath();
if (cachedDir == null || cachedDir.isEmpty()) {
options.getLogger().log(SentryLevel.ERROR, "No cache dir path is defined in options.");
options.setEnableNdk(false);
androidOptions
.getLogger()
.log(SentryLevel.ERROR, "No cache dir path is defined in options.");
androidOptions.setEnableNdk(false);
return;
}

try {
final Method method = sentryNdkClass.getMethod("init", SentryOptions.class);
final Method method = sentryNdkClass.getMethod("init", SentryAndroidOptions.class);
final Object[] args = new Object[1];
args[0] = options;
args[0] = androidOptions;
method.invoke(null, args);

options.getLogger().log(SentryLevel.DEBUG, "NdkIntegration installed.");
androidOptions.getLogger().log(SentryLevel.DEBUG, "NdkIntegration installed.");
} catch (NoSuchMethodException e) {
options.setEnableNdk(false);
options
androidOptions.setEnableNdk(false);
androidOptions
.getLogger()
.log(SentryLevel.ERROR, "Failed to invoke the SentryNdk.init method.", e);
} catch (Throwable e) {
options.setEnableNdk(false);
options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e);
androidOptions.setEnableNdk(false);
androidOptions.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e);
}
} else {
options.setEnableNdk(false);
androidOptions.setEnableNdk(false);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.sentry.android.core;

import io.sentry.protocol.DebugImage;
import java.util.List;
import org.jetbrains.annotations.Nullable;

final class NoOpDebugImagesLoader implements IDebugImagesLoader {

private static final NoOpDebugImagesLoader instance = new NoOpDebugImagesLoader();

private NoOpDebugImagesLoader() {}

public static NoOpDebugImagesLoader getInstance() {
return instance;
}

@Override
public @Nullable List<DebugImage> loadDebugImages() {
return null;
}

@Override
public void clearDebugImages() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public final class SentryAndroidOptions extends SentryOptions {
/** Enable or disable automatic breadcrumbs for App Components Using ComponentCallbacks */
private boolean enableAppComponentBreadcrumbs = true;

/** Interface that loads the debug images list */
private @NotNull IDebugImagesLoader debugImagesLoader = NoOpDebugImagesLoader.getInstance();

public SentryAndroidOptions() {
setSentryClientName(BuildConfig.SENTRY_ANDROID_SDK_NAME + "/" + BuildConfig.VERSION_NAME);
setSdkVersion(createSdkVersion());
Expand Down Expand Up @@ -170,4 +173,23 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
enableSystemEventBreadcrumbs = enable;
enableAppLifecycleBreadcrumbs = enable;
}

/**
* Returns the Debug image loader
*
* @return the image loader
*/
public @NotNull IDebugImagesLoader getDebugImagesLoader() {
return debugImagesLoader;
}

/**
* Sets the image loader
*
* @param debugImagesLoader the image loader
*/
public void setDebugImagesLoader(final @NotNull IDebugImagesLoader debugImagesLoader) {
this.debugImagesLoader =
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
debugImagesLoader != null ? debugImagesLoader : NoOpDebugImagesLoader.getInstance();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ class NdkIntegrationTest {
assertFalse(options.isEnableNdk)
}

private fun getOptions(enableNdk: Boolean = true, logger: ILogger = mock(), cacheDir: String? = "abc"): SentryOptions {
return SentryOptions().apply {
private fun getOptions(enableNdk: Boolean = true, logger: ILogger = mock(), cacheDir: String? = "abc"): SentryAndroidOptions {
return SentryAndroidOptions().apply {
setLogger(logger)
isDebug = true
isEnableNdk = enableNdk
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.android.core

import io.sentry.protocol.DebugImage
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
Expand Down Expand Up @@ -35,4 +36,23 @@ class SentryAndroidOptionsTest {
it.version == BuildConfig.VERSION_NAME
})
}

@Test
fun `init should set NoOpDebugImagesLoader`() {
val sentryOptions = SentryAndroidOptions()
assertEquals(NoOpDebugImagesLoader.getInstance(), sentryOptions.debugImagesLoader)
}

@Test
fun `set debugImagesLoader accepts non null value`() {
val sentryOptions = SentryAndroidOptions().apply {
debugImagesLoader = CustomDebugImagesLoader()
}
assertNotNull(sentryOptions.debugImagesLoader)
}

private class CustomDebugImagesLoader : IDebugImagesLoader {
override fun loadDebugImages(): List<DebugImage>? = null
override fun clearDebugImages() {}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package io.sentry.android.core

import io.sentry.SentryOptions

class SentryNdk {
companion object {
@JvmStatic
fun init(options: SentryOptions) {
fun init(options: SentryAndroidOptions) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.sentry.android.ndk;

import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.IDebugImagesLoader;
import io.sentry.android.core.SentryAndroidOptions;
import io.sentry.protocol.DebugImage;
import io.sentry.util.Objects;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

final class DebugImagesLoader implements IDebugImagesLoader {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved

private final @NotNull SentryOptions options;

private final @NotNull NativeModuleListLoader moduleListLoader;

private static @Nullable List<DebugImage> debugImages;

/** we need to lock it because it could be called from different threads */
private static final @NotNull Object debugImagesLock = new Object();

DebugImagesLoader(
final @NotNull SentryAndroidOptions options,
final @NotNull NativeModuleListLoader moduleListLoader) {
this.options = Objects.requireNonNull(options, "The SentryAndroidOptions is required.");
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
this.moduleListLoader =
Objects.requireNonNull(moduleListLoader, "The NativeModuleListLoader is required.");
}

/**
* Returns the list of debug images loaded by sentry-native.
*
* @return the list or null.
*/
@Override
public @Nullable List<DebugImage> loadDebugImages() {
synchronized (debugImagesLock) {
if (debugImages == null) {
philipphofmann marked this conversation as resolved.
Show resolved Hide resolved
try {
final DebugImage[] debugImagesArr = moduleListLoader.loadModuleList();
if (debugImagesArr != null) {
debugImages = Arrays.asList(debugImagesArr);
options
.getLogger()
.log(SentryLevel.DEBUG, "Debug images loaded: %d", debugImages.size());
}
} catch (Exception e) {
options.getLogger().log(SentryLevel.ERROR, e, "Failed to load debug images.");
}
}
}
return debugImages;
}

/** Clears the caching of debug images on sentry-native and here. */
@Override
public void clearDebugImages() {
synchronized (debugImagesLock) {
try {
moduleListLoader.clearModuleList();

options.getLogger().log(SentryLevel.INFO, "Debug images cleared.");
} catch (Exception e) {
options.getLogger().log(SentryLevel.ERROR, e, "Failed to clear debug images.");
}
debugImages = null;
}
}

@VisibleForTesting
@Nullable
List<DebugImage> getCachedDebugImages() {
return debugImages;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.sentry.android.ndk;

import io.sentry.protocol.DebugImage;
import org.jetbrains.annotations.Nullable;

final class NativeModuleListLoader {
marandaneto marked this conversation as resolved.
Show resolved Hide resolved

public @Nullable DebugImage[] loadModuleList() {
return nativeLoadModuleList();
}

public void clearModuleList() {
nativeClearModuleList();
}

public static native DebugImage[] nativeLoadModuleList();

public static native void nativeClearModuleList();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.sentry.android.ndk;

import io.sentry.SentryOptions;
import io.sentry.android.core.SentryAndroidOptions;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

Expand All @@ -20,11 +20,13 @@ private SentryNdk() {}
System.loadLibrary("sentry-android");
}

private static native void initSentryNative(@NotNull final SentryOptions options);
private static native void initSentryNative(@NotNull final SentryAndroidOptions options);

public static void init(@NotNull final SentryOptions options) {
public static void init(@NotNull final SentryAndroidOptions options) {
SentryNdkUtil.addPackage(options.getSdkVersion());
initSentryNative(options);
options.addScopeObserver(new NdkScopeObserver(options));

options.setDebugImagesLoader(new DebugImagesLoader(options, new NativeModuleListLoader()));
}
}
Loading