use failure::{Error, Fail};
use regex::Regex;
use crate::backtrace_support::{demangle_symbol, error_typename, filename, strip_symbol};
use crate::hub::Hub;
use crate::internals::Uuid;
use crate::protocol::{Event, Exception, Frame, Level, Stacktrace};
lazy_static::lazy_static! {
static ref MODULE_SPLIT_RE: Regex = Regex::new(r"^(.*)::(.*?)$").unwrap();
static ref FRAME_RE: Regex = Regex::new(
r#"(?xm)
^
\s*(?:\d+:)?\s* # frame number (missing for inline)
(?:
(?P<addr_old>0x[a-f0-9]+) # old style address prefix
\s-\s
)?
(?P<symbol>[^\r\n\(]+) # symbol name
(?:
\s\((?P<addr_new>0x[a-f0-9]+)\) # new style address in parens
)?
(?:
\r?\n
\s+at\s # padded "at" in new line
(?P<path>[^\r\n]+?) # path to source file
(?::(?P<lineno>\d+))? # optional source line
)?
$
"#
)
.unwrap();
}
fn parse_stacktrace(bt: &str) -> Option<Stacktrace> {
let mut last_address = None;
let frames = FRAME_RE
.captures_iter(&bt)
.map(|captures| {
let abs_path = captures.name("path").map(|m| m.as_str().to_string());
let filename = abs_path.as_ref().map(|p| filename(p));
let real_symbol = captures["symbol"].to_string();
let symbol = strip_symbol(&real_symbol);
let function = demangle_symbol(symbol);
last_address = captures
.name("addr_new")
.or_else(|| captures.name("addr_old"))
.and_then(|m| m.as_str().parse().ok())
.or(last_address);
Frame {
symbol: if symbol != function {
Some(symbol.into())
} else {
None
},
function: Some(function),
instruction_addr: last_address,
abs_path,
filename,
lineno: captures
.name("lineno")
.map(|x| x.as_str().parse::<u64>().unwrap()),
..Default::default()
}
})
.collect();
Stacktrace::from_frames_reversed(frames)
}
fn fail_typename<F: Fail + ?Sized>(f: &F) -> (Option<String>, String) {
if let Some(name) = f.name() {
if let Some(caps) = MODULE_SPLIT_RE.captures(name) {
(Some(caps[1].to_string()), caps[2].to_string())
} else {
(None, name.to_string())
}
} else {
(None, error_typename(f))
}
}
pub fn exception_from_single_fail<F: Fail + ?Sized>(
f: &F,
bt: Option<&failure::Backtrace>,
) -> Exception {
let (module, ty) = fail_typename(f);
Exception {
ty,
module,
value: Some(f.to_string()),
stacktrace: bt
.map(failure::Backtrace::to_string)
.and_then(|x| parse_stacktrace(&x)),
..Default::default()
}
}
pub fn event_from_error(err: &failure::Error) -> Event<'static> {
let mut exceptions = vec![];
for (idx, cause) in err.iter_chain().enumerate() {
let bt = match cause.backtrace() {
Some(bt) => Some(bt),
None if idx == 0 => Some(err.backtrace()),
None => None,
};
exceptions.push(exception_from_single_fail(cause, bt));
}
exceptions.reverse();
Event {
exception: exceptions.into(),
level: Level::Error,
..Default::default()
}
}
pub fn event_from_fail<F: Fail + ?Sized>(fail: &F) -> Event<'static> {
let mut exceptions = vec![exception_from_single_fail(fail, fail.backtrace())];
let mut ptr: Option<&dyn Fail> = None;
while let Some(cause) = ptr.map(Fail::cause).unwrap_or_else(|| fail.cause()) {
exceptions.push(exception_from_single_fail(cause, cause.backtrace()));
ptr = Some(cause);
}
exceptions.reverse();
Event {
exception: exceptions.into(),
level: Level::Error,
..Default::default()
}
}
pub fn capture_error(err: &Error) -> Uuid {
Hub::with_active(|hub| hub.capture_error(err))
}
pub fn capture_fail<F: Fail + ?Sized>(fail: &F) -> Uuid {
Hub::with_active(|hub| hub.capture_fail(fail))
}
pub trait FailureHubExt {
fn capture_error(&self, err: &Error) -> Uuid;
fn capture_fail<F: Fail + ?Sized>(&self, fail: &F) -> Uuid;
}
impl FailureHubExt for Hub {
fn capture_error(&self, err: &Error) -> Uuid {
self.capture_event(event_from_error(err))
}
fn capture_fail<F: Fail + ?Sized>(&self, fail: &F) -> Uuid {
self.capture_event(event_from_fail(fail))
}
}
pub trait FailureResultExt {
type Value;
fn fallible_unwrap(self) -> Self::Value;
}
impl<T, E> FailureResultExt for Result<T, E>
where
E: Into<Error>,
{
type Value = T;
fn fallible_unwrap(self) -> Self::Value {
match self {
Ok(v) => v,
Err(e) => {
let e: Error = e.into();
panic!(e)
}
}
}
}
#[test]
fn test_parse_stacktrace() {
use crate::protocol::Addr;
let backtrace = r#"
2: <failure::error::error_impl::ErrorImpl as core::convert::From<F>>::from::h3bae66c036570137 (0x55a12174de62)
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error/error_impl.rs:19
<failure::error::Error as core::convert::From<F>>::from::hc7d0d62dae166cea
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error/mod.rs:36
failure::error_message::err_msg::he322d3ed9409189a
at /root/.cargo/registry/src/github.com-1ecc6299db9ec823/failure-0.1.5/src/error_message.rs:12
rust::inline2::h562e5687710b6a71
at src/main.rs:5
rust::not_inline::h16f5b6019e5f0815
at src/main.rs:10
7: main (0x55e3895a4dc7)
"#;
let stacktrace = parse_stacktrace(backtrace).expect("stacktrace");
assert_eq!(stacktrace.frames.len(), 6);
assert_eq!(stacktrace.frames[0].function, Some("main".into()));
assert_eq!(
stacktrace.frames[0].instruction_addr,
Some(Addr(0x55e3_895a_4dc7))
);
assert_eq!(
stacktrace.frames[1].function,
Some("rust::not_inline".into())
);
assert_eq!(
stacktrace.frames[1].instruction_addr,
Some(Addr(0x55a1_2174_de62))
);
}