Sentry-javascript: Fonctions Google Cloud - Configuration du client Sentry

Créé le 18 juin 2019  ·  35Commentaires  ·  Source: getsentry/sentry-javascript

Salut l'équipe, j'adore Sentry, merci !

J'utilise Google Cloud Functions avec Typescript pour un nouveau projet et j'aimerais utiliser Sentry pour capturer les erreurs.

D'après ce commentaire , il semble que Cloud Functions ne laisse pas les tiers se connecter au gestionnaire d'erreurs global de Node.

Est-ce toujours exact ? Puis-je faire fonctionner la bibliothèque Sentry JS avec Google Cloud Functions ?

Connexe : https://forum.sentry.io/t/google-cloud-functions-client-setup/1970

Tous les 35 commentaires

Hey, merci! :)

Est-ce toujours exact ? Puis-je faire fonctionner la bibliothèque Sentry JS avec Google Cloud Functions ?

Selon https://github.com/googleapis/nodejs-error-reporting#catching -and-reporting-application-wide-uncaught-errors et https://github.com/googleapis/nodejs-error-reporting#unhandled - rejets, il est possible de le faire maintenant, cependant, je ne l'ai pas encore testé moi-même.

Je peux le faire dans quelques jours, mais vous pouvez essayer si vous avez du temps libre :)

Merci @kamilogorek. Intéressant!

Je ne sais pas comment cela interagit avec Google Cloud Functions. Je suis au milieu de la planification technique maintenant, donc je ne pourrai pas tester si tôt. Mais je vous ferai savoir si / quand je le fais. :)

À l'heure actuelle, nous ne faisons qu'emballer toutes nos fonctions Cloud dans :

try () {
...
} catch (e) {
  Sentry.captureException(e);
}

Mais nous ne voyons pas le code source ou un autre contexte dans Sentry.

Y a-t-il une meilleure façon de faire cette intégration?

@vpontis vient de le tester moi-même et la résolution de contexte semble très bien fonctionner. Quelle est votre fonction/configuration ?

image

Salut @kamilogorek merci pour l'aide!

J'ai reproduit ce que vous obtenez avec Hello World, donc c'est génial.

Mais il n'y a pas de variables de contexte. Comme vous ne pouvez pas voir la valeur des variables dans la portée ? Ou est-ce seulement une fonctionnalité Python ?

image


De plus, il semble que les erreurs les plus compliquées que je reçois surviennent lorsque je fais des écritures/lectures dans la base de données et qu'il y a une erreur sur un flux/événement.

Par exemple, dans la capture d'écran ci-dessous, je ne peux même pas dire de quelle fonction il a été appelé.

Quelle est votre recommandation ? Existe-t-il un moyen d'obtenir un StackTrace plus long qui inclut l'appel du gestionnaire de fonction d'origine ? Ou dois-je simplement ajouter plus de chapelure ?

image


De plus, avez-vous une idée de ce qui se passe avec ces erreurs de code source introuvable ?

image

Mais il n'y a pas de variables de contexte. Comme vous ne pouvez pas voir la valeur des variables dans la portée ? Ou est-ce seulement une fonctionnalité Python ?

C'est une fonctionnalité Python uniquement. Malheureusement, JS ne fournit pas de mécanisme pour implémenter cette fonctionnalité.

Par exemple, dans la capture d'écran ci-dessous, je ne peux même pas dire de quelle fonction il a été appelé. Quelle est votre recommandation ? Existe-t-il un moyen d'obtenir un StackTrace plus long qui inclut l'appel du gestionnaire de fonction d'origine ? Ou dois-je simplement ajouter plus de chapelure ?

Il n'y a aucun moyen de dire ce qui a appelé cette fonction spécifique, car elle a été déclenchée par un socket. Et comme vous l'avez mentionné, la meilleure façon de suivre cela est d'utiliser le fil d'Ariane lorsque vous effectuez des requêtes db, des appels proxy ou des demandes de service externes.

Et comme les instances sans serveur ont une longue durée de vie, vous pouvez appeler :

Sentry.configureScope(scope => scope.clear())
// or
Sentry.configureScope(scope => scope.clearBreadcrumbs())

pour faire table rase.

De plus, avez-vous une idée de ce qui se passe avec ces erreurs de code source introuvable ?

Désactivez Enable JavaScript source fetching dans les paramètres de vos projets, par exemple. https://sentry.io/settings/kamil-ogorek/projects/testing-project/
C'est une application de nœud sans serveur, donc ça ne sert à rien de faire ça.

C'est une fonctionnalité Python uniquement. Malheureusement, JS ne fournit pas de mécanisme pour implémenter cette fonctionnalité.

Mince. J'ai adoré ça à propos de Python !

Super utile, merci Kamil. Je vais mettre en œuvre ces modifications et je reviendrai vers vous ici si j'ai d'autres questions. Je suis sûr que ce problème sera utile à d'autres personnes utilisant Sentry sur JS sans serveur.

Impressionnant! Je fermerai le problème pour le triage, mais n'hésitez pas à me contacter à tout moment et je le rouvrirai si nécessaire ! :)

@kamilogorek J'ai ajouté des fils d'Ariane sur Google Cloud Functions et je pense que je vois des fils d'Ariane provenant de différents appels de fonction.

Cela a-t-il du sens? Comment puis-je limiter le fil d'Ariane à une seule requête HTTP avec Google Cloud Functions ?

C'est logique, mais pouvez-vous me montrer un exemple de code que vous utilisez dans vos fonctions cloud ? De cette façon, il serait plus facile de tout comprendre.

@kamilogorek vous avez été vraiment utile !

Nous sommes passés à l'utilisation de Koa avec Docker / Node. Nous avons donc l'intégration Koa configurée comme ceci : https://docs.sentry.io/platforms/node/koa/

Cela fonctionne plutôt bien, sauf pour les fils d'Ariane, nous voyons des fils d'Ariane pour toutes les demandes sur le serveur, pas seulement des fils d'Ariane liés à la demande actuelle.

Y a-t-il moyen d'arranger ça?

Y a-t-il moyen d'arranger ça?

Il y en a, mais je dois le tester avant de fournir le code ici :)
J'essaierai de vous répondre dans environ 1 à 2 jours.

@kamilogorek tu es une légende absolue mon ami

@vpontis donc la seule chose que vous devez vraiment faire est de créer une instance de domaine dans l'un de vos middlewares. Une fois qu'il est là, le SDK le détectera et l'utilisera pour séparer les contextes. Vous pouvez également y déplacer parseRequest . (exemple basé sur le https://github.com/koajs/examples/blob/master/errors/app.js )

const Sentry = require("@sentry/node");
const Koa = require("koa");
const app = (module.exports = new Koa());
const domain = require("domain");

Sentry.init({
  // ...
});

app.use(async function(ctx, next) {
  const local = domain.create();
  local.add(ctx);
  local.on("error", next);
  local.run(() => {
    Sentry.configureScope(scope => {
      scope.addEventProcessor(event => Sentry.Handlers.parseRequest(event, ctx.request));
    });
    next();
  });
});

app.use(async function(ctx, next) {
  try {
    await next();
  } catch (err) {
    // some errors will have .status
    // however this is not a guarantee
    ctx.status = err.status || 500;
    ctx.type = "html";
    ctx.body = "<p>Something <em>exploded</em>, please contact Maru.</p>";

    // since we handled this manually we'll
    // want to delegate to the regular app
    // level error handling as well so that
    // centralized still functions correctly.
    ctx.app.emit("error", err, ctx);
  }
});

// response

app.use(async function() {
  throw new Error("boom boom");
});

// error handler

app.on("error", function(err) {
  Sentry.captureException(err);
});

if (!module.parent) app.listen(3000);

parseRequest accepte certaines options que vous pouvez utiliser pour choisir les données de requête que vous souhaitez extraire - https://github.com/getsentry/sentry-javascript/blob/f71c17426c7053d46fe3e2e35e77c564749d0eb7/packages/node/src/handlers .ts#L177

Merci @kamilogorek !

Quelques idées:

  1. Vous pouvez lier le nœud réel req et res au domaine qui sont stockés sur ctx.req et `ctx.res

  2. Vous pouvez passer dans le nœud req à parseRequest

  3. Pourquoi appelez-vous ctx.app.emit("error", err, ctx); après avoir intercepté manuellement l'erreur ?

    // since we handled this manually we'll
    // want to delegate to the regular app
    // level error handling as well so that
    // centralized still functions correctly.
    ctx.app.emit("error", err, ctx);

Ne serait-ce pas un cas où vous souhaitez simplement renvoyer la réponse et ne pas transmettre l'erreur au gestionnaire d'erreurs centralisé ?

  1. En koa, next vaut async . Cela causera-t-il des problèmes à l'intérieur d'un nœud domain.run(...)

Faites-moi savoir si cela a du sens. C'est déjà super, super utile. Je le testerai plus tard cette semaine.

Ah re 3, je vois que vous ne faites que copier cela à partir de l'exemple, donc je vais ignorer cette partie :). A tester maintenant...

Hmm, j'ai du mal à faire fonctionner ça. Je pense que cela est dû au mélange de rappels de domaine et de fonctions async dans Koa.

De plus, il semble que les domaines ne fonctionnent même pas dans le nœud 12 (je prévois de mettre à niveau bientôt pour obtenir la prise en charge de la trace de pile asynchrone).

Quoi qu'il en soit, je ne comprends pas comment fonctionne ce code avec domaine, donc j'hésite à le mettre dans une partie cruciale de l'application.

Existe-t-il un autre moyen de mettre le "hub" actuel sur ctx et d'appeler addBreadcrumb associé d'une manière ou d'une autre à la requête actuelle ? Je ne sais pas trop comment fonctionnent les concentrateurs et la gestion des messages dans Sentry...

J'ai fait fonctionner ce code (ce sont deux fonctions middleware successives) mais je ne me sens pas à l'aise avec _pourquoi_ ça marche...

export const setSentryDomain = async (ctx, next) => {
  await new Promise(async (resolve, reject) => {
    const local = domain.create();

    local.add(ctx.req);
    local.add(ctx.res);

    local.run(async () => {
      Sentry.configureScope((scope) => {
        scope.addEventProcessor((event) => Sentry.Handlers.parseRequest(event, ctx.req));
      });

      await next();
      resolve();
    });
  });
};

export const catchErrors = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    console.log('got error');
    ctx.app.emit('error', err, ctx);
  }
};

J'appelle ça une nuit. Je n'ai pas trouvé de solution de travail qui me satisfasse.

J'ai également réalisé que la plupart de mes fils d'Ariane proviennent d'instructions console.log qui sont automatiquement capturées par Sentry. Je dois donc trouver comment faire en sorte que ces fils d'Ariane soient sur la bonne portée...

J'ai fouillé dans le code et les domaines semblent résoudre ce problème. Mais les domaines ne semblent pas fiables avec les fonctions asynchrones et ils vont bientôt disparaître...

Existe-t-il un autre moyen de mettre le "hub" actuel sur ctx et d'appeler addBreadcrumb associé d'une manière ou d'une autre à la demande actuelle ?

Vous pouvez affecter le hub directement au ctx , mais cela ne fonctionnera pas pour les fils d'Ariane capturés automatiquement, car ceux-ci nécessitent Sentry.getCurrentHub() pour renvoyer le hub, auquel le fil d'Ariane doit être affecté. Et l'une des façons dont il le détecte est d'utiliser domain.active .

Mais les domaines ne semblent pas fiables avec les fonctions asynchrones et ils vont bientôt disparaître...

Malheureusement, ils disparaissent depuis décembre 2014 , sans remplacement clair (il y a des crochets asynchrones, mais ils ne sont pas parfaits non plus) en vue, et ils n'ont pas été retirés du noyau du nœud au cours de ces 5 années.

Nous jouons avec zone.js maintenant, car cela aiderait énormément, mais c'est toujours un énorme PoC pour l'instant et nous ne pouvons pas dire quand ou même si nous l'utiliserons un jour pour remplacer des domaines.

Salut @kamilogorek ,

J'essaie de capturer les erreurs non gérées dans Sentry et j'ai essayé ce que vous aviez mentionné dans https://github.com/getsentry/sentry-javascript/issues/2122#issuecomment -503440087

Mais je suppose que Google ne vous permet pas de vous connecter au process.on('uncaughtException') , je n'ai pas pu enregistrer d'erreurs dans Sentry en utilisant cette approche.

Y a-t-il un autre moyen que vous recommanderiez d'essayer, envelopper chaque corps de fonction dans un bloc try catch ne semble pas être la solution idéale.

Nous fournissons une méthode wrap , mais je ne pense pas que ce soit beaucoup mieux que try/catch tbh.

exports.helloBackground = (data, context) => {
  return `Hello ${data.name || 'World'}!`;
};

// becomes

exports.helloBackground = (data, context) => {
  return Sentry.wrap(() => {
    return `Hello ${data.name || 'World'}!`;
  })
};

Il me semble que ce problème pourrait valoir la peine de rester ouvert en tant que demande de fonctionnalité pour prendre en charge les fonctions firebase/google cloud de manière plus simple.

J'ai été incroyablement impressionné par la facilité de configuration côté client et déçu lorsque j'ai réalisé que la configuration côté serveur serait beaucoup plus complexe. Existe-t-il un plan pour améliorer l'expérience sur GCP (fonctions en particulier) ?

@goleary Cela vous dérangerait-il d'ouvrir un nouveau numéro décrivant exactement ce que vous aimeriez voir ?
Ce fil est déjà assez large et difficile à suivre.

Merci

N'y a-t-il toujours pas de moyen plus simple de configurer cela? Idéalement, pouvoir le définir une fois globalement, sans avoir à le définir pour chaque fonction ?

@marcospgp non, et il n'y en aura malheureusement pas, car Google lui-même ne fournit pas de mécanisme permettant cela. Voir leur propre journaliste - https://cloud.google.com/error-reporting/docs/setup/nodejs il utilise également des appels manuels.

Hmm intéressant, j'ai configuré Sentry sur les fonctions cloud de Firebase (qui utilise les fonctions google cloud dans les coulisses) et je viens de recevoir un rapport d'erreur - donc ça semble fonctionner !

Voici mon index.js, où se trouve tout le code Sentry :

const admin = require("firebase-admin");
const functions = require("firebase-functions");
const Sentry = require("@sentry/node");

/**
 * Set up Sentry for error reporting
 */
if (functions.config().sentry && functions.config().sentry.dsn) {
  Sentry.init({ dsn: functions.config().sentry.dsn });
} else {
  console.warn(
    "/!\\ sentry.dsn environment variable not found. Skipping setting up Sentry..."
  );
}

admin.initializeApp();

const { function1 } = require("./cloud-functions/function1");
const { function2 } = require("./cloud-functions/function2");

module.exports = {
  function1,
  function2
};

@marcospgp était le index.js ci-dessus capable d'envoyer des exceptions non interceptées dans ./cloud-functions/function1 et ./cloud-functions/function2 à Sentry, ou avez-vous dû vous connecter activement à Sentry dans ces fichiers ?

Je viens d'essayer la solution @marcospgp , mais il ne semble pas enregistrer les exceptions non interceptées, il doit les enregistrer manuellement (en utilisant sentry.captureException() ?)

Identique à @goleary ici, rien n'est enregistré.

Il semble également que .wrap() ne soit plus disponible.

Cela signifie-t-il que nous devrions encapsuler manuellement tout le code dans try/catch ?

@Dinduks c'est ce que je fais. La surcharge est un peu ennuyeuse, mais pouvoir utiliser sentinelle vaut l'effort par rapport à la journalisation de la fonction cloud Firebase (pas génial).

@Dinduks wrap est toujours là, voir : https://github.com/getsentry/sentry-javascript/blob/master/packages/browser/src/exports.ts#L40
Cependant, gardez à l'esprit qu'il exécute la fonction immédiatement et vous renvoie la valeur de retour. Donc, je ne sais pas si c'est plus utile que l'essai / capture régulier qui vous permet également d'effectuer une action de secours de l'utilisateur.

const myHandler = (req, res) => Sentry.wrap(() => {
  someFunctionThatCanBreak(req);
  return res.send(200);
});

Je ne pense pas que ce soit une bonne idée, mais j'ai créé un wrapper qui ressemble à ceci.

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

export const sentryWrapper = (f) => {
  return async function () {
    try {
      // eslint-disable-next-line prefer-rest-params
      return await f.apply(this, arguments);
    } catch (e) {
      Sentry.captureException(e);
      await Sentry.flush(2000);
      throw new Error(e);
    }
  };
};

Il sera utilisé comme suit.

export const getChannelVideoTest = functions.https.onRequest(
  sentryWrapper(async (req, res) => {
    someFunctionThatCanBreak(req);
    return res.send(200);
  }),
);

J'aimerais savoir s'il existe un meilleur moyen.

@kamilogorek
J'ai du mal avec ça aussi.

J'ai réussi Sentry.init() et j'ai des problèmes lorsque j'enveloppe tout mon code dans une instruction try {} catch {} et un appel manuel à Sentry.captureException et Sentry.flush() .
Cependant, je ne peux rien obtenir si je supprime l'instruction try/catch .
Il en va de même pour la surveillance des performances où je n'obtiens rien à moins que je crée manuellement une transaction avec Sentry.startTransaction() au début de la fonction.

Est-ce prévu ?
Existe-t-il un moyen d'envoyer des erreurs non gérées à Sentry ?
Si non, cela signifie-t-il que l'onglet performances définira toujours le taux d'échec à 0 % ? (parce que nous attrapons l'erreur et la signalons manuellement, la transaction est fermée correctement, a donc un statut ok ?)

@axelvaindal , nous ne prenons pas encore en charge la surveillance des performances sans serveur. Quant à cette question :

Existe-t-il un moyen d'envoyer des erreurs non gérées à Sentry ?

Alors non, pas vraiment, car GCF ne fournit pas de moyen de se connecter aux exceptions/rejets non gérés, nous ne sommes donc pas en mesure d'intercepter cela. Vous devez envelopper vos gestionnaires (voir le commentaire au-dessus du vôtre) afin de l'attraper manuellement.

Vous pouvez également lire notre implémentation du gestionnaire AWSLambda pour avoir des idées sur la façon d'améliorer cet extrait - https://github.com/getsentry/sentry-javascript/blob/master/packages/serverless/src/awslambda.ts

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