Protokoll

Das Android-Loggingsystem ist auf universelle Zugänglichkeit und Benutzerfreundlichkeit ausgelegt. Es wird davon ausgegangen, dass alle Logdaten als Zeichenfolge dargestellt werden können. Diese Annahme entspricht den meisten Anwendungsfällen, insbesondere wenn die Lesbarkeit von Logs ohne spezielle Tools entscheidend ist. In Umgebungen, in denen eine hohe Logging-Leistung und eingeschränkte Log-Größen erforderlich sind, ist textbasiertes Logging jedoch möglicherweise nicht optimal. Ein solches Szenario ist WindowManager, das ein robustes Protokollierungssystem erfordert, das Protokolle für Fensterübergänge in Echtzeit mit minimalen Auswirkungen auf das System verarbeiten kann.

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

  • Die Menge der für die Protokollierung verwendeten Ressourcen ist geringer.
  • Aus Entwicklersicht ist es dasselbe wie die Verwendung des standardmäßigen Android-Logging-Frameworks.
  • Unterstützt das Aktivieren oder Deaktivieren von Log-Anweisungen zur Laufzeit.
  • Bei Bedarf können Sie weiterhin Logeinträge in logcat schreiben.

Zur Optimierung der Speichernutzung verwendet ProtoLog einen String-Interning-Mechanismus, bei dem ein kompilierter Hash der Nachricht berechnet und gespeichert wird. Zur Leistungssteigerung führt ProtoLog während der Kompilierung (für Systemdienste) String-Interning durch. Zur Laufzeit werden nur die Nachrichten-ID und die Argumente aufgezeichnet. Außerdem wird beim Generieren eines ProtoLog-Traces oder beim Abrufen eines Fehlerberichts automatisch das zur Kompilierzeit erstellte Nachrichtendictionary in ProtoLog eingebunden. So können Nachrichten aus jedem Build decodiert werden.

Bei ProtoLog wird die Nachricht in einem binären Format (Proto) in einem Perfetto-Trace gespeichert. Die Nachricht wird in trace_processor von Perfetto decodiert. Dabei werden die binären Proto-Nachrichten decodiert, die 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, wtf.

Clientseitiges ProtoLog

Ursprünglich war ProtoLog nur für die Serverseite des WindowManagers 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, aber die Verwendung von ProtoLog erforderte einen komplizierten Boilerplate-Einrichtungscode. Außerdem war die Proto-Protokollierung auf Systemserver- und System-UI-Prozesse beschränkt, was die Einbindung in andere Prozesse erschwerte und für jeden Prozess einen separaten Speicherpuffer erforderte. ProtoLog ist jetzt jedoch 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. ProtoLog auf der Clientseite bietet zwar vergleichbare Vorteile hinsichtlich der Speichernutzung wie ProtoLog in Systemdiensten, verursacht aber einen geringfügig höheren Leistungsaufwand und bietet nicht den Vorteil der Speicherreduzierung durch angepinnten Speicher wie das serverseitige Pendant.

ProtoLog-Gruppen

ProtoLog-Nachrichten sind in Gruppen namens ProtoLogGroups unterteilt, ähnlich wie Logcat-Nachrichten nach TAG unterteilt sind. Diese ProtoLogGroups dienen als Cluster von Nachrichten, die zur Laufzeit gemeinsam aktiviert oder deaktiviert werden können. Außerdem steuern sie, ob Nachrichten während der Kompilierung entfernt werden sollen und wo sie protokolliert werden sollen (Proto, Logcat oder beides). Jede ProtoLogGroup umfasst 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

Intern verwenden ProtoLog-Formatstrings android.text.TextUtils#formatSimple(String, Object...), sodass die Syntax dieselbe ist.

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

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

ProtoLog in einem neuen Dienst verwenden

So verwenden Sie ProtoLog in einem neuen Prozess:

  1. Erstellen Sie eine ProtoLogGroup-Definition für diesen Dienst.

  2. Initialisieren Sie die Definition vor der ersten Verwendung (z. B. beim Erstellen des Prozesses):

    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 ProtoLog zur Kompilierzeit in einem Prozess aktivieren möchten, müssen Sie die Build-Regeln ändern und die Binärdatei protologtool 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. Erstere ist standardmäßig aktiviert.

Sie können ProtoLog mit Perfetto konfigurieren, wenn Sie einen Trace starten. Sie können ProtoLog auch lokal über die adb-Befehlszeile 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