Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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);
}
}
@dmarcato

This comment has been minimized.

Copy link

dmarcato commented May 9, 2015

You forgot the part where you register the service in the manifest ;)

@mullender

This comment has been minimized.

Copy link

mullender commented May 9, 2015

Don't forget to add this to your androidmanifest.xml

<service
            android:name="<namespace>.LeakSlackUploadService"
            android:enabled="false" />
@mullender

This comment has been minimized.

Copy link

mullender commented May 9, 2015

Same thing for hipchat:

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.Field;
import retrofit.http.FormUrlEncoded;
import retrofit.http.Header;
import retrofit.http.POST;
import retrofit.http.Path;

public final class HipChatNotifyService extends DisplayLeakService {

    /**
     * See https://api.slack.com/ for documentation.
     */
    public interface HipChatApi {
        String ENDPOINT = "https://api.hipchat.com";
        String TOKEN = "Bearer <room auth token>";
        String MEMORY_LEAK_CHANNEL = "<room number or id>";

        /**
         * @param token          A room notification token, generated via a room's admin page on the HipChat website
         * @param id_or_name     The id or name of the room.
         *                       Valid length range: 1 - 100.
         * @param message        The message body.
         *                       Valid length range: 1 - 10000.
         * @param message_format Determines how the message is treated by our server and rendered inside HipChat applications
         *                       html - Message is rendered as HTML and receives no special treatment. Must be valid HTML and entities must be escaped (e.g.: '&amp;' instead of '&'). May contain basic tags: a, b, i, strong, em, br, img, pre, code, lists, tables.
         *                       text - Message is treated just like a message sent by a user. Can include @mentions, emoticons, pastes, and auto-detected URLs (Twitter, YouTube, images, etc).
         *                       Valid values: html, text.
         *                       Defaults to 'html'.
         * @param color          Background color for message.
         *                       Valid values: yellow, green, red, purple, gray, random.
         *                       Defaults to 'yellow'.
         * @param notify         Whether this message should trigger a user notification (change the tab color, play a sound, notify mobile phones, etc). Each recipient's notification preferences are taken into account.
         *                       Defaults to false.
         * @return
         */
        @FormUrlEncoded
        @POST("/v2/room/{id_or_name}/notification")
        String postMessage(@Header("Authorization") String token, @Path("id_or_name") String id_or_name, @Field("message") String message,
                           @Field("message_format") String message_format, @Field("color") String color,
                           @Field("notify") boolean notify);
    }


    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 HipChatApi hipChatApi;

    @Override
    public void onCreate() {
        super.onCreate();
        hipChatApi = new RestAdapter.Builder() //
            .setEndpoint(HipChatApi.ENDPOINT) //
            .build() //
            .create(HipChatApi.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 + ")";
        }

        try {
            hipChatApi.postMessage(HipChatApi.TOKEN, HipChatApi.MEMORY_LEAK_CHANNEL,
                                   String.format("%s has leaked:\n%s", name, leakInfo), "text", "yellow", true);
        } catch (RetrofitError e) {
            Log.e(TAG, "Error when uploading heap dump", e);
        }
    }
}
@pyricau

This comment has been minimized.

Copy link
Owner Author

pyricau commented May 9, 2015

Thanks guys!

@c99koder

This comment has been minimized.

Copy link

c99koder commented May 18, 2015

Create a non-fatal issue ticket in Crashlytics for leaks:

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

    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
        if (!result.leakFound || result.excludedLeak) {
            return;
        }
        Crashlytics.log("*** Memory Leak ***");
        for(String s : leakInfo.split("\n")) {
            Crashlytics.log(s);
        }
        Crashlytics.log("*** End Of Leak ***");

        String name = classSimpleName(result.className);
        if (!heapDump.referenceName.equals("")) {
            name += "(" + heapDump.referenceName + ")";
        }
        Crashlytics.logException(new Exception(name + " has leaked"));
    }
}
@lizheng90

This comment has been minimized.

Copy link

lizheng90 commented Jul 7, 2015

Hi, I followed the code trying to upload leak traces to HipChat room, but get the retrofit.RetrofitError: 400 Bad Request, do you know how to fix this issue?

Have resolved this, because of too long Http request body.

@kikoso

This comment has been minimized.

Copy link

kikoso commented Oct 23, 2015

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

@daphnegit

This comment has been minimized.

Copy link

daphnegit commented Dec 28, 2015

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

@parahall

This comment has been minimized.

Copy link

parahall commented Mar 16, 2016

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

This comment has been minimized.

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

This comment has been minimized.

Copy link

prabintim commented Nov 22, 2016

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

@jiurchuk

This comment has been minimized.

Copy link

jiurchuk commented Feb 13, 2019

LeakCanary.refWatcher(application)
    .listenerServiceClass(LeakUploadService.class)
    .buildAndInstall();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.