Android에서 로그 수준을 활성화 / 비활성화하는 방법


149

예를 들어 디버깅 할 로깅 문이 많이 있습니다.

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

장치 전화 에이 응용 프로그램을 배포하는 동안 로깅을 활성화 / 비활성화 할 수있는 곳에서 자세한 로깅을 해제하고 싶습니다.


답변:


80

일반적인 방법은 이름이 loglevel 인 int를 만들고 로그 수준을 기반으로 디버그 수준을 정의하는 것입니다.

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

나중에 모든 디버그 출력 레벨에 대해 LOGLEVEL을 변경할 수 있습니다.


1
훌륭하지만, 예제에서 DEBUG를 어떻게 비활성화하지만 여전히 경고가 표시됩니까? ...
Andre Bossard

1
if 문이 .apk 바이트 코드로 끝나지 않습니까? 응용 프로그램을 배포 할 때 로깅을 (일반적으로) 끄고 싶지만 if 문은 제거되지 않았다고 생각했습니다.
chessofnerd

2
귀하의 예에서는 DEBUG 메시지가 표시되지만 WARN은 표시되지 않습니까? 당신은 일반적으로 반대하고 싶지 않습니까?
Sam

15
사용자 정의 변수 대신 BuildConfig.DEBUG를 사용하십시오
hB0

1
@chessofnerd "Java에서 if 내부의 코드는 컴파일 된 코드의 일부가 될 수 없습니다. 컴파일해야하지만 컴파일 된 바이트 코드에는 기록되지 않습니다." stackoverflow.com/questions/7122723/…
stoooops 1

197

안드로이드 문서는 로그 레벨에 대한 다음 말한다 :

Verbose는 개발 중을 제외하고는 응용 프로그램으로 컴파일해서는 안됩니다. 디버그 로그는 컴파일되지만 런타임에 제거됩니다. 오류, 경고 및 정보 로그는 항상 유지됩니다.

따라서 다른 답변에서 제안한대로 ProGuard를 사용하여 로그 상세 로깅 문을 제거하는 것이 좋습니다 .

설명서에 따르면 시스템 속성을 사용하여 개발 장치에 로깅을 구성 할 수 있습니다. 세트 속성이 log.tag.<YourTag>되고 상기 다음 값 중 하나로 설정한다 : VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, 또는 SUPPRESS. 이에 대한 자세한 내용은 설명서를 참조하십시오.isLoggable() 방법 .

setprop명령을 사용하여 속성을 임시로 설정할 수 있습니다 . 예를 들면 다음과 같습니다.

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

또는 '/data/local.prop'파일에서 다음과 같이 지정할 수 있습니다.

log.tag.MyAppTag=WARN

최신 버전의 Android 에서는 /data/local.prop가 읽기 전용이어야합니다 . 부팅시이 파일을 읽으므로 업데이트 후 다시 시작해야합니다. 만약/data/local.prop 세계 쓰기가 가능, 그것은 가능성이 무시됩니다.

마지막으로 System.setProperty()메소드를 사용하여 프로그래밍 방식으로 설정할 수 있습니다 .


4
나는 같은 경험을했습니다. API 문서는 이것이 어떻게 작동 해야하는지에 대해 명확하지 않으며 심지어 android.util.Config더 이상 사용되지 않는 상수 를 언급하는 것처럼 보입니다 . API 문서에 지정된 하드 코딩 된 값은 빌드마다 다를 수 있으므로 쓸모가 없습니다. 따라서 ProGuard 경로는 우리에게 가장 적합한 솔루션 인 것 같습니다.
Christopher Orr

3
/data/local.prop 파일, setprop 메소드 또는 System.setProperty를 사용하여 Android 로깅을 구성하는 데 행운이 있었습니까? Log.isLoggable (TAG, VERBOSE)을 가져 와서 true를 반환하는 데 약간의 문제가 있습니다.
seanoshea 2019

2
안드로이드 디버깅 작업을 받았습니다. 요령은 Log.d ( "xyz")와 같은 것을 호출하면 로거에 대해 디버그가 비활성화 된 경우에도 메시지가 logcat에 기록된다는 것입니다. 이는 일반적으로 기록 후 필터링이 발생 함을 의미합니다. Log.isLoggable (TAG, Log.VERBOSE)) 전에 필터링하기 위해 {Log.v (TAG, "my log message"); }가 필요합니다. 이것은 일반적으로 매우 귀찮습니다. slf4j-android의 수정 된 버전을 사용하여 원하는 것을 얻습니다.
17:34에

2
@Dave는 local.prop 메소드를 올바르게 작동시킬 수있었습니다. 또한이 작업을 수행 할 수 없으며 log.tag.test = INFO 항목을 만든 다음 adb 쉘에서 setprop log.tag.test SUPPRESS를 실행하여 변경하려고 시도했지만 아무것도 변경하지 않았습니다. 또한 System.getProperty 및 System.setProperty를 사용하면 아무것도 수행되지 않습니다. 업데이트를 원했습니다. 감사.
jjNford

2
+1 "API 문서는 이것이 어떻게 작동해야하는지 명확하지 않습니다."
Alan

90

가장 쉬운 방법은 배포 전에 ProGuard 를 통해 컴파일 된 JAR을 실행 하는 것입니다.

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

이것은 다른 모든 ProGuard 최적화와는 별도로 바이트 코드에서 직접 자세한 로그 문장을 제거합니다.


여기에는 설정을 정의 할 수있는 log.property 파일이 포함되어 있습니다.
d-man

1
proguard를 사용하여 줄을 제거하면 프로덕션의 스택 추적이 코드와 일치하지 않을 수 있습니다.
larham1

3
@ larham1 : ProGuard는 바이트 코드에서 작동하므로 로깅 호출을 제거해도 내장 라인 번호 메타 데이터가 변경되지 않는다고 생각합니다.
Christopher Orr

19
이것을 명심하십시오-Log.v ()에 대한 실제 호출이 제거 되더라도 인수는 여전히 평가됩니다. 따라서 Log.v (TAG, generateLog ())와 같이 값 비싼 메소드 호출이 있으면 핫 코드 경로에 있으면 성능이 저하 될 수 있습니다. toString () 또는 String.format ()과 같은 것들도 중요 할 수 있습니다.
Błażej Czapp

4
@GaneshKrishnan 아니요, 사실이 아닙니다. Log.v ()에 대한 호출은 제거되지만 기본적으로 문자열을 빌드하기위한 메소드 호출은 제거되지 않습니다. ProGuard의 저자 가이 답변을 참조하십시오 : stackoverflow.com/a/6023505/234938
Christopher Orr

18

변수 경로 목록을 사용하는 래퍼 클래스를 만드는 간단한 경로를 사용했습니다.

 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

1
위에서 언급했듯이 이 기술을 구현하기 위해 수정 된 slf4j-android 버전을 사용했습니다.
17:36에

3
그것에 대해 큰 우려가 있습니다, stackoverflow.com/questions/2446248/…
OneWorld

10

더 좋은 방법은 SLF4J API + 일부 구현을 사용하는 것입니다.

Android 애플리케이션의 경우 다음을 사용할 수 있습니다.

  1. Android Logger 는 가볍지 만 구성하기 쉬운 SLF4J 구현입니다 (<50 Kb).
  2. LOGBack은 가장 강력하고 최적화 된 구현이지만 크기는 약 1Mb입니다.
  3. 당신의 취향에 의한 다른 것들 : slf4j-android, slf4android.

2
Android에서는 적절한 것이 호환되지 logback-android않기 때문에 사용해야 logback합니다. logback-android-1.0.10-1.jar는 429KB로 제공되는 기능을 고려하면 그리 나쁘지는 않지만 대부분의 개발자는 Proguard를 사용하여 응용 프로그램을 최적화합니다.
tony19

이것은 if 문을 사용하여 로깅 전에 로그 레벨을 확인하는 것을 막지 않습니다. 참조 stackoverflow.com/questions/4958860/...
월드

8

사용해야합니다

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }

2
isLoggable의 출력을 구성하는 방법은 무엇입니까? 매니페스트에서 isDebugable을 false로 설정하면 디버그 및 상세 정보를 로그 할 수 없습니까?
OneWorld 2016 년

5

proguard (@Christopher의 답변 참조)로 로깅을 제거하는 것은 쉽고 빠르지 만 파일에 디버그 로깅이 있으면 프로덕션의 스택 추적이 소스와 일치하지 않습니다.

대신, proguard가 프로덕션에만 사용된다고 가정 할 때 개발과 프로덕션에서 서로 다른 로깅 수준을 사용하는 기술이 있습니다. proguard가 지정된 클래스 이름의 이름을 바꿨는지 확인하여 프로덕션을 인식합니다 (이 예에서는 "com.foo.Bar"를 사용합니다.이를 proguard에서 이름을 바꿀 정규화 된 클래스 이름으로 바꿉니다).

이 기술은 공통 로깅을 사용합니다.

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);
    }
}


3

표준 안드로이드 로그 클래스에 대한 작은 드롭 인 대체품이 있습니다-https: //github.com/zserge/log

기본적으로 할 일은 수입을에서 android.util.Log로 대체 하는 것 trikita.log.Log입니다. 그런 다음 귀하 Application.onCreate()또는 일부 정적 이니셜 라이저에서 BuilConfig.DEBUG또는 다른 플래그를 확인 하고 최소 로그 레벨을 사용 Log.level(Log.D)하거나 Log.level(Log.E)변경하십시오. Log.useLog(false)로깅을 전혀 비활성화 하는 데 사용할 수 있습니다 .


2

이 Log 확장 클래스를 볼 수 있습니다 : https://github.com/dbauduin/Android-Tools/tree/master/logs .

로그를 세부적으로 제어 할 수 있습니다. 예를 들어 모든 로그를 비활성화하거나 일부 패키지 또는 클래스의 로그 만 비활성화 할 수 있습니다.

또한 유용한 기능을 추가합니다 (예 : 각 로그에 태그를 전달하지 않아도 됨).


2

이 문제와 로깅과 관련된 다른 일반적인 문제를 해결하는 유틸리티 / 래퍼를 만들었습니다.

다음과 같은 기능을 가진 디버깅 유틸리티 :

  • Log 클래스가 제공하는 일반적인 기능은 LogMode로 감싸 져 있습니다 .
  • 메소드 진입 종료 로그 : 스위치로 끌 수 있음
  • 선택적 디버깅 : 특정 클래스를 디버깅합니다.
  • Method Execution-Time Measurement : 개별 메소드의 실행 시간과 클래스의 모든 메소드에 소요되는 총 시간을 측정합니다.

사용하는 방법?

  • 프로젝트에 수업을 포함 시키십시오.
  • android.util.Log 메소드를 사용하는 것처럼 사용하십시오.
  • 앱의 메소드 시작 및 종료시 entry_log ()-exit_log () 메소드를 호출하여 시작-종료 로그 기능을 사용하십시오.

설명서를 충분하게 만들려고 노력했습니다.

이 유틸리티를 개선하기위한 제안을 환영합니다.

무료 사용 / 공유

GitHub 에서 다운로드하십시오 .


2

더 복잡한 해결책이 있습니다. 전체 스택 추적이 발생하고 필요한 경우에만 성능 toString () 메소드가 호출됩니다 (성능). 프로덕션 모드에서는 BuildConfig.DEBUG 속성이 false이므로 모든 추적 및 디버그 로그가 제거됩니다. 핫스팟 컴파일러는 최종 정적 속성이 해제되어 호출을 제거 할 수 있습니다.

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();
    }
}

이처럼 사용하십시오 :

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); 

1

디버깅 목적으로 개발하는 동안 말 그대로 콘솔에 쓰려고하는 매우 간단한 로깅 시나리오에서는 프로덕션을 빌드하고 로그 또는 시스템에 대한 모든 호출을 주석 처리하기 전에 검색하고 교체하는 것이 가장 쉽습니다. out.println.

예를 들어 "로그"를 사용하지 않았다고 가정합니다. Log.d 또는 Log.e 등을 호출 할 수없는 곳이면 어디에서나 전체 솔루션을 찾아서 교체하여 "Log"를 대체 할 수 있습니다. "// Log"로 모든 로깅 호출을 주석으로 처리하거나 제 경우에는 System.out.println을 어디에서나 사용하고 있으므로 프로덕션으로 가기 전에 "System.out.println"에 대한 전체 검색 및 바꾸기를 수행하고 "//System.out.println".

나는 이것이 이상적이지 않다는 것을 알고 있으며 Log와 System.out.println에 대한 호출을 찾아서 주석 처리하는 기능이 Eclipse에 내장되어 있다면 좋을 것입니다. 그러나 그렇게 할 때까지 가장 쉽고 빠르며 가장 좋은 방법은 검색 및 바꾸기로 주석 처리합니다. 이렇게하면 소스 코드를 편집하고 일부 로그 수준 구성 등을 확인하여 오버 헤드를 추가하지 않기 때문에 스택 추적 행 번호가 일치하지 않을까 걱정할 필요가 없습니다.


1

내 응용 프로그램에는 "state"라는 정적 부울 변수가있는 Log 클래스를 래핑하는 클래스가 있습니다. 내 코드 전체에서 실제로 Log에 쓰기 전에 정적 메서드를 사용하여 "state"변수의 값을 확인합니다. 그런 다음 "state"변수를 설정하는 정적 메소드를 사용하여 앱이 생성 한 모든 인스턴스에서 값이 공통되도록합니다. 즉, 앱이 실행 중일 때도 한 번의 호출로 앱의 모든 로깅을 활성화 또는 비활성화 할 수 있습니다. 지원 전화에 유용합니다 ... 디버깅 할 때 총을 고수해야하지만 표준 로그 클래스를 사용하여 회귀하지 않아야합니다 ...

Java가 부울 var에 값이 할당되지 않은 경우 false로 해석하는 것이 유용합니다 (매우 편리함). 로깅을 켜야 할 때까지 false로 남겨 둘 수 있습니다.


1

Log로컬 컴포넌트에서 클래스 를 사용하고 메소드를 v / i / e / d로 정의 할 수 있습니다 . 필요에 따라 더 전화를 걸 수 있습니다.
예는 아래와 같습니다.

    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);
    }

여기서 message는 for string이며 args인쇄하려는 값입니다.


0

나를 위해 종종 각 TAG에 대해 다른 로그 수준을 설정할 수있는 것이 유용합니다.

이 매우 간단한 래퍼 클래스를 사용하고 있습니다.

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);
    }

}

이제 각 클래스의 시작 부분에서 TAG마다 로그 레벨을 설정하십시오.

Log2.setLogLevel(TAG, LogLevels.INFO);

0

다른 방법은 로그를 열고 닫는 기능이있는 로깅 플랫폼을 사용하는 것입니다. 예를 들어 다음과 같은 문제에 따라 로그가 열려 있고 닫혀있는 프로덕션 앱에서도 유연성이 크게 향상 될 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.