1use gethostname::gethostname;
2use serde::Serialize;
3use std::path::{Path, PathBuf};
4
5#[cfg(feature = "origin-backtrace")]
6use backtrace::{Backtrace, BacktraceSymbol};
7
8#[derive(Debug, Clone, Serialize)]
17pub struct Origin {
18 pub function_name: String,
19 pub file: String,
20 pub line_number: u32,
21 pub hostname: String,
22}
23
24impl Default for Origin {
25 fn default() -> Self {
26 Self {
27 function_name: "Unknown".to_string(),
28 file: "Unknown".to_string(),
29 line_number: 0,
30 hostname: gethostname().to_string_lossy().to_string(),
31 }
32 }
33}
34
35impl Origin {
36 pub fn from_callsite(file: &str, line_number: u32, module_path: &str) -> Self {
38 Self::from_callsite_with_options(file, line_number, module_path, false)
39 }
40
41 pub fn from_callsite_with_options(
43 file: &str,
44 line_number: u32,
45 module_path: &str,
46 canonicalize_paths: bool,
47 ) -> Self {
48 let base_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
49 Self::from_callsite_with_base(
50 file,
51 line_number,
52 module_path,
53 canonicalize_paths,
54 &base_dir,
55 )
56 }
57
58 pub fn from_callsite_with_base(
60 file: &str,
61 line_number: u32,
62 module_path: &str,
63 canonicalize_paths: bool,
64 base_dir: &Path,
65 ) -> Self {
66 let file = absolutize(file, canonicalize_paths, base_dir)
67 .to_string_lossy()
68 .to_string();
69
70 Self {
71 function_name: module_path.to_string(),
72 file,
73 line_number,
74 hostname: gethostname().to_string_lossy().to_string(),
75 }
76 }
77
78 #[cfg(feature = "origin-backtrace")]
79 pub fn capture(place_in_stack: Option<usize>) -> Self {
81 let place_in_stack = place_in_stack.unwrap_or(6);
82
83 if let Some(symbol) = get_symbol(place_in_stack) {
84 return Self::from(symbol);
85 }
86
87 Self::default()
88 }
89
90 pub fn with_function_name(mut self, function_name: impl Into<String>) -> Self {
92 self.function_name = function_name.into();
93 self
94 }
95
96 pub fn with_file(mut self, file: impl Into<String>) -> Self {
98 self.file = file.into();
99 self
100 }
101}
102
103#[cfg(feature = "origin-backtrace")]
104impl From<BacktraceSymbol> for Origin {
105 fn from(symbol: BacktraceSymbol) -> Self {
106 let function_name = symbol
107 .name()
108 .map(|v| v.to_string())
109 .unwrap_or_else(|| "Unknown".to_string());
110
111 let file = symbol
112 .filename()
113 .map(|v| v.to_string_lossy().to_string())
114 .unwrap_or_else(|| "Unknown".to_string());
115
116 let line_number = symbol.lineno().unwrap_or(0) as u32;
117
118 Self {
119 function_name,
120 file,
121 line_number,
122 hostname: gethostname().to_string_lossy().to_string(),
123 }
124 }
125}
126
127#[cfg(feature = "origin-backtrace")]
128fn get_symbol(place_in_stack: usize) -> Option<BacktraceSymbol> {
129 let trace = Backtrace::new();
130
131 let mut found = 0;
132 for frame in trace.frames() {
133 for symbol in frame.symbols() {
134 if let Some(file_path) = symbol.filename() {
135 let as_string = file_path.to_string_lossy();
136 if is_user_frame(as_string.as_ref()) {
137 found += 1;
138 if found == place_in_stack {
139 return Some(symbol.to_owned());
140 }
141 }
142 }
143 }
144 }
145
146 None
147}
148
149#[cfg(feature = "origin-backtrace")]
150fn is_user_frame(path: &str) -> bool {
151 !path.contains("/.cargo/registry") && !path.contains("/rustc/")
152}
153
154fn absolutize(file: &str, canonicalize_paths: bool, base_dir: &Path) -> PathBuf {
155 let p = Path::new(file);
156
157 if p.is_absolute() {
158 return p.to_path_buf();
159 }
160
161 let joined = base_dir.join(p);
162
163 if canonicalize_paths {
164 joined.canonicalize().unwrap_or(joined)
165 } else if joined.exists() {
166 joined
167 } else {
168 p.to_path_buf()
169 }
170}