geary/src/engine/api/geary-problem-report.vala
Michael James Gratton bcca75f5a8 Include a back trace in problem report technical details.
This adds a dependcy on libunwind for generating the back trace.

* src/CMakeLists.txt: Require libunwind-generic package and libunwind
  VAPI. Update docs and debian/control with new dependencies.

* src/engine/api/geary-problem-report.vala (ProblemReport): Generate a
  stack trace in the default constructor if an error is specified.

* src/client/components/main-window-info-bar.vala
  (MainWindowInfoBar::format_details): Include stack trafe from problem
  report in output if present.

* ui/main-window-info-bar.ui: Add a ScrolledWindow around the TextView
  since the details could now be quite large.

* bindings/vapi/libunwind.vapi: Add bindings for libunwind courtesy
  Guillaume Poirier-Morency, add Error enum.
2017-11-18 15:25:28 +11:00

222 lines
6.7 KiB
Vala

/*
* Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2017 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/** Describes available problem types. */
public enum Geary.ProblemType {
/** Indicates an engine problem not covered by one of the other types. */
GENERIC_ERROR,
/** Indicates an error opening, using or closing the account database. */
DATABASE_FAILURE,
/** Indicates a problem establishing a connection. */
CONNECTION_ERROR,
/** Indicates a problem caused by a network operation. */
NETWORK_ERROR,
/** Indicates a non-network related server error. */
SERVER_ERROR,
/** Indicates credentials supplied for authentication were rejected. */
LOGIN_FAILED,
/** Indicates an outgoing message was sent, but not saved. */
SEND_EMAIL_SAVE_FAILED;
/** Determines the appropriate problem type for an IOError. */
public static ProblemType for_ioerror(IOError error) {
if (error is IOError.CONNECTION_REFUSED ||
error is IOError.HOST_NOT_FOUND ||
error is IOError.HOST_UNREACHABLE ||
error is IOError.NETWORK_UNREACHABLE) {
return ProblemType.CONNECTION_ERROR;
}
if (error is IOError.CONNECTION_CLOSED ||
error is IOError.NOT_CONNECTED) {
return ProblemType.NETWORK_ERROR;
}
return ProblemType.GENERIC_ERROR;
}
}
/**
* Describes a error that the engine encountered, for reporting to the client.
*/
public class Geary.ProblemReport : Object {
/**
* Represents an individual stack frame in a call back-trace.
*/
public class StackFrame {
/** Name of the function being called. */
public string name = "unknown";
internal StackFrame(Unwind.Cursor frame) {
uint8 proc_name[256];
int ret = -frame.get_proc_name(proc_name);
if (ret == Unwind.Error.SUCCESS ||
ret == Unwind.Error.NOMEM) {
this.name = (string) proc_name;
}
}
public string to_string() {
return this.name;
}
}
/** Describes the type of being reported. */
public ProblemType problem_type { get; private set; }
/** The exception caused the problem, if any. */
public Error? error { get; private set; default = null; }
/** A back trace from when the problem report was constructed. */
public Gee.List<StackFrame>? backtrace = null;
public ProblemReport(ProblemType type, Error? error) {
this.problem_type = type;
this.error = error;
if (error != null) {
// Some kind of exception occurred, so build a trace. This
// is far from perfect, but at least we will know where it
// was getting caught.
this.backtrace = new Gee.LinkedList<StackFrame>();
Unwind.Context trace = Unwind.Context();
Unwind.Cursor cursor = Unwind.Cursor.local(trace);
// This misses the first frame, but that's this
// constructor call, so we don't really care.
while (cursor.step() != 0) {
this.backtrace.add(new StackFrame(cursor));
}
}
}
/** Returns a string representation of the report, for debugging only. */
public string to_string() {
return "%s: %s".printf(
this.problem_type.to_string(),
format_full_error() ?? "no error reported"
);
}
/** Returns a string representation of the error type, for debugging. */
public string? format_error_type() {
string type = null;
if (this.error != null) {
const string QUARK_SUFFIX = "-quark";
string ugly_domain = this.error.domain.to_string();
if (ugly_domain.has_suffix(QUARK_SUFFIX)) {
ugly_domain = ugly_domain.substring(
0, ugly_domain.length - QUARK_SUFFIX.length
);
}
StringBuilder nice_domain = new StringBuilder();
string separator = (ugly_domain.index_of("_") != -1) ? "_" : "-";
foreach (string part in ugly_domain.split(separator)) {
if (part.length > 0) {
if (part == "io") {
nice_domain.append("IO");
} else {
nice_domain.append(part.up(1));
nice_domain.append(part.substring(1));
}
}
}
type = "%s %i".printf(nice_domain.str, this.error.code);
}
return type;
}
/** Returns a string representation of the complete error, for debugging. */
public string? format_full_error() {
string error = null;
if (this.error != null) {
error = String.is_empty(this.error.message)
? "%s: no message specified".printf(format_error_type())
: "%s: \"%s\"".printf(format_error_type(), this.error.message);
}
return error;
}
}
/**
* Describes an account-related error that the engine encountered.
*/
public class Geary.AccountProblemReport : ProblemReport {
/** The account related to the problem report. */
public AccountInformation account { get; private set; }
public AccountProblemReport(ProblemType type, AccountInformation account, Error? error) {
base(type, error);
this.account = account;
}
/** Returns a string representation of the report, for debugging only. */
public new string to_string() {
return "%s: %s".printf(this.account.id, base.to_string());
}
}
/**
* Describes a service-related error that the engine encountered.
*/
public class Geary.ServiceProblemReport : AccountProblemReport {
/** The service related to the problem report. */
public Service service_type { get; private set; }
/** The endpoint for the report's service type. */
public Endpoint endpoint {
owned get {
return (this.service_type == Service.IMAP)
? this.account.get_imap_endpoint()
: this.account.get_smtp_endpoint();
}
}
public ServiceProblemReport(ProblemType type, AccountInformation account, Service service_type, Error? error) {
base(type, account, error);
this.service_type = service_type;
}
/** Returns a string representation of the report, for debugging only. */
public new string to_string() {
return "%s: %s: %s: %s".printf(
this.account.id,
this.service_type.to_string(),
this.problem_type.to_string(),
format_full_error() ?? "no error reported"
);
}
}