Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next/swc): setup native next-swc crash reporter #38076

Merged
merged 3 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
685 changes: 682 additions & 3 deletions packages/next-swc/Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/next-swc/crates/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ tracing-subscriber = "0.3.9"
tracing-chrome = "0.5.0"
wasmer = { version = "2.3.0", optional = true, default-features = false }
wasmer-wasi = { version = "2.3.0", optional = true, default-features = false }
sentry = "0.27.0"

[build-dependencies]
napi-build = "1"
serde = "1"
serde_json = "1"
19 changes: 19 additions & 0 deletions packages/next-swc/crates/napi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::{
extern crate napi_build;

fn main() {
// Emit current platform's target-triple into a text file to create static const
// in util.rs
let out_dir = env::var("OUT_DIR").expect("Outdir should exist");
let dest_path = Path::new(&out_dir).join("triple.txt");
let mut f =
Expand All @@ -19,5 +21,22 @@ fn main() {
)
.expect("Failed to write target triple text");

// Emit current package.json's version field into a text file to create static
// const in util.rs This is being used to set correct release version for
// the sentry's crash reporter.
let pkg_file =
File::open(Path::new("../../package.json")).expect("Should able to open package.json");
let json: serde_json::Value = serde_json::from_reader(pkg_file).unwrap();
let pkg_version_dest_path = Path::new(&out_dir).join("package.txt");
let mut package_version_writer = BufWriter::new(
File::create(&pkg_version_dest_path).expect("Failed to create package version text"),
);
write!(
package_version_writer,
"{}",
json["version"].as_str().unwrap()
)
.expect("Failed to write target triple text");

napi_build::setup();
}
3 changes: 3 additions & 0 deletions packages/next-swc/crates/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ fn init(mut exports: JsObject) -> napi::Result<()> {
)?;
exports.create_named_method("teardownTraceSubscriber", util::teardown_trace_subscriber)?;

exports.create_named_method("initCrashReporter", util::init_crash_reporter)?;
exports.create_named_method("teardownCrashReporter", util::teardown_crash_reporter)?;

Ok(())
}

Expand Down
48 changes: 47 additions & 1 deletion packages/next-swc/crates/napi/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ DEALINGS IN THE SOFTWARE.

use anyhow::{anyhow, Context, Error};
use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
use sentry::{types::Dsn, ClientInitGuard, ClientOptions};
use serde::de::DeserializeOwned;
use std::{any::type_name, cell::RefCell, convert::TryFrom, path::PathBuf};
use std::{
any::type_name, borrow::Cow, cell::RefCell, convert::TryFrom, env, path::PathBuf, str::FromStr,
};
use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
use tracing_subscriber::{filter, prelude::*, util::SubscriberInitExt, Layer};

static TARGET_TRIPLE: &str = include_str!(concat!(env!("OUT_DIR"), "/triple.txt"));
static PACKAGE_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/package.txt"));

#[contextless_function]
pub fn get_target_triple(env: Env) -> napi::ContextlessResult<JsString> {
env.create_string(TARGET_TRIPLE).map(Some)
Expand Down Expand Up @@ -144,3 +149,44 @@ pub fn teardown_trace_subscriber(cx: CallContext) -> napi::Result<JsUndefined> {
}
cx.env.get_undefined()
}

/// Initialize crash reporter to collect unexpected native next-swc crashes.
#[js_function(1)]
pub fn init_crash_reporter(cx: CallContext) -> napi::Result<JsExternal> {
// Attempts to follow https://nextjs.org/telemetry's debug behavior.
// However, this is techinically not identical to the behavior of the telemetry
// itself as sentry's debug option does not provides full payuload output.
let debug = env::var("NEXT_TELEMETRY_DEBUG").map_or_else(|_| false, |v| v == "1");
let dsn = if debug {
None
} else {
Dsn::from_str("https://7619e5990e3045cda747e50e6ed087a7@o205439.ingest.sentry.io/6528434")
.ok()
};

let guard = sentry::init(ClientOptions {
release: Some(Cow::Borrowed(PACKAGE_VERSION)),
dsn,
debug,
..Default::default()
});

let guard_cell = RefCell::new(Some(guard));
cx.env.create_external(guard_cell, None)
}

