Skip to content

Commit 30032a6

Browse files
authored
Introduce ErrorInformation type (#796)
1 parent e0a935f commit 30032a6

File tree

8 files changed

+576
-409
lines changed

8 files changed

+576
-409
lines changed

ntex-error/CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes
22

3+
## [1.1.0] - 2026-03-09
4+
5+
* Introduce ErrorInformation type
6+
37
## [1.0.1] - 2026-03-08
48

59
* Allow to create Backtrace

ntex-error/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ntex-error"
3-
version = "1.0.1"
3+
version = "1.1.0"
44
authors = ["ntex contributors <team@ntex.rs>"]
55
description = "ntex error management"
66
keywords = ["network", "framework"]
@@ -25,4 +25,5 @@ foldhash = { workspace = true }
2525
ntex-bytes = { workspace = true }
2626

2727
[dev-dependencies]
28+
ntex = { workspace = true }
2829
thiserror = { workspace = true }

ntex-error/src/bt.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//! Backtrace
2+
use std::collections::HashMap;
3+
use std::hash::{BuildHasher, Hasher};
4+
use std::panic::Location;
5+
use std::{cell::RefCell, fmt, fmt::Write, os, ptr, sync::Arc};
6+
7+
use backtrace::{BacktraceFmt, BacktraceFrame, BytesOrWideString};
8+
9+
thread_local! {
10+
static FRAMES: RefCell<HashMap<*mut os::raw::c_void, BacktraceFrame>> = RefCell::new(HashMap::default());
11+
static REPRS: RefCell<HashMap<u64, Arc<str>>> = RefCell::new(HashMap::default());
12+
}
13+
static mut START: Option<(&'static str, u32)> = None;
14+
static mut START_ALT: Option<(&'static str, u32)> = None;
15+
16+
pub fn set_backtrace_start(file: &'static str, line: u32) {
17+
unsafe {
18+
START = Some((file, line));
19+
}
20+
}
21+
22+
#[doc(hidden)]
23+
pub fn set_backtrace_start_alt(file: &'static str, line: u32) {
24+
unsafe {
25+
START_ALT = Some((file, line));
26+
}
27+
}
28+
29+
#[derive(Clone)]
30+
/// Representation of a backtrace.
31+
///
32+
/// This structure can be used to capture a backtrace at various
33+
/// points in a program and later used to inspect what the backtrace
34+
/// was at that time.
35+
pub struct Backtrace(Arc<str>);
36+
37+
impl Backtrace {
38+
/// Create new backtrace
39+
pub fn new(loc: &Location<'_>) -> Self {
40+
let repr = FRAMES.with(|c| {
41+
let mut cache = c.borrow_mut();
42+
let mut idx = 0;
43+
let mut st = foldhash::fast::FixedState::default().build_hasher();
44+
let mut idxs: [*mut os::raw::c_void; 128] = [ptr::null_mut(); 128];
45+
46+
backtrace::trace(|frm| {
47+
let ip = frm.ip();
48+
st.write_usize(ip as usize);
49+
cache.entry(ip).or_insert_with(|| {
50+
let mut f = BacktraceFrame::from(frm.clone());
51+
f.resolve();
52+
f
53+
});
54+
idxs[idx] = ip;
55+
idx += 1;
56+
57+
idx < 128
58+
});
59+
60+
let id = st.finish();
61+
62+
REPRS.with(|r| {
63+
let mut reprs = r.borrow_mut();
64+
if let Some(repr) = reprs.get(&id) {
65+
repr.clone()
66+
} else {
67+
let mut frames: [Option<&BacktraceFrame>; 128] = [None; 128];
68+
for (idx, ip) in idxs.as_ref().iter().enumerate() {
69+
if !ip.is_null() {
70+
frames[idx] = Some(&cache[ip]);
71+
}
72+
}
73+
74+
find_loc(loc, &mut frames);
75+
76+
#[allow(static_mut_refs)]
77+
{
78+
if let Some(start) = unsafe { START } {
79+
find_loc_start(start, &mut frames);
80+
}
81+
if let Some(start) = unsafe { START_ALT } {
82+
find_loc_start(start, &mut frames);
83+
}
84+
}
85+
86+
let bt = Bt(&frames[..]);
87+
let mut buf = String::new();
88+
let _ = write!(&mut buf, "\n{bt:?}");
89+
let repr: Arc<str> = Arc::from(buf);
90+
reprs.insert(id, repr.clone());
91+
repr
92+
}
93+
})
94+
});
95+
96+
Self(repr)
97+
}
98+
99+
/// Backtrace repr
100+
pub fn repr(&self) -> &str {
101+
&self.0
102+
}
103+
}
104+
105+
fn find_loc(loc: &Location<'_>, frames: &mut [Option<&BacktraceFrame>]) {
106+
for (idx, frm) in frames.iter_mut().enumerate() {
107+
if let Some(f) = frm {
108+
for sym in f.symbols() {
109+
if let Some(fname) = sym.filename()
110+
&& let Some(lineno) = sym.lineno()
111+
&& fname.ends_with(loc.file())
112+
&& lineno == loc.line()
113+
{
114+
for f in frames.iter_mut().take(idx) {
115+
*f = None;
116+
}
117+
return;
118+
}
119+
}
120+
} else {
121+
break;
122+
}
123+
}
124+
}
125+
126+
fn find_loc_start(loc: (&str, u32), frames: &mut [Option<&BacktraceFrame>]) {
127+
let mut idx = 0;
128+
while idx < frames.len() {
129+
if let Some(frm) = &frames[idx] {
130+
for sym in frm.symbols() {
131+
if let Some(fname) = sym.filename()
132+
&& let Some(lineno) = sym.lineno()
133+
&& fname.ends_with(loc.0)
134+
&& (loc.1 == 0 || lineno == loc.1)
135+
{
136+
for f in frames.iter_mut().skip(idx) {
137+
if f.is_some() {
138+
*f = None;
139+
} else {
140+
return;
141+
}
142+
}
143+
break;
144+
}
145+
}
146+
}
147+
idx += 1;
148+
}
149+
}
150+
151+
struct Bt<'a>(&'a [Option<&'a BacktraceFrame>]);
152+
153+
impl fmt::Debug for Bt<'_> {
154+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
155+
let cwd = std::env::current_dir();
156+
let mut print_path =
157+
move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
158+
let path = path.into_path_buf();
159+
if let Ok(cwd) = &cwd
160+
&& let Ok(suffix) = path.strip_prefix(cwd)
161+
{
162+
return fmt::Display::fmt(&suffix.display(), fmt);
163+
}
164+
fmt::Display::fmt(&path.display(), fmt)
165+
};
166+
167+
let mut f = BacktraceFmt::new(fmt, backtrace::PrintFmt::Short, &mut print_path);
168+
f.add_context()?;
169+
for frm in self.0.iter().flatten() {
170+
f.frame().backtrace_frame(frm)?;
171+
}
172+
f.finish()?;
173+
Ok(())
174+
}
175+
}
176+
177+
impl fmt::Debug for Backtrace {
178+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179+
fmt::Display::fmt(&self.0, f)
180+
}
181+
}
182+
183+
impl fmt::Display for Backtrace {
184+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185+
fmt::Display::fmt(&self.0, f)
186+
}
187+
}

ntex-error/src/chain.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::{error, fmt, panic::Location, sync::Arc};
2+
3+
use crate::{Backtrace, Error, ErrorDiagnostic, ErrorKind, ErrorRepr};
4+
5+
#[derive(Debug, Clone)]
6+
pub struct ErrorChain<K: ErrorKind> {
7+
error: Arc<dyn ErrorDiagnostic<Kind = K>>,
8+
}
9+
10+
impl<K: ErrorKind> ErrorChain<K> {
11+
#[track_caller]
12+
pub fn new<E>(error: E) -> Self
13+
where
14+
E: ErrorDiagnostic + Sized,
15+
K: ErrorKind + From<E::Kind>,
16+
{
17+
Self {
18+
error: Arc::new(ErrorRepr::new(error, None, Location::caller())),
19+
}
20+
}
21+
}
22+
23+
impl<E, K> From<Error<E>> for ErrorChain<K>
24+
where
25+
E: ErrorDiagnostic<Kind = K> + Sized,
26+
K: ErrorKind,
27+
{
28+
fn from(err: Error<E>) -> Self {
29+
Self { error: err.inner }
30+
}
31+
}
32+
33+
impl<K> error::Error for ErrorChain<K>
34+
where
35+
K: ErrorKind,
36+
{
37+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
38+
self.error.source()
39+
}
40+
}
41+
42+
impl<K> fmt::Display for ErrorChain<K>
43+
where
44+
K: ErrorKind,
45+
{
46+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47+
fmt::Display::fmt(&self.error, f)
48+
}
49+
}
50+
51+
impl<K> ErrorDiagnostic for ErrorChain<K>
52+
where
53+
K: ErrorKind,
54+
{
55+
type Kind = K;
56+
57+
fn kind(&self) -> Self::Kind {
58+
self.error.kind()
59+
}
60+
61+
fn service(&self) -> Option<&'static str> {
62+
self.error.service()
63+
}
64+
65+
fn signature(&self) -> &'static str {
66+
self.error.signature()
67+
}
68+
69+
fn backtrace(&self) -> Option<&Backtrace> {
70+
self.error.backtrace()
71+
}
72+
}

ntex-error/src/info.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use std::{fmt, fmt::Write, sync::Arc};
2+
3+
use ntex_bytes::{ByteString, BytesMut};
4+
5+
use crate::{Backtrace, Error, ErrorDiagnostic, ErrorKind, ErrorRepr, ErrorType};
6+
7+
trait ErrorInfo: fmt::Debug + 'static {
8+
fn error_type(&self) -> ErrorType;
9+
10+
fn error_signature(&self) -> ByteString;
11+
12+
fn service(&self) -> Option<&'static str>;
13+
14+
fn signature(&self) -> &'static str;
15+
16+
fn backtrace(&self) -> Option<&Backtrace>;
17+
}
18+
19+
impl<E, K> ErrorInfo for ErrorRepr<E, K>
20+
where
21+
E: ErrorDiagnostic,
22+
K: ErrorKind + From<E::Kind>,
23+
{
24+
fn error_type(&self) -> ErrorType {
25+
self.kind().error_type()
26+
}
27+
28+
fn error_signature(&self) -> ByteString {
29+
let mut buf = BytesMut::new();
30+
let _ = write!(&mut buf, "{}", self.kind());
31+
ByteString::try_from(buf).unwrap()
32+
}
33+
34+
fn service(&self) -> Option<&'static str> {
35+
ErrorDiagnostic::service(self)
36+
}
37+
38+
fn signature(&self) -> &'static str {
39+
ErrorDiagnostic::signature(self)
40+
}
41+
42+
fn backtrace(&self) -> Option<&Backtrace> {
43+
ErrorDiagnostic::backtrace(self)
44+
}
45+
}
46+
47+
#[derive(Debug, Clone)]
48+
pub struct ErrorInformation {
49+
inner: Arc<dyn ErrorInfo>,
50+
}
51+
52+
impl ErrorInformation {
53+
pub fn error_type(&self) -> ErrorType {
54+
self.inner.error_type()
55+
}
56+
57+
pub fn error_signature(&self) -> ByteString {
58+
self.inner.error_signature()
59+
}
60+
61+
pub fn service(&self) -> Option<&'static str> {
62+
self.inner.service()
63+
}
64+
65+
pub fn signature(&self) -> &'static str {
66+
self.inner.signature()
67+
}
68+
69+
pub fn backtrace(&self) -> Option<&Backtrace> {
70+
self.inner.backtrace()
71+
}
72+
}
73+
74+
impl<E> From<Error<E>> for ErrorInformation
75+
where
76+
E: ErrorDiagnostic,
77+
{
78+
fn from(err: Error<E>) -> Self {
79+
Self { inner: err.inner }
80+
}
81+
}

0 commit comments

Comments
 (0)