web-dev-qa-db-fra.com

Comment console.log une erreur avec trace de pile dans node.js?

J'ai essayé de déboguer mon application de nœud pour trouver la source d'une erreur dans mon journal qui n'apparaît que sous la forme "Error: Can't set headers after they are sent", Sans aucune information de trace ni aucun contexte.

En l'occurrence, je pense que j'ai maintenant corrigé cela ... J'utilise connect-timeout et je continuais à traiter un rappel passé à une opération de réseau asynchrone, que le rappel finirait par essayer pour faire une res.send(), bien que req.timedout ait été défini sur "vrai" par connect-timeout pendant le fonctionnement du réseau.

MAIS je ne comprends toujours pas pourquoi mon journal n'affichait aucune information de trace pour cette erreur. Partout où une erreur est retournée dans mon code je l'enregistre sur la console avec:

console.log(err);

Si des informations de trace sont disponibles dans l'objet err, et qu'elles semblent être placées dans err.stack, L'instruction ci-dessus ne devrait-elle pas vider le contenu entier de err (y compris err.stack) au journal de la console? Je comprends que je ne perdrais aucune information en faisant ce qui précède, par exemple à:

console.log(err.stack);

Mais des articles comme celui-ci semblent suggérer le contraire (bien que l'article lié ait été mis à jour).

Je vais en fait plus loin et j'ajoute du texte pertinent pour aider à localiser l'erreur:

console.log('error in dodgyFunction:', err);

Mais malgré cela, je n'obtenais toujours que "Error: Can't set headers after they are sent", Sans le contexte que je mettrais. Serait-ce parce que ce message d'erreur de la console est généré dans une bibliothèque externe (comme express)? Je pensais que les bibliothèques externes devraient renvoyer les erreurs au code principal pour être traitées en conséquence?

Edit: voici un exemple où j'ai mis ma vérification d'erreur et de timeout, en haut de la fonction de rappel passée à l'opération asynchrone:

var execFile = require('child_process').execFile;
execFile('dodgycommand', options, function(error, stdout, stderr) {
    if (req.timedout) {
        console.log('timeout detected whilst running dodgycommand, so aborting...');
        return;
    }
    if (error) {
        console.log('error running dodgycommand:', error);
        res.sendStatus(400);
        return;
    }

    // ... it's safe to continue ...

}

Je suis fondamentalement ce même schéma tout au long.

6
drmrbrewer

Je viens de comprendre ce qui se passait et j'espère que cela aidera les autres à éviter l'erreur de ce débutant.

Pour certains de mes journaux d'erreurs, j'utilisais quelque chose comme le suivant, en utilisant la concaténation de chaînes pour construire le message d'erreur:

console.log('error in function abc: ' + err + ' whilst doing xyz');

alors qu'ailleurs j'utilisais quelque chose comme le suivant, passant juste les morceaux du message d'erreur comme arguments séparés à console.log:

console.log('error in function xyz:', err, 'whilst doing abc');

Je vois maintenant que cela donne des résultats différents!

Le premier doit stringifier err afin qu'il puisse être concaténé avec les autres parties du message, et selon this , ce faisant, il utilise simplement la partie message.

Cependant, sous cette dernière forme, l'objet err doit être traité par console.log non frelaté et jeté dans son ensemble.

Cela explique pourquoi je ne voyais parfois pas tout le contenu de l'erreur, comme je m'y attendais, et d'autres fois.

En ce qui concerne les messages de journal de la console mis en place par d'autres bibliothèques, une autre chose à vérifier est que vous ne filtrez pas les parties "pile" des messages de journal dans votre visionneuse de journaux ... il s'avère que je était (afin d'économiser sur le quota de journaux ... j'utilise papertrail) ... d'oh. Je le faisais en filtrant toutes les lignes commençant par ____at (quatre espaces suivis de 'at'), par exemple ____at Request.self.callback.

7
drmrbrewer

Votre modèle semble généralement commun, bien que je dise qu'en règle générale je ne l'aime pas, plus à ce sujet dans une seconde.

Quant à votre question principale, il est vraiment difficile d'y répondre en fonction de ce que vous avez fourni. Si vous montrez le code réel plutôt que le "je suis généralement ce modèle", cela pourrait aider. Mais il est également possible que l'erreur se soit produite quelque part à laquelle vous ne vous attendiez pas, et donc votre console.log N'a pas été appelé du tout.

On dirait que vous recherchez les meilleures pratiques, donc je vais vous donner ce que je pense être le meilleur que j'ai trouvé jusqu'à présent.

Tout d'abord, n'utilisez pas console.log Pour vous connecter. Ce n'est pas horrible, mais vous pouvez faire beaucoup, beaucoup mieux. Mon préféré est d'utiliser morgan comme middleware pour la journalisation des informations de demande, et debug pour la journalisation des applications.

Avec debug, vous pouvez configurer des niveaux de journalisation personnalisés et écouter le niveau de votre choix avec le niveau de granularité souhaité. Tout est contrôlé en définissant la variable d'environnement DEBUG, et en production, vous pouvez rediriger vers un fichier ou toute autre destination que vous souhaitez. De plus, de nombreux modules de nœuds (y compris Express et Connect) utilisent Debug comme enregistreur sous le capot, donc en modifiant votre variable DEBUG, vous pouvez voir autant ou peu de leur journalisation interne que vous le souhaitez. très utile pour déterminer ce qui n'a pas fonctionné où.

Deuxièmement, comme je l'ai dit, je n'utilise pas du tout le modèle que vous avez quand il s'agit de routage. J'ai trouvé qu'il était facile d'envoyer accidentellement des en-têtes plus d'une fois si je ne fais pas attention, donc mon middleware retourne toujours next() et les réponses ne sont envoyées que dans des gestionnaires réels que je ne peux être sûr de déclencher qu'une seule fois. En ce qui concerne les erreurs, je passe toujours next(e) que je peux ensuite gérer dans une fonction de gestion des erreurs. J'ai également créé la bibliothèque praeter pour fournir des erreurs standard basées sur des codes d'état Web et un gestionnaire d'erreurs générique.

Le motif ressemble à ceci:

// middleware function to put something on the request object
app.use((req, res, next) => {
  MyModel.doSomething((e, thing) => {
    if (e) return next(e);
    if (!thing) return next(new NotFound()); // NotFound is an error in praeter that equates to a 404. 
    req.thing = thing;
    return next();
  });
});

Puis plus tard

// log in here is a reference to my debug configured log object
app.use((err, req, res, next) => {
  log.error(err);
  log.error(err.stack);
  return res.status(err.statusCode || 500).send(err.message)
});

Notez qu'il s'agit d'un exemple simple d'un gestionnaire d'erreur final. J'en ai souvent plusieurs où je pourrais gérer différents codes d'erreur différemment, selon les besoins de l'application.

5
Paul

J'ai installé n maintenant et je peux confirmer ce qui suit:

Noeud 4.0.0

L'utilisation de console.log(err) imprime uniquement le message d'erreur.

Noeud 7.7.0 (dernier)

L'utilisation de console.log(err) imprime le message d'erreur et la pile complète.


J'ai confirmé que ce comportement a changé sur la version 6.0.0. Donc, si vous utilisez une version plus ancienne, je vous suggère de mettre à jour votre Node.js ou d'utiliser console.log(err.stack) à la place pour imprimer la pile complète.

3
Zanon