| /** |
| * Copyright (c) 2015 Codetrails GmbH. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| */ |
| package org.eclipse.epp.logging.aeri.core.util; |
| |
| import static com.google.common.base.Charsets.UTF_8; |
| import static com.google.common.base.MoreObjects.firstNonNull; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static java.text.MessageFormat.format; |
| import static org.apache.commons.lang3.StringUtils.*; |
| import static org.eclipse.epp.logging.aeri.core.Constants.HIDDEN; |
| import static org.eclipse.epp.logging.aeri.core.IModelPackage.Literals.*; |
| import static org.eclipse.epp.logging.aeri.core.l10n.LogMessages.WARN_CYCLIC_EXCEPTION; |
| import static org.eclipse.epp.logging.aeri.core.util.Logs.log; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.SystemUtils; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.EMap; |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.ecore.EAttribute; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.epp.logging.aeri.core.Constants; |
| import org.eclipse.epp.logging.aeri.core.IBundle; |
| import org.eclipse.epp.logging.aeri.core.IModelFactory; |
| import org.eclipse.epp.logging.aeri.core.IReport; |
| import org.eclipse.epp.logging.aeri.core.IStackTraceElement; |
| import org.eclipse.epp.logging.aeri.core.IStatus; |
| import org.eclipse.epp.logging.aeri.core.IThrowable; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.osgi.framework.Bundle; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.Sets; |
| import com.google.common.hash.Hasher; |
| import com.google.common.hash.Hashing; |
| |
| @SuppressWarnings("null") |
| public class Reports { |
| |
| public static Hasher newHasher() { |
| return Hashing.murmur3_128().newHasher(); |
| } |
| |
| public static IReport newReport(org.eclipse.core.runtime.IStatus event) { |
| checkNotNull(event); |
| IReport mReport = IModelFactory.eINSTANCE.createReport(); |
| mReport.setJavaRuntimeVersion(SystemUtils.JAVA_RUNTIME_VERSION); |
| mReport.setEclipseBuildId(System.getProperty("eclipse.buildId", "-")); |
| mReport.setEclipseProduct(System.getProperty("eclipse.product", "-")); |
| mReport.setOsgiArch(System.getProperty("osgi.arch", "-")); |
| mReport.setOsgiWs(System.getProperty("osgi.ws", "-")); |
| mReport.setOsgiOs(System.getProperty(org.osgi.framework.Constants.FRAMEWORK_OS_NAME, "-")); |
| mReport.setOsgiOsVersion(System.getProperty(org.osgi.framework.Constants.FRAMEWORK_OS_VERSION, "-")); |
| mReport.setStatus(newStatus(event)); |
| includeBundles(mReport); |
| return mReport; |
| } |
| |
| private static void includeBundles(IReport report) { |
| checkNotNull(report); |
| StackTracePackagesCollector v = new StackTracePackagesCollector(); |
| visit(report, v); |
| Set<String> uniqueBundleNames = Sets.newHashSet(); |
| for (String packageName : v.packages) { |
| while (packageName.contains(".")) { |
| org.osgi.framework.Bundle guessedBundleForPackageName = Platform.getBundle(packageName); |
| packageName = StringUtils.substringBeforeLast(packageName, "."); |
| if (guessedBundleForPackageName != null) { |
| if (uniqueBundleNames.add(guessedBundleForPackageName.getSymbolicName())) { |
| IBundle mBundle = newBundle(guessedBundleForPackageName); |
| report.getPresentBundles().add(mBundle); |
| } |
| continue; |
| } |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public static IStatus newStatus(org.eclipse.core.runtime.IStatus status) { |
| checkNotNull(status); |
| |
| IStatus mStatus = IModelFactory.eINSTANCE.createStatus(); |
| mStatus.setMessage(removeSourceFileContents(status.getMessage())); |
| mStatus.setSeverity(status.getSeverity()); |
| mStatus.setCode(status.getCode()); |
| mStatus.setPluginId(status.getPlugin()); |
| |
| org.osgi.framework.Bundle bundle = Platform.getBundle(status.getPlugin()); |
| if (bundle != null) { |
| mStatus.setPluginVersion(bundle.getVersion().toString()); |
| } |
| |
| List<IStatus> mChildren = mStatus.getChildren(); |
| Throwable exception = status.getException(); |
| // CoreException handling |
| for (Throwable cur = exception; cur != null; cur = cur.getCause()) { |
| if (cur instanceof CoreException) { |
| CoreException coreException = (CoreException) cur; |
| org.eclipse.core.runtime.IStatus coreExceptionStatus = coreException.getStatus(); |
| IStatus mCoreExceptionStatus = newStatus(coreExceptionStatus); |
| String detachedMessage = format("{0} [detached from CoreException of Status ''{1}'' by Error Reporting]", |
| mCoreExceptionStatus.getMessage(), mStatus.getMessage()); |
| mCoreExceptionStatus.setMessage(detachedMessage); |
| mChildren.add(mCoreExceptionStatus); |
| // further CoreExceptions are handled in the detached Status |
| break; |
| } |
| } |
| // Multistatus handling |
| for (org.eclipse.core.runtime.IStatus child : status.getChildren()) { |
| mChildren.add(newStatus(child)); |
| } |
| // some stacktraces from ui.monitoring should be filtered |
| boolean needFiltering = "org.eclipse.ui.monitoring".equals(status.getPlugin()) && (status.getCode() == 0 || status.getCode() == 1); |
| if (needFiltering) { |
| MultiStatusFilter.filter(mStatus); |
| } |
| |
| if (exception != null) { |
| IThrowable mException = newThrowable(exception); |
| mStatus.setException(mException); |
| } |
| |
| mStatus.setFingerprint(newStatusFingerprint(mStatus)); |
| |
| return mStatus; |
| } |
| |
| /** |
| * Computes the exact fingerprint of the given status object. Two statuses have the same fingerprint only iff they have the same plug-in |
| * ids, plug-in versions, and messages (including its children statuses) and the exact same exceptions. |
| */ |
| public static String newStatusFingerprint(IStatus mStatus) { |
| checkNotNull(mStatus); |
| |
| final Hasher hasher = newHasher(); |
| ModelSwitch<Hasher> s = new ModelSwitch<Hasher>() { |
| @Override |
| public Hasher caseStatus(IStatus object) { |
| hasher.putString(stripToEmpty(object.getPluginId()), UTF_8); |
| hasher.putString(stripToEmpty(object.getPluginVersion()), UTF_8); |
| hasher.putString(stripToEmpty(object.getMessage()), UTF_8); |
| hasher.putInt(object.getSeverity()); |
| hasher.putInt(object.getCode()); |
| return null; |
| } |
| |
| @Override |
| public Hasher caseStackTraceElement(IStackTraceElement object) { |
| hasher.putString(stripToEmpty(object.getClassName()), UTF_8); |
| hasher.putString(stripToEmpty(object.getMethodName()), UTF_8); |
| hasher.putInt(object.getLineNumber()); |
| return null; |
| } |
| |
| @Override |
| public Hasher caseThrowable(IThrowable object) { |
| hasher.putString(stripToEmpty(object.getClassName()), UTF_8); |
| hasher.putString(stripToEmpty(object.getMessage()), UTF_8); |
| return null; |
| } |
| }; |
| |
| visit(mStatus, s); |
| String hash = hasher.hash().toString(); |
| return hash; |
| } |
| |
| private static String removeSourceFileContents(@Nullable String message) { |
| message = defaultString(message); |
| if (message.contains(Constants.SOURCE_BEGIN_MESSAGE)) { |
| return Constants.SOURCE_FILE_REMOVED; |
| } else { |
| return message; |
| } |
| } |
| |
| public static IThrowable newThrowable(Throwable throwable) { |
| checkNotNull(throwable); |
| |
| IThrowable mThrowable = IModelFactory.eINSTANCE.createThrowable(); |
| mThrowable.setMessage(throwable.getMessage()); |
| mThrowable.setClassName(throwable.getClass().getName()); |
| List<IStackTraceElement> mStackTrace = mThrowable.getStackTrace(); |
| for (StackTraceElement stackTraceElement : throwable.getStackTrace()) { |
| IStackTraceElement mStackTraceElement = newStackTraceElement(stackTraceElement); |
| mStackTrace.add(mStackTraceElement); |
| } |
| Throwable cause = throwable.getCause(); |
| if (cause != null) { |
| if (cause == throwable) { |
| log(WARN_CYCLIC_EXCEPTION, cause.toString()); |
| return mThrowable; |
| } |
| mThrowable.setCause(newThrowable(cause)); |
| } |
| return mThrowable; |
| } |
| |
| /** |
| * Returns a hash of the given IThrowable. The hash may exclude the throwables' messages and the stack trace elements' line numbers if |
| * the respective parameters are set to false. |
| */ |
| public static String newThrowableFingerprint(IThrowable throwable, final boolean includeMessages, final boolean includeLineNumbers) { |
| checkNotNull(throwable); |
| |
| final Hasher hasher = newHasher(); |
| ModelSwitch<Hasher> s = new ModelSwitch<Hasher>() { |
| |
| @Override |
| public Hasher caseThrowable(IThrowable object) { |
| hasher.putString(stripToEmpty(object.getClassName()), UTF_8); |
| if (includeMessages) { |
| hasher.putString(stripToEmpty(object.getMessage()), UTF_8); |
| } |
| return null; |
| } |
| |
| @Override |
| public Hasher caseStackTraceElement(IStackTraceElement object) { |
| hasher.putString(stripToEmpty(object.getClassName()), UTF_8); |
| hasher.putString(stripToEmpty(object.getMethodName()), UTF_8); |
| if (includeLineNumbers) { |
| hasher.putInt(object.getLineNumber()); |
| } |
| return null; |
| } |
| |
| }; |
| |
| visit(throwable, s); |
| String hash = hasher.hash().toString(); |
| return hash; |
| } |
| |
| public static IStackTraceElement newStackTraceElement(StackTraceElement stackTraceElement) { |
| checkNotNull(stackTraceElement); |
| |
| IStackTraceElement mStackTraceElement = IModelFactory.eINSTANCE.createStackTraceElement(); |
| mStackTraceElement.setClassName(defaultString(stackTraceElement.getClassName(), Constants.MISSING)); |
| mStackTraceElement.setMethodName(defaultString(stackTraceElement.getMethodName(), Constants.MISSING)); |
| mStackTraceElement.setFileName(stackTraceElement.getFileName()); |
| mStackTraceElement.setLineNumber(stackTraceElement.getLineNumber()); |
| mStackTraceElement.setNative(stackTraceElement.isNativeMethod()); |
| return mStackTraceElement; |
| } |
| |
| public static IBundle newBundle(Bundle bundle) { |
| checkNotNull(bundle); |
| |
| IBundle mBundle = IModelFactory.eINSTANCE.createBundle(); |
| mBundle.setName(bundle.getSymbolicName()); |
| mBundle.setVersion(bundle.getVersion().toString()); |
| return mBundle; |
| } |
| |
| public static IReport copy(IReport report) { |
| checkNotNull(report); |
| return EcoreUtil.copy(report); |
| } |
| |
| public static IStatus copy(IStatus status) { |
| checkNotNull(status); |
| |
| return EcoreUtil.copy(status); |
| } |
| |
| public static IThrowable copy(IThrowable throwable) { |
| checkNotNull(throwable); |
| return EcoreUtil.copy(throwable); |
| } |
| |
| public static IStackTraceElement copy(IStackTraceElement stackTraceElement) { |
| checkNotNull(stackTraceElement); |
| return EcoreUtil.copy(stackTraceElement); |
| } |
| |
| public static IBundle copy(IBundle bundle) { |
| checkNotNull(bundle); |
| return EcoreUtil.copy(bundle); |
| } |
| |
| public static String toPrettyString(IReport report) { |
| return toPrettyString(report, new HashMap<>()); |
| } |
| |
| public static String toPrettyString(IReport report, Map<String, String> reportProcessorIdsToReadable) { |
| checkNotNull(report); |
| PrettyPrintVisitor prettyPrintVisitor = new PrettyPrintVisitor(reportProcessorIdsToReadable); |
| visit(report, prettyPrintVisitor); |
| return prettyPrintVisitor.print(); |
| } |
| |
| public static <T, K extends EObject> T visit(K object, ModelSwitch<T> s) { |
| checkNotNull(object); |
| checkNotNull(s); |
| T t = s.doSwitch(object); |
| if (t != null) { |
| return t; |
| } |
| TreeIterator<EObject> allContents = EcoreUtil.getAllContents(object, true); |
| for (TreeIterator<EObject> iterator = allContents; iterator.hasNext();) { |
| EObject modelElement = iterator.next(); |
| t = s.doSwitch(modelElement); |
| if (t != null) { |
| return t; |
| } |
| } |
| return null; |
| } |
| |
| private static final class AnonymizeStackTracesSwitch extends ModelSwitch<Void> { |
| private final List<Pattern> regexes; |
| |
| private AnonymizeStackTracesSwitch(List<Pattern> regexes) { |
| checkNotNull(regexes); |
| this.regexes = regexes; |
| } |
| |
| @Override |
| public Void caseThrowable(IThrowable object) { |
| checkNotNull(object); |
| for (Pattern regex : regexes) { |
| if (regex.matcher(object.getClassName()).matches()) { |
| return null; |
| } |
| } |
| object.setClassName(HIDDEN); |
| return null; |
| } |
| |
| @Override |
| public Void caseStackTraceElement(IStackTraceElement element) { |
| checkNotNull(element); |
| String input = element.getClassName() + "." + element.getMethodName(); |
| for (Pattern regex : regexes) { |
| if (regex.matcher(input).matches()) { |
| return null; |
| } |
| } |
| // if no matcher matched, replace the frame with HIDDEN.HIDDEN: |
| element.setClassName(HIDDEN); |
| element.setMethodName(HIDDEN); |
| element.setFileName(HIDDEN); |
| element.setLineNumber(-1); |
| return null; |
| } |
| } |
| |
| private static final class StackTracePackagesCollector extends ModelSwitch<Object> { |
| public TreeSet<String> packages = Sets.newTreeSet(); |
| |
| @Override |
| public Object caseStackTraceElement(IStackTraceElement element) { |
| checkNotNull(element); |
| String pkg = replace(substringBeforeLast(element.getClassName(), "."), ".internal.", "."); |
| packages.add(pkg); |
| |
| return null; |
| } |
| } |
| |
| public static void anonymizeMessages(IReport report) { |
| checkNotNull(report); |
| |
| visit(report, new ModelSwitch<Void>() { |
| @Override |
| public Void caseThrowable(IThrowable object) { |
| checkNotNull(object); |
| if (object.eIsSet(THROWABLE__MESSAGE)) { |
| object.setMessage(HIDDEN); |
| } |
| return null; |
| } |
| |
| @Override |
| public Void caseStatus(IStatus object) { |
| checkNotNull(object); |
| |
| if (object.eIsSet(STATUS__MESSAGE)) { |
| object.setMessage(HIDDEN); |
| } |
| return null; |
| } |
| }); |
| } |
| |
| public static void anonymizeStackTraces(IReport report, final List<Pattern> regexes) { |
| checkNotNull(report); |
| checkNotNull(regexes); |
| |
| visit(report, new AnonymizeStackTracesSwitch(regexes)); |
| } |
| |
| public static void anonymizeStackTraces(IThrowable throwable, final List<Pattern> regexes) { |
| checkNotNull(throwable); |
| checkNotNull(regexes); |
| visit(throwable, new AnonymizeStackTracesSwitch(regexes)); |
| } |
| |
| public static void anonymizeStackTraceElements(IStackTraceElement element, final List<Pattern> regexes) { |
| visit(element, new AnonymizeStackTracesSwitch(regexes)); |
| } |
| |
| private static class MultiStatusFilter { |
| |
| public static void filter(IStatus status) { |
| checkNotNull(status); |
| HashSet<IThrowable> throwables = new HashSet<>(); |
| filter(status, throwables); |
| } |
| |
| private static void filter(IStatus status, Set<IThrowable> throwables) { |
| EList<IStatus> children = status.getChildren(); |
| int removedCount = 0; |
| for (int i = children.size() - 1; i >= 0; i--) { |
| IStatus childStatus = children.get(i); |
| if (filterChild(childStatus, throwables)) { |
| children.remove(i); |
| removedCount++; |
| } else { |
| filter(childStatus, throwables); |
| } |
| } |
| if (removedCount > 0) { |
| status.setMessage( |
| String.format("%s [%d child-status duplicates removed by Error Reporting]", status.getMessage(), removedCount)); |
| } |
| } |
| |
| private static boolean filterChild(IStatus status, Set<IThrowable> throwables) { |
| IThrowable throwable = status.getException(); |
| if (throwable.getStackTrace().isEmpty()) { |
| return true; |
| } |
| for (IThrowable t : throwables) { |
| if (stackTraceMatches(throwable, t)) { |
| return true; |
| } |
| } |
| throwables.add(throwable); |
| return false; |
| } |
| |
| private static boolean stackTraceMatches(IThrowable throwable, IThrowable t) { |
| EList<IStackTraceElement> stackTrace = throwable.getStackTrace(); |
| EList<IStackTraceElement> stackTrace2 = t.getStackTrace(); |
| if (stackTrace.size() != stackTrace2.size()) { |
| return false; |
| } |
| for (int i = 0; i < stackTrace.size(); i++) { |
| IStackTraceElement ste = stackTrace.get(i); |
| IStackTraceElement ste2 = stackTrace2.get(i); |
| if (!classNameAndMethodNameEqual(ste, ste2)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean classNameAndMethodNameEqual(IStackTraceElement ste, IStackTraceElement ste2) { |
| return ste.getClassName().equals(ste2.getClassName()) && ste.getMethodName().equals(ste2.getMethodName()); |
| } |
| |
| } |
| |
| private static class PrettyPrintVisitor extends ModelSwitch<Object> { |
| private static final int RIGHT_PADDING = 20; |
| private StringBuilder reportStringBuilder = new StringBuilder(); |
| private StringBuilder statusStringBuilder = new StringBuilder(); |
| private StringBuilder bundlesStringBuilder = new StringBuilder(); |
| private StringBuilder auxiliaryInformationStringBuilder = new StringBuilder(); |
| private Map<String, String> directiveToReadable; |
| |
| PrettyPrintVisitor(Map<String, String> reportProcessorIdsToReadable) { |
| this.directiveToReadable = reportProcessorIdsToReadable; |
| appendHeadline("BUNDLES", bundlesStringBuilder); |
| } |
| |
| private void appendAttributes(EObject object, StringBuilder builder) { |
| for (EAttribute attribute : object.eClass().getEAllAttributes()) { |
| // String format = "%-" + RIGHT_PADDING + "s%s\n"; |
| // String line = String.format(format, attribute.getName(), value); |
| Object value = firstNonNull(object.eGet(attribute), ""); |
| builder.append(StringUtils.rightPad(attribute.getName(), RIGHT_PADDING)); |
| builder.append(value); |
| builder.append('\n'); |
| } |
| builder.append("\n"); |
| } |
| |
| private void appendHeadline(String headline, StringBuilder builder) { |
| if (builder.length() != 0) { |
| builder.append("\n"); |
| } |
| String line = headline.replaceAll(".", "-") + "\n"; |
| builder.append(line); |
| builder.append(headline + "\n"); |
| builder.append(line); |
| } |
| |
| @Override |
| public Object caseReport(IReport report) { |
| checkNotNull(report); |
| |
| appendHeadline("REPORT", reportStringBuilder); |
| appendAttributes(report, reportStringBuilder); |
| |
| EMap<String, String> auxiliaryInformation = report.getAuxiliaryInformation(); |
| appendProvidedInformation(auxiliaryInformation, auxiliaryInformationStringBuilder); |
| return null; |
| } |
| |
| private void appendProvidedInformation(EMap<String, String> providedInformation, StringBuilder builder) { |
| for (Entry<String, String> entry : providedInformation.entrySet()) { |
| String processorId = entry.getKey(); |
| if (directiveToReadable.containsKey(processorId)) { |
| processorId = directiveToReadable.get(processorId); |
| } |
| String information = entry.getValue(); |
| appendHeadline(processorId, auxiliaryInformationStringBuilder); |
| builder.append(information); |
| } |
| } |
| |
| @Override |
| public Object caseStatus(IStatus status) { |
| checkNotNull(status); |
| |
| appendHeadline("STATUS", statusStringBuilder); |
| appendAttributes(status, statusStringBuilder); |
| IThrowable exception = status.getException(); |
| if (exception != null) { |
| statusStringBuilder.append("Exception:"); |
| append(exception, statusStringBuilder); |
| } |
| return null; |
| } |
| |
| private void append(IThrowable throwable, StringBuilder builder) { |
| builder.append(String.format("%s: %s\n", throwable.getClassName(), throwable.getMessage())); |
| for (IStackTraceElement element : throwable.getStackTrace()) { |
| builder.append(String.format("\t at %s.%s(%s:%s)\n", element.getClassName(), element.getMethodName(), element.getFileName(), |
| element.getLineNumber())); |
| } |
| IThrowable cause = throwable.getCause(); |
| if (cause != null) { |
| statusStringBuilder.append("Caused by: "); |
| append(cause, builder); |
| } |
| } |
| |
| @Override |
| public Object caseBundle(IBundle bundle) { |
| checkNotNull(bundle); |
| appendAttributes(bundle, bundlesStringBuilder); |
| return null; |
| } |
| |
| public String print() { |
| return new StringBuilder().append(statusStringBuilder).append("\n").append(reportStringBuilder).append(bundlesStringBuilder) |
| .append("\n").append(auxiliaryInformationStringBuilder).toString(); |
| } |
| } |
| } |