Sentry-javascript: Masalah pelacakan untuk @sentry/* SDKs

Dibuat pada 29 Mar 2018  ·  24Komentar  ·  Sumber: getsentry/sentry-javascript

Diskusi SDK Generasi Selanjutnya

Seperti yang mungkin Anda perhatikan, kami telah mulai meluncurkan versi pra-rilis untuk baris JavaScript SDK kami berikutnya. Anda dapat mengidentifikasi mereka dengan ruang nama @sentry/* di NPM. Tujuan dalam jajaran baru ini adalah untuk menyediakan antarmuka yang lebih nyaman dan konsistensi yang ditingkatkan antara berbagai lingkungan JavaScript.

SDK dikembangkan di mono-repositori yang terletak di paket .

Antarmuka yang Diperbarui

import { init, captureMessage } from '@sentry/browser';

init({
  dsn: '__DSN__',
  // ...
});

captureMessage('Hello, world!');

Perpustakaan minimal

Fitur baru dari jajaran SDK ini adalah paket minimal . Ini memungkinkan penulis perpustakaan menambahkan dukungan untuk Sentry SDK tanpa harus menggabungkan seluruh SDK atau bergantung pada platform tertentu. Jika perpustakaan disertakan dan Sentry SDK hadir, itu akan bekerja secara otomatis:

import * as Sentry from '@sentry/minimal';

// Add a breadcrumb for future events
Sentry.addBreadcrumb({
  message: 'My Breadcrumb',
  // ...
});

// Capture exceptions, messages or manual events
Sentry.captureMessage('Hello, world!');
Sentry.captureException(new Error('Good bye'));
Sentry.captureEvent({
  message: 'Manual',
  stacktrace: [
    // ...
  ],
});

Kami berharap dapat melihat penulis perpustakaan mengadopsi fitur ini di masa mendatang untuk memfasilitasi pelacakan kesalahan yang lebih baik di seluruh ekosistem.

Konsep ruang lingkup

Kami memperkenalkan konsep baru yang kami sebut Scope . Scope menyimpan status remah roti, konteks, dan metadata lainnya yang terisolasi. raven-node dan raven-js memiliki fitur serupa, yang secara ambigu disebut "konteks". Selalu ada "default" Scope yang menangani semua hal seperti yang kami lakukan sebelum Anda tidak perlu melakukan apa-apa, tetapi kami juga mendukung mendorong Scope baru jika Anda ingin memiliki informasi konteks yang terisolasi memungkinkan katakan misalnya, setiap permintaan yang masuk dalam aplikasi simpul harus memiliki Scope sendiri.

Untuk menambahkan extra , tags atau user ke acara Anda, Anda harus menelepon:

import * as Sentry from '@sentry/browser';

// Set user information, as well as tags and further extras
Sentry.configureScope(scope => {
  scope.setExtra('battery', 0.7);
  scope.setTag('user_mode', 'admin');
  scope.setUser({ id: '4711' });
  // scope.clear();
});

Sentry.captureMessage("Hello World!"); // This event contains all scope information from the global scope

Jika Anda ingin memiliki informasi yang terisolasi hanya untuk acara tertentu, Anda dapat:

import * as Sentry from '@sentry/browser';

Sentry.getDefaultHub().withScope(() => {
   // We are here in an isolated new Scope, we inherited all the stuff from the parent 
   // but after this functions returns the Scope is popped and removed

  Sentry.configureScope(scope => {
    scope.setExtra('battery', 0.9); // We overwrite battery extra
  });

  Sentry.captureMessage("Hello World!"); // This will contain all scope info from before + battery is overwritten just for this message
});
Discussion

Komentar yang paling membantu

Hai! Saya mempertahankan raven-for-redux , dan saya mulai melihat membangun versi perpustakaan itu untuk mendukung SDK baru. Saya menantikan untuk memiliki API yang dapat bekerja secara konsisten untuk Node (perenderan sisi server) React Native dan Browser. Ketidakcocokan sebelumnya telah menjadi sumber kebingungan bagi pengguna saya.

Saya pikir satu-satunya hal yang saya lihat hilang adalah cara untuk mengonfigurasi ruang lingkup dengan malas. Dengan raven-for-redux kami mengizinkan pengguna melampirkan seluruh status aplikasi mereka sebagai konteks. Karena seluruh status mungkin terlalu besar, atau mungkin perlu diedit dengan cara tertentu, kami mengizinkan pengguna untuk menentukan fungsi transformasi status yang akan kami jalankan sebelum laporan dikirim. Ini terlalu mahal untuk dilakukan pada setiap pembaruan status, jadi sebelumnya kami mengandalkan Raven. setDataCallback untuk menjalankan transformasi dengan malas (hanya ketika tiba saatnya untuk benar-benar mengirimkan pesan/kesalahan).

Jika Anda mendukung pendaftaran panggilan balik seperti ini, sebaiknya juga mendukung pembatalan pendaftaran panggilan balik, karena dalam konteks rendering sisi server kami mungkin ingin (dengan malas) menyetel cakupan ini sekali per permintaan.

Metode API seperti Sentry.configureScope , kecuali lazy, mungkin bisa menyelesaikan pekerjaan:

import createStore from 'redux';

const store = createStore(/* ... */);

