Skip to content

Instantly share code, notes, and snippets.

@pyricau
Created May 9, 2015 00:03
Show Gist options
  • Star 91 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save pyricau/06c2c486d24f5f85f7f0 to your computer and use it in GitHub Desktop.
Save pyricau/06c2c486d24f5f85f7f0 to your computer and use it in GitHub Desktop.
Sending Leak Traces to a Slack Channel (and HipChat, see the comments)
import android.util.Log;
import com.squareup.leakcanary.AnalysisResult;
import com.squareup.leakcanary.DisplayLeakService;
import com.squareup.leakcanary.HeapDump;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.http.Multipart;
import retrofit.http.POST;
import retrofit.http.Part;
import retrofit.mime.TypedFile;
public final class LeakSlackUploadService extends DisplayLeakService {
/** See https://api.slack.com/ for documentation. */
public interface SlackApi {
String TOKEN = "xoxp-SOME-USER-TOKEN";
String MEMORY_LEAK_CHANNEL = "SOME-CHANNEL-TOKEN";
@Multipart @POST("/api/files.upload") UploadFileResponse uploadFile(@Part("token") String token,
@Part("file") TypedFile file, @Part("filetype") String filetype,
@Part("filename") String filename, @Part("title") String title,
@Part("initial_comment") String initialComment, @Part("channels") String channels);
}
public static class UploadFileResponse {
boolean ok;
String error;
@Override public String toString() {
return "UploadFileResponse{" +
"ok=" + ok +
", error='" + error + '\'' +
'}';
}
}
private static final String TAG = "LeakListenerService";
private static String classSimpleName(String className) {
int separator = className.lastIndexOf('.');
return separator == -1 ? className : className.substring(separator + 1);
}
private SlackApi slackApi;
@Override public void onCreate() {
super.onCreate();
slackApi = new RestAdapter.Builder() //
.setEndpoint("https://slack.com") //
.build() //
.create(SlackApi.class);
}
@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak) {
return;
}
String name = classSimpleName(result.className);
if (!heapDump.referenceName.equals("")) {
name += "(" + heapDump.referenceName + ")";
}
String title = name + " has leaked";
String initialComment = leakInfo;
try {
slackApi.uploadFile(SlackApi.TOKEN,
new TypedFile("application/octet-stream", heapDump.heapDumpFile), null,
heapDump.heapDumpFile.getName(), title, initialComment, SlackApi.MEMORY_LEAK_CHANNEL);
} catch (RetrofitError e) {
Log.e(TAG, "Error when uploading heap dump", e);
}
}
}
public class SquareDebugApplication extends SquareApplication {
@Override protected RefWatcher installLeakCanary() {
return LeakCanary.install(app, LeakSlackUploadService.class);
}
}
@kikoso
Copy link

kikoso commented Oct 23, 2015

@mullender, why are you setting up it to android:enabled="false" ?

@huangdaphne3
Copy link

Has anyone ever upload to flowdock or bug system (i.e. Asana, Jira)?

@parahall
Copy link

Hey :)
After some digging here the snippet for uploading leaks using retrofit 2:

public final class LeakSlackUploadService extends DisplayLeakService
        implements Callback<LeakSlackUploadService.UploadFileResponse> {

    private SlackApi slackApi;


    @Override
    public void onCreate() {
        super.onCreate();
        slackApi = new Retrofit.Builder()
                .baseUrl("https://slack.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build() //
                .create(SlackApi.class);
    }

    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
        Log.e(TAG, "afterDefaultHandling " + leakInfo);
        if (!result.leakFound || result.excludedLeak) {
            Log.e(TAG, "!result.leakFound || result.excludedLeak");
            return;
        }
        String name = classSimpleName(result.className);
        if (!heapDump.referenceName.equals("")) {
            name += "(" + heapDump.referenceName + ")";
        }

        String title = name + " has leaked5";
        String initialComment = leakInfo.substring(0, leakInfo.indexOf("Details:", 0));
        RequestBody file = RequestBody
                .create(MediaType.parse("multipart/form-data"), heapDump.heapDumpFile);
        MultipartBody.Part body =
                MultipartBody.Part.createFormData("file", heapDump.heapDumpFile.getName(), file);
        final Call<UploadFileResponse> call = slackApi.uploadFile(SlackApi.TOKEN,
                body,
                null,
                heapDump.heapDumpFile.getName(), title, initialComment,
                SlackApi.MEMORY_LEAK_CHANNEL);
        call.enqueue(this);
    }

    @Override
    public void onResponse(Call<UploadFileResponse> call,
            Response<UploadFileResponse> response) {
        Log.d(TAG, response.body().toString());

    }

    @Override
    public void onFailure(Call<UploadFileResponse> call, Throwable t) {
        Log.d(TAG, t.getLocalizedMessage());
    }

    /** See https://api.slack.com/ for documentation. */
    public interface SlackApi {

        String TOKEN = "YOUR_TOKEN_SLACK";

        String MEMORY_LEAK_CHANNEL = "YOUR_CHANNEL_FOR_MEMORY_LEAKS";

        @Multipart
        @POST("/api/files.upload")
        Call<UploadFileResponse> uploadFile(
                @Query("token") String token,
                @Part MultipartBody.Part file, @Query("filetype") String filetype,
                @Query("filename") String filename, @Query("title") String title,
                @Query("initial_comment") String initialComment,
                @Query("channels") String channels);
    }

    public static class UploadFileResponse {

        boolean ok;

        String error;

        @Override
        public String toString() {
            return "UploadFileResponse{" +
                    "ok=" + ok +
                    ", error='" + error + '\'' +
                    '}';
        }
    }

    private static final String TAG = "LeakListenerService";

    private static String classSimpleName(String className) {
        int separator = className.lastIndexOf('.');
        return separator == -1 ? className : className.substring(separator + 1);
    }
}

@prabintim
Copy link

prabintim commented Nov 21, 2016

(Solution below) Since LeakCanary 1.5, the install method mentioned above is no longer available.

Deprecated source (below)

public static RefWatcher androidWatcher(Context context, Listener heapDumpListener, ExcludedRefs excludedRefs) {
        AndroidDebuggerControl debuggerControl = new AndroidDebuggerControl();
        AndroidHeapDumper heapDumper = new AndroidHeapDumper(context);
        heapDumper.cleanup();
        return new RefWatcher(new AndroidWatchExecutor(), debuggerControl, GcTrigger.DEFAULT, heapDumper, heapDumpListener, excludedRefs);
    }

Any ideas how to refactor the solution posted above to fit v1.5?
------------ Below is the solution -----------------

AndroidRefWatcherBuilder refWatcher = LeakCanary.refWatcher(this).listenerServiceClass(LeakSlackUploadService.class);
refWatcher.buildAndInstall();

@prabintim
Copy link

When I do LeakCanary.refWatcher(this).listenerServiceClass(LeakSlackUploadService.class); for builds with leakcanary-android-no-op, it throws no Static method exception.

@jiurchuk
Copy link

jiurchuk commented Feb 13, 2019

LeakCanary.refWatcher(application)
    .listenerServiceClass(LeakUploadService.class)
    .buildAndInstall();

@parahall
Copy link

I wonder if anyone created code recipe to upload leak to slack for version 2.7 and Kotlin :) @pyricau

@pyricau
Copy link
Author

pyricau commented Sep 15, 2021

no. Slack bot means no aggregation so it's not as helpful as say Bugsnag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment