diff options
author | Ben Gruver <bgruv@google.com> | 2015-04-03 00:07:10 -0700 |
---|---|---|
committer | Ben Gruver <bgruv@google.com> | 2015-04-03 00:07:10 -0700 |
commit | 5deac6ede291dbdc58f53a802aaab6ccacae0f7b (patch) | |
tree | db83f5811866b00db9cd401276fcfbf211c45dfc | |
parent | 93ae7badcd0bf77580b9ccd6f7febe662279b4d0 (diff) | |
download | smali-5deac6ede291dbdc58f53a802aaab6ccacae0f7b.tar.gz |
Add an error reporter that can create issues on github
4 files changed, 390 insertions, 0 deletions
diff --git a/smalidea/META-INF/plugin.xml b/smalidea/META-INF/plugin.xml index 866056a4..cedfd4ab 100644 --- a/smalidea/META-INF/plugin.xml +++ b/smalidea/META-INF/plugin.xml @@ -32,6 +32,7 @@ <referencesSearch implementation="org.jf.smalidea.findUsages.SmaliClassReferenceSearcher"/> <usageTargetProvider implementation="org.jf.smalidea.findUsages.SmaliUsageTargetProvider" /> <usageTypeProvider implementation="org.jf.smalidea.findUsages.SmaliUsageTypeProvider"/> + <errorHandler implementation="org.jf.smalidea.errorReporting.ErrorReporter"/> </extensions> <application-components> diff --git a/smalidea/src/main/java/org/jf/smalidea/errorReporting/ErrorReporter.java b/smalidea/src/main/java/org/jf/smalidea/errorReporting/ErrorReporter.java new file mode 100644 index 00000000..92aef72b --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/errorReporting/ErrorReporter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jf.smalidea.errorReporting; + +import com.intellij.diagnostic.IdeErrorsDialog; +import com.intellij.diagnostic.LogMessageEx; +import com.intellij.diagnostic.ReportMessages; +import com.intellij.errorreport.bean.ErrorBean; +import com.intellij.ide.DataManager; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.idea.IdeaLogger; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.diagnostic.ErrorReportSubmitter; +import com.intellij.openapi.diagnostic.IdeaLoggingEvent; +import com.intellij.openapi.diagnostic.SubmittedReportInfo; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; + +import java.awt.*; +import java.util.Map; + +/** + * Sends crash reports to Github. + * + * Based on the go-lang plugin's error reporter + * (https://github.com/dlsniper/google-go-lang-idea-plugin/commit/c451006cc9fc926ca347189951baa94f4032c5c4) + */ +public class ErrorReporter extends ErrorReportSubmitter { + + @Override + public String getReportActionText() { + return "Report as issue on smali's github repo"; + } + + @Override + public boolean submit(IdeaLoggingEvent[] events, String additionalInfo, Component parentComponent, + final Consumer<SubmittedReportInfo> consumer) { + IdeaLoggingEvent event = events[0]; + ErrorBean bean = new ErrorBean(event.getThrowable(), IdeaLogger.ourLastActionId); + + final DataContext dataContext = DataManager.getInstance().getDataContext(parentComponent); + + bean.setDescription(additionalInfo); + bean.setMessage(event.getMessage()); + + Throwable throwable = event.getThrowable(); + if (throwable != null) { + final PluginId pluginId = IdeErrorsDialog.findPluginId(throwable); + if (pluginId != null) { + final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(pluginId); + if (ideaPluginDescriptor != null && !ideaPluginDescriptor.isBundled()) { + bean.setPluginName(ideaPluginDescriptor.getName()); + bean.setPluginVersion(ideaPluginDescriptor.getVersion()); + } + } + } + + Object data = event.getData(); + + if (data instanceof LogMessageEx) { + bean.setAttachments(((LogMessageEx)data).getAttachments()); + } + + Map<String, String> reportValues = ITNProxy.createParameters(bean); + + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + + Consumer<String> successCallback = new Consumer<String>() { + @Override + public void consume(String token) { + final SubmittedReportInfo reportInfo = new SubmittedReportInfo( + null, "Issue " + token, SubmittedReportInfo.SubmissionStatus.NEW_ISSUE); + consumer.consume(reportInfo); + + ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, + "Submitted", + NotificationType.INFORMATION, + null).setImportant(false).notify(project); + } + }; + + Consumer<Exception> errorCallback = new Consumer<Exception>() { + @Override + public void consume(Exception e) { + String message = String.format("<html>There was an error while creating a GitHub issue: %s<br>" + + "Please consider manually creating an issue on the " + + "<a href=\"https://github.com/JesusFreke/smali/issues\">Smali Issue Tracker</a></html>", + e.getMessage()); + ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, + message, + NotificationType.ERROR, + NotificationListener.URL_OPENING_LISTENER).setImportant(false).notify(project); + } + }; + + GithubFeedbackTask task = new GithubFeedbackTask(project, "Submitting error report", true, reportValues, + successCallback, errorCallback); + + if (project == null) { + task.run(new EmptyProgressIndicator()); + } else { + ProgressManager.getInstance().run(task); + } + return true; + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/errorReporting/GithubFeedbackTask.java b/smalidea/src/main/java/org/jf/smalidea/errorReporting/GithubFeedbackTask.java new file mode 100644 index 00000000..28149494 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/errorReporting/GithubFeedbackTask.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jf.smalidea.errorReporting; + +import com.google.common.io.CharStreams; +import com.google.gson.Gson; +import com.intellij.ide.plugins.IdeaPluginDescriptorImpl; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.LinkedHashMap; +import java.util.Map; + +public class GithubFeedbackTask extends Task.Backgroundable { + private final Consumer<String> myCallback; + private final Consumer<Exception> myErrorCallback; + private final Map<String, String> myParams; + + public GithubFeedbackTask(@Nullable Project project, + @NotNull String title, + boolean canBeCancelled, + Map<String, String> params, + final Consumer<String> callback, + final Consumer<Exception> errorCallback) { + super(project, title, canBeCancelled); + + myParams = params; + myCallback = callback; + myErrorCallback = errorCallback; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); + try { + String token = sendFeedback(myParams); + myCallback.consume(token); + } + catch (Exception e) { + myErrorCallback.consume(e); + } + } + + private static String getToken() { + InputStream stream = GithubFeedbackTask.class.getClassLoader().getResourceAsStream("token"); + if (stream == null) { + return null; + } + try { + return CharStreams.toString(new InputStreamReader(stream, "UTF-8")); + } catch (IOException ex) { + return null; + } + } + + public static String sendFeedback(Map<String, String> environmentDetails) throws IOException { + String url = "https://api.github.com/repos/JesusFreke/smali/issues"; + String userAgent = "smalidea plugin"; + + IdeaPluginDescriptorImpl pluginDescriptor = + (IdeaPluginDescriptorImpl) PluginManager.getPlugin(PluginId.getId("org.jf.smalidea")); + + if (pluginDescriptor != null) { + String name = pluginDescriptor.getName(); + String version = pluginDescriptor.getVersion(); + userAgent = name + " (" + version + ")"; + } + + HttpURLConnection httpURLConnection = connect(url); + httpURLConnection.setDoOutput(true); + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setRequestProperty("User-Agent", userAgent); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + + String token = getToken(); + if (token != null) { + httpURLConnection.setRequestProperty("Authorization", "token " + token); + } + OutputStream outputStream = httpURLConnection.getOutputStream(); + + try { + outputStream.write(convertToGithubIssueFormat(environmentDetails)); + } finally { + outputStream.close(); + } + + int responseCode = httpURLConnection.getResponseCode(); + if (responseCode != 201) { + throw new RuntimeException("Expected HTTP_CREATED (201), obtained " + responseCode); + } + + return Long.toString(System.currentTimeMillis()); + } + + private static byte[] convertToGithubIssueFormat(Map<String, String> environmentDetails) { + LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(5); + result.put("title", "[auto-generated] Crash in plugin"); + result.put("body", generateGithubIssueBody(environmentDetails)); + + return ((new Gson()).toJson(result)).getBytes(Charset.forName("UTF-8")); + } + + private static String generateGithubIssueBody(Map<String, String> body) { + String errorDescription = body.get("error.description"); + if (errorDescription == null) { + errorDescription = ""; + } + body.remove("error.description"); + + String errorMessage = body.get("error.message"); + if (errorMessage == null || errorMessage.isEmpty()) { + errorMessage = "invalid error"; + } + body.remove("error.message"); + + String stackTrace = body.get("error.stacktrace"); + if (stackTrace == null || stackTrace.isEmpty()) { + stackTrace = "invalid stacktrace"; + } + body.remove("error.stacktrace"); + + String result = ""; + + if (!errorDescription.isEmpty()) { + result += errorDescription + "\n\n"; + } + + for (Map.Entry<String, String> entry : body.entrySet()) { + result += entry.getKey() + ": " + entry.getValue() + "\n"; + } + + result += "\n```\n" + stackTrace + "\n```\n"; + + result += "\n```\n" + errorMessage + "\n```"; + + return result; + } + + private static HttpURLConnection connect(String url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) ((new URL(url)).openConnection()); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + return connection; + } +} diff --git a/smalidea/src/main/java/org/jf/smalidea/errorReporting/ITNProxy.java b/smalidea/src/main/java/org/jf/smalidea/errorReporting/ITNProxy.java new file mode 100644 index 00000000..5bf93e32 --- /dev/null +++ b/smalidea/src/main/java/org/jf/smalidea/errorReporting/ITNProxy.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jf.smalidea.errorReporting; + +import com.intellij.errorreport.bean.ErrorBean; +import com.intellij.idea.IdeaLogger; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.diagnostic.Attachment; +import com.intellij.openapi.updateSettings.impl.UpdateSettings; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.SystemProperties; +import com.intellij.util.containers.ContainerUtil; + +import java.util.Calendar; +import java.util.Map; + +/** + * @author stathik + * @since Aug 4, 2003 + */ +public class ITNProxy { + + public static Map<String, String> createParameters(ErrorBean error) { + Map<String, String> params = ContainerUtil.newLinkedHashMap(40); + + params.put("protocol.version", "1"); + + params.put("os.name", SystemProperties.getOsName()); + params.put("java.version", SystemProperties.getJavaVersion()); + params.put("java.vm.vendor", SystemProperties.getJavaVmVendor()); + + ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx(); + ApplicationNamesInfo namesInfo = ApplicationNamesInfo.getInstance(); + Application application = ApplicationManager.getApplication(); + params.put("app.name", namesInfo.getProductName()); + params.put("app.name.full", namesInfo.getFullProductName()); + params.put("app.name.version", appInfo.getVersionName()); + params.put("app.eap", Boolean.toString(appInfo.isEAP())); + params.put("app.internal", Boolean.toString(application.isInternal())); + params.put("app.build", appInfo.getBuild().asString()); + params.put("app.version.major", appInfo.getMajorVersion()); + params.put("app.version.minor", appInfo.getMinorVersion()); + params.put("app.build.date", format(appInfo.getBuildDate())); + params.put("app.build.date.release", format(appInfo.getMajorReleaseBuildDate())); + params.put("app.compilation.timestamp", IdeaLogger.getOurCompilationTimestamp()); + + UpdateSettings updateSettings = UpdateSettings.getInstance(); + params.put("update.channel.status", updateSettings.getSelectedChannelStatus().getCode()); + params.put("update.ignored.builds", StringUtil.join(updateSettings.getIgnoredBuildNumbers(), ",")); + + params.put("plugin.name", error.getPluginName()); + params.put("plugin.version", error.getPluginVersion()); + + params.put("last.action", error.getLastAction()); + params.put("previous.exception", error.getPreviousException() == null ? null : Integer.toString(error.getPreviousException())); + + params.put("error.message", error.getMessage()); + params.put("error.stacktrace", error.getStackTrace()); + params.put("error.description", error.getDescription()); + + params.put("assignee.id", error.getAssigneeId() == null ? null : Integer.toString(error.getAssigneeId())); + + for (Attachment attachment : error.getAttachments()) { + params.put("attachment.name", attachment.getName()); + params.put("attachment.value", attachment.getEncodedBytes()); + } + + return params; + } + + private static String format(Calendar calendar) { + return calendar == null ? null : Long.toString(calendar.getTime().getTime()); + } +} |