1use std::{
30 cell::RefCell,
31 env,
32 fs::OpenOptions,
33 io::{self, BufRead, Write},
34 path::PathBuf,
35 sync::Mutex,
36 time::Instant,
37};
38
39use anyhow::anyhow;
40use napi::bindgen_prelude::{External, Status};
41use once_cell::sync::Lazy;
42use owo_colors::OwoColorize;
43use terminal_hyperlink::Hyperlink;
44use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
45use tracing_subscriber::{Layer, filter, prelude::*, util::SubscriberInitExt};
46use turbopack_core::error::PrettyPrintError;
47
48static LOG_THROTTLE: Mutex<Option<Instant>> = Mutex::new(None);
49static LOG_DIVIDER: &str = "---------------------------";
50static PANIC_LOG: Lazy<PathBuf> = Lazy::new(|| {
51 let mut path = env::temp_dir();
52 path.push(format!("next-panic-{:x}.log", rand::random::<u128>()));
53 path
54});
55
56pub fn log_internal_error_and_inform(internal_error: &anyhow::Error) {
57 if cfg!(debug_assertions)
58 || env::var("SWC_DEBUG") == Ok("1".to_string())
59 || env::var("CI").is_ok_and(|v| !v.is_empty())
60 || env::var("NEXT_TEST_CI").is_ok_and(|v| !v.is_empty())
62 {
63 eprintln!(
64 "{}: An unexpected Turbopack error occurred:\n{}",
65 "FATAL".red().bold(),
66 PrettyPrintError(internal_error)
67 );
68 return;
69 }
70
71 let mut last_error_time = LOG_THROTTLE.lock().unwrap();
73 if let Some(last_error_time) = last_error_time.as_ref() {
74 if last_error_time.elapsed().as_secs() < 1 {
75 return;
77 }
78 }
79 *last_error_time = Some(Instant::now());
80
81 let size = std::fs::metadata(PANIC_LOG.as_path()).map(|m| m.len());
82 if let Ok(size) = size {
83 if size > 512 * 1024 {
84 let new_lines = {
86 let log_read = OpenOptions::new()
87 .read(true)
88 .open(PANIC_LOG.as_path())
89 .unwrap_or_else(|_| panic!("Failed to open {}", PANIC_LOG.to_string_lossy()));
90
91 io::BufReader::new(&log_read)
92 .lines()
93 .skip(1)
94 .skip_while(|line| match line {
95 Ok(line) => !line.starts_with(LOG_DIVIDER),
96 Err(_) => false,
97 })
98 .collect::<Vec<_>>()
99 };
100
101 let mut log_write = OpenOptions::new()
102 .create(true)
103 .truncate(true)
104 .write(true)
105 .open(PANIC_LOG.as_path())
106 .unwrap_or_else(|_| panic!("Failed to open {}", PANIC_LOG.to_string_lossy()));
107
108 for line in new_lines {
109 match line {
110 Ok(line) => {
111 writeln!(log_write, "{line}").unwrap();
112 }
113 Err(_) => {
114 break;
115 }
116 }
117 }
118 }
119 }
120
121 let mut log_file = OpenOptions::new()
122 .create(true)
123 .append(true)
124 .open(PANIC_LOG.as_path())
125 .unwrap_or_else(|_| panic!("Failed to open {}", PANIC_LOG.to_string_lossy()));
126
127 let internal_error_str: String = PrettyPrintError(internal_error).to_string();
128 writeln!(log_file, "{}\n{}", LOG_DIVIDER, &internal_error_str).unwrap();
129
130 let title = format!(
131 "Turbopack Error: {}",
132 internal_error_str.lines().next().unwrap_or("Unknown")
133 );
134 let version_str = format!(
135 "Turbopack version: `{}`\nNext.js version: `{}`",
136 env!("VERGEN_GIT_DESCRIBE"),
137 env!("NEXTJS_VERSION")
138 );
139 let new_discussion_url = if supports_hyperlinks::supports_hyperlinks() {
140 "clicking here.".hyperlink(
141 format!(
142 "https://github.com/vercel/next.js/discussions/new?category=turbopack-error-report&title={}&body={}&labels=Turbopack,Turbopack%20Panic%20Backtrace",
143 &urlencoding::encode(&title),
144 &urlencoding::encode(&format!("{}\n\nError message:\n```\n{}\n```", &version_str, &internal_error_str))
145 )
146 )
147 } else {
148 format!(
149 "clicking here: https://github.com/vercel/next.js/discussions/new?category=turbopack-error-report&title={}&body={}&labels=Turbopack,Turbopack%20Panic%20Backtrace",
150 &urlencoding::encode(&title),
151 &urlencoding::encode(&format!("{}\n\nError message:\n```\n{}\n```", &version_str, &title))
152 )
153 };
154
155 eprintln!(
156 "\n-----\n{}: An unexpected Turbopack error occurred. A panic log has been written to \
157 {}.\n\nTo help make Turbopack better, report this error by {}\n-----\n",
158 "FATAL".red().bold(),
159 PANIC_LOG.to_string_lossy(),
160 &new_discussion_url
161 );
162}
163
164#[napi]
165pub fn get_target_triple() -> &'static str {
166 env!("VERGEN_CARGO_TARGET_TRIPLE")
167}
168
169pub trait MapErr<T>: Into<Result<T, anyhow::Error>> {
170 fn convert_err(self) -> napi::Result<T> {
171 self.into()
172 .map_err(|err| napi::Error::new(Status::GenericFailure, format!("{err:?}")))
173 }
174}
175
176impl<T> MapErr<T> for Result<T, anyhow::Error> {}
177
178#[cfg(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc"))]
181#[non_exhaustive]
182pub struct DhatProfilerGuard(dhat::Profiler);
183
184#[cfg(not(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc")))]
189#[non_exhaustive]
190pub struct DhatProfilerGuard;
191
192impl DhatProfilerGuard {
193 pub fn try_init() -> Option<Self> {
195 #[cfg(feature = "__internal_dhat-heap")]
196 {
197 println!("[dhat-heap]: Initializing heap profiler");
198 Some(Self(dhat::Profiler::new_heap()))
199 }
200 #[cfg(feature = "__internal_dhat-ad-hoc")]
201 {
202 println!("[dhat-ad-hoc]: Initializing ad-hoc profiler");
203 Some(Self(dhat::Profiler::new_ad_hoc()))
204 }
205 #[cfg(not(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc")))]
206 {
207 None
208 }
209 }
210}
211
212impl Drop for DhatProfilerGuard {
213 fn drop(&mut self) {
214 #[cfg(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc"))]
215 println!("[dhat]: Teardown profiler");
216 }
217}
218
219#[napi]
222pub fn init_custom_trace_subscriber(
223 trace_out_file_path: Option<String>,
224) -> napi::Result<External<RefCell<Option<FlushGuard>>>> {
225 let trace_out_file_path = trace_out_file_path.map(PathBuf::from);
226
227 let mut layer = ChromeLayerBuilder::new().include_args(true);
228 if let Some(trace_out_file) = trace_out_file_path {
229 let dir = trace_out_file
230 .parent()
231 .ok_or_else(|| anyhow!("Not able to find path to the trace output"))
232 .convert_err()?;
233 std::fs::create_dir_all(dir)?;
234
235 layer = layer.file(trace_out_file);
236 }
237
238 let (chrome_layer, guard) = layer.build();
239 tracing_subscriber::registry()
240 .with(chrome_layer.with_filter(filter::filter_fn(|metadata| {
241 !metadata.target().contains("cranelift") && !metadata.name().contains("log ")
242 })))
243 .try_init()
244 .expect("Failed to register tracing subscriber");
245
246 let guard_cell = RefCell::new(Some(guard));
247 Ok(External::new(guard_cell))
248}
249
250#[napi]
254pub fn teardown_trace_subscriber(guard_external: External<RefCell<Option<FlushGuard>>>) {
255 let guard_cell = &*guard_external;
256
257 if let Some(guard) = guard_cell.take() {
258 drop(guard);
259 }
260}