/// Trying to drop crash reporter guard if exists. This is the way to hold
/// guards to not to be dropped immediately after crash reporter is initialized
/// in napi context.
#[js_function(1)]
pub fn teardown_crash_reporter(cx: CallContext) -> napi::Result<JsUndefined> {
let guard_external = cx.get::<JsExternal>(0)?;
let guard_cell = &*cx
.env
.get_value_external::<RefCell<Option<ClientInitGuard>>>(&guard_external)?;

if let Some(guard) = guard_cell.take() {
drop(guard);
}
cx.env.get_undefined()
}
7 changes: 6 additions & 1 deletion packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
import { recursiveCopy } from '../lib/recursive-copy'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { lockfilePatchPromise, teardownTraceSubscriber } from './swc'
import {
lockfilePatchPromise,
teardownTraceSubscriber,
teardownCrashReporter,
} from './swc'
import { injectedClientEntries } from './webpack/plugins/client-entry-plugin'
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'
import { flatReaddir } from '../lib/flat-readdir'
Expand Down Expand Up @@ -2326,6 +2330,7 @@ export default async function build(
// Ensure all traces are flushed before finishing the command
await flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/next/build/output/store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import createStore from 'next/dist/compiled/unistore'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { flushAllTraces } from '../../trace'
import { teardownTraceSubscriber } from '../swc'
import { teardownCrashReporter, teardownTraceSubscriber } from '../swc'
import * as Log from './log'

export type OutputState =
Expand Down Expand Up @@ -92,6 +92,7 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
return
}

Expand Down Expand Up @@ -119,6 +120,7 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
return
}

Expand All @@ -135,4 +137,5 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
})
1 change: 1 addition & 0 deletions packages/next/build/swc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export function parse(src: string, options: any): any
export const lockfilePatchPromise: { cur?: Promise<void> }
export function initCustomTraceSubscriber(traceFileName?: string): void
export function teardownTraceSubscriber(): void
export function teardownCrashReporter(): void
export function loadBindings(): Promise<void>
31 changes: 31 additions & 0 deletions packages/next/build/swc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure'
import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
import { downloadWasmSwc } from '../../lib/download-wasm-swc'
import { version as nextVersion } from 'next/package.json'
import { Telemetry } from '../../telemetry/storage'

const ArchName = arch()
const PlatformName = platform()
Expand All @@ -18,6 +19,7 @@ let wasmBindings
let downloadWasmPromise
let pendingBindings
let swcTraceFlushGuard
let swcCrashReporterFlushGuard
export const lockfilePatchPromise = {}

export async function loadBindings() {
Expand Down Expand Up @@ -215,6 +217,17 @@ function loadNative() {
}

if (bindings) {
// Initialize crash reporter, as earliest as possible from any point of import.
// The first-time import to next-swc is not predicatble in the import tree of next.js, which makes
// we can't rely on explicit manual initialization as similar to trace reporter.
if (!swcCrashReporterFlushGuard) {
// Crash reports in next-swc should be treated in the same way we treat telemetry to opt out.
let telemetry = new Telemetry({ distDir: process.cwd() })
if (telemetry.isEnabled) {
swcCrashReporterFlushGuard = bindings.initCrashReporter?.()
}
}

nativeBindings = {
isWasm: false,
transform(src, options) {
Expand Down Expand Up @@ -278,6 +291,7 @@ function loadNative() {
getTargetTriple: bindings.getTargetTriple,
initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
teardownTraceSubscriber: bindings.teardownTraceSubscriber,
teardownCrashReporter: bindings.teardownCrashReporter,
}
return nativeBindings
}
Expand Down Expand Up @@ -377,3 +391,20 @@ export const teardownTraceSubscriber = (() => {
}
}
})()

export const teardownCrashReporter = (() => {
let flushed = false
return () => {
if (!flushed) {
flushed = true
try {
let bindings = loadNative()
if (swcCrashReporterFlushGuard) {
bindings.teardownCrashReporter(swcCrashReporterFlushGuard)
}
} catch (e) {
// Suppress exceptions, this fn allows to fail to load native bindings
}
}
}
})()