const unsubscribe = Sentry.configureScopeOnError(scope => {
  scope.setExtra('state', transformState(store.getState()));
  scope.setUser(deriveUserFromState(store.getState()));
});

Pilihan lain adalah mengekspos beberapa cara untuk mendaftarkan panggilan balik onError generik yang akan dipanggil sebelum data lingkup dikumpulkan. Dengan ini, kita bisa membangun configureScopeOnError kita sendiri :

import createStore from 'redux';

const store = createStore(/* ... */);

const unsubscribe = Sentry.onError(()=> {
  Sentry.configureScope((scope) => {
    scope.setExtra('state', transformState(store.getState()));
    scope.setUser(deriveUserFromState(store.getState()));
  });
});

Ini hanya ide-ide yang langsung terlintas dalam pikiran. Mungkin Anda melihat cara lain untuk melakukannya, atau API yang lebih jelas?

Semua 24 komentar

https://github.com/getsentry/sentry-cordova dan https://github.com/getsentry/sentry-electron keduanya menggunakan SDK baru kami secara internal. Kami sudah mendapat banyak masukan dan API publik dan secara umum seharusnya cukup aman sekarang.

Hai! Saya mempertahankan raven-for-redux , dan saya mulai melihat membangun versi perpustakaan itu untuk mendukung SDK baru. Saya menantikan untuk memiliki API yang dapat bekerja secara konsisten untuk Node (perenderan sisi server) React Native dan Browser. Ketidakcocokan sebelumnya telah menjadi sumber kebingungan bagi pengguna saya.

Saya pikir satu-satunya hal yang saya lihat hilang adalah cara untuk mengonfigurasi ruang lingkup dengan malas. Dengan raven-for-redux kami mengizinkan pengguna melampirkan seluruh status aplikasi mereka sebagai konteks. Karena seluruh status mungkin terlalu besar, atau mungkin perlu diedit dengan cara tertentu, kami mengizinkan pengguna untuk menentukan fungsi transformasi status yang akan kami jalankan sebelum laporan dikirim. Ini terlalu mahal untuk dilakukan pada setiap pembaruan status, jadi sebelumnya kami mengandalkan Raven. setDataCallback untuk menjalankan transformasi dengan malas (hanya ketika tiba saatnya untuk benar-benar mengirimkan pesan/kesalahan).

Jika Anda mendukung pendaftaran panggilan balik seperti ini, sebaiknya juga mendukung pembatalan pendaftaran panggilan balik, karena dalam konteks rendering sisi server kami mungkin ingin (dengan malas) menyetel cakupan ini sekali per permintaan.

Metode API seperti Sentry.configureScope , kecuali lazy, mungkin bisa menyelesaikan pekerjaan:

import createStore from 'redux';

const store = createStore(/* ... */);

const unsubscribe = Sentry.configureScopeOnError(scope => {
  scope.setExtra('state', transformState(store.getState()));
  scope.setUser(deriveUserFromState(store.getState()));
});

Pilihan lain adalah mengekspos beberapa cara untuk mendaftarkan panggilan balik onError generik yang akan dipanggil sebelum data lingkup dikumpulkan. Dengan ini, kita bisa membangun configureScopeOnError kita sendiri :

import createStore from 'redux';

const store = createStore(/* ... */);

const unsubscribe = Sentry.onError(()=> {
  Sentry.configureScope((scope) => {
    scope.setExtra('state', transformState(store.getState()));
    scope.setUser(deriveUserFromState(store.getState()));
  });
});

Ini hanya ide-ide yang langsung terlintas dalam pikiran. Mungkin Anda melihat cara lain untuk melakukannya, atau API yang lebih jelas?

@captbaritone Terima kasih telah meluangkan waktu untuk melihat SDK baru kami.
Saya harap saya memahami pertanyaannya dengan benar jika demikian saya pikir saya akan memiliki solusi yang baik untuk ini.

Untuk SDK baru, kami menemukan sesuatu yang kami sebut integrations .
Integration pada dasarnya dapat berupa apa saja yang terhubung ke aliran klien yang mengirim acara, bahkan memancarkan acara.

Jadi saya pikir apa yang ingin Anda lakukan adalah membangun integrasi.
Lihat integrasi ini misalnya: https://github.com/getsentry/sentry-javascript/blob/master/packages/browser/src/integrations/sdkinformation.ts

Kami memperkenalkan eventProcessor yang pada dasarnya adalah "stackable" dataCallback .
Mereka akan dieksekusi dalam urutan di mana mereka diinstal.

jadi Anda bisa melakukan sesuatu seperti ini:

public install(): void {
    getCurrentHub().addEventProcessor(async (event: SentryEvent) => {
        // mutate event before sending in here
        // return mutated event OR null of you want to discard it
    });
}

Integrasi ini (dalam node) juga mengonfigurasi cakupan:
https://github.com/getsentry/sentry-javascript/blob/master/packages/node/src/integrations/onunhandledrejection.ts

Dan penggunaan integrasi Anda juga akan sangat mudah:

import * as Sentry from '@sentry/browser';
import { ReduxIntegration } from "raven-for-redux";

init({
  dsn: '__DSN__',
  integrations: (integrations) => {
     // integrations include all default integrations we provide
     // you could do this:
     integrations.push(new ReduxIntegration(/* you can pass anything here e.g. store*/));
     return integrations;
  }
});

Saya harap Anda mengerti, integrations sangat kuat dan membuat SDK kami dapat diperluas dengan cara yang logis dan dapat dimengerti.
Tolong beri tahu saya jika ini membantu.

Kita. Adalah. Selesai. :kirim:

Hai, saya tidak yakin apakah ini tempat yang tepat untuk bertanya, tetapi saat ini saya sedang bermain dengan SDK baru dan memiliki beberapa pertanyaan:

1: Bagaimana cara menetapkan tingkat log khusus saat mencatat pengecualian melalui .captureException ? Sebelumnya ini mungkin dan terkadang kami mencatat kesalahan sebagai warning jika itu bukan masalah kritis, ini tampaknya tidak mungkin lagi, mungkin izinkan kami mengatur level log pada scope ?

2: Bagaimana tepatnya _does_ scope bekerja, dokumennya sama sekali tidak jelas tentang ini?

Saya berasumsi ini:

Sentry.configureScope(scope => {
  scope.setUser({ id: 'superuser' });
  Sentry.captureException(err);
});

2.1: Apakah saya perlu secara eksplisit memanggil scope.clear() untuk menghindari konfigurasi ke leak ke peristiwa lain yang direkam?
2.1.1: Jika pengaturan cakupan tetap ada, bagaimana cara menentukan pengaturan cakupan global?
2.2: Apakah configureScope dipanggil secara sinkron?

Selanjutnya, bagaimana kita mengatur tag global sekarang?

Jadi, saya bermain-main dengan ini sebentar, memanggil clear setelah menggunakan captureException tidak melampirkan tag/pengguna/dll APAPUN. apa pun, saya tidak mengerti bagaimana Anda mencatat satu pengecualian saat melampirkan data spesifik pengguna di node SDK baru pada saat ini.

@SimonSchick Terima kasih atas umpan balik Anda, saya juga ingin menyebutkan bahwa kami sedang mengerjakan dokumen saat kami berbicara, direncanakan bahwa kami mengirimkan dokumen baru hari ini yang akan menjernihkan semuanya tetapi saya ingin memberi Anda jawaban atas pertanyaan Anda secara langsung .

1.: Kami menjatuhkan parameter kedua "sewenang-wenang" di captureException karena tidak jelas bagi banyak pengguna apa yang harus diteruskan di sana, level berfungsi, tetapi tidak semua properti dari suatu peristiwa melakukannya, jadi kami memutuskan untuk menjatuhkannya. captureMessage di sisi lain sekarang memiliki parameter kedua yang hanya mengambil level, yang sekarang merupakan kasus penggunaan yang disukai untuk skenario yang Anda jelaskan.
Ada cara lain untuk mengatur level, bahkan untuk panggilan captureException tetapi saya ingin tahu apakah untuk kasus spesifik Anda, captureMessage sudah cukup. (Anda dapat mengaktifkan attachStacktrace Anda juga akan mendapatkan stacktrace dengan captureMessage ).

2.: Seperti yang saya sebutkan, dokumen lingkup sedang dalam pengerjaan, tetapi Anda dapat menganggapnya seperti tumpukan yang menyimpan informasi konteks. Contoh yang Anda berikan hampir benar dan berfungsi seperti yang Anda harapkan.

2.1.: Untuk kasus ini (sementara belum didokumentasikan di sini), kami memperkenalkan cara ke push dan pop a scope. Setiap SDK setelah init membuat cakupan "root", tetapi jika Anda ingin mengirim informasi konteks tertentu dengan suatu peristiwa, Anda harus push cakupan baru seperti ini:

Sentry.getCurrentHub().pushScope();
Sentry.configureScope(scope => {
  scope.setUser({ id: 'superuser' });
});
Sentry.captureException(err);
Sentry.getCurrentHub().pushScope();

Dalam contoh kode ini kita push lingkup baru (yang merupakan tiruan dari lingkup saat ini) ke dalam tumpukan, semua panggilan capture akan memiliki informasi konteks ini diatur dalam configureScope sampai Anda menelepon pop lagi.

2.1.1: Lingkup tinggal di memori dan tidak disimpan ke disk atau sesuatu seperti itu, mendefinisikan konteks global dapat dilakukan di lingkup root.

2.2.: Ya, configureScope sinkron.


Anda dapat mengatur tag global hanya dengan

Sentry.configureScope(scope => {
  scope.setTag(bla, 'blub');
});

Memanggil clear dalam lingkup, mengatur ulang semua yang ada di sana, tags , extra , breadcrumbs fingerprints , clear adalah membantu dalam skenario di mana Anda mendorong lingkup baru, menginginkan keadaan segar dan kosong, memanggil clear dalam lingkup yang didorong baru dan setelah memunculkannya, Anda masih akan memiliki lingkup global yang menyimpan semua barang Anda sebelumnya.

Ini sedikit komentar yang panjang tapi saya harap ini membuat semuanya lebih jelas.

@SimonSchick Kami sedang mendiskusikan apakah kami harus menambahkan sesuatu seperti:

captureException(exception, scope => {
  scope.setTag('bla', 'blub');
  scope.setLevel('warning');
});

akan membuat Anda diposting.

Pembaruan: Sudah ada di master dan rc berikutnya akan berisi itu.
Kami mengekspos withScope dan menambahkan level pada cakupan, sehingga kode akhir terlihat seperti ini:

Sentry.withScope(scope => {
  scope.setTag(bla, 'blub').setLevel('warning');
  Sentry.captureException(new Error('bla'));
});

Contoh ini hanya akan menetapkan tag dan level untuk pengecualian ini.
Jika Anda ingin mengatur sesuatu secara global, Anda masih perlu menggunakan configureScope .

Terima kasih atas balasannya, dapatkah Anda menjelaskan perbedaan antara .withScope vs. .configureScope Saya berasumsi denganScope auto push dan pops?

Selanjutnya, saya mengatasi batasan dengan membuat instance NodeClient dan Scope secara manual dan kemudian mengkloning ruang lingkup untuk setiap acara karena itu tampaknya juga merupakan API publik.

Kodenya kira-kira seperti ini:

    const client = new NodeClient({
      dsn,
      release: dInfo.getCommit(),
    });
    client.install();

    const scope = new Scope();
    scope.setTag('buildId', dInfo.getBuild());
    log.addStream({
      level: cfg.remote.level,
      stream: new SentryWriteStream(client, scope), // as `baseScope`
    });
...
// insode of the event logging setup
    const scope = Scope.clone(this.baseScope);
    if (record.user) {
      scope.setUser({
        email: record.user.email,
        id: record.user.id.toString(),
        username: record.user.name,
      });
    }
    scope.setTag('hostname', record.processInfo.hostname.toString());
    scope.setTag('pid', record.processInfo.pid.toString());
    for (const [key, value] of Object.entries(record.tags)) {
      scope.setTag(key, value.toString());
    }

    for (const [key, value] of Object.entries(record.fields)) {
      scope.setExtra(key, value);
    }
...
client.captureException(err, undefined, this.setupScope(record))

Berharap untuk tidak melakukan itu :)

Sekali lagi, terima kasih atas balasan cepatnya, saya mengerti ini adalah RC jadi saya sangat menghargai kalian bereaksi dengan cepat!

@SimonSchick Benar, withScope mendorong dan muncul untuk Anda, sementara configureScope hanya memberi Anda ruang lingkup saat ini untuk berubah.
Meskipun kami umumnya mendukung pembuatan klien khusus, kami tidak menyarankan API ini karena ada beberapa kelemahan yang menyertainya, misalnya: tidak ada integrasi secara default, kami memandu orang untuk menggunakan init .
Jadi, Anda benar-benar perlu tahu apa yang Anda lakukan.
Selain itu, kami ingin melakukannya bukan sebagai bagian dari kontrak "API publik" kami.
Apa yang Anda lakukan dalam contoh Anda pada dasarnya adalah apa yang kami lakukan dengan withScope .

Meskipun kami biasanya mendukung pembuatan klien khusus, kami tidak menyarankan API ini karena ada beberapa kelemahan yang menyertainya, misalnya: tidak ada integrasi secara default, kami memandu orang untuk menggunakan init.

Ya itu yang saya pikirkan, saya akan menggunakan API baru setelah paket dirilis :)

Hai, yang di sana!

Saat ini saya sedang memigrasi basis kode kami untuk menggunakan SDK baru, dan mencoba mencari tahu apa cara yang baik untuk mereplikasi perilaku parseUser dari klien lawas?

Sementara withScope mungkin berfungsi untuk menangkap kesalahan manual, saya bertanya-tanya bagaimana saya bisa mencapai hal yang sama (melewati data pengguna tambahan yang diuraikan dari permintaan yang berakhir dengan kesalahan) untuk penangan kesalahan global.

Sejauh ini saya akhirnya memiliki beforeSend yang mencoba mengekstrak dan mendekode informasi token dari event.request , dan kemudian (jika berhasil) hanya menambahkannya ke acara sebelum mengembalikannya.
Namun, saya tidak sepenuhnya yakin apakah itu cara terbaik untuk mengimplementasikan ini, maka tanyakan di sini.

Atau haruskah saya membuka masalah terpisah sama sekali?

Terima kasih sebelumnya!

Anda biasanya ingin mengatur pengguna pada cakupan global saat login (aplikasi halaman tunggal) atau mengaturnya pada pemuatan halaman.

@SimonSchick Saya sedang berbicara tentang metode parseUser dari legacy node client , dan bermigrasi ke @sentry/node , lupa menyebutkannya.
Untuk front-end, jelas, apa yang Anda katakan.

Bisakah Anda memberi tahu kami sedikit lebih banyak tentang kasus penggunaan Anda?

Jika Anda memiliki sistem dengan middlewares, seharusnya cukup mudah menggunakan scope.setUser di masing-masing pengendali kesalahan.

@kblcuk Anda memiliki akses ke permintaan itu sendiri di semua middlewares, sehingga Anda dapat mengurai pengguna secara manual dengan cara apa pun yang Anda butuhkan

// express example

function yourCustomUserParser (req, res, next) {
  Sentry.configureScope((scope) => scope.setUser({
    username: req.specialUserField.username,
    id: req.specialUserField.getId()
  }));

  next();
}

// order matters
app.use(Sentry.Handlers.requestHandler());
app.use(yourCustomUserParser);

app.get('/', function mainHandler(req, res) {
  throw new Error('Broke!');
});

app.use(Sentry.Handlers.errorHandler());
app.listen(3000);

@kamilogorek bukankah Sentry.configureScope di dalam middleware sangat rentan terhadap kondisi balapan?

@SimonSchick tidak saat digunakan bersama requestHandler , karena membuat Sentry Hub di domain baru untuk setiap permintaan. Dan kemudian getCurrentHub , yang digunakan secara internal oleh configureScope mendeteksinya: https://github.com/getsentry/sentry-javascript/blob/d9bb595ca6b450332ead51b750c37f63e69860ba/packages/hub/src/hub. ts#L326 -L360

Bagian utama:
https://github.com/getsentry/sentry-javascript/blob/d9bb595ca6b450332ead51b750c37f63e69860ba/packages/hub/src/hub.ts#L354 -L358

Saya mencoba menggunakan configureScope untuk mengatur konteks pengguna selama sesi. Menurut doc, itu harus sesederhana

Sentry.configureScope((scope) => {
  scope.setUser({"email": "[email protected]"});
});

Ini tidak berhasil untuk saya. Saya juga mencoba menggunakan kode yang sedikit dimodifikasi yang diposting oleh @HazAT di atas:

Sentry.getCurrentHub().pushScope();
Sentry.configureScope(scope => {
  scope.setUser({ id: 'superuser' });
});
Sentry.getCurrentHub().pushScope();

Ini juga tidak berhasil untuk saya. Apakah saya perlu mengkonfigurasi Scope setiap kali captureException dipanggil? Saya pikir saya bisa mengaturnya sekali dan itu akan diatur untuk keseluruhan sesi pengguna kecuali jika diubah.

Saya memeriksa modul Sentry itu sendiri dan melihat ada fungsi setUser yang dapat saya panggil secara langsung:

Sentry.setUser({
  email: user.email,
  id: user.id,
  type: user.current_profile.type,
  firstName: user.current_profile.first_name,
  lastName: user.current_profile.last_name,
});

Yang ini berhasil untuk saya dan saya melihat info pengguna terlampir pada masalah di log peristiwa. Bisakah seseorang membantu menjelaskan apa yang mungkin saya lakukan salah ketika mencoba menggunakan konfigurasi yang disarankan?

@brianmock Ini benar-benar aneh, ketiganya harus berfungsi dan mereka melakukannya di sini:
https://codesandbox.io/s/sentrybrowser-simple-capturemessage-r8xfn

Tidak yakin apa yang terjadi. Bisakah Anda mengonfirmasi itu berfungsi dalam contoh yang saya posting?

@HazAT berfungsi dalam contoh yang diposting dan saya menemukan masalah saya, itu salah saya. Ada kondisi balapan antara pengecualian dan pengaturan konteks pengguna sehingga terkadang info pengguna ada di sana dan terkadang tidak dan saya kebetulan melihatnya berfungsi dengan setUser alih-alih configureScope.

Terima kasih atas waktunya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat