[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
是不是你要的都有了,有任何問題隨時評論或者私信就行了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章