feat(next/swc): setup native next-swc crash reporter with platform supports (#38221)

This is second attempt to https://github.com/vercel/next.js/pull/38076 . Most of changes are identical to previous PR. Main difference is introducing features `native-tls` and `rustls` for the sentry's downstream feature. Few platform targets we build (mostly where we cross compiles) fails to find native openssl for the specified target. For those, we falls back to rustls instead. The only exception is aarch64_windows, neither openssl nor rustls can be compiled straightforwardly, For those platform we bail out and do not init sentry at all. There are nearly 0 users on aarch64_windows anyway. We could try to located target's openssl binary, but the effort required seems not worth enough. 

Also PR changed `server_name` property to not to include real device hostname to avoid possible PII concerns.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
This commit is contained in:
OJ Kwon 2022-07-07 10:37:50 -07:00 committed by GitHub
parent 0299f14a7e
commit 46dde0dd4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 947 additions and 12 deletions

View file

@ -1261,7 +1261,7 @@ jobs:
rustup default "${RUST_TOOLCHAIN}" &&
rustup target add x86_64-unknown-linux-musl &&
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi &&
turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-musl &&
turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-musl --cargo-flags=--no-default-features --features sentry_rustls &&
strip packages/next-swc/native/next-swc.*.node
- host: macos-latest
target: 'aarch64-apple-darwin'
@ -1293,7 +1293,7 @@ jobs:
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
build: |
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target armv7-unknown-linux-gnueabihf
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target armv7-unknown-linux-gnueabihf --cargo-flags=--no-default-features --features sentry_rustls
arm-linux-gnueabihf-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
target: aarch64-linux-android
@ -1303,7 +1303,7 @@ jobs:
export CXX="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++"
export PATH="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-linux-android
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-linux-android --cargo-flags=--no-default-features --features sentry_rustls
/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
target: armv7-linux-androideabi
@ -1313,7 +1313,7 @@ jobs:
export CXX="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++"
export PATH="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" "pnpm@${PNPM_VERSION}"
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target armv7-linux-androideabi
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target armv7-linux-androideabi --cargo-flags=--no-default-features --features sentry_rustls
/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip packages/next-swc/native/next-swc.*.node
- host: ubuntu-latest
target: 'aarch64-unknown-linux-musl'
@ -1324,13 +1324,13 @@ jobs:
rustup toolchain install "${RUST_TOOLCHAIN}" &&
rustup default "${RUST_TOOLCHAIN}" &&
rustup target add aarch64-unknown-linux-musl &&
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-musl &&
turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-musl --cargo-flags=--no-default-features --features sentry_rustls &&
llvm-strip -x packages/next-swc/native/next-swc.*.node
- host: windows-latest
target: 'aarch64-pc-windows-msvc'
build: |
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" "pnpm@${PNPM_VERSION}"
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target aarch64-pc-windows-msvc
turbo run build-native-no-plugin --cache-dir=".turbo" -- --release --target aarch64-pc-windows-msvc --cargo-flags=--no-default-features
if: ${{ needs.build.outputs.isRelease == 'true' }}
needs: build
name: stable - ${{ matrix.settings.target }} - node@16

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ publish = false
crate-type = ["cdylib", "rlib"]
[features]
default = ["sentry_native_tls"]
# Instead of enabling all the plugin-related features by default, make it explicitly specified
# when build (i.e napi --build --features plugin), same for the wasm as well.
# this is due to some of transitive dependencies have features cannot be enabled at the same time
@ -19,6 +20,8 @@ plugin = [
"wasmer-wasi/default",
"next-swc/plugin"
]
sentry_native_tls = ["_sentry_native_tls"]
sentry_rustls = ["_sentry_rustls"]
[dependencies]
anyhow = "1.0"
@ -44,6 +47,20 @@ 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 }
# There are few build targets we can't use native-tls which default features rely on,
# allow to specify alternative (rustls) instead via features.
# Note to opt in rustls default-features should be disabled
# (--no-default-features --features sentry_rustls)
_sentry_native_tls = { package = "sentry", version = "0.27.0", optional = true }
_sentry_rustls = { package = "sentry", version = "0.27.0", default-features = false, features = [
"backtrace",
"contexts",
"panic",
"rustls",
"reqwest"
], optional = true }
[build-dependencies]
napi-build = "1"
serde = "1"
serde_json = "1"

View file

@ -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 =
@ -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();
}

View file

@ -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(())
}

View file

@ -29,11 +29,13 @@ DEALINGS IN THE SOFTWARE.
use anyhow::{anyhow, Context, Error};
use napi::{CallContext, Env, JsBuffer, JsExternal, JsString, JsUndefined, JsUnknown, Status};
use serde::de::DeserializeOwned;
use std::{any::type_name, cell::RefCell, convert::TryFrom, path::PathBuf};
use std::{any::type_name, cell::RefCell, convert::TryFrom, env, path::PathBuf};
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)
@ -144,3 +146,72 @@ 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");
#[cfg(not(all(target_os = "windows", target_arch = "aarch64")))]
let guard = {
#[cfg(feature = "sentry_native_tls")]
use _sentry_native_tls::{init, types::Dsn, ClientOptions};
#[cfg(feature = "sentry_rustls")]
use _sentry_rustls::{init, types::Dsn, ClientOptions};
use std::{borrow::Cow, str::FromStr};
let dsn = if debug {
None
} else {
Dsn::from_str(
"https://7619e5990e3045cda747e50e6ed087a7@o205439.ingest.sentry.io/6528434",
)
.ok()
};
Some(init(ClientOptions {
release: Some(Cow::Borrowed(PACKAGE_VERSION)),
dsn,
debug,
// server_name includes device host name, which _can_ be considered as PII depends on
// the machine name.
server_name: Some(Cow::Borrowed("[REDACTED]")),
..Default::default()
}))
};
// aarch64_msvc neither compiles native-tls nor rustls for sentry transport
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
let guard: Option<usize> = None;
let guard_cell = RefCell::new(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> {
#[cfg(not(all(target_os = "windows", target_arch = "aarch64")))]
{
#[cfg(feature = "sentry_native_tls")]
use _sentry_native_tls::ClientInitGuard;
#[cfg(feature = "sentry_rustls")]
use _sentry_rustls::ClientInitGuard;
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()
}

View file

@ -104,7 +104,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'
@ -2315,6 +2319,7 @@ export default async function build(
// Ensure all traces are flushed before finishing the command
await flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
}
}

View file

@ -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 =
@ -92,6 +92,7 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
return
}
@ -119,6 +120,7 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
return
}
@ -135,4 +137,5 @@ store.subscribe((state) => {
// Ensure traces are flushed after each compile in development mode
flushAllTraces()
teardownTraceSubscriber()
teardownCrashReporter()
})

View file

@ -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>

View file

@ -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()
@ -18,6 +19,7 @@ let wasmBindings
let downloadWasmPromise
let pendingBindings
let swcTraceFlushGuard
let swcCrashReporterFlushGuard
export const lockfilePatchPromise = {}
export async function loadBindings() {
@ -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) {
@ -278,6 +291,7 @@ function loadNative() {
getTargetTriple: bindings.getTargetTriple,
initCustomTraceSubscriber: bindings.initCustomTraceSubscriber,
teardownTraceSubscriber: bindings.teardownTraceSubscriber,
teardownCrashReporter: bindings.teardownCrashReporter,
}
return nativeBindings
}
@ -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
}
}
}
})()