422 votes

Comment supprimer tous les appels de journalisation de débogage avant de générer la version de diffusion d'une application Android?

Selon Google, je dois "désactiver tout appel aux méthodes Log dans le code source" avant de publier mon application Android sur Google Play. Extrait de la section 3 de la liste de contrôle de publication:

Assurez-vous de désactiver le journalisation et de désactiver l'option de débogage avant de compiler votre application pour la publication. Vous pouvez désactiver la journalisation en supprimant les appels aux méthodes Log dans vos fichiers source.

Mon projet open source est assez volumineux et il est pénible de le faire manuellement à chaque fois que je publie. De plus, supprimer une ligne Log est potentiellement difficile, par exemple:

if(condition)
  Log.d(LOG_TAG, "Quelque chose");
data.load();
data.show();

Si je commente la ligne Log, alors la condition s'applique à la ligne suivante, et il y a des chances que load() ne soit pas appelé. Ces situations sont-elles assez rares pour que je puisse décider qu'elles ne devraient pas exister?

Alors, y a-t-il une meilleure façon au niveau du code source de le faire? Ou peut-être une syntaxe ProGuard astucieuse pour supprimer efficacement mais en toute sécurité toutes les lignes Log?

2 votes

+1 car je ne me rappelais pas que cela figurait dans la liste de publication.

52 votes

Pour commenter une ligne non bloquée, j'utilise ";//" au lieu de "//".

0 votes

Si vous avez besoin de pouvoir annuler cela, vous voudrez probablement utiliser sed 's_^\(\s*Journal\.\)_;//'`date|tr -s \ -`'\1_g' à la place.

514voto

Christopher Orr Points 58514

Je trouve qu'une solution beaucoup plus facile est de simplement oublier tous les vérifications if partout et d'utiliser simplement ProGuard pour supprimer tous les appels des méthodes Log.d() ou Log.v() lorsque nous appelons notre cible Ant release.

De cette façon, nous avons toujours les informations de débogage affichées pour les constructions régulières et nous n'avons pas à apporter de modifications au code pour les constructions de version. ProGuard peut également effectuer plusieurs passes sur le bytecode pour supprimer d'autres déclarations indésirables, blocs vides et peut automatiquement insérer en ligne des méthodes courtes lorsque c'est approprié.

Par exemple, voici une configuration ProGuard très basique pour Android :

-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5

-keep class * extends android.app.Activity
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}

Donc vous enregistrez cela dans un fichier, puis appelez ProGuard depuis Ant, en passant votre JAR fraîchement compilé et le JAR de plateforme Android que vous utilisez.

Voir aussi les exemples dans le manuel de ProGuard.


Mise à jour (4,5 ans plus tard) : De nos jours j'utilise Timber pour les journaux Android.

Non seulement c'est un peu plus agréable que l'implémentation par défaut de Log — le tag de journalisation est défini automatiquement, et il est facile de journaliser des chaînes formatées et des exceptions — mais vous pouvez également spécifier différents comportements de journalisation à l'exécution.

Dans cet exemple, les déclarations de journalisation ne seront écrites que dans logcat dans les constructions de débogage de mon application :

Timber est configuré dans la méthode onCreate() de mon Application :

if (BuildConfig.DEBUG) {
  Timber.plant(new Timber.DebugTree());
}

Ensuite, n'importe où ailleurs dans mon code je peux facilement journaliser :

Timber.d("Téléchargement de l'URL : %s", url);
try {
  // ...
} catch (IOException ioe) {
  Timber.e(ioe, "Des choses mauvaises sont arrivées !");
}

Voir l'application d'exemple de Timber pour un exemple plus avancé, où toutes les déclarations de journalisation sont envoyées à logcat pendant le développement et, en production, aucune déclaration de débogage n'est journalisée, mais les erreurs sont signalées silencieusement à Crashlytics.

0 votes

Pour Android, n'est-ce pas ? Votre projet est-il open source ? Pourrais-je obtenir votre fichier Ant quelque part ? :-) Merci beaucoup !

0 votes

J'ai ajouté un fichier de configuration de base que vous pouvez utiliser lors de l'appel de ProGuard à partir d'une cible Ant.

68 votes

Et pourquoi n'est-ce pas dans le fichier proguard par défaut?

124voto

Reiner Points 623

Toutes les bonnes réponses, mais lorsque j'ai terminé mon développement, je ne voulais pas utiliser des déclarations conditionnelles autour de tous les appels Log, et je ne voulais pas non plus utiliser d'outils externes.

La solution que j'utilise est donc de remplacer la classe android.util.Log par ma propre classe Log :

public class Log {
    static final boolean LOG = BuildConfig.DEBUG;

    public static void i(String tag, String string) {
        if (LOG) android.util.Log.i(tag, string);
    }
    public static void e(String tag, String string) {
        if (LOG) android.util.Log.e(tag, string);
    }
    public static void d(String tag, String string) {
        if (LOG) android.util.Log.d(tag, string);
    }
    public static void v(String tag, String string) {
        if (LOG) android.util.Log.v(tag, string);
    }
    public static void w(String tag, String string) {
        if (LOG) android.util.Log.w(tag, string);
    }
}

La seule chose que j'ai dû faire dans tous les fichiers source était de remplacer l'import de android.util.Log par ma propre classe.

144 votes

Le seul problème avec cette approche est que, si vous faites Log.d("tag", "Traité : " + new ItemCounter(blabla) + " éléments "), même si ce message de journal ne s'affiche pas dans votre version publiée, un StringBuilder est utilisé pour créer le message, ce qui pourrait être coûteux à créer.

9 votes

Cette solution a un gros problème. espinchi a mentionné juste la pointe de l'iceberg. Le problème est que lorsque vous appelez Log.d("tag", someValue.toString()); il est très facile d'oublier de vérifier que someValue n'est pas nul, ce qui signifie qu'il pourrait provoquer une NullPointerException en production. Il suggère une solution sécurisée mais elle vous trompera. Nous utilisons un private static boolean DEBUG et ensuite if(DEBUG) Log.d(TAG, msg);

0 votes

Est-ce que ProGuard (avec les paramètres par défaut) détecterait ces appels vides à Log.d() comme du code inutilisé et les supprimerait ?

61voto

hackbod Points 55292

Je suggère d'avoir un boolean statique quelque part indiquant s'il faut ou non enregistrer :

class MyDebug {
  static final boolean LOG = true;
}

Ensuite, partout où vous voulez enregistrer dans votre code, faites simplement ceci :

if (MyDebug.LOG) {
  if (condition) Log.i(...);
}

Maintenant, lorsque vous définissez MyDebug.LOG sur false, le compilateur supprimera tout le code à l'intérieur de telles vérifications (puisqu'il s'agit d'un final statique, il sait au moment de la compilation que le code n'est pas utilisé).

Pour des projets plus importants, vous voudrez peut-être commencer à avoir des booleans dans des fichiers individuels pour pouvoir activer ou désactiver facilement le journalisation au besoin. Par exemple, voici les différentes constantes de journalisation que nous avons dans le gestionnaire de fenêtres :

static final String TAG = "WindowManager";
static final boolean DEBUG = false;
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_ANIM = false;
static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_RESIZE = false;
static final boolean DEBUG_LAYERS = false;
static final boolean DEBUG_INPUT = false;
static final boolean DEBUG_INPUT_METHOD = false;
static final boolean DEBUG_VISIBILITY = false;
static final boolean DEBUG_WINDOW_MOVEMENT = false;
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
static final boolean MEASURE_LATENCY = false;

Avec un code correspondant comme :

    if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v(
        TAG, "Ajout de la fenêtre " + fenêtre + " à "
        + (i+1) + " de " + mWindows.size() + " (après " + pos + ")");

1 votes

Je voterais également pour une telle approche. Elle est également utilisée dans l'exemple officiel de la facturation intégrée de Google.

4 votes

Ne serait-il pas moins verbeux de passer la condition en premier paramètre ?

1 votes

Il semble que ce soit la meilleure solution, bien qu'elle nécessite un code supplémentaire sur chaque instruction de journalisation : Les numéros de ligne sont conservés (faiblesse de l'approche ProGuard), Aucun code pour créer le message de journalisation n'est exécuté (faiblesse de l'approche de la classe wrapper et apparemment aussi de l'approche de la bibliothèque de journalisation). L'utilisation de cette approche dans l'exemple de facturation in-app de Google selon @LA_ soutient également mes réflexions.

30voto

Nicolas Raoul Points 13912

La solution Proguard de Christopher est la meilleure, mais si pour une raison quelconque vous n'aimez pas Proguard, voici une solution très basique:

Commenter les journaux:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/Log\./;\/\/ Log\./g'

Décommenter les journaux:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/;\/\/ Log\./Log\./g'

Une contrainte est que vos instructions de journalisation ne doivent pas s'étendre sur plusieurs lignes.

(Exécutez ces lignes dans un shell UNIX à la racine de votre projet. Si vous utilisez Windows, obtenez une couche UNIX ou utilisez des commandes équivalentes pour Windows)

1 votes

Besoin d'un "" après le -i dans Sed si vous exécutez sur Mac (comme indiqué ici ) Merci.

0 votes

Je pense que c'est probablement ce que je vais finir par utiliser pour quelque chose sur lequel je travaille car je n'ai pas eu beaucoup de chance en le faisant avec Proguard du tout.

0 votes

Et si vous avez un Log après une branche while non entre crochets, comme vous l'avez suggéré dans votre premier post?

7voto

Zvi Points 179

Je considérerais d'utiliser la fonctionnalité de journalisation de roboguice au lieu de android.util.Log intégré

Leur fonctionnalité désactive automatiquement les journaux de débogage et verbeux pour les versions finales. De plus, vous obtenez gratuitement quelques fonctionnalités intéressantes (par exemple, un comportement de journalisation personnalisable, des données supplémentaires pour chaque journal, etc.)

L'utilisation de proguard pourrait être assez contraignante et je ne ferais pas les efforts de le configurer et de le faire fonctionner avec votre application à moins d'avoir une bonne raison pour cela (désactiver les journaux n'en est pas une bonne)

0 votes

Une approche très sympathique lorsque vous ne pouvez pas utiliser l'obscurcissement.... particulièrement à cause du roboguice qui casse à cause de proguard LOL

1 votes

Lien mis à jour pour l'installation de journalisation de Robojuice : github.com/roboguice/roboguice/wiki/Logging-via-Ln

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X