Sentry-javascript: [@sentry/node] Unterstützung für AWS Lambda und andere serverlose Lösungen

Erstellt am 28. Juli 2018  ·  77Kommentare  ·  Quelle: getsentry/sentry-javascript

  • @sentry/node-Version 4.0.0-beta.11
  • Ich verwende gehostetes Sentry

Wie ist das aktuelle Verhalten?

Ich verwende @sentry/node, um Ausnahmen in der AWS-Lambda-Funktion zu erfassen.

    .catch(err => {
      Sentry.captureException(err)
      context.fail()
    })

Es beendet jedoch den Prozess, wenn context.fail() aufgerufen wird und die Ausnahme nicht in Sentry landet.

Ich könnte einen Workaround machen wie:

    .catch(err => {
      Sentry.captureException(err)
      setTimeout(() => context.fail(), 1000)
    })

Was ist das erwartete Verhalten?

Es wäre schön, wenn ich so etwas tun könnte:

    .catch(err => {
      Sentry.captureException(err, () => context.fail())
    })

Oder etwas, das den Rückruf global handhabt.

Hilfreichster Kommentar

@LinusU Wir werden höchstwahrscheinlich ein spezielles serverloses Paket für dieses Szenario erstellen. Wir müssen nur etwas Zeit finden, da es Ende des Jahres ist und es jetzt eng wird. Sie auf dem Laufenden halten!

Alle 77 Kommentare

Dies kann helfen, denke ich, https://blog.sentry.io/2018/06/20/how-droplr-uses-sentry-to-debug-serverless (es verwendet eine alte Raven-Version, die einen Rückruf hatte, aber ich bin meistens auf eine callbackWaitsForEmptyEventLoop -Flagge zeigen.

Es gibt noch keinen offiziellen Weg, da wir immer noch Dinge in der Beta ausprobieren, aber es ist mit diesem Code machbar:

import { init, getDefaultHub } from '@sentry/node';

init({
  dsn: 'https://my-dsn.com/1337'
});

exports.myHandler = async function(event, context) {
  // your code

  await getDefaultHub().getClient().captureException(error, getDefaultHub().getScope());
  context.fail();
}

@kamilogorek Danke für den Hinweis. Ich werde es versuchen und die Erkenntnisse wiedergeben.

@kamilogorek Dein Vorschlag funktioniert. Ich freue mich auf einen offizielleren Weg.

@vietbui
In 4.0.0-rc.1 haben wir auf dem Client eine Funktion namens close eingeführt, die Sie so nennen:

import { getCurrentHub } from '@sentry/node';

getCurrentHub().getClient().close(2000).then(result => {
      if (!result) {
        console.log('We reached the timeout for emptying the request buffer, still exiting now!');
      }
      global.process.exit(1);
})

close wartet, bis alle Anfragen gesendet wurden, es wird immer aufgelöst (Ergebnis = falsches Timeout wurde erreicht), bis das Timeout erreicht ist (in diesem Beispiel 2000 ms).
Dies ist unsere offizielle API.
Während der vorherige Ansatz immer noch funktioniert, funktioniert die Methode close in allen Fällen.

@HazAT Schön. Danke für all die harte Arbeit.

In 4.0.3 nenne ich es in meiner Lambda-Funktion so:

try {
  ...
} catch (err) {
  await getCurrentHub().getClient().captureException(err, getCurrentHub().getScope())
  throw err
}

getDefaultHub() ist nicht mehr verfügbar.

@vietbui es heißt jetzt getCurrentHub , da wir unsere API mit SDKs anderer Sprachen vereinheitlichen mussten.

@kamilogorek Danke für die Klarstellung. Es gibt ein Problem mit dem getCurrentHub -Ansatz, da der von mir eingerichtete Bereich irgendwie nicht in Sentry gelandet ist.

Am Ende habe ich einen anderen Ansatz gewählt, als von @HazAT vorgeschlagen, um Ausnahmen in meinen Lambda-Funktionen zu erfassen:

try {
  ...
} catch (err) {
  Sentry.captureException(err)
  await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))
  throw err
}

Und es funktioniert perfekt.

Ist dies der empfohlene Weg, um zu warten/Sentry zu zwingen, Ereignisse zu senden?

@albinekb ja – https://docs.sentry.io/learn/draining/?platform=browser

Diese Lösung funktioniert bei mir aus irgendeinem Grund nicht. Es funktioniert nur das erste Mal in der Produktion, wenn es eine Kaltstartzeit gibt, und danach nicht mehr. hier ist Beispielcode

'use strict'

const Sentry =  require('@sentry/node')
Sentry.init({
  dsn: 'xxx',
  environment: process.env.STAGE
});

module.exports.createPlaylist = async (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false
  if(!event.body) {
    Sentry.captureException(error)
    await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Missing body parameters'
    }
  }
  return {
    statusCode: 200,
  }
};

@Andriy-Kulak Das steht auch in den Dokumenten:

After shutdown the client cannot be used any more so make sure to only do that right before you shut down the application.

Ich weiß also nicht, wie wir damit in Lambda umgehen können, wo wir nicht wissen, wann die Anwendung beendet wird. Am besten wäre es, den Wachposten pro Anfrage zu entleeren, wie wir es mit der alten API könnten?

@HazAT könnten wir das bitte wieder öffnen? Ich denke, es ist wichtig, eine Möglichkeit zu haben, damit auf Lambda zu arbeiten, das zu einem immer häufigeren Ziel für die Bereitstellung wird.

Dies hindert mich derzeit daran, auf die neueste Version zu aktualisieren ...

Ich persönlich würde es vorziehen, eine Zusage/Rückruf zu erhalten, wenn ich einen Fehler melde. Eine Möglichkeit, die Warteschlange zu leeren, ohne sie danach tatsächlich zu schließen, wäre das Nächstbeste ...

Aus welchem ​​Grund wurde der Rückruf von captureException entfernt?

@albinekb es funktioniert überhaupt nicht, wenn ich die folgende Zeile entferne

await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))

@ LinusU Was ist die Lösung und die Sentry- oder Raven-Lösung verwenden Sie?

Für mich funktioniert im Grunde das Folgende mit sentry/node @4.3.0 , aber ich muss Lambda manuell einige Zeit warten lassen (in diesem Fall habe ich 2 Sekunden eingegeben), damit Sentry das tut, was es tun muss. Ich bin mir nicht sicher, warum es dort sein muss, weil wir darauf warten, dass der Wachposten die captureException -Anfrage beendet. Wenn ich die Wartezeit danach nicht habe, dann scheint sentry den Fehler nicht zu senden.

'use strict'

const Sentry =  require('@sentry/node')
Sentry.init({
  dsn: 'xxx',
  environment: process.env.STAGE
});

module.exports.createPlaylist = async (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false
  if(!event.body) {
    const error = new Error('Missing body parameters in createPlaylist')
    await Sentry.captureException(error)
    await new Promise(resolve => {setTimeout(resolve, 2000)})
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Missing body parameters'
    }
  }
  return {
    statusCode: 200,
  }
};

Wir werden auch auf Lambda darauf verbrannt. Wir haben mit den neuen Libs angefangen und sind total fertig, wenn wir überlegen, zu Raven zurückzukehren. Wir schreiben gerade Tests, um zu versuchen, den Hub zu schließen und dann neu zu initialisieren, was eine praktikable Problemumgehung wäre, wenn er Wasser hält. Aber immer noch hacky / wahrscheinlich Probleme unter Last verursachen.

Persönlich würde ich eine Art flush() bevorzugen, die ein Versprechen zurückgibt – es ist schwer, einen Nachteil zu finden. Glaubst du, es würde jemals passieren?

Was ist die Lösung und Sentry- oder Raven-Lösung verwenden Sie?

Ich verwende den folgenden Express-Error-Handler:

app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  let status = (err.status || err.statusCode || 500) as number

  if (process.env.NODE_ENV === 'test') {
    return next(err)
  }

  if (status < 400 || status >= 500) {
    Raven.captureException(err, () => next(err))
  } else {
    next(err)
  }
})

Ich verwende dann Scandium , um die Express-App für Lambda bereitzustellen

edit: das ist mit Raven "raven": "^2.6.3",

Die Traum-API wäre ungefähr so ​​😍

Sentry.captureException(err: Error): Promise<void>

@LinusU https://github.com/getsentry/sentry-javascript/blob/master/packages/core/src/baseclient.ts#L145 -L152 🙂

Sie müssen die Client-Instanz jedoch direkt verwenden, um sie zu erhalten. Der Grund dafür ist, dass entschieden wurde, dass das Hauptszenario ein „Fire and Forget“-Verhalten ist, also keine asynchrone Methode. Intern haben wir jedoch eine asynchrone API, die wir selbst verwenden.

Scheint, dass das, was ich eigentlich will, eher so etwas ist:

const backend = client.getBackend()
const event = await backend.eventFromException(error)
await client.processEvent(event, finalEvent => backend.sendEvent(finalEvent))

Um all das Anstehen und Puffern zu überspringen ...

Ich verstehe, dass das Design auf "Fire and Forgot" zugeschnitten ist und auf einen lang laufenden Server läuft, und es ist wahrscheinlich ziemlich gut darin, da es viel Pufferung usw. macht. Das Problem ist, dass dies genau das Gegenteil ist die Sie für Lambda, App Engine und andere "serverlose" Architekturen benötigen, die immer häufiger verwendet werden.

Wäre es möglich, eine spezielle Methode zu haben, die das Ereignis so schnell wie möglich sendet und ein Promise zurückgibt, das wir await können? Das wäre perfekt für die Serverless-Szenarien!

class Sentry {
  // ...

  async unbufferedCaptureException(err: Error): Promise<void> {
    const backend = this.client.getBackend()
    const event = await backend.eventFromException(error)
    await this.client.processEvent(event, finalEvent => backend.sendEvent(finalEvent))
  }

  // ...
}

@LinusU Wir werden höchstwahrscheinlich ein spezielles serverloses Paket für dieses Szenario erstellen. Wir müssen nur etwas Zeit finden, da es Ende des Jahres ist und es jetzt eng wird. Sie auf dem Laufenden halten!

Wir werden höchstwahrscheinlich ein spezielles serverloses Paket für dieses Szenario erstellen

Das wäre fantastisch! 😍

@mtford90

Wann genau würde ich diese bessere Lösung verwenden? Soweit ich weiß, ist es nicht möglich zu wissen, wann das Lambda heruntergefahren wird - außerdem scheint es albern, eine beliebige Zeit auf das Herunterfahren zu warten, damit Sentry seine Sache erledigen kann - insbesondere bei teuren Lambda-Funktionen mit hohem Speicher / CPU.

(Apropos Ablassen)

Es soll als letztes verwendet werden, bevor der Serverprozess geschlossen wird. Timeout in der Methode drain ist die maximale Zeit, die wir warten, bevor wir den Prozess beenden, was nicht bedeutet, dass wir diese Zeit immer aufbrauchen. Wenn der Server vollständig reaktionsfähig ist, sendet er sofort alle verbleibenden Ereignisse.

Es gibt keine Möglichkeit, dies per se zu wissen, aber es gibt eine Möglichkeit, dem Lambda mit dem Callback-Argument des Handlers mitzuteilen, wann es heruntergefahren werden soll.

Auch @LinusU , ich habe Ihren vorherigen Kommentar noch einmal gelesen, insbesondere diesen Teil:

Wäre es möglich, eine spezielle Methode zu haben, die das Ereignis so schnell wie möglich sendet und ein Versprechen zurückgibt, auf das wir warten können? Das wäre perfekt für die Serverless-Szenarien!

So haben wir unseren Puffer implementiert. Jeder captureX -Aufruf auf dem Client fügt es dem Puffer hinzu, das ist richtig, aber es wird in keiner Weise in die Warteschlange gestellt, es wird sofort ausgeführt und dieses Muster wird nur verwendet, damit wir die Information erhalten, ob alles war erfolgreich an Sentry durchgeschickt.

https://github.com/getsentry/sentry-javascript/blob/0f0dc37a4276aa2b832da451307bc4cd5413b34d/packages/core/src/requestbuffer.ts#L12 -L18

Das bedeutet, wenn Sie so etwas in AWS Lambda tun (vorausgesetzt, Sie möchten den Standard-Client verwenden, was der einfachste Fall ist):

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

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = (event, context, callback) => {
    try {
      // do something
    catch (err) {
      Sentry.getCurrentHub()
        .getClient()
        .captureException(err)
        .then((status) => {
          // request status
          callback(null, 'Hello from Lambda');
        })
    }
};

Sie können sicher sein, dass es sofort gesendet wurde und es keinen Zeit-/Verarbeitungsaufwand gab.

@kamilogorek
Bedeutet dies, dass so etwas in einem async/await-Handler funktionieren sollte (wo Sie den Rückruf nicht verwenden)?

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

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
    try {
      // do something

      return 'Hello from Lambda';
    catch (err) {
      await Sentry.getCurrentHub().getClient().captureException(err);
      return 'Hello from Lambda with error';
    }
};

@jviolas total! :)

Scheint, als würden die folgenden Änderungen dann für mich funktionieren ☺️

-import Raven = require('raven')
+import * as Sentry from '@sentry/node'

 // ...

-Raven.config(config.SENTRY_DSN)
+Sentry.init({ dsn: config.SENTRY_DSN })

 // ...

 app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
   let status = (err.status || err.statusCode || 500) as number

   if (process.env.NODE_ENV === 'test') {
     return next(err)
   }

   if (status < 400 || status >= 500) {
-    Raven.captureException(err, () => next(err))
+    Sentry.getCurrentHub().getClient().captureException(err).then(() => next(err))
   } else {
     next(err)
   }
 })

Um ehrlich zu sein, ist jede Zeile ein bisschen hässlicher geworden 😆 aber ich denke, dass es unter der Haube besser ist ...

@kamilogorek Ich konnte getCurrentHub() in den Dokumenten auf Ihrer Website nicht finden, wird diese API garantiert nicht ohne einen größeren Semver-Bump kaputt gehen? ❤️

@kamilogorek Ich konnte getCurrentHub() in den Dokumenten auf Ihrer Website nicht finden. Wird diese API garantiert nicht ohne einen größeren Semver-Bump kaputt gehen? ❤️

Ja, es ist garantiert. Es ist der Teil des @sentry/hub -Pakets, das hier beschrieben wird – https://docs.sentry.io/enriching-error-data/scopes/?platform=browser

Wir diskutieren hier in diesem Thread eine Art "fortgeschrittene Verwendung" und sind noch nicht an dem Punkt angelangt, sie zu dokumentieren. Das machen wir irgendwann :)

Was uns hier fehlt, ist eindeutig eine Dokumentation und bewährte Verfahren für diese Art von fortgeschrittenen Anwendungsfällen. Es wird wirklich gut, wenn es dokumentiert wird, oder sogar ein Blog-Beitrag kann ein guter Anfang sein.
Ansonsten ist das neue SDK wirklich einfach zu bedienen und die Vereinheitlichung ist wirklich nett.

@kamilogorek
Bedeutet dies, dass so etwas in einem async/await-Handler funktionieren sollte (wo Sie den Rückruf nicht verwenden)?

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

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
    try {
      // do something

      return 'Hello from Lambda';
    catch (err) {
      await Sentry.getCurrentHub().getClient().captureException(err);
      return 'Hello from Lambda with error';
    }
};

Etwas wie oben vorgeschlagen zu tun, funktioniert, außer dass ich keinen zusätzlichen Kontext hinzufügen kann. Wenn ich zum Beispiel mache:

Sentry.configureScope(scope => {
   scope.setExtra('someExtraInformation', information);
});
await Sentry.getCurrentHub().getClient().captureException(err);

Ich werde 'someExtraInformation' in Sentry nicht sehen.

Jemand hat oben in diesem Thread eine alternative Methode vorgeschlagen, und das funktioniert, scheint aber hacky zu sein (erzwingt eine Zeitüberschreitung).

Sentry.configureScope(scope => {
  scope.setExtra('someExtraInformation', information);
});
Sentry.captureException(error);
await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve));

@kamilogorek @jviolas

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

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
   try {
     // do something

     return 'Hello from Lambda';
   catch (err) {
     await Sentry.getCurrentHub().getClient().captureException(err);
     return 'Hello from Lambda with error';
   }
};

Kann dies auch auf _nicht erfasste Ausnahmen_ angewendet werden? Es scheint, dass die Änderung der Sentry.Integrations.OnUncaughtException -Integration der offizielle Weg ist, dies zu tun, aber die Dokumentation ist im Moment ziemlich schlecht.

+1 dafür. Wenigstens etwas offiziell Dokumentiertes wäre gut. Serverless wächst ab 2019 schnell, ich möchte wirklich die offizielle Unterstützung von Sentry dafür sehen. Eine der Ideen, die ich hier gelesen habe und die mir wirklich gefallen hat, war, etwas wie Sentry.flush() zu haben, um alle Ereignisse zu senden, die in der Warteschlange stehen.

@rdsedmundo Können Sie erläutern, warum dieser Ansatz für Sie nicht funktioniert?

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

Sentry.getCurrentHub().getClient().close(2000).then(result => {
      if (!result) {
        console.log('We reached the timeout for emptying the request buffer, still exiting now!');
      }
      global.process.exit(1);
})

Dies ist unser offizieller Ansatz und im Grunde Sentry.flush() .
Referenz: https://docs.sentry.io/error-reporting/configuration/draining/?platform=javascript

@HazAT Das Problem damit tritt auf, wenn Sie an die Wiederverwendung von AWS Lambda-Containern denken. Was in TL;DR-Begriffen bedeutet, dass ein Prozess, der gerade eine Anfrage bedient hat, eine neue Marke bedienen kann, wenn er in einem kurzen Zeitfenster erfolgt. Wenn ich die Verbindung mit diesem von Ihnen angegebenen Snippet schließe und der Container wiederverwendet wird, muss ich einen neuen Hub für die neue Anfrage erstellen. Ich kann leicht sehen, dass dies schwierig wird. Deshalb wäre ein einfaches await Sentry.flush() eine bessere Lösung:

import Sentry from './sentry'; // this calls Sentry.init under the hood

export const handler = async (event, context) => {
  try {
    ...
  } catch (error) {
    Sentry.captureException(error);
    await Sentry.flush(); // could even be called on the finally block

    return formatError(error);
  }
}

@rdsedmundo Ich bin mir nicht sicher, ob ich vielleicht etwas falsch verstehe, aber wenn du es tust

import Sentry from './sentry'; // this calls Sentry.init under the hood

export const handler = async (event, context) => {
  try {
    ...
  } catch (error) {
    Sentry.captureException(error);
    await Sentry.getCurrentHub().getClient().close(2000);

    return formatError(error);
  }
}

Es ist genau wie await Sentry.flush , nur dass Sie das Timeout definieren.

Das Promise löst sich nach 2000ms sicher mit false , wenn noch Sachen in der Queue waren.
Andernfalls wird close mit true aufgelöst, wenn die Warteschlange geleert wurde, bevor das Zeitlimit erreicht wurde.

Oder wird der Container wiederverwendet, bevor alle Versprechungen eingelöst sind? (das kann ich mir nicht vorstellen)

@HazAT ist nicht das Problem, dass close(...) verhindert, dass der Client erneut verwendet wird? Lambda verwendet denselben Node-Prozess wieder, sodass die Aufrufe ungefähr so ​​​​aussehen würden, was meiner Meinung nach nach dem ersten Aufruf von close nicht mehr funktioniert.

  • Sentry.init()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • ...

Nein, close entsorgt den Client nicht, es ist nur dazu da, die Transportwarteschlange zu entleeren.
Ich stimme zu, dass der Name close in diesem Zusammenhang irreführend sein kann, aber zumindest in JS/Node macht close nichts mit dem Client und es ist vollkommen in Ordnung, ihn danach immer noch zu verwenden.

Bearbeiten: Wenn das tatsächlich das "Problem" war, werde ich die Dokumentation aktualisieren, um dies klarzustellen.

Cool. Aber die Dokumentation ist dann falsch:

After shutdown the client cannot be used any more so make sure to only do that right before you shut down the application.

OK, wir haben diese Angelegenheit gerade intern im Team besprochen.
Ihr hattet Recht und obwohl sich JavaScript derzeit nicht so verhält, wie wir es dokumentiert haben 🙈, werden wir eine flush -Funktion einführen, die genau das tut, was ihr erwartet.

Im Moment können Sie also close ohne Probleme verwenden (nicht sicher, ob wir es ändern werden, um den Client in Zukunft zu entsorgen/deaktivieren).
Aber es wird eine flush -Funktion geben, die da ist, um die Warteschlange _nur_ flush zu füllen.

Ich werde dieses Problem aktualisieren, sobald die Funktion gelandet ist.

Da ich mich in all diesen Kommentaren ein wenig verloren habe, sollte der Express-Fehlerhandler (der den aus diesem Repo nachahmt) so aussehen?

function getStatusCodeFromResponse(error) {
    const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
    return statusCode ? parseInt(statusCode, 10) : 500;
}

app.use(async (err, req, res, next) => {
    const status = getStatusCodeFromResponse(err);

    if (status >= 500) {
        Sentry.captureException(err)

        await Sentry.getCurrentHub().getClient().close(2000)
    }

    next(err)
})

Es sieht so aus, als ob es funktioniert und keine zusätzlichen Daten wie im Code von @rreynier verliert.

Ich persönlich fühle das

await Sentry.getCurrentHub().getClient().captureException(err)

ist sauberer als:

Sentry.captureException(err)
await Sentry.getCurrentHub().getClient().close(2000)

close liest sich wirklich so, als würde es den Client schließen ...

Vollständiges Beispiel:

import * as Sentry from '@sentry/node'

// ...

Sentry.init({ dsn: config.SENTRY_DSN })

// ...

app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  let status = (err.status || err.statusCode || 500) as number

  if (process.env.NODE_ENV === 'test') {
    return next(err)
  }

  if (status < 400 || status >= 500) {
    Sentry.getCurrentHub().getClient().captureException(err).then(() => next(err))
  } else {
    next(err)
  }
})

@LinusU Ich habe das versucht und aus irgendeinem Grund werden keine zusätzlichen Daten zusammen mit dem Stack-Trace gesendet. Es sendet im Grunde nur Stack-Trace. Keine Informationen über Benutzer, Betriebssystem oder irgendetwas.

Aha, das ist gar nicht gut 😞

Während wir auf flush warten, können Sie als zuverlässigere Problemumgehung als die beiden oben genannten Optionen einen Bericht erstellen und auf das Ergebnis warten, _und_ den Bereich einschließen, indem Sie das folgende Snippet verwenden:

const scope = Sentry.getCurrentHub().getScope();
await Sentry.getCurrentHub().getClient().captureException(error, scope);

Ich verwende dies und es scheint zuverlässig für mich zu funktionieren, mit gemeldeten Fehlern, die alles enthalten, was ich erwarten würde.

Ich verwende das alles eigentlich mit Netlify Functions, aber die Theorie ist die gleiche wie bei Lambda etc. Ich habe einen Beitrag mit allen Details darüber geschrieben, wie man das zum Laufen bringt, falls es jemanden interessiert: https://httptoolkit. tech/blog/netlify-function-error-reporting-with-sentry/

Ich verwende diesen Helfer derzeit in allen meinen Lambdas.

@pimterry Ist das nicht im Grunde die gleiche Lösung wie von @LinusU vorgeschlagen? Ich habe es versucht und es sendet auch keine zusätzlichen Daten.

Dieser Ansatz hat für mich bisher @ondroan funktioniert

@ondrowan es ist dasselbe, aber manuelles Erfassen und Einschließen des aktuellen Bereichs. Das sollte ausreichen, um Ihnen funktionierende Ausnahmen zu verschaffen, denke ich. Bei der vorherigen Version erhielt ich unbeschriftete Ereignisse, und jetzt mit dieser Änderung kommen meine Ausnahmen mit all den zusätzlichen normalen Details durch.

@vietbui @albinekb @Andriy-Kulak @LinusU @dwelch2344 @jviolas @rreynier @guillaumekh @rdsedmundo @ondrowan @pimterry @zeusdeux Ich bin mir nicht sicher, wer noch an diesem Anwendungsfall interessiert ist, also entschuldigen Sie, wenn ich Sie nicht anrufen sollte.

Ab 4.6.0 gibt es keinen client/hub Tanz mehr. Sie können einfach eine unserer captureX -Methoden aufrufen und dann mit Sentry.flush() auf die Antwort warten, sobald alles an den Server gesendet wurde. Alle Bereichs-/zusätzlichen Daten sollten ohne Entwicklerinteraktion beibehalten werden.

Hier ist ein Beispiel mit erfolgreichen/zeitüberschreitenden Anfragen.

image

Ich hoffe es hilft! :)

Schön!

Gibt es noch Pläne, ein minimales Paket nur zum Erfassen von Ausnahmen von Lambda und anderen serverlosen Lösungen zu erstellen? Ich denke, das wäre noch eine richtig schöne Ergänzung ❤️

@LinusU hoffentlich ja, aber wir werden gerade mit SDKs für andere Sprachen überschwemmt 😅

Vielen Dank an alle für alle möglichen Lösungen, tl;dr für alle, die hierher kommen

Verwenden Sie: await Sentry.flush() , um alle ausstehenden Anfragen zu senden, dies wurde in 4.6.x eingeführt.

Wenn Sie dies schließen, können Sie gerne ein neues Thema eröffnen, falls etwas fehlt (aber dieser Thread ist schon super lang).

Prost 👍 🎉

@kamilogorek Hey! Eine kurze Info, ich verwende Sentry.flush in meiner App anstelle der alten Problemumgehung und keiner der Fehler wird gemeldet. Ich kehre derzeit von der aktualisierten Methode flush zur alten Problemumgehung zurück.

@zeusdeux gibt es eine Möglichkeit, einige Debug-Informationen/Repro-Fälle dafür bereitzustellen?
Sie überschreiben die Methode captureException , die das Ereignis zum Puffer hinzufügt, und dann sollten Sie await auf den Rückgabewert flush setzen. Haben Sie versucht, es auf die "normale Weise" zu verwenden?

@kamilogorek Ich wünschte, ich hätte Debug-Informationen, aber es gibt nichts in den Protokollen. Ich habe immer await auf dem überschriebenen captureException . Meinen Sie mit dem normalen Weg, ohne captureException zu überschreiben?

@zeusdeux genau, rufen Sie einfach unser natives Sentry.captureException(error) ohne Überschreibungen auf.

Ihr Helfer wird also sein:

import * as Sentry from '@sentry/node'

export function init({ host, method, lambda, deployment }) {
  const environment = host === process.env.PRODUCTION_URL ? 'production' : host

  Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment,
    beforeSend(event, hint) {
      if (hint && hint.originalException) {
        // eslint-disable-next-line
        console.log('Error:', hint.originalException);
      }
      return event;
    }
  })

  Sentry.configureScope(scope => {
    scope.setTag('deployment', deployment)
    scope.setTag('lambda', lambda)
    scope.setTag('method', method)
  })
}

und im Code nennen Sie es:

import * as Sentry from '@sentry/node'

try {
  // ...
} catch (err) {
  Sentry.captureException(err);
  await Sentry.flush(2000);
  return respondWithError('Something went wrong', 500);
}

@kamilogorek Ich werde es versuchen und berichten. Danke auch für den Tipp zu beforeSend ^_^

await Sentry.flush(2000);

funktioniert bei mir auch ~nicht~.

@tanduong können Sie eine Repro-Hülle bereitstellen? Nur zu sagen, dass es nicht geht, ist nicht allzu hilfreich 😅

@kamilogorek eigentlich habe ich das gerade herausgefunden

await Sentry.getCurrentHub().getClient().close(2000)

funktioniert bei mir auch nicht, weil meine Lambda-Funktion an VPC angehängt ist.

Ich bestätige das

await Sentry.flush(2000);

funktioniert tatsächlich.

Übrigens, wie würden Sie mit Lambda in VPC umgehen? An ein NAT-Gateway anschließen? Ich will nur Sentry, aber nicht das öffentliche Internet.

@tanduong Sentry befindet sich im öffentlichen Internet, also müssen Sie ein NAT-Gateway haben, wenn Ihr Lambda in Ihrer VPC ausgeführt wird. Andernfalls müssten Sie die gehostete Sentry-Option erkunden.

Was macht der flush(2000) eigentlich? Ich hatte diesen Code meistens gut funktioniert, aber jetzt habe ich ein paar captureMessage Aufrufe, die gleichzeitig stattfinden, es ist jedes Mal eine Zeitüberschreitung!

Leeren der internen Nachrichtenwarteschlange über die Leitung

Ok, das macht absolut Sinn. Ich denke, mein Problem ist, dass dieses Versprechen nie zurückkehrt, wenn es nichts anderes zu spülen gibt? Immer wenn ich mein umschlossenes captureException fn gleichzeitig ausführe, überschreitet es meinen Handler.

export const captureMessage = async (
  message: string,
  extras?: any,
): Promise<boolean> =>
  new Promise((resolve) => {
    Sentry.withScope(async (scope) => {
      if (typeof extras !== 'undefined') {
        scope.setExtras(extras)
      }
      Sentry.captureMessage(message)
      await Sentry.flush(2000)
      resolve(true)
    })
  })

await Sentry.flush() wird nach dem ersten captureMessage-Aufruf nicht wirklich beendet.

Ich habe, glaube ich, ein ähnliches Problem wie @enapupe. Ruft man parallel await client.flush(2000); auf, wird nur das erste Promise aufgelöst. Dies kann in AWS-Lambda-Kontexten passieren, in denen der Client zwischen mehreren gleichzeitigen Aufrufen an den Handler wiederverwendet wird.

Ich verwende Code wie diesen:

 let client = Sentry.getCurrentHub().getClient();
  if (client) {
    // flush the sentry client if it has any events to send
    log('begin flushing sentry client');
    try {
      await client.flush(2000);
    } catch (err) {
      console.error('sentry client flush error:', err);
    }
    log('end flushing sentry client');
  }

Aber wenn ich schnell hintereinander zwei Aufrufe an meine Lambda-Funktion mache, bekomme ich:

  app begin flushing sentry client +2ms
  app begin flushing sentry client +0ms
  app end flushing sentry client +2ms

Sie können sehen, dass das zweite Versprechen nie gelöst wird.

@esetnik Ich habe dazu ein Problem eingereicht: https://github.com/getsentry/sentry-javascript/issues/2131
Meine aktuelle Problemumgehung ist eine Wrapper-Flush-Fn, die immer auflöst (basierend auf einem Timeout):

const resolveAfter = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms))

const flush = (timeout: number) =>
  Promise.race([resolveAfter(timeout), Sentry.flush(timeout)])

@enapupe Ich habe einen Hinweis zu Ihrer Problemumgehung in # 2131 hinzugefügt. Ich glaube, es wird eine Leistungsregression bei gleichzeitigem Flush verursachen.

Falls jemand irgendwelche Probleme hat.
Das funktioniert wunderbar

@SarasArya @HazAT
Zunächst einmal ... Danke, dass Sie Ihre Lösung geteilt haben! :)
Es gibt einen Rückruf der configureScope-Methode, der meiner Meinung nach vor der captureException aufgerufen werden soll, aber nicht im selben "Thread" ausgeführt wird.
Könnte dies nicht zum Auftreten von Race Conditions führen?

@cibergarri Ich glaube nicht, sieht für mich synchron aus, falls Sie dort eine asynchrone Methode haben, dann gäbe es Rennbedingungen.
Bedenken Sie, dass es wie .map von Arrays ist, dass hier dasselbe passiert. Falls Sie Probleme haben, sich darum zu kümmern. Ich hoffe das hilft.

Ja, es ist völlig in Ordnung, das zu tun

Update: Sentry unterstützt jetzt die automatische Fehlererfassung für Node/Lambda-Umgebungen: https://docs.sentry.io/platforms/node/guides/aws-lambda/

Ich verwende @sentry/serverless wie folgt:

const Sentry = require("@sentry/serverless");
Sentry.AWSLambda.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  environment: appEnv
});

exports.main = Sentry.AWSLambda.wrapHandler(async (event, context) => {
     try{
           //my code
     }catch(error){
          Sentry.captureException(error);
          await Sentry.flush(3000);
     }

});

Bei Lambda geht das nicht.
In meiner Testumgebung hat es funktioniert, aber in Prod, wo es viele gleichzeitige Ausführungen gibt und die Container wiederverwendet werden, werden nur etwa 10 % der Gesamtmenge protokolliert.

Irgendein Rat?

@armando25723

Bitte sagen Sie, wie hat Ihre Maßnahme, dass es die Ausnahmeereignisse verliert? Haben Sie ein Codebeispiel, wie eine solche verlorene Ausnahme ausgelöst wurde? Brauchen Sie mehr Kontext.

const Sentry = require("@sentry/serverless"); // "version": "5.27.3"
Sentry.AWSLambda.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  environment: appEnv
});
exports.main = Sentry.AWSLambda.wrapHandler(async (event, context) => {
     try{
           throw new Error('Test Error');
     }catch(error){
          Sentry.captureException(error);
          await Sentry.flush(3000);
     }
});

Was ist los?
Wenn die Funktion mehrmals mit kurzen Intervallen zwischen den Aufrufen aufgerufen wird, wird das Ereignis nur einmal protokolliert.
Wenn das Zeitintervall zwischen den Aufrufen größer ist, werden alle Ereignisse protokolliert.

Ich gehe davon aus, dass das Problem darin besteht, dass der Aufruf über einen wiederverwendeten Container erfolgt.

Ich habe es auch versucht
await Sentry.captureException(error);
und:
await Sentry.flush();
und ohne Spülung
gleiches Ergebnis

@marshall-lee was empfehlt ihr? Sollte ich ein Problem erstellen, stecke ich hier fest.

@ armando25723 Sieht so aus, als würde der Server beim Senden dieser Ereignisse mit 429 (zu viele Ausnahmen) antworten. Wir werfen das im Falle von Szenarios mit Überquoten/Ratenbegrenzung. Wissen Sie, ob Sie nacheinander Fehler senden oder das Kontingent überschreiten? Wir können weitere Fehler beheben, wenn Sie der Meinung sind, dass dies echte Fehlerereignisse sind, die gelöscht werden, und Sie unter unserem 5.000-Limit für das kostenlose Kontingent liegen.

@ajjindal Alle anderen Projekte funktionieren gut mit Sentry. Der Organisations-Slug ist „alegra“, der Projektname ist mail-dispatch-serverless unter #mail-micros. Wir verwenden Sentry schon lange, aber zum ersten Mal mit Serverless. Dies ist keine kostenlose Stufe, ich kann Ihnen nicht genau sagen, welchen Plan wir verwenden, aber es ist eine kostenpflichtige Stufe.
Es wäre nett, wenn Sie mir helfen könnten, weiter zu debuggen.
Danke für die Antwort : )

PD: ist Teamplan

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen