Sentry-javascript: [@sentry/node] Prise en charge d'AWS Lambda et d'autres solutions sans serveur

Créé le 28 juil. 2018  ·  77Commentaires  ·  Source: getsentry/sentry-javascript

  • @sentry/nœud version 4.0.0-beta.11
  • J'utilise Sentry hébergé

Quel est le comportement actuel ?

J'utilise @sentry/node pour capturer l'exception sur la fonction AWS lambda.

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

Cependant, il tue le processus lorsque context.fail() est appelé et que l'exception ne se retrouve pas dans Sentry.

Je pourrais faire une solution de contournement comme:

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

Quel est le comportement attendu ?

Ce serait bien si je pouvais faire quelque chose comme:

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

Ou quelque chose qui gère globalement le rappel.

Commentaire le plus utile

@LinusU, nous créerons très probablement un package sans serveur spécifique pour ce scénario. Nous avons juste besoin de trouver du temps, car c'est la fin de l'année et il y a beaucoup de monde maintenant. Vous tiendrons au courant!

Tous les 77 commentaires

Cela peut aider je suppose https://blog.sentry.io/2018/06/20/how-droplr-uses-sentry-to-debug-serverless (il utilise l'ancienne version de corbeau, qui avait un rappel, mais je suis pointant principalement vers un drapeau callbackWaitsForEmptyEventLoop .

Il n'y a pas encore de moyen officiel, car nous essayons toujours des choses en version bêta, mais c'est faisable avec ce code :

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 Merci pour le pointeur. Je vais essayer et rejouer les apprentissages.

@kamilogorek Votre suggestion fonctionne. J'attends avec impatience une manière plus officielle.

@vietbui
Dans 4.0.0-rc.1 nous avons introduit une fonction sur le client appelée close , vous l'appelez comme ceci :

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 attendra que toutes les requêtes soient envoyées, il se résoudra toujours (résultat = faux délai d'attente a été atteint), jusqu'à ce que le délai d'attente soit atteint (dans cet exemple 2000 ms).
Ceci est notre API officielle.
Bien que l'approche précédente fonctionne toujours, la méthode close fonctionne pour tous les cas.

@HazAT Joli. Merci pour tout ce dur travail.

En 4.0.3, je l'appelle ainsi dans ma fonction lambda :

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

getDefaultHub() n'est plus disponible.

@vietbui , il s'appelle maintenant getCurrentHub , car nous avons dû unifier notre API avec d'autres SDK de langues.

@kamilogorek Merci pour la clarification. Il y a un problème avec l'approche getCurrentHub car la portée que j'ai configurée ne s'est pas retrouvée dans Sentry.

En fin de compte, j'ai adopté une approche différente, comme suggéré par @HazAT pour capturer l'exception dans mes fonctions lambda :

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

Et cela fonctionne parfaitement.

Est-ce la méthode recommandée pour attendre/forcer la sentinelle à envoyer des événements ?

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

Cette solution ne fonctionne pas pour moi pour une raison quelconque. Cela ne fonctionne que la première fois en production lorsqu'il y a une heure de démarrage à froid et ne fonctionne plus après cela. voici un exemple de code

'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 C'est également indiqué dans la documentation :

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

Je ne sais donc pas comment nous pouvons gérer cela dans lambda où nous ne savons pas quand l'application sera tuée. Le mieux serait de drainer la sentinelle par requête comme nous le pouvions avec l'ancienne API ?

@HazAT pourrions-nous rouvrir cela, s'il vous plaît ? Je pense qu'il est important d'avoir un moyen de travailler avec cela sur Lambda, qui devient une cible de déploiement de plus en plus courante.

Cela m'empêche actuellement de passer à la dernière version...

Personnellement, je préférerais pouvoir obtenir une promesse/un rappel lors du signalement d'une erreur. Avoir un moyen de vider la file d'attente sans la fermer par la suite serait la meilleure chose à faire...

Quelle était la raison de supprimer le rappel de captureException ?

@albinekb ça ne marche pas du tout si je supprime la ligne suivante

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

@LinusU quelle est la solution et la solution sentinelle ou corbeau que vous utilisez ?

Pour moi, ce qui suit fonctionne avec sentry/node @4.3.0 , mais je dois faire attendre manuellement lambda un certain temps (dans ce cas, je mets 2 secondes) pour que la sentinelle fasse ce qu'elle doit faire. Ce dont je ne sais pas pourquoi il doit être là, car nous attendons que la sentinelle termine la demande captureException . Si je n'ai pas le délai d'attente par la suite, la sentinelle ne semble pas envoyer l'erreur.

'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,
  }
};

Nous sommes également brûlés à ce sujet sur Lambda. Nous avons commencé avec les nouvelles bibliothèques et sommes totalement bloqués, envisageant de revenir à Raven. Nous écrivons actuellement des tests pour tenter de fermer le concentrateur, puis de le réinitialiser, ce qui serait une solution de contournement réalisable s'il tient la route. Mais toujours hacky / susceptible de causer des problèmes sous charge.

Personnellement, je préférerais une sorte de flush() qui renvoie une promesse - difficile de trouver un inconvénient. Pensez-vous que cela arriverait jamais?

quelle est la solution et la solution sentinelle ou corbeau que vous utilisez ?

J'utilise le gestionnaire d'erreurs express suivant :

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

J'utilise ensuite scandium pour déployer l'application Express sur Lambda

edit: c'est avec Raven "raven": "^2.6.3",

L'API de rêve serait quelque chose comme ça 😍

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

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

Vous devez cependant utiliser l'instance client directement pour l'obtenir. La raison en est qu'il a été décidé que le scénario principal est un comportement de type "feu et oubli", ce n'est donc pas une méthode asynchrone. Cependant, en interne, nous avons une API asynchrone que nous utilisons nous-mêmes.

Il semble que ce que je veux réellement ressemble plus à:

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

Afin d'éviter toute la file d'attente et la mise en mémoire tampon...

Je comprends que la conception est adaptée au "feu et oublié", et pour fonctionner sur un serveur de longue durée, et c'est probablement assez bon dans ce domaine car il fait beaucoup de mise en mémoire tampon, etc. Le problème est que c'est exactement le contraire que vous souhaitez pour Lambda, App Engine et d'autres architectures "sans serveur", qui deviennent de plus en plus courantes.

Serait-il possible d'avoir une méthode spéciale qui envoie l'événement aussi vite que possible, et renvoie un Promise que nous pouvons await ? Ce serait parfait pour les scénarios sans serveur !

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, nous créerons très probablement un package sans serveur spécifique pour ce scénario. Nous avons juste besoin de trouver du temps, car c'est la fin de l'année et il y a beaucoup de monde maintenant. Vous tiendrons au courant!

nous allons très probablement créer un package sans serveur spécifique pour ce scénario

Ce serait génial! 😍

@mtford90

quand exactement utiliserais-je cette meilleure solution ? Autant que je sache, il n'est pas possible de savoir quand le lambda sera arrêté - de plus, il semble idiot d'attendre un temps arbitraire pour l'arrêt pour permettre à la sentinelle de faire son travail - en particulier sur les fonctions coûteuses de mémoire élevée/cpu lambda.

(en parlant de vidange)

Il est destiné à être utilisé comme la dernière chose avant de fermer le processus du serveur. Le délai d'attente dans la méthode drain est le temps maximum que nous attendrons avant d'arrêter le processus, ce qui ne signifie pas que nous utiliserons toujours ce temps. Si le serveur est entièrement réactif, il enverra immédiatement tous les événements restants.

Il n'y a aucun moyen de le savoir en soi, mais il existe un moyen de dire au lambda quand il doit être arrêté en utilisant l'argument de rappel du gestionnaire.

Aussi @LinusU , j'ai relu votre commentaire précédent, plus précisément cette partie :

Serait-il possible d'avoir une méthode spéciale qui envoie l'événement aussi vite que possible et renvoie une promesse que nous pouvons attendre ? Ce serait parfait pour les scénarios sans serveur !

C'est ainsi que nous avons implémenté notre tampon. Chaque appel captureX sur le client l'ajoutera au tampon, c'est correct, mais il n'est en aucun cas mis en file d'attente, il est exécuté immédiatement et ce modèle n'est utilisé que pour que nous puissions obtenir les informations si tout était envoyé avec succès à Sentry.

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

Cela signifie que si vous faites quelque chose comme ça dans AWS Lambda (en supposant que vous souhaitiez utiliser le client par défaut, ce qui est le cas le plus simple) :

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');
        })
    }
};

Vous pouvez être sûr qu'il a été envoyé immédiatement et qu'il n'y a pas eu de surcharge de temps/de traitement.

@kamilogorek
Cela signifie-t-il que quelque chose comme ça devrait fonctionner dans un gestionnaire async/wait (où vous n'utilisez pas le rappel) ?

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 totalement ! :)

Il semble que les changements suivants fonctionneraient pour moi alors ☺️

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

Pour être honnête, chaque ligne est devenue un peu plus moche 😆 mais je suppose que c'est mieux sous le capot...

@kamilogorek Je n'ai pas trouvé getCurrentHub() dans la documentation de votre site Web, cette API est-elle garantie de ne pas se casser sans une bosse de semver majeure ? ❤️

@kamilogorek Je n'ai pas trouvé getCurrentHub() dans la documentation de votre site Web, cette API est-elle garantie de ne pas se casser sans une bosse de semver majeure ? ❤️

Oui, c'est garanti. C'est la partie du package @sentry/hub qui est décrite ici - https://docs.sentry.io/enriching-error-data/scopes/?platform=browser

Nous discutons un peu des "utilisations avancées" ici dans ce fil et nous n'avons pas encore atteint le point de les documenter. On finira par faire ça :)

De toute évidence, ce qui nous manque ici, c'est de la documentation et des bonnes pratiques dans ce type de cas d'utilisation avancés. Ce sera vraiment bien quand ce sera documenté ou même un article de blog peut être un bon début.
Sinon, le nouveau SDK est vraiment simple à utiliser et l'unification est vraiment sympa.

@kamilogorek
Cela signifie-t-il que quelque chose comme ça devrait fonctionner dans un gestionnaire async/wait (où vous n'utilisez pas le rappel) ?

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';
    }
};

Faire quelque chose comme suggéré ci-dessus fonctionne, sauf que je ne peux pas ajouter de contexte supplémentaire. Par exemple, si je fais :

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

Je ne verrai pas réellement "someExtraInformation" dans Sentry.

Quelqu'un a suggéré une méthode alternative en haut de ce fil, et cela fonctionne, mais semble hacky (forçant un délai d'attente).

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';
   }
};

Cela peut-il également s'appliquer aux _exceptions non détectées_ ? Il semble que la modification de l'intégration Sentry.Integrations.OnUncaughtException soit la manière officielle de le faire, mais la documentation est assez pauvre en ce moment.

+1 pour ça. Au moins avoir quelque chose de documenté officiellement serait bien. Serverless se développe rapidement à partir de 2019, je veux vraiment voir le support officiel de Sentry à ce sujet. L'une des idées que j'ai lues ici et que j'ai vraiment appréciée était d'avoir quelque chose comme Sentry.flush() pour envoyer tous les événements en file d'attente.

@rdsedmundo Pouvez-vous expliquer pourquoi cette approche ne fonctionne pas pour vous ?

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);
})

C'est notre approche officielle et fondamentalement Sentry.flush() .
réf : https://docs.sentry.io/error-reporting/configuration/draining/?platform=javascript

@HazAT Le problème avec cela survient lorsque vous pensez à la réutilisation des conteneurs AWS Lambda. Ce qui, en termes TL; DR, signifie qu'un processus qui vient de servir une demande peut servir une nouvelle marque s'il est effectué dans un court laps de temps. Si je ferme la connexion avec cet extrait que vous avez donné et que le conteneur est réutilisé, je dois réussir à créer un nouveau hub pour la nouvelle requête. Je peux facilement voir que cela devient délicat. C'est pourquoi un simple await Sentry.flush() serait une meilleure solution :

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 Je ne sais pas si j'ai peut-être mal compris quelque chose mais si c'est le cas

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);
  }
}

C'est exactement comme await Sentry.flush seulement que vous définissez le délai d'attente.

La promesse se résout après 2000 ms à coup sûr avec false s'il y avait encore des choses dans la file d'attente.
Sinon, close sera résolu avec true si la file d'attente a été vidée avant que le délai d'attente ne soit atteint.

Ou le conteneur sera-t-il réutilisé avant que toutes les promesses ne soient résolues ? (je ne peux pas imaginer ça)

@HazAT n'est-ce pas le problème que close(...) empêchera le client d'être réutilisé ? Lambda réutilise le même processus Node afin que les appels ressemblent à ceci, ce qui, je suppose, cessera de fonctionner après le premier appel à close ?

  • 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()
  • ...

Non, close ne supprime pas le client, il sert juste à vider la file d'attente de transport.
Je suis d'accord que le nom close dans ce contexte peut être trompeur mais au moins dans JS/Node close ne fait rien avec le client et c'est parfaitement bien de continuer à l'utiliser par la suite.

Edit : si c'était réellement le "problème", je mettrai à jour la documentation pour que cela soit clair.

Cool. Mais la documentation est fausse alors:

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

OK, nous venons de discuter de cette question en interne dans l'équipe.
Vous aviez raison et bien que JavaScript ne se comporte pas actuellement comme nous l'avons documenté 🙈 nous allons introduire une fonction flush qui fera exactement ce que vous attendez.

Donc, pour le moment, vous pouvez utiliser close sans aucun problème (je ne sais pas si nous allons le changer pour supprimer/désactiver le client à l'avenir).
Mais il y aura une fonction flush qui est là pour _juste_ flush la file d'attente.

Je mettrai à jour ce problème une fois la fonctionnalité débarquée.

Étant donné que je me suis un peu perdu dans tous ces commentaires, est-ce à quoi devrait ressembler le gestionnaire d'erreurs Express (imitant celui de ce dépôt) ?

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

Il semble que cela fonctionne et il ne perd pas de données supplémentaires comme dans le code de @rreynier .

Personnellement j'ai l'impression que

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

est plus propre que :

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

close se lit vraiment comme s'il allait fermer le client...

Exemple complet :

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 J'ai essayé cela et pour une raison quelconque, il n'envoie pas de données supplémentaires avec la trace de la pile. Il envoie essentiellement une trace de pile. Aucune information sur l'utilisateur, le système d'exploitation ou quoi que ce soit.

Ah, c'est pas bon du tout 😞

Pendant que nous attendons flush , comme solution de contournement plus fiable que les deux options ci-dessus, vous pouvez signaler et attendre le résultat, _et_ inclure la portée, en utilisant l'extrait ci-dessous :

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

J'utilise ceci, et cela semble fonctionner de manière fiable pour moi, avec des erreurs signalées, y compris tout ce à quoi je m'attendais.

J'utilise en fait tout cela avec Netlify Functions, mais la théorie est la même avec Lambda, etc. J'ai écrit un article avec tous les détails sur la façon de faire fonctionner cela, si quelqu'un est intéressé : https://httptoolkit tech/blog/netlify-function-error-reporting-with-sentry/

J'utilise cette aide dans tous mes lambdas actuellement.

@pimterry N'est-ce pas fondamentalement la même solution que celle suggérée par @LinusU ? Je l'ai essayé et il n'envoie pas non plus de données supplémentaires.

Cette approche a fonctionné pour moi jusqu'à présent @ondrowan

@ondrowan c'est la même chose, mais en saisissant manuellement et en incluant la portée actuelle. Cela devrait être suffisant pour vous permettre de travailler des exceptions si je pense. Avec la version précédente, j'obtenais des événements sans étiquette, et maintenant, avec ce changement, mes exceptions apparaissent avec tous les détails supplémentaires normaux.

@vietbui @albinekb @Andriy-Kulak @LinusU @dwelch2344 @jviolas @rreynier @guillaumekh @rdsedmundo @ondrowan @pimterry @zeusdeux je ne sais pas qui est toujours intéressé par ce cas d'utilisation, alors excusez-moi si je ne dois pas vous appeler.

À partir 4.6.0 , il n'y a plus de danse client/hub . Vous pouvez simplement appeler n'importe quelle méthode captureX puis utiliser Sentry.flush() pour attendre la réponse une fois que tout est envoyé au serveur. Toutes les données étendues/supplémentaires doivent être conservées sans aucune interaction avec les développeurs.

Voici un exemple avec des requêtes réussies/expirées.

image

J'espère que ça aide! :)

Agréable!

Est-il toujours prévu de créer un package minimal uniquement pour capturer les exceptions de Lambda et d'autres solutions sans serveur ? Je pense que ce serait quand même un très bel ajout ❤️

@LinusU , espérons-le, oui, mais nous sommes submergés de SDK d'autres langues en ce moment 😅

Merci à tous pour toutes les solutions possibles, tl;dr pour tous ceux qui viennent ici

Utilisez : await Sentry.flush() pour envoyer toutes les requêtes en attente, cela a été introduit dans 4.6.x .

En fermant ceci, n'hésitez pas à ouvrir un nouveau sujet au cas où quelque chose manquerait (mais ce fil est déjà très long).

Bravo 👍 🎉

@kamilogorek Salut ! Pour info, j'utilise Sentry.flush dans mon application à la place de l'ancienne solution de contournement et aucune des erreurs n'est signalée. Je reviens actuellement à l'ancienne solution de contournement à partir de la méthode flush mise à jour .

@zeusdeux est-il possible de fournir des informations de débogage/cas de reproduction pour cela ?
Vous remplacez la méthode captureException qui ajoute l'événement au tampon, puis vous devriez await sur la valeur de retour flush . Avez-vous essayé de l'utiliser de manière "normale" ?

@kamilogorek J'aimerais avoir des informations de débogage mais il n'y a rien dans les journaux. J'ai toujours fait await sur le captureException . Par la manière habituelle, voulez-vous dire sans remplacer captureException ?

@zeusdeux exactement, appelez simplement notre Sentry.captureException(error) natif sans aucun remplacement.

Ainsi, votre aide sera :

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

et dans le code tu l'appelles :

import * as Sentry from '@sentry/node'

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

@kamilogorek Je vais essayer et faire rapport. Aussi, merci pour le conseil sur beforeSend ^_^

await Sentry.flush(2000);

ne fonctionne pas non plus pour moi.

@tanduong pouvez-vous fournir un étui de reproduction ? Le simple fait de dire que cela ne fonctionne pas n'est pas trop utile 😅

@kamilogorek en fait, je viens de découvrir que

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

ne fonctionne pas pour moi non plus car ma fonction lambda est attachée à VPC.

Je confirme que

await Sentry.flush(2000);

fonctionne réellement.

BTW, alors comment géreriez-vous lambda dans VPC ? Attacher à une passerelle NAT ? Je veux juste Sentry mais pas l'internet public.

@tanduong Sentry est sur Internet public, donc oui, vous devez avoir une passerelle NAT si votre lambda s'exécute dans votre VPC. Sinon, vous devrez explorer l'option Sentry hébergée.

Que fait réellement le flush(2000) ? J'avais ce code qui fonctionnait généralement bien, mais maintenant j'ai quelques appels captureMessage en même temps, il expire à chaque fois !

Vidage de la file d'attente interne des messages sur le réseau

Ok, c'est tout à fait logique. Je pense que mon problème alors c'est que cette promesse ne revient jamais quand il n'y a rien d'autre à vider ? Chaque fois que j'exécute mon captureException fn enveloppé simultanément, mon gestionnaire expire.

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() ne se termine pas vraiment après le premier appel de captureMessage.

J'ai ce que je pense être un problème similaire à @enapupe. Si vous appelez await client.flush(2000); en parallèle, seule la première promesse est résolue. Cela peut se produire dans les contextes AWS lambda où le client est réutilisé parmi plusieurs appels simultanés au gestionnaire.

J'utilise un code comme celui-ci :

 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');
  }

Mais quand je fais deux appels à ma fonction lambda en succession rapide, j'obtiens :

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

Vous pouvez voir que la deuxième promesse n'est jamais résolue.

@esetnik J'ai déposé un problème à ce sujet : https://github.com/getsentry/sentry-javascript/issues/2131
Ma solution de contournement actuelle est un wrapper flush fn qui résout toujours (basé sur un délai d'attente):

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

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

@enapupe J'ai ajouté une note sur votre solution de contournement dans # 2131. Je crois que cela entraînera une régression des performances sur le vidage simultané.

Au cas où quelqu'un aurait des problèmes.
Cela fonctionne à merveille

@SarasArya @HazAT
Tout d'abord... Merci d'avoir partagé votre solution ! :)
Il y a un rappel de la méthode configureScope que je suppose qu'il est censé être appelé avant captureException mais cela ne se fait pas dans le même "thread".
Cela ne pourrait-il pas conduire à l'apparition de conditions de course ?

@cibergarri Je ne pense pas, ça me semble synchrone, au cas où vous auriez une méthode asynchrone là-dedans, alors il y aurait des conditions de concurrence.
Considérez comme .map of array que la même chose se produit ici. Au cas où vous auriez des problèmes pour envelopper votre tête. J'espère que cela aide.

Ouais, c'est tout à fait bien de faire ça

Mise à jour : Sentry prend désormais en charge la capture d'erreurs automatisée pour les environnements Node/Lambda : https://docs.sentry.io/platforms/node/guides/aws-lambda/

J'utilise @sentry/serverless comme ceci :

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);
     }

});

Il ne fonctionne pas sur lambda.
Dans mon environnement de test, cela fonctionnait, mais dans la production où il y a beaucoup d'exécutions simultanées et où les conteneurs sont réutilisés, il enregistre environ 10 % du montant total.

Aucun conseil?

@armando25723

Veuillez dire comment votre mesure a-t-elle perdu les événements d'exception ? Avez-vous un exemple de code de la façon dont une telle exception perdue a été levée? Besoin de plus de contexte.

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);
     }
});

Qu'est-ce qui se passe?
Si la fonction est appelée plusieurs fois avec un court intervalle entre les appels, l'événement n'est enregistré qu'une seule fois.
Si l'intervalle de temps entre les appels est plus long, tous les événements sont consignés.

Je suppose que le problème est lorsque l'invocation est sur un conteneur réutilisé.

j'ai essayé aussi
await Sentry.captureException(error);
et:
await Sentry.flush();
et sans rinçage
même résultat

@marshall-lee que recommandez-vous ? Dois-je créer un problème, je suis coincé ici.

@ armando25723 On dirait que le serveur répond avec 429 (trop d'exceptions) lors de l'envoi de ces événements. Nous le lançons en cas de scénarios de limitation de quota/taux. Savez-vous si vous envoyez séquentiellement des erreurs ou si vous dépassez le quota ? Nous pouvons déboguer davantage si vous pensez qu'il s'agit de véritables événements d'erreur supprimés et que vous êtes sous notre limite de 5 000 pour le niveau gratuit.

@ajjindal tous les autres projets fonctionnent bien avec sentinelle. Le slug d'organisation est "alegra", le nom du projet est mail-dispatch-serverless sous #mail-micros. Nous utilisons Sentry depuis longtemps, mais pour la première fois avec Serverless. Ce n'est pas un niveau gratuit, je ne peux pas vous dire exactement quel plan nous utilisons, mais c'est un plan payant.
Ce serait bien si vous pouviez m'aider à déboguer davantage.
Merci pour votre réponse : )

PD : est le plan d'équipe

Cette page vous a été utile?
0 / 5 - 0 notes