next_swc_napi/
util.rs

1/*
2Copyright (c) 2017 The swc Project Developers
3
4Permission is hereby granted, free of charge, to any
5person obtaining a copy of this software and associated
6documentation files (the "Software"), to deal in the
7Software without restriction, including without
8limitation the rights to use, copy, modify, merge,
9publish, distribute, sublicense, and/or sell copies of
10the Software, and to permit persons to whom the Software
11is furnished to do so, subject to the following
12conditions:
13
14The above copyright notice and this permission notice
15shall be included in all copies or substantial portions
16of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
19ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
20TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
21PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
22SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
25IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26DEALINGS IN THE SOFTWARE.
27*/
28
29use 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        // Next's run-tests unsets CI and sets NEXT_TEST_CI
61        || 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    // hold open this mutex guard to prevent concurrent writes to the file!
72    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            // Throttle panic logging to once per second
76            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            // Truncate the earliest error from log file if it's larger than 512KB
85            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/// An opaque type potentially wrapping a [`dhat::Profiler`] instance. If we
179/// were not compiled with dhat support, this is an empty struct.
180#[cfg(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc"))]
181#[non_exhaustive]
182pub struct DhatProfilerGuard(dhat::Profiler);
183
184/// An opaque type potentially wrapping a [`dhat::Profiler`] instance. If we
185/// were not compiled with dhat support, this is an empty struct.
186///
187/// [`dhat::Profiler`]: https://docs.rs/dhat/latest/dhat/struct.Profiler.html
188#[cfg(not(any(feature = "__internal_dhat-heap", feature = "__internal_dhat-ad-hoc")))]
189#[non_exhaustive]
190pub struct DhatProfilerGuard;
191
192impl DhatProfilerGuard {
193    /// Constructs an instance if we were compiled with dhat support.
194    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/// Initialize tracing subscriber to emit traces. This configures subscribers
220/// for Trace Event Format <https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview>.
221#[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/// Teardown currently running tracing subscriber to flush out remaining traces.
251/// This should be called when parent node.js process exits, otherwise generated
252/// trace may drop traces in the buffer.
253#[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}