프로토콜 로그

Android 로깅 시스템은 모든 로그 데이터를 문자 시퀀스로 나타낼 수 있다고 가정하여 범용 접근성과 사용 편의성을 목표로 합니다. 이 가정은 대부분의 사용 사례, 특히 전문 도구 없이 로그 가독성이 중요한 경우와 일치합니다. 하지만 높은 로깅 성능과 제한된 로그 크기가 요구되는 환경에서는 텍스트 기반 로깅이 최적이 아닐 수 있습니다. 이러한 시나리오 중 하나는 WindowManager로, 최소한의 시스템 영향으로 실시간 창 전환 로그를 처리할 수 있는 강력한 로깅 시스템이 필요합니다.

ProtoLog는 WindowManager 및 유사한 서비스의 로깅 요구사항을 해결하는 대안입니다. logcat 대비 ProtoLog의 주요 이점은 다음과 같습니다.

  • 로깅에 사용되는 리소스 양이 더 적습니다.
  • 개발자 관점에서 보면 기본 Android 로깅 프레임워크를 사용하는 것과 동일합니다.
  • 런타임에 사용 설정 또는 중지할 수 있는 로그 문을 지원합니다.
  • 필요한 경우 logcat에 로깅할 수 있습니다.

메모리 사용량을 최적화하기 위해 ProtoLog는 메시지의 컴파일된 해시를 계산하고 저장하는 문자열 인터닝 메커니즘을 사용합니다. 성능 향상을 위해 ProtoLog는 컴파일 중에 문자열 인턴을 실행하여 런타임에 메시지 식별자와 인수만 기록합니다 (시스템 서비스의 경우). 또한 ProtoLog 추적을 생성하거나 버그 신고를 획득할 때 ProtoLog는 컴파일 시간에 생성된 메시지 사전을 자동으로 통합하여 모든 빌드에서 메시지 디코딩을 지원합니다.

ProtoLog를 사용하면 메시지가 Perfetto 트레이스 내에 바이너리 형식 (proto)으로 저장됩니다. 메시지 디코딩은 Perfetto의 trace_processor 내에서 발생합니다. 이 프로세스는 바이너리 proto 메시지를 디코딩하고, 삽입된 메시지 사전을 사용하여 메시지 식별자를 문자열로 변환하고, 동적 인수를 사용하여 문자열을 포맷하는 것으로 구성됩니다.

ProtoLog는 android.utils.Log와 동일한 로그 수준을 지원합니다(d, v, i, w, e, wtf).

클라이언트 측 ProtoLog

처음에 ProtoLog는 단일 프로세스 및 구성요소 내에서 작동하는 WindowManager의 서버 측 전용으로 설계되었습니다. 이후 시스템 UI 프로세스의 WindowManager 셸 코드를 포함하도록 확장되었지만 ProtoLog를 사용하려면 복잡한 상용구 설정 코드가 필요했습니다. 또한 Proto 로깅은 시스템 서버 및 시스템 UI 프로세스로 제한되어 다른 프로세스에 통합하기가 어려웠으며 각 프로세스에 별도의 메모리 버퍼 설정이 필요했습니다. 하지만 이제 ProtoLog를 클라이언트 측 코드에서 사용할 수 있으므로 추가 상용구 코드가 필요하지 않습니다.

시스템 서비스 코드와 달리 클라이언트 측 코드는 일반적으로 컴파일 시간 문자열 인턴을 건너뜁니다. 대신 문자열 인턴은 백그라운드 스레드에서 동적으로 발생합니다. 따라서 클라이언트 측 ProtoLog는 시스템 서비스의 ProtoLog와 비슷한 메모리 사용 이점을 제공하지만 성능 오버헤드가 약간 더 높고 서버 측 대응 항목의 고정 메모리 메모리 감소 이점이 없습니다.

ProtoLog 그룹

ProtoLog 메시지는 ProtoLogGroups라는 그룹으로 정리됩니다. 이는 Logcat 메시지가 TAG로 정리되는 방식과 유사합니다. 이러한 ProtoLogGroups는 런타임에 집합적으로 사용 설정하거나 사용 중지할 수 있는 메시지 클러스터 역할을 합니다. 또한 컴파일 중에 메시지를 삭제해야 하는지, 메시지를 로깅해야 하는 위치 (proto, logcat 또는 둘 다)를 제어합니다. 각 ProtoLogGroup는 다음 속성으로 구성됩니다.

  • enabled: false로 설정하면 이 그룹의 메시지가 컴파일 중에 제외되고 런타임에 사용할 수 없습니다.
  • logToProto: 이 그룹이 바이너리 형식으로 로깅하는지 정의합니다.
  • logToLogcat: 이 그룹이 Logcat에 로깅하는지 정의합니다.
  • tag: 로깅된 메시지의 소스 이름입니다.

ProtoLog를 사용하는 각 프로세스에는 ProtoLogGroup 인스턴스가 구성되어 있어야 합니다.

지원되는 인수 유형

내부적으로 ProtoLog 형식 문자열은 android.text.TextUtils#formatSimple(String, Object...)를 사용하므로 구문이 동일합니다.

ProtoLog는 다음 인수 유형을 지원합니다.

  • %b - 불리언
  • %d, %x - 정수 유형 (short, integer, long)
  • %f - 부동 소수점 유형 (float 또는 double)
  • %s - 문자열
  • %% - 리터럴 백분율 문자

%04d%10b와 같은 너비 및 정밀도 수정자는 지원되지만 argument_indexflags는 지원되지 않습니다.

새 서비스에서 ProtoLog 사용

새 프로세스에서 ProtoLog를 사용하려면 다음을 실행하세요.

  1. 이 서비스의 ProtoLogGroup 정의를 만듭니다.

  2. 처음 사용하기 전에 정의를 초기화합니다 (예: 프로세스 생성 시).

    Protolog.init(ProtologGroup.values());

  3. android.util.Log과 동일한 방식으로 Protolog을 사용합니다.

    ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);

컴파일 시간 최적화 사용 설정

프로세스에서 컴파일 시간 ProtoLog를 사용 설정하려면 빌드 규칙을 변경하고 protologtool 바이너리를 호출해야 합니다.

ProtoLogTool는 문자열 인턴을 실행하고 ProtoLog 호출을 업데이트하는 코드 변환 바이너리입니다. 이 바이너리는 이 예와 같이 모든 ProtoLog 로깅 호출을 변환합니다.

ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);

다음으로 변환합니다.

if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
    int protoLogParam0 = value1;
    String protoLogParam1 = String.valueOf(value2);
    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}

이 예에서 ProtoLog, ProtoLogImpl, ProtoLogGroup는 인수로 제공되는 클래스이며 (가져오거나, 정적으로 가져오거나, 전체 경로일 수 있으며 와일드카드 가져오기는 허용되지 않음) x는 로깅 메서드입니다.

변환은 소스 수준에서 실행됩니다. 형식 문자열, 로그 수준, 로그 그룹 이름에서 해시가 생성되고 ProtoLogGroup 인수 뒤에 삽입됩니다. 실제로 생성된 코드는 인라인 처리되고 파일의 줄 번호 지정을 유지하기 위해 여러 개의 새 줄 문자가 추가됩니다.

예:

genrule {
    name: "wm_shell_protolog_src",
    srcs: [
        ":protolog-impl", // protolog lib
        ":wm_shell_protolog-groups", // protolog groups declaration
        ":wm_shell-sources", // source code
    ],
    tools: ["protologtool"],
    cmd: "$(location protologtool) transform-protolog-calls " +
        "--protolog-class com.android.internal.protolog.ProtoLog " +
        "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
        "--loggroups-jar $(location :wm_shell_protolog-groups) " +
        "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
        "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
        "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
        "--output-srcjar $(out) " +
        "$(locations :wm_shell-sources)",
    out: ["wm_shell_protolog.srcjar"],
}

명령줄 옵션

ProtoLog의 주요 장점 중 하나는 런타임에 사용 설정하거나 사용 중지할 수 있다는 것입니다. 예를 들어 빌드에서 상세 로깅을 기본적으로 사용 중지하고 특정 문제를 디버깅하기 위해 로컬 개발 중에 사용 설정할 수 있습니다. 이 패턴은 예를 들어 그룹 WM_DEBUG_WINDOW_TRANSITIONSWM_DEBUG_WINDOW_TRANSITIONS_MIN을 사용하여 WindowManager에서 사용되며, 다양한 유형의 전환 로깅을 지원합니다. 전자는 기본적으로 사용 설정됩니다.

트레이스를 시작할 때 Perfetto를 사용하여 ProtoLog를 구성할 수 있습니다. adb 명령줄을 사용하여 ProtoLog를 로컬로 구성할 수도 있습니다.

adb shell cmd protolog_configuration 명령어는 다음 인수를 지원합니다.

help
  Print this help text.

groups (list | status)
  list - lists all ProtoLog groups registered with ProtoLog service"
  status <group> - print the status of a ProtoLog group"

logcat (enable | disable) <group>"
  enable or disable ProtoLog to logcat

효과적인 사용을 위한 팁

ProtoLog는 메시지와 전달된 모든 문자열 인수에 문자열 인턴을 사용합니다. 즉, ProtoLog의 이점을 더 많이 얻으려면 메시지가 반복되는 값을 변수로 격리해야 합니다.

예를 들어 다음 문을 살펴보겠습니다.

Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);

컴파일 시간에 최적화되면 다음과 같이 변환됩니다.

ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);

ProtoLog가 A,B,C 인수를 사용하여 코드에 사용되는 경우:

Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");

메모리에 다음 메시지가 표시됩니다.

Dict:
  0x123: "%s"
  0x111: "The argument value is A"
  0x222: "The argument value is B"
  0x333: "The argument value is C"

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

대신 ProtoLog 문이 다음과 같이 작성된 경우

Protolog.v(MY_GROUP, "The argument value is %s", argument);

메모리 내 버퍼는 다음과 같이 됩니다.

Dict:
  0x123: "The argument value is %s" (24 b)
  0x111: "A" (1 b)
  0x222: "B" (1 b)
  0x333: "C" (1 b)

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

이 시퀀스를 사용하면 메모리 사용량이 35% 줄어듭니다.

Winscope 뷰어

Winscope의 ProtoLog 뷰어 탭에는 ProtoLog 트레이스가 표 형식으로 정리되어 표시됩니다. 로그 수준, 태그, 소스 파일 (ProtoLog 문이 있는 위치), 메시지 콘텐츠별로 트레이스를 필터링할 수 있습니다. 모든 열은 필터링 가능합니다. 첫 번째 열의 타임스탬프를 클릭하면 타임라인이 메시지 타임스탬프로 이동합니다. 또한 현재 시간으로 이동을 클릭하면 ProtoLog 표가 타임라인에서 선택한 타임스탬프로 다시 스크롤됩니다.

ProtoLog 뷰어

그림 1. ProtoLog 뷰어