프로토콜 로그

Android 로깅 시스템은 모든 로그 데이터를 문자 시퀀스로 나타낼 수 있다고 가정하여 보편적인 접근성과 사용 편의성을 목표로 합니다. 이 가정은 특히 전문 도구 없이 로그 가독성이 중요한 대부분의 사용 사례에 적합합니다. 그러나 로깅 성능이 높고 로그 크기가 제한된 환경에서는 텍스트 기반 로깅이 최적의 방법이 아닐 수 있습니다. 이러한 시나리오 중 하나는 WindowManager입니다. 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 메시지는 Logcat 메시지가 TAG로 구성되는 것과 유사하게 ProtoLogGroups라는 그룹으로 구성됩니다. 이러한 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. Protologandroid.util.Log와 동일한 방식으로 사용합니다.

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

트레이스를 시작할 때 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 뷰어