Numéros de ligne de script R en cas d'erreur?

105

Si j'exécute un long script R à partir de la ligne de commande (R --slave script.R), comment puis-je lui faire donner des numéros de ligne en cas d'erreurs?

Je ne veux pas ajouter de commandes de débogage au script si possible - je veux juste que R se comporte comme la plupart des autres langages de script ...

fourchette et attendre
la source
31
Les mises à jour? Quatre 4 ans plus tard, il semble que le problème persiste, malgré toute l'adoption généralisée de R.
Gui Ambros
J'ai aussi un très long script R avec beaucoup de petites sorties, je veux imprimer (souligné) (souligné) LINE / FILE (souligné) (souligné) (numéros de ligne et nom de script) comme ça en C, au lieu de coder en dur les numéros de ligne dans la source.
mosh
Je ne sais pas si R en interne a vraiment une notion de «numéros de ligne». Cependant, il a une notion de tâches complètes, c'est-à-dire de tâches de haut niveau. On pourrait, par exemple, définir facilement un gestionnaire de tâches pour indiquer à l'un quelle tâche de niveau supérieur a échoué. Bien sûr, ce n'est pas très confortable pour ceux qui ont de grandes chaînes ou de grandes déclarations conditionnelles.
russellpierce

Réponses:

45

Cela ne vous donnera pas le numéro de ligne, mais cela vous dira où l'échec se produit dans la pile d'appels, ce qui est très utile:

traceback()

[Edit:] Lors de l'exécution d'un script à partir de la ligne de commande, vous devrez sauter un ou deux appels, voir traceback () pour les sessions R interactives et non interactives

Je ne connais pas d'autre moyen de le faire sans les suspects de débogage habituels:

  1. déboguer()
  2. navigateur()
  3. options (error = recover) [suivi des options (error = NULL) pour le restaurer]

Vous voudrez peut-être consulter cet article lié

[Edit:] Désolé ... je viens de voir que vous exécutez ceci à partir de la ligne de commande. Dans ce cas, je suggérerais de travailler avec la fonctionnalité d'options (erreur). Voici un exemple simple:

options(error = quote({dump.frames(to.file=TRUE); q()}))

Vous pouvez créer un script aussi élaboré que vous le souhaitez en cas d'erreur, vous devez donc simplement décider des informations dont vous avez besoin pour le débogage.

Sinon, s'il y a des domaines spécifiques qui vous préoccupent (par exemple la connexion à une base de données), enveloppez-les dans une fonction tryCatch ().

Shane
la source
La solution liée dans le premier bloc [Edit:] fonctionne pour moi. La meilleure approche semble être le commentaire de @dshepherd, c'est-à-dire add options(error=function() { traceback(2); if(!interactive()) quit("no", status = 1, runLast = FALSE) })(voir commentaire de la réponse acceptée). Je pense qu'il serait logique de l'ajouter à la réponse ici plutôt que de fournir uniquement un lien vers un autre fil.
cryo111
1
une nouvelle option qui vous permet d'obtenir les numéros de ligne dans le traceback github.com/aryoda/tryCatchLog
lunguini
13

Faire options(error=traceback)fournit un peu plus d'informations sur le contenu des lignes menant à l'erreur. Il provoque l'apparition d'une trace en cas d'erreur et pour certaines erreurs, le numéro de ligne est précédé du préfixe #. Mais c'est aléatoire, de nombreuses erreurs n'obtiendront pas de numéros de ligne.

Hugh Perkins
la source
2
Cela ne fonctionne pas tout à fait pour moi. Je n'ai qu'un seul fichier, et il n'affiche pas le numéro de ligne, il dit juste No traceback availableaprès l'erreur.
Mark Lakata
11

Le support pour cela sera disponible dans R 2.10 et versions ultérieures. Duncan Murdoch vient de poster sur r-devel le 10 septembre 2009 à propos de findLineNum et setBreapoint :

Je viens d'ajouter quelques fonctions à R-devel pour aider au débogage. findLineNum()trouve quelle ligne de quelle fonction correspond à une ligne particulière de code source; setBreakpoint()prend la sortie de findLineNumet appelle trace()pour y définir un point d'arrêt.

Ceux-ci reposent sur la présence d'informations de débogage de référence source dans le code. Il s'agit de la valeur par défaut pour le code lu par source(), mais pas pour les packages. Pour obtenir les références source dans le code du package, définissez la variable d'environnement R_KEEP_PKG_SOURCE=yes, ou dans R, définissez options(keep.source.pkgs=TRUE), puis installez le package à partir du code source. Lisez ?findLineNumpour plus de détails sur la façon de lui dire de rechercher dans les packages, plutôt que de limiter la recherche à l'environnement global.

Par exemple,

x <- " f <- function(a, b) {
             if (a > b)  {
                 a
             } else {
                 b
             }
         }"


eval(parse(text=x))  # Normally you'd use source() to read a file...

findLineNum("<text>#3")   # <text> is a dummy filename used by
parse(text=)

Cela imprimera

 f step 2,3,2 in <environment: R_GlobalEnv>

et vous pouvez utiliser

setBreakpoint("<text>#3")

pour y définir un point d'arrêt.

Il y a encore quelques limitations (et probablement des bogues) dans le code; Je vais réparer ça

Dirk Eddelbuettel
la source
Merci. Inscrivez-vous aussi à la liste de diffusion r-devel. J'évite r-help en supposant que cela obstruerait ma boîte de réception (r-sig-finance le fait déjà).
Shane
1
Je ne comprends pas vraiment comment cela fonctionne à partir de la ligne de commande sans fouiller dans le script R
Herman Toothrot
1
@hirse: C'est presque dix votre ancienne réponse. Pourquoi diable l'avez-vous reformaté pour faire comme si je citais? Je ne l'étais pas, et votre changement ne reflète pas mon intention.
Dirk Eddelbuettel
"Duncan Murdoch vient de publier:" ressemble beaucoup à une citation, mais si ce n'est pas correct, veuillez annuler la modification. Je voulais le rendre plus lisible pour moi-même et je n'ai pas vérifié la date avant d'avoir terminé. Si toute la réponse est trop obsolète, vous pouvez également la supprimer pour dissiper toute confusion pour les futurs lecteurs.
hirse
Pouvez-vous s'il vous plaît revenir en arrière? Je vous remercie.
Dirk Eddelbuettel
6

Vous le faites en définissant

options(show.error.locations = TRUE)

Je me demande simplement pourquoi ce paramètre n'est pas une valeur par défaut dans R? Cela devrait être, comme dans toutes les autres langues.

TMS
la source
1
Pour des informations générales sur cette option, voir stat.ethz.ch/R-manual/R-devel/library/base/html/options.html
R Yoda
1
Cela fonctionnait auparavant, mais a été désactivé car il n'est pas fiable. Je pense que c'est une tentative de vous forcer à utiliser le RStudio qui sera finalement non libre.
Eric Leschinski
6
J'en doute. R core et RStudio sont des organisations très différentes, et R core en particulier sont de fervents open-sourcers.
Ben Bolker
A travaillé sur CentOS 6.9, R-3.4.2
irritable_phd_syndrom
Cela vaut peut-être la peine de mentionner que vous devez définir les options à l'avance, avant de rechercher du code.
JAponte
3

La spécification de l'option R globale pour la gestion des erreurs non catastrophiques a fonctionné pour moi, ainsi qu'un flux de travail personnalisé pour conserver les informations sur l'erreur et examiner ces informations après l'échec. J'utilise actuellement la version 3.4.1 de R. Ci-dessous, j'ai inclus une description du flux de travail qui a fonctionné pour moi, ainsi que du code que j'ai utilisé pour définir l'option de gestion d'erreur globale dans R.

Comme je l'ai configuré, la gestion des erreurs crée également un fichier RData contenant tous les objets de la mémoire de travail au moment de l'erreur. Cette sauvegarde peut être relue dans R en utilisant load(), puis les différents environnements tels qu'ils existaient au moment de l'erreur peuvent être inspectés de manière interactive à l'aide de debugger(errorDump).

Je noterai que j'ai pu obtenir des numéros de ligne dans la traceback()sortie de toutes les fonctions personnalisées de la pile, mais uniquement si j'ai utilisé l' keep.source=TRUEoption lors de l'appel source()de toutes les fonctions personnalisées utilisées dans mon script. Sans cette option, la définition de l'option de gestion globale des erreurs comme ci-dessous a envoyé la sortie complète du traceback()à un journal d'erreurs nommé error.log, mais les numéros de ligne n'étaient pas disponibles.

Voici les étapes générales que j'ai suivies dans mon flux de travail et comment j'ai pu accéder au vidage de la mémoire et au journal des erreurs après un échec R non interactif.

  1. J'ai mis ce qui suit en haut du script principal que j'appelais depuis la ligne de commande. Cela définit l'option globale de gestion des erreurs pour la session R. Mon script principal a été appelé myMainScript.R. Les différentes lignes du code sont suivies de commentaires décrivant ce qu'elles font. Fondamentalement, avec cette option, lorsque R rencontre une erreur qui se déclenche stop(), il crée un fichier de vidage RData (* .rda) de la mémoire de travail dans tous les environnements actifs du répertoire ~/myUsername/directoryForDumpet écrira également un journal d'erreurs nommé error.logavec des informations utiles dans le même répertoire. Vous pouvez modifier cet extrait de code pour ajouter une autre gestion en cas d'erreur (par exemple, ajouter un horodatage au fichier de vidage et les noms de fichiers du journal des erreurs, etc.).

    options(error = quote({
      setwd('~/myUsername/directoryForDump'); # Set working directory where you want the dump to go, since dump.frames() doesn't seem to accept absolute file paths.
      dump.frames("errorDump", to.file=TRUE, include.GlobalEnv=TRUE); # First dump to file; this dump is not accessible by the R session.
      sink(file="error.log"); # Specify sink file to redirect all output.
      dump.frames(); # Dump again to be able to retrieve error message and write to error log; this dump is accessible by the R session since not dumped to file.
      cat(attr(last.dump,"error.message")); # Print error message to file, along with simplified stack trace.
      cat('\nTraceback:');
      cat('\n');
      traceback(2); # Print full traceback of function calls with all parameters. The 2 passed to traceback omits the outermost two function calls.
      sink();
      q()}))
  2. Assurez-vous qu'à partir du script principal et des appels de fonction ultérieurs, à chaque fois qu'une fonction est générée, l'option keep.source=TRUEest utilisée. Autrement dit, pour rechercher une fonction, vous utiliseriez source('~/path/to/myFunction.R', keep.source=TRUE). Ceci est nécessaire pour que la traceback()sortie contienne des numéros de ligne. Il semble que vous puissiez également définir cette option globalement en utilisant options( keep.source=TRUE ), mais je n'ai pas testé cela pour voir si cela fonctionne. Si vous n'avez pas besoin de numéros de ligne, vous pouvez omettre cette option.

  3. Depuis le terminal (en dehors de R), appelez le script principal en mode batch en utilisant Rscript myMainScript.R. Cela démarre une nouvelle session R non interactive et exécute le script myMainScript.R. L'extrait de code donné à l'étape 1 qui a été placé en haut de myMainScript.Rdéfinit l'option de gestion des erreurs pour la session R non interactive.
  4. Rencontrez une erreur quelque part dans l'exécution de myMainScript.R. Cela peut être dans le script principal lui-même ou imbriqué plusieurs fonctions en profondeur. Lorsque l'erreur est rencontrée, la gestion sera effectuée comme spécifié à l'étape 1 et la session R se terminera.
  5. Un fichier de vidage RData nommé errorDump.rdaet et le journal des erreurs nommé error.logsont créés dans le répertoire spécifié par '~/myUsername/directoryForDump'dans le paramètre de l'option de gestion des erreurs globale.
  6. À votre guise, inspectez error.logpour examiner les informations sur l'erreur, y compris le message d'erreur lui-même et la trace de pile complète menant à l'erreur. Voici un exemple du journal généré en cas d'erreur; notez que les numéros après le #caractère sont les numéros de ligne de l'erreur à différents points de la pile d'appels:

    Error in callNonExistFunc() : could not find function "callNonExistFunc"
    Calls: test_multi_commodity_flow_cmd -> getExtendedConfigDF -> extendConfigDF
    
    Traceback:
    3: extendConfigDF(info_df, data_dir = user_dir, dlevel = dlevel) at test_multi_commodity_flow.R#304
    2: getExtendedConfigDF(config_file_path, out_dir, dlevel) at test_multi_commodity_flow.R#352
    1: test_multi_commodity_flow_cmd(config_file_path = config_file_path, 
    spot_file_path = spot_file_path, forward_file_path = forward_file_path, 
    data_dir = "../", user_dir = "Output", sim_type = "spot", 
    sim_scheme = "shape", sim_gran = "hourly", sim_adjust = "raw", 
    nsim = 5, start_date = "2017-07-01", end_date = "2017-12-31", 
    compute_averages = opt$compute_averages, compute_shapes = opt$compute_shapes, 
    overwrite = opt$overwrite, nmonths = opt$nmonths, forward_regime = opt$fregime, 
    ltfv_ratio = opt$ltfv_ratio, method = opt$method, dlevel = 0)
  7. À votre guise, vous pouvez charger errorDump.rdadans une session R interactive en utilisant load('~/path/to/errorDump.rda'). Une fois chargé, appelez debugger(errorDump)pour parcourir tous les objets R en mémoire dans l'un des environnements actifs. Voir l'aide R sur debugger()pour plus d'informations.

Ce flux de travail est extrêmement utile lors de l'exécution de R dans un certain type d'environnement de production où des sessions R non interactives sont lancées sur la ligne de commande et que vous souhaitez conserver des informations sur les erreurs inattendues. La possibilité de vider la mémoire dans un fichier que vous pouvez utiliser pour inspecter la mémoire de travail au moment de l'erreur, ainsi que les numéros de ligne de l'erreur dans la pile d'appels, facilitent le débogage post-mortem rapide de la cause de l'erreur.

bmosov01
la source
0

D'abord, options(show.error.locations = TRUE)et ensuite traceback(). Le numéro de la ligne d'erreur sera affiché après #

den2042
la source