[bug统计] sentry接入详解

写在开头

公司前端使用sentry进行bug的统计与分析,为了方便工具统一,无奈我们客户端也要集成sentry,无奈国内使用较少,资料匮乏,唯一有的博客还和google翻译的一模一样,针对点不明确。在集成的过程中踩了点坑,分享需要的朋友。

sentry介绍

Sentry通过在应用程序的运行时中使用SDK捕获数据。和国内bugly和友盟差不多,使用过程中较好的一点针对上传到后台的bug有很详细的关于机型,当时内存等等的详细统计,使用感觉还算良好。

学习链接只推荐官网文档了。其他博客就不推荐了,看我的保证接入成功,并有你想要的功能。
sentry官网文档

sentry集成

1.app / build.gradle中使用Gradle(Android Studio):

implementation 'io.sentry:sentry-android:1.7.27'
implementation 'org.slf4j:slf4j-nop:1.7.25'

2.写一个自己的SentryManager

SelfSentryClientFactory为自定义的AndroidSentryClientFactory,至于他的作用,后续会讲解

public class SentryManager {

    //初始化sentry
    public static void init(Context context, String sentryDsn) {
        Sentry.init(sentryDsn, new SelfSentryClientFactory(context));
    }

    //主动发送Throwable消息
    public static void sendSentryExcepiton(Throwable throwable) {
        Sentry.capture(throwable);
    }

    //主动发送Event消息
    public static void sendSentryExcepiton(Event event) {
        Sentry.capture(event);
    }

    //主动发送EventBuilder消息
    public static void sendSentryExcepiton(EventBuilder throwable) {
        Sentry.capture(throwable);
    }

    //组合EventBuilder消息并发送 可以通过logger区分
    public static void sendSentryExcepiton(String message, String logger, Throwable throwable) {
        sendSentryExcepiton(new EventBuilder().withMessage(message).withLevel(Event.Level.ERROR).withLogger(logger).withSentryInterface(new ExceptionInterface(throwable)));
    }
}

3.然后在application中掉用初始化就行了,初始化需要的一个DSN字符串。在您的sentry后台获取一下。
在这里插入图片描述

然后你就可以模拟一个崩溃,稍等一会sentry就可以统计到了。

sentry自定义上传参数

思路上就是看sentry-android库中人家怎么添加的参数,然后集成重写就行了。过程中你会发现我们不仅要重写AndroidSentryClientFactory和AndroidEventBuilderHelper两个类还要把ANRWatchDog和ApplicationNotResponding两个类在你的项目中重写写一份,因为在库中这俩类是私有的,但是我们在项目中又要引用。

下面看下ApplicationNotResponding和AndroidEventBuilderHelper的继承类。我做了详细的注释。我保留了所有的参数, CustomMap为我们自定义的参数map。

public class SelfEventBuilderHelper extends AndroidEventBuilderHelper {

    private Context ctx;

    public SelfEventBuilderHelper(Context ctx) {
        super(ctx);
        this.ctx = ctx;
    }

    protected Map<String, Map<String, Object>> getContexts() {
        Map<String, Map<String, Object>> contexts = new HashMap<>();
        Map<String, Object> deviceMap = new HashMap<>();
        Map<String, Object> osMap = new HashMap<>();
        Map<String, Object> appMap = new HashMap<>();
        Map<String, Object> CustomMap = new HashMap<>();
        contexts.put("os", osMap);
        contexts.put("device", deviceMap);
        contexts.put("app", appMap);
        contexts.put("custom", CustomMap);

        // Device
        deviceMap.put("manufacturer", Build.MANUFACTURER);//产品/硬件的制造商
        deviceMap.put("brand", Build.BRAND);//与产品/硬件相关联的消费者可见的品牌(如果有的话)
        deviceMap.put("model", Build.MODEL);//最终产品的最终用户可见名称
        deviceMap.put("family", getFamily());//"Nexus 6P" -> "Nexus" 返回设备的姓
        deviceMap.put("model_id", Build.ID);//可以是变更列表号,也可以是“M4-rc20”这样的标签
        deviceMap.put("battery_level", getBatteryLevel(ctx));//获取设备当前电池电量(占总电量的百分比)如果未知则为空
        deviceMap.put("orientation", getOrientation(ctx));//获取设备当前的屏幕方向 如果未知则为空
        deviceMap.put("simulator", isEmulator());//是否是模拟器
        deviceMap.put("arch", Build.CPU_ABI);//本机代码的指令集的名称(CPU类型+ ABI约定)
        deviceMap.put("storage_size", getTotalInternalStorage());//获取内部存储的总量,以字节为单位
        deviceMap.put("free_storage", getUnusedInternalStorage());//获取未使用的内部存储数量,以字节为单位
        deviceMap.put("external_storage_size", getTotalExternalStorage());//获取外部存储的总量,以字节为单位,如果没有外部存储,则为null
        deviceMap.put("external_free_storage", getUnusedExternalStorage());//获取未使用外部存储的总量,以字节为单位,如果没有外部存储,则为null
        deviceMap.put("charging", isCharging(ctx));//检查设备当前是否处于充电状态,如果未知则为空
        deviceMap.put("online", isConnected(ctx));//是否具有internet访问

        //当前屏幕尺寸 density dpi
        DisplayMetrics displayMetrics = getDisplayMetrics(ctx);
        if (displayMetrics != null) {
            int largestSide = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
            int smallestSide = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
            String resolution = Integer.toString(largestSide) + "x" + Integer.toString(smallestSide);
            deviceMap.put("screen_resolution", resolution);
            deviceMap.put("screen_density", displayMetrics.density);
            deviceMap.put("screen_dpi", displayMetrics.densityDpi);
        }

        //当前内存信息
        ActivityManager.MemoryInfo memInfo = getMemInfo(ctx);
        if (memInfo != null) {
            deviceMap.put("free_memory", memInfo.availMem);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                deviceMap.put("memory_size", memInfo.totalMem);
            }
            deviceMap.put("low_memory", memInfo.lowMemory);
        }

        // Operating System 操作系统相关
        osMap.put("name", "Android");
        osMap.put("version", Build.VERSION.RELEASE);//用户可见的版本字符串
        osMap.put("build", Build.DISPLAY);//用于向用户显示的构建ID字符串
        osMap.put("kernel_version", getKernelVersion());//获取设备的当前内核版本
        osMap.put("rooted", isRooted());//是否root

        // App
        PackageInfo packageInfo = getPackageInfo(ctx);
        if (packageInfo != null) {
            appMap.put("app_version", packageInfo.versionName);
            appMap.put("app_build", packageInfo.versionCode);
            appMap.put("app_identifier", packageInfo.packageName);
        }

        appMap.put("app_name", getApplicationName(ctx));
        appMap.put("app_start_time", stringifyDate(new Date()));
        
        CustomMap.put("test", “test”);
        return contexts;
    }
}
public class SelfSentryClientFactory extends AndroidSentryClientFactory {

    public static final String TAG = SelfSentryClientFactory.class.getName();
    private static volatile ANRWatchDog anrWatchDog;
    private Context ctx;

    public SelfSentryClientFactory(Context ctx) {
        super(ctx);
        this.ctx = ctx.getApplicationContext();
        if (this.ctx == null) {
            this.ctx = ctx;
        }
    }

    @Override
    public SentryClient createSentryClient(Dsn dsn) {
        if (!checkPermission(Manifest.permission.INTERNET)) {
            Log.e(TAG, Manifest.permission.INTERNET + " is required to connect to the Sentry server,"
                    + " please add it to your AndroidManifest.xml");
        }

        Log.d(TAG, "Sentry init with ctx='" + ctx.toString() + "'");

        String protocol = dsn.getProtocol();
        if (protocol.equalsIgnoreCase("noop")) {
            Log.w(TAG, "*** Couldn't find a suitable DSN, Sentry operations will do nothing!"
                    + " See documentation: https://docs.sentry.io/clients/java/modules/android/ ***");
        } else if (!(protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https"))) {
            String async = Lookup.lookup(DefaultSentryClientFactory.ASYNC_OPTION, dsn);
            if (async != null && async.equalsIgnoreCase("false")) {
                throw new IllegalArgumentException("Sentry Android cannot use synchronous connections, remove '"
                        + DefaultSentryClientFactory.ASYNC_OPTION + "=false' from your options.");
            }

            throw new IllegalArgumentException("Only 'http' or 'https' connections are supported in"
                    + " Sentry Android, but received: " + protocol);
        }

        SentryClient sentryClient = super.createSentryClient(dsn);
        sentryClient.addBuilderHelper(new SelfEventBuilderHelper(ctx));

        boolean enableAnrTracking = "true".equalsIgnoreCase(Lookup.lookup("anr.enable", dsn));
        Log.d(TAG, "ANR is='" + String.valueOf(enableAnrTracking) + "'");
        if (enableAnrTracking && anrWatchDog == null) {
            String timeIntervalMsConfig = Lookup.lookup("anr.timeoutIntervalMs", dsn);
            int timeoutIntervalMs = timeIntervalMsConfig != null
                    ? Integer.parseInt(timeIntervalMsConfig)
                    : 5000;

            Log.d(TAG, "ANR timeoutIntervalMs is='" + String.valueOf(timeoutIntervalMs) + "'");

            anrWatchDog = new ANRWatchDog(timeoutIntervalMs, new ANRWatchDog.ANRListener() {
                @Override
                public void onAppNotResponding(ApplicationNotResponding error) {
                    Log.d(TAG, "ANR triggered='" + error.getMessage() + "'");

                    EventBuilder builder = new EventBuilder();
                    builder.withTag("thread_state", error.getState().toString());
                    ExceptionMechanism mechanism = new ExceptionMechanism("anr", false);
                    Throwable throwable = new ExceptionMechanismThrowable(mechanism, error);
                    builder.withSentryInterface(new ExceptionInterface(throwable));

                    Sentry.capture(builder);
                }
            });
            anrWatchDog.start();
        }

        return sentryClient;
    }

    private boolean checkPermission(String permission) {
        int res = ctx.checkCallingOrSelfPermission(permission);
        return (res == PackageManager.PERMISSION_GRANTED);
    }
}

sentry自动上传mapping文件

1.project/build.gradle

classpath 'io.sentry:sentry-android-gradle-plugin:1.7.27'

2.app / build.gradle

	apply plugin: 'io.sentry.android.gradle'
	sentry {
    // 禁用或启用ProGuard的自动配置
    autoProguardConfig true

    // 启用或禁用自动上传映射文件
    autoUpload true
	}

3.建立sentry.properties文件

    defaults.project=your-project
	defaults.org=your-org
	auth.token=YOUR_AUTH_TOKEN
4.重点来了,然后跑项目。

你会发现,会报错token认证失败。为什么呢?因为在执行assembleRelease,执行插件代码的时候,sentry会自动链接他自己的服务器,而不是你的企业的sentry服务器,所以您要指定一下服务器url。命令如下:

sentry-cli --url 你的服务器Url login

注意,空格一定要有,空格标识这是传给sentry服务器的两个参数。然后输入你项目的token就行了。再次打包Ok!!!

文末总结

1.bug上传
2.自定义参数
3.mapping自动上传帮助定位bug
是不是你要的都有了,有任何问题随时评论或者私信就行了。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值