Protokoll

Das Android-Loggingsystem zielt auf universelle Zugänglichkeit und Benutzerfreundlichkeit ab, wobei davon ausgegangen wird, dass alle Logdaten als Zeichenfolge dargestellt werden können. Diese Annahme stimmt mit den meisten Anwendungsfällen überein, insbesondere wenn die Lesbarkeit von Logs ohne spezielle Tools entscheidend ist. In Umgebungen, in denen eine hohe Logging-Leistung und eingeschränkte Loggrößen erforderlich sind, ist textbasiertes Logging jedoch nicht optimal. Ein solches Szenario ist WindowManager, das ein robustes Loggingsystem erfordert, das Fensterübergangsprotokolle in Echtzeit mit minimalen Auswirkungen auf das System verarbeitet.

ProtoLog ist die Alternative, um die Logging-Anforderungen von WindowManager und ähnlichen Diensten zu erfüllen. ProtoLog bietet folgende Vorteile gegenüber logcat:

  • Verbraucht weniger Ressourcen für die Protokollierung.
  • Aus Entwicklersicht funktioniert es genauso wie das standardmäßige Android-Logging-Framework.
  • Ermöglicht das Aktivieren oder Deaktivieren von Log-Anweisungen zur Laufzeit.
  • Kann auch in logcat protokolliert werden.

Zur Optimierung der Speichernutzung verwendet ProtoLog einen String-Interning-Mechanismus. Bei diesem Mechanismus wird ein kompilierter Hash der Nachricht berechnet und gespeichert. Zur Verbesserung der Leistung führt ProtoLog während der Kompilierung für Systemdienste String-Interning durch. Zur Laufzeit werden nur die Nachrichten-ID und die Argumente aufgezeichnet. Wenn Sie einen ProtoLog-Trace generieren oder einen Fehlerbericht erhalten, wird das zur Kompilierzeit erstellte Nachrichtendictionary automatisch in ProtoLog aufgenommen. Dadurch wird die Nachrichtendecodierung aus jedem Build ermöglicht.

ProtoLog speichert Nachrichten in einem binären Format (Proto) in einem Perfetto-Trace. Die Nachricht wird in trace_processor von Perfetto decodiert. Dabei werden die binären Proto-Nachrichten decodiert, Nachrichten-IDs mithilfe des eingebetteten Nachrichtendictionarys in Strings übersetzt und der String mit dynamischen Argumenten formatiert.

ProtoLog unterstützt dieselben Logebenen wie android.utils.Log, nämlich d, v, i, w, e und wtf.

Clientseitiges ProtoLog

Ursprünglich war ProtoLog nur für die Serverseite des WindowManager vorgesehen, der in einem einzelnen Prozess und einer einzelnen Komponente ausgeführt wird. Später wurde sie auf den WindowManager-Shellcode im System-UI-Prozess ausgeweitet. Für die Verwendung von ProtoLog war jedoch ein komplizierter Standard-Einrichtungscode erforderlich. Außerdem wurde die Protokollierung von Proto auf Systemserver- und System-UI-Prozesse beschränkt. Das machte es schwierig, sie in andere Prozesse einzubinden, und erforderte für jeden eine separate Einrichtung des Arbeitsspeicherpuffers. ProtoLog ist jetzt für clientseitigen Code verfügbar, sodass kein zusätzlicher Boilerplate-Code mehr erforderlich ist.

Im Gegensatz zu Systemdiensten wird beim clientseitigen Code in der Regel kein String-Interning zur Kompilierzeit durchgeführt. Stattdessen erfolgt das Interning von Strings dynamisch in einem Hintergrundthread. Clientseitiges ProtoLog bietet zwar ähnliche Vorteile bei der Speichernutzung wie ProtoLog in Systemdiensten, verursacht aber einen etwas höheren Leistungsaufwand und bietet nicht die Speicherreduzierung durch angepinnten Speicher, die sein serverseitiges Pendant bietet.

ProtoLog-Gruppen

ProtoLog-Nachrichten sind in Gruppen namens ProtoLogGroups unterteilt, ähnlich wie Logcat-Nachrichten nach TAG organisiert sind. Diese ProtoLogGroups dienen als Nachrichtengruppen, die Sie zur Laufzeit aktivieren oder deaktivieren können. Sie steuern auch, ob Nachrichten während der Kompilierung entfernt werden und wo sie protokolliert werden (Proto, Logcat oder beides). Jedes ProtoLogGroup enthält die folgenden Attribute:

  • enabled: Wenn diese Option auf false festgelegt ist, werden Nachrichten in dieser Gruppe während der Kompilierung ausgeschlossen und sind zur Laufzeit nicht verfügbar.
  • logToProto: Definiert, ob diese Gruppe im Binärformat protokolliert wird.
  • logToLogcat: Definiert, ob diese Gruppe in logcat protokolliert wird.
  • tag: Name der Quelle der protokollierten Nachricht.

Für jeden Prozess, der ProtoLog verwendet, muss eine ProtoLogGroup-Instanz konfiguriert sein.

Unterstützte Argumenttypen

ProtoLog formatiert Strings intern mit android.text.TextUtils#formatSimple(String, Object...), daher ist die Syntax dieselbe.

ProtoLog unterstützt die folgenden Argumenttypen:

  • %b – boolescher Wert
  • %d, %x – Ganzzahltyp (short, integer oder long)
  • %f: Gleitkommatyp (float oder double)
  • %s – String
  • %% – ein Prozentzeichen

Breiten- und Genauigkeitsmodifikatoren wie %04d und %10b werden unterstützt. argument_index und flags werden jedoch nicht unterstützt.

ProtoLog in einem neuen Dienst verwenden

So verwenden Sie ProtoLog in einem neuen Dienst:

  1. Erstellen Sie eine ProtoLogGroup-Definition für diesen Dienst.
  2. Initialisieren Sie die Definition vor der ersten Verwendung. Sie können sie beispielsweise beim Erstellen des Prozesses initialisieren:

    ProtoLog.init(ProtoLogGroup.values());
    
  3. Verwenden Sie ProtoLog auf dieselbe Weise wie android.util.Log:

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

Compile-time-Optimierung aktivieren

Wenn Sie die ProtoLog-Kompilierzeit in einem Prozess aktivieren möchten, müssen Sie die Build-Regeln ändern und die protologtool-Binärdatei aufrufen.

ProtoLogTool ist eine Binärdatei für die Codetransformation, die String-Interning ausführt und den ProtoLog-Aufruf aktualisiert. Dieses Binärprogramm transformiert jeden ProtoLog-Logging-Aufruf, wie in diesem Beispiel gezeigt:

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

in:

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

In diesem Beispiel sind ProtoLog, ProtoLogImpl und ProtoLogGroup die als Argumente bereitgestellten Klassen (können importiert, statisch importiert oder als vollständiger Pfad angegeben werden; Platzhalterimporte sind nicht zulässig) und x ist die Protokollierungsmethode.

Die Transformation erfolgt auf Quellebene. Ein Hash wird aus dem Formatstring, der Protokollebene und dem Namen der Protokollgruppe generiert und nach dem ProtoLogGroup-Argument eingefügt. Der tatsächlich generierte Code wird inline eingefügt und es wird eine Reihe von Zeilenumbruchzeichen hinzugefügt, um die Zeilennummerierung in der Datei beizubehalten.

Beispiel:

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"],
}

Befehlszeilenoptionen

Einer der Hauptvorteile von ProtoLog ist, dass Sie es zur Laufzeit aktivieren oder deaktivieren können. Sie können beispielsweise ein ausführlicheres Logging in einem Build haben, das standardmäßig deaktiviert ist, und es während der lokalen Entwicklung aktivieren, um ein bestimmtes Problem zu beheben. Dieses Muster wird beispielsweise in WindowManager mit den Gruppen WM_DEBUG_WINDOW_TRANSITIONS und WM_DEBUG_WINDOW_TRANSITIONS_MIN verwendet, um verschiedene Arten der Übergangsprotokollierung zu ermöglichen. Die erste Gruppe ist standardmäßig aktiviert.

Sie können ProtoLog mit Perfetto konfigurieren, wenn Sie einen Trace starten. Sie können ProtoLog auch lokal über die Befehlszeile adb konfigurieren.

Der Befehl adb shell cmd protolog_configuration unterstützt die folgenden Argumente:

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

Tipps für eine effektive Nutzung

ProtoLog verwendet String-Interning sowohl für die Nachricht als auch für alle übergebenen String-Argumente. Damit ProtoLog optimal genutzt werden kann, sollten wiederholte Werte in Variablen isoliert werden.

Betrachten Sie beispielsweise die folgende Aussage:

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

Bei der Optimierung zur Kompilierzeit wird Folgendes daraus:

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

Wenn ProtoLog im Code mit den Argumenten A,B,C verwendet wird:

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

Das führt zu den folgenden Meldungen im Speicher:

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)

Wenn die ProtoLog-Anweisung stattdessen so geschrieben wurde:

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

Der In-Memory-Puffer würde so aussehen:

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)

Diese Sequenz führt zu einer um 35% geringeren Speichernutzung.

Winscope-Betrachter

Auf dem Tab „ProtoLog viewer“ von Winscope werden ProtoLog-Traces in Tabellenform angezeigt. Sie können Traces nach Logebene, Tag, Quelldatei (in der die ProtoLog-Anweisung vorhanden ist) und Nachrichteninhalten filtern. Alle Spalten sind filterbar. Wenn Sie in der ersten Spalte auf den Zeitstempel klicken, wird die Zeitachse zum Zeitstempel der Nachricht verschoben. Wenn Sie auf Zur aktuellen Zeit springen klicken, wird die ProtoLog-Tabelle außerdem wieder zu dem in der Zeitachse ausgewählten Zeitstempel gescrollt:

ProtoLog-Betrachter

Abbildung 1: ProtoLog-Betrachter