Bagaimana cara mengaktifkan / menonaktifkan level log di Android?

149

Saya memiliki banyak pernyataan logging untuk debug misalnya.

Log.v(TAG, "Message here");
Log.w(TAG, " WARNING HERE");

saat menggunakan aplikasi ini di ponsel perangkat saya ingin mematikan logging verbose dari mana saya dapat mengaktifkan / menonaktifkan logging.

b-man
sumber
Kemungkinan duplikat level pencatatan Android

Jawaban:

80

Cara yang umum adalah membuat int bernama loglevel, dan menentukan level debug berdasarkan loglevel.

public static int LOGLEVEL = 2;
public static boolean ERROR = LOGLEVEL > 0;
public static boolean WARN = LOGLEVEL > 1;
...
public static boolean VERBOSE = LOGLEVEL > 4;

    if (VERBOSE) Log.v(TAG, "Message here"); // Won't be shown
    if (WARN) Log.w(TAG, "WARNING HERE");    // Still goes through

Kemudian, Anda bisa mengubah LOGLEVEL untuk semua level output debug.

Cytown
sumber
1
bagus, tetapi bagaimana Anda menonaktifkan DEBUG dalam contoh Anda, tetapi masih menampilkan peringatan ....
Andre Bossard
1
Bukankah pernyataan if berakhir pada kode byte .apk? Saya pikir kami ingin (umumnya) mematikan logging ketika aplikasi dikerahkan tetapi pernyataan if tidak akan dihapus.
chessofnerd
2
dalam contoh Anda, pesan DEBUG akan ditampilkan, sementara WARNs tidak akan? tidakkah Anda biasanya ingin sebaliknya?
Sam
15
Gunakan BuildConfig.DEBUG sebagai ganti variabel khusus
hB0
1
@chessofnerd "di Jawa, kode di dalam if bahkan tidak akan menjadi bagian dari kode yang dikompilasi. Itu harus dikompilasi, tetapi tidak akan ditulis ke bytecode yang dikompilasi." stackoverflow.com/questions/7122723/…
stoooops
197

The Dokumentasi Android mengatakan berikut tentang Log Tingkat :

Verbose tidak boleh dikompilasi ke dalam aplikasi kecuali selama pengembangan. Debug log dikompilasi tetapi dilucuti saat runtime. Kesalahan, peringatan dan log info selalu disimpan.

Jadi, Anda mungkin ingin mempertimbangkan menghapus pernyataan log Verbose logout, mungkin menggunakan ProGuard seperti yang disarankan dalam jawaban lain .

Menurut dokumentasi, Anda dapat mengonfigurasi pencatatan pada perangkat pengembangan menggunakan Properti Sistem. Properti untuk set log.tag.<YourTag>dan harus ditetapkan ke salah satu dari nilai berikut: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, atau SUPPRESS. Informasi lebih lanjut tentang ini tersedia dalam dokumentasi untuk isLoggable()metode ini.

Anda dapat mengatur properti sementara menggunakan setpropperintah. Sebagai contoh:

C:\android>adb shell setprop log.tag.MyAppTag WARN
C:\android>adb shell getprop log.tag.MyAppTag
WARN

Atau, Anda dapat menentukannya di file '/ data / local.prop' sebagai berikut:

log.tag.MyAppTag=WARN

Versi Android yang lebih baru tampaknya mengharuskan / data / lokal.prop hanya dibaca . File ini dibaca saat boot sehingga Anda harus memulai ulang setelah memperbaruinya. Jika/data/local.prop dunia dapat ditulis, itu kemungkinan akan diabaikan.

Akhirnya, Anda dapat mengaturnya secara terprogram menggunakan System.setProperty()metode ini .

Dave Webb
sumber
4
Saya memiliki pengalaman yang sama; dokumen API cukup tidak jelas tentang bagaimana ini seharusnya bekerja dan bahkan tampaknya menyebutkan sebagian besar android.util.Configkonstanta yang ditinggalkan. Nilai-nilai hardcoded yang ditentukan dalam API API tidak berguna karena ini (seharusnya) bervariasi berdasarkan build. Karenanya rute ProGuard sepertinya solusi terbaik bagi kami.
Christopher Orr
3
Pernahkah Anda beruntung mengkonfigurasi Android logging menggunakan file /data/local.prop, metode setprop, atau dengan System.setProperty? Saya mengalami sedikit kesulitan mendapatkan Log.isLoggable (TAG, VERBOSE) untuk mengembalikan true bagi saya.
seanoshea
2
Saya membuat debugging Android berfungsi. Kuncinya adalah ketika Anda memanggil sesuatu seperti Log.d ("xyz") pesan ditulis ke logcat bahkan jika debug dinonaktifkan untuk logger. Ini berarti penyaringan umumnya terjadi setelah ditulis. Untuk memfilter sebelum sesuatu seperti Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, "pesan log saya"); } diperlukan. Ini biasanya cukup melelahkan. Saya menggunakan versi modifikasi dari slf4j-android untuk mendapatkan yang saya inginkan.
phreed
2
@ Pernahkah Anda bisa membuat metode local.prop bekerja dengan benar. Saya juga tidak dapat melakukan ini, saya telah membuat entri log.tag.test = INFO dan kemudian mencoba mengubahnya dengan menjalankan setprop log.tag.test SUPPRESS dari adb shell dan tidak mengubah apa pun. Juga menggunakan System.getProperty dan System.setProperty tidak melakukan apa-apa. Ingin mendapat pembaruan dari Anda. Terima kasih.
JJNford
2
+1 untuk komentar "Dokumentasi API sangat tidak jelas tentang bagaimana ini seharusnya bekerja".
Alan
90

Cara termudah adalah menjalankan JAR yang dikompilasi melalui ProGuard sebelum penerapan, dengan konfigurasi seperti:

-assumenosideeffects class android.util.Log {
    public static int v(...);
}

Itu akan - terlepas dari semua optimisasi ProGuard lainnya - menghapus pernyataan log verbose langsung dari bytecode.

Christopher Orr
sumber
apakah itu berisi file log.property di mana kita dapat menetapkan pengaturan.
d-man
1
menghapus garis dengan proguard berarti bahwa jejak tumpukan Anda dari produksi mungkin tidak sejalan dengan kode Anda.
larham1
3
@ larham1: ProGuard bekerja pada bytecode, jadi saya akan membayangkan menghapus panggilan logging tidak akan mengubah metadata nomor baris yang disematkan.
Christopher Orr
19
Pikirkan ini - bahkan jika panggilan aktual ke Log.v () sedang dilucuti, argumennya masih dievaluasi. Jadi, jika Anda memiliki beberapa panggilan metode mahal di dalam, misalnya Log.v (TAG, generateLog ()) mungkin akan merusak kinerja Anda jika ada dalam beberapa jalur kode panas. Bahkan hal-hal seperti toString () atau String.format () dapat menjadi signifikan.
Błażej Czapp
4
@ GaneshKrishnan Tidak, itu tidak benar. Panggilan ke Log.v () dilucuti tetapi, secara default, metode panggilan untuk membangun string tidak akan dihapus. Lihat jawaban ini dari penulis ProGuard: stackoverflow.com/a/6023505/234938
Christopher Orr
18

Saya mengambil rute sederhana - membuat kelas wrapper yang juga menggunakan daftar parameter variabel.

 public class Log{
        public static int LEVEL = android.util.Log.WARN;


    static public void d(String tag, String msgFormat, Object...args)
    {
        if (LEVEL<=android.util.Log.DEBUG)
        {
            android.util.Log.d(tag, String.format(msgFormat, args));
        }
    }

    static public void d(String tag, Throwable t, String msgFormat, Object...args)
    {
        if (LEVEL<=android.util.Log.DEBUG)
        {
            android.util.Log.d(tag, String.format(msgFormat, args), t);
        }
    }

    //...other level logging functions snipped
kdahlhaus
sumber
1
Seperti yang saya sebutkan di atas. Saya telah menggunakan versi modifikasi dari slf4j-android untuk menerapkan teknik ini.
phreed
3
Ada kekhawatiran besar tentang hal itu, lihat stackoverflow.com/questions/2446248/…
OneWorld
10

Cara yang lebih baik adalah dengan menggunakan SLF4J API + beberapa implementasinya.

Untuk aplikasi Android Anda dapat menggunakan yang berikut:

  1. Android Logger adalah implementasi SLF4J yang ringan namun mudah dikonfigurasi (<50 Kb).
  2. LOGBack adalah implementasi yang paling kuat dan optimal tetapi ukurannya sekitar 1 Mb.
  3. Lainnya sesuai selera Anda: slf4j-android, slf4android.
Fortess Nsk
sumber
2
Di Android, Anda harus menggunakan logback-android(karena yang logbacktepat tidak kompatibel). logback-android-1.0.10-1.jaradalah 429 KB, yang tidak terlalu buruk mengingat fitur yang disediakan, tetapi sebagian besar pengembang akan menggunakan Proguard untuk mengoptimalkan aplikasi mereka.
tony19
Ini tidak menyelamatkan Anda dari menggunakan pernyataan if untuk memeriksa level log sebelum login. Lihat stackoverflow.com/questions/4958860/…
OneWorld
8

Kamu harus menggunakan

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }
Diego Torres Milano
sumber
2
Bagaimana cara mengkonfigurasi output isLoggable? Apakah debug dan verbose tidak masuk jika isDebugable diset false dalam manifes?
OneWorld
5

Menghapus penebangan dengan proguard (lihat jawaban dari @Christopher) mudah dan cepat, tetapi itu menyebabkan tumpukan jejak dari produksi ke ketidakcocokan sumber jika ada debug logging di file.

Alih-alih, inilah teknik yang menggunakan tingkat penebangan yang berbeda dalam pengembangan vs produksi, dengan asumsi bahwa proguard hanya digunakan dalam produksi. Ia mengenali produksi dengan melihat apakah proguard telah mengubah nama kelas yang diberikan (dalam contoh, saya menggunakan "com.foo.Bar" - Anda akan menggantinya dengan nama kelas yang sepenuhnya memenuhi syarat yang Anda tahu akan diganti namanya dengan proguard).

Teknik ini memanfaatkan logging umum.

private void initLogging() {
    Level level = Level.WARNING;
    try {
        // in production, the shrinker/obfuscator proguard will change the
        // name of this class (and many others) so in development, this
        // class WILL exist as named, and we will have debug level
        Class.forName("com.foo.Bar");
        level = Level.FINE;
    } catch (Throwable t) {
        // no problem, we are in production mode
    }
    Handler[] handlers = Logger.getLogger("").getHandlers();
    for (Handler handler : handlers) {
        Log.d("log init", "handler: " + handler.getClass().getName());
        handler.setLevel(level);
    }
}
larham1
sumber
3

Ada pengganti drop-in kecil untuk kelas Log android standar - https://github.com/zserge/log

Pada dasarnya yang harus Anda lakukan adalah mengganti impor dari android.util.Logmenjadi trikita.log.Log. Kemudian di Anda Application.onCreate()atau di beberapa pemeriksa statis memeriksa untuk BuilConfig.DEBUGatau bendera lain dan menggunakan Log.level(Log.D)atau Log.level(Log.E)untuk mengubah tingkat log minimal. Anda dapat menggunakan Log.useLog(false)untuk menonaktifkan logging sama sekali.

Eric Weyant
sumber
2

Mungkin Anda dapat melihat kelas ekstensi Log ini: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Ini memungkinkan Anda untuk memiliki kontrol yang baik pada log. Misalnya Anda dapat menonaktifkan semua log atau hanya log dari beberapa paket atau kelas.

Selain itu, ia menambahkan beberapa fungsi yang berguna (misalnya Anda tidak harus melewati tag untuk setiap log).

Keledai
sumber
2

Saya membuat Utilitas / Pembungkus yang memecahkan masalah ini + masalah umum lainnya di sekitar Penebangan.

Utilitas Debugging dengan fitur-fitur berikut:

  • Fitur biasa yang disediakan oleh kelas Log yang dibungkus oleh LogMode s.
  • Metode Entri-Keluar log: Dapat dimatikan dengan sakelar
  • Debugging Selektif: Debug kelas khusus.
  • Metode Eksekusi-Pengukuran Waktu: Mengukur waktu Eksekusi untuk metode individu serta waktu kolektif yang dihabiskan untuk semua metode kelas.

Cara Penggunaan?

  • Sertakan kelas dalam proyek Anda.
  • Gunakan itu seperti Anda menggunakan metode android.util.Log, untuk memulai.
  • Gunakan fitur Entri-Keluar log dengan menempatkan panggilan ke metode entry_log () - exit_log () di awal dan akhir metode di aplikasi Anda.

Saya telah mencoba membuat dokumentasi mandiri.

Saran untuk meningkatkan Utilitas ini dipersilakan.

Bebas digunakan / dibagikan.

Unduh dari GitHub .

Vinay W
sumber
2

Berikut ini solusi yang lebih kompleks. Anda akan mendapatkan jejak tumpukan penuh dan metode toString () akan dipanggil hanya jika diperlukan (Kinerja). Atribut BuildConfig.DEBUG akan salah dalam mode produksi sehingga semua jejak dan log debug akan dihapus. Kompilator hot spot memiliki kesempatan untuk menghapus panggilan karena sifat statis final.

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import android.util.Log;

public class Logger {

    public enum Level {
        error, warn, info, debug, trace
    }

    private static final String DEFAULT_TAG = "Project";

    private static final Level CURRENT_LEVEL = BuildConfig.DEBUG ? Level.trace : Level.info;

    private static boolean isEnabled(Level l) {
        return CURRENT_LEVEL.compareTo(l) >= 0;
    }

    static {
        Log.i(DEFAULT_TAG, "log level: " + CURRENT_LEVEL.name());
    }

    private String classname = DEFAULT_TAG;

    public void setClassName(Class<?> c) {
        classname = c.getSimpleName();
    }

    public String getClassname() {
        return classname;
    }

    public boolean isError() {
        return isEnabled(Level.error);
    }

    public boolean isWarn() {
        return isEnabled(Level.warn);
    }

    public boolean isInfo() {
        return isEnabled(Level.info);
    }

    public boolean isDebug() {
        return isEnabled(Level.debug);
    }

    public boolean isTrace() {
        return isEnabled(Level.trace);
    }

    public void error(Object... args) {
        if (isError()) Log.e(buildTag(), build(args));
    }

    public void warn(Object... args) {
        if (isWarn()) Log.w(buildTag(), build(args));
    }

    public void info(Object... args) {
        if (isInfo()) Log.i(buildTag(), build(args));
    }

    public void debug(Object... args) {
        if (isDebug()) Log.d(buildTag(), build(args));
    }

    public void trace(Object... args) {
        if (isTrace()) Log.v(buildTag(), build(args));
    }

    public void error(String msg, Throwable t) {
        if (isError()) error(buildTag(), msg, stackToString(t));
    }

    public void warn(String msg, Throwable t) {
        if (isWarn()) warn(buildTag(), msg, stackToString(t));
    }

    public void info(String msg, Throwable t) {
        if (isInfo()) info(buildTag(), msg, stackToString(t));
    }

    public void debug(String msg, Throwable t) {
        if (isDebug()) debug(buildTag(), msg, stackToString(t));
    }

    public void trace(String msg, Throwable t) {
        if (isTrace()) trace(buildTag(), msg, stackToString(t));
    }

    private String buildTag() {
        String tag ;
        if (BuildConfig.DEBUG) {
            StringBuilder b = new StringBuilder(20);
            b.append(getClassname());

            StackTraceElement stackEntry = Thread.currentThread().getStackTrace()[4];
            if (stackEntry != null) {
                b.append('.');
                b.append(stackEntry.getMethodName());
                b.append(':');
                b.append(stackEntry.getLineNumber());
            }
            tag = b.toString();
        } else {
            tag = DEFAULT_TAG;
        }
    }

    private String build(Object... args) {
        if (args == null) {
            return "null";
        } else {
            StringBuilder b = new StringBuilder(args.length * 10);
            for (Object arg : args) {
                if (arg == null) {
                    b.append("null");
                } else {
                    b.append(arg);
                }
            }
            return b.toString();
        }
    }

    private String stackToString(Throwable t) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(500);
        baos.toString();
        t.printStackTrace(new PrintStream(baos));
        return baos.toString();
    }
}

gunakan seperti ini:

Loggor log = new Logger();
Map foo = ...
List bar = ...
log.error("Foo:", foo, "bar:", bar);
// bad example (avoid something like this)
// log.error("Foo:" + " foo.toString() + "bar:" + bar); 
Andreas Mager
sumber
1

Dalam skenario pencatatan yang sangat sederhana, di mana Anda benar-benar hanya mencoba menulis ke konsol selama pengembangan untuk keperluan debugging, mungkin lebih mudah untuk hanya melakukan pencarian dan penggantian sebelum produksi Anda membangun dan berkomentar semua panggilan ke Log atau Sistem. print.nl.

Misalnya, dengan asumsi Anda tidak menggunakan "Log." di mana saja di luar panggilan ke Log.d atau Log.e, dll, Anda bisa mencari dan mengganti seluruh solusi untuk mengganti "Log." dengan "// Log." untuk mengomentari semua panggilan logging Anda, atau dalam kasus saya saya hanya menggunakan System.out.println di mana-mana, jadi sebelum pergi ke produksi saya hanya akan melakukan pencarian penuh dan ganti untuk "System.out.println" dan ganti dengan "//System.out.println".

Saya tahu ini tidak ideal, dan alangkah baiknya jika kemampuan untuk menemukan dan berkomentar panggilan ke Log dan System.out.println dibangun ke Eclipse, tetapi sampai itu terjadi cara termudah dan tercepat dan terbaik untuk melakukan ini adalah mengomentari dengan mencari dan mengganti. Jika Anda melakukan ini, Anda tidak perlu khawatir tentang ketidakcocokan nomor baris jejak stack, karena Anda mengedit kode sumber Anda, dan Anda tidak menambahkan overhead dengan memeriksa beberapa konfigurasi level log, dll.

Jim
sumber
1

Dalam aplikasi saya, saya memiliki kelas yang membungkus kelas Log yang memiliki boolean var statis yang disebut "negara". Sepanjang kode saya, saya memeriksa nilai variabel "state" menggunakan metode statis sebelum benar-benar menulis ke Log. Saya kemudian memiliki metode statis untuk mengatur variabel "state" yang memastikan nilainya umum di semua instance yang dibuat oleh aplikasi. Ini berarti saya dapat mengaktifkan atau menonaktifkan semua logging untuk Aplikasi dalam satu panggilan - bahkan ketika Aplikasi sedang berjalan. Berguna untuk panggilan dukungan ... Ini berarti Anda harus tetap menggunakan senjata saat melakukan debug dan tidak mundur menggunakan kelas Log standar ...

Ini juga berguna (mudah) bahwa Java mengartikan boolean var sebagai false jika belum diberi nilai, yang artinya dapat dibiarkan sebagai false sampai Anda perlu menyalakan logging :-)

Sialan
sumber
1

Kita dapat menggunakan kelas Logdi komponen lokal kita dan mendefinisikan metodenya sebagai v / i / e / d. Berdasarkan kebutuhan kita dapat melakukan panggilan lebih lanjut.
contoh ditunjukkan di bawah ini.

    public class Log{
        private static boolean TAG = false;
        public static void d(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.d(enable_tag, message+args);
        }
        public static void e(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.e(enable_tag, message+args);
        }
        public static void v(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.v(enable_tag, message+args);
        }
    }
    if we do not need any print(s), at-all make TAG as false for all else 
    remove the check for type of Log (say Log.d).
    as 
    public static void i(String enable_tag, String message,Object...args){
    //      if(TAG)
            android.util.Log.i(enable_tag, message+args);
    }

di sini pesan untuk stringdan dan argsmerupakan nilai yang ingin Anda cetak.

Gyanendra Tripathi
sumber
0

Bagi saya seringkali berguna untuk mengatur level log yang berbeda untuk setiap TAG.

Saya menggunakan kelas wrapper yang sangat sederhana ini:

public class Log2 {

    public enum LogLevels {
        VERBOSE(android.util.Log.VERBOSE), DEBUG(android.util.Log.DEBUG), INFO(android.util.Log.INFO), WARN(
                android.util.Log.WARN), ERROR(android.util.Log.ERROR);

        int level;

        private LogLevels(int logLevel) {
            level = logLevel;
        }

        public int getLevel() {
            return level;
        }
    };

    static private HashMap<String, Integer> logLevels = new HashMap<String, Integer>();

    public static void setLogLevel(String tag, LogLevels level) {
        logLevels.put(tag, level.getLevel());
    }

    public static int v(String tag, String msg) {
        return Log2.v(tag, msg, null);
    }

    public static int v(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.VERBOSE) {
                return -1;
            }
        }
        return Log.v(tag, msg, tr);
    }

    public static int d(String tag, String msg) {
        return Log2.d(tag, msg, null);
    }

    public static int d(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.DEBUG) {
                return -1;
            }
        }
        return Log.d(tag, msg);
    }

    public static int i(String tag, String msg) {
        return Log2.i(tag, msg, null);
    }

    public static int i(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.INFO) {
                return -1;
            }
        }
        return Log.i(tag, msg);
    }

    public static int w(String tag, String msg) {
        return Log2.w(tag, msg, null);
    }

    public static int w(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.WARN) {
                return -1;
            }
        }
        return Log.w(tag, msg, tr);
    }

    public static int e(String tag, String msg) {
        return Log2.e(tag, msg, null);
    }

    public static int e(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.ERROR) {
                return -1;
            }
        }
        return Log.e(tag, msg, tr);
    }

}

Sekarang cukup atur level log per TAG di awal setiap kelas:

Log2.setLogLevel(TAG, LogLevels.INFO);
Jack Miller
sumber
0

Cara lain adalah dengan menggunakan platform logging yang memiliki kemampuan membuka dan menutup log. Ini dapat memberikan banyak fleksibilitas kadang-kadang bahkan pada aplikasi produksi yang lognya harus terbuka dan yang ditutup tergantung pada masalah yang Anda miliki misalnya:

Elisha Sterngold
sumber