Das Android-Loggingsystem soll universell zugänglich und einfach zu verwenden sein. Dabei wird davon ausgegangen, 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 gegenüber logcat folgende Vorteile:
- 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 Arbeitsspeichernutzung 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 eingebunden. Dadurch wird die Nachrichtendecodierung in 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 erweitert, um den WindowManager-Shell-Code im System-UI-Prozess zu umfassen. Für die Verwendung von ProtoLog war jedoch ein komplizierter Boilerplate-Einrichtungscode erforderlich. Außerdem wurde die Proto-Protokollierung 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 Speicherpuffers. 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 String-Interning dynamisch in einem Hintergrundthread. Clientseitiges ProtoLog bietet zwar Vorteile bei der Speichernutzung, die mit ProtoLog in Systemdiensten vergleichbar sind, verursacht aber einen etwas höheren Leistungsaufwand und bietet nicht den Vorteil der Speicherreduzierung durch angepinnten Speicher, den das serverseitige 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). Jede ProtoLogGroup enthält die folgenden Attribute:
enabled: Wenn diese Option auffalsefestgelegt 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...). Die Syntax ist also dieselbe.
ProtoLog unterstützt die folgenden Argumenttypen:
%b– boolescher Wert%d,%x– Integraltyp (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:
- Erstellen Sie eine
ProtoLogGroup-Definition für diesen Dienst. Initialisieren Sie die Definition vor der ersten Verwendung. Sie können sie beispielsweise beim Erstellen des Prozesses initialisieren:
ProtoLog.init(ProtoLogGroup.values());Verwenden Sie
ProtoLogauf dieselbe Weise wieandroid.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 Codeumwandlung, die String-Interning durchfü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"],
}
Befehlszeilen-Optionen
Einer der Hauptvorteile von ProtoLog ist, dass Sie es zur Laufzeit aktivieren oder deaktivieren können. Sie können beispielsweise in einem Build ausführlicheres Logging aktivieren, 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 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 Sie ProtoLog optimal nutzen können, sollten in den Nachrichten wiederholte Werte in Variablen isoliert werden.
Betrachten Sie beispielsweise die folgende Anweisung:
Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);
Bei der Optimierung zur Kompilierzeit wird daraus Folgendes:
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 Nachrichten im Arbeitsspeicher:
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 einem um 35% geringeren Speicherbedarf.
Winscope Viewer
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 klicken, wird die ProtoLog-Tabelle außerdem wieder zu dem in der Zeitachse ausgewählten Zeitstempel gescrollt:
Abbildung 1. ProtoLog-Viewer