Protokoll

Das Android-Protokollierungssystem soll universell zugänglich und einfach zu bedienen sein. Dabei wird davon ausgegangen, dass alle Protokolldaten als Zeichenfolge dargestellt werden können. Diese Annahme trifft auf die meisten Anwendungsfälle zu, insbesondere wenn die Lesbarkeit von Protokollen ohne spezielle Tools entscheidend ist. In Umgebungen, in denen eine hohe Protokollierungsleistung und eine begrenzte Protokollgröße erforderlich sind, ist die textbasierte Protokollierung jedoch möglicherweise nicht optimal. Ein solches Szenario ist WindowManager, für das ein robustes Logging-System erforderlich ist, das Echtzeit-Logs für Fensterübergänge mit minimalen Systemauswirkungen verarbeiten kann.

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

  • Die für die Protokollierung verwendeten Ressourcen sind geringer.
  • Aus Entwicklersicht ist das dasselbe wie die Verwendung des standardmäßigen Android-Logging-Frameworks.
  • Unterstützt Protokollanweisungen, die zur Laufzeit aktiviert oder deaktiviert werden können.
  • Sie können bei Bedarf weiterhin in Logcat protokollieren.

Zur Optimierung der Speichernutzung verwendet ProtoLog einen String-Internierungsmechanismus, bei dem ein kompilierter Hash der Nachricht berechnet und gespeichert wird. Für eine bessere Leistung führt ProtoLog während der Kompilierung (für Systemdienste) eine Stringinternierung durch und zeichnet bei der Laufzeit nur die Nachrichten-ID und die Argumente auf. Wenn Sie einen ProtoLog-Trace generieren oder einen Fehlerbericht abrufen, wird das bei der Kompilierung erstellte Nachrichtenwörterbuch automatisch in ProtoLog eingefügt. So ist die Nachrichtendekodierung aus jedem Build möglich.

Mit ProtoLog wird die Nachricht in einem Binärformat (Proto) in einer Perfecto-Spur gespeichert. Die Dekodierung der Nachricht erfolgt in Perfettos trace_processor. Dabei werden die binären Proto-Nachrichten decodiert, die Nachrichten-IDs mithilfe des eingebetteten Nachrichten-Wörterbuchs in Strings übersetzt und der String mithilfe dynamischer Argumente formatiert.

ProtoLog unterstützt dieselben Lobenen wie android.utils.Log: d, v, i, w, e und wtf.

Clientseitiges ProtoLog

Ursprünglich war ProtoLog ausschließlich für die Serverseite des WindowManagers gedacht, der in einem einzigen Prozess und einer einzigen Komponente ausgeführt wurde. Anschließend wurde er erweitert, um den WindowManager-Shell-Code im System-UI-Prozess zu umfassen. Für die Verwendung von ProtoLog war jedoch ein komplexer Boilerplate-Einrichtungscode erforderlich. Außerdem war die Proto-Protokollierung auf Systemserver- und System-UI-Prozesse beschränkt, was die Einbindung in andere Prozesse erschwert und für jeden Prozess eine separate Arbeitsspeicher-Bufferkonfiguration erforderte. ProtoLog ist jetzt jedoch auch für clientseitigen Code verfügbar, sodass kein zusätzlicher Boilerplate-Code mehr erforderlich ist.

Im Gegensatz zum Code von Systemdiensten wird beim clientseitigen Code in der Regel das String-Interning zur Kompilierungszeit übersprungen. Stattdessen erfolgt die Stringinternierung dynamisch in einem Hintergrund-Thread. Daher bietet ProtoLog auf der Clientseite zwar vergleichbare Vorteile bei der Speichernutzung wie ProtoLog in Systemdiensten, verursacht aber einen geringfügig höheren Leistungsoverhead und bietet nicht den Vorteil der Speicherreduzierung durch angehängten Arbeitsspeicher wie sein serverseitiges Pendant.

ProtoLog-Gruppen

ProtoLog-Nachrichten sind in Gruppen namens ProtoLogGroups organisiert, ähnlich wie Logcat-Nachrichten nach TAG organisiert sind. Diese ProtoLogGroups dienen als Nachrichtencluster, die zur Laufzeit gemeinsam aktiviert oder deaktiviert werden können. Außerdem wird festgelegt, ob Nachrichten während der Kompilierung entfernt werden sollen und wo sie protokolliert werden sollen (proto, logcat oder beides). Jedes ProtoLogGroup umfasst die folgenden Eigenschaften:

  • enabled: Wenn diese Option auf false festgelegt ist, werden Meldungen in dieser Gruppe während der Kompilierung ausgeschlossen und sind zur Laufzeit nicht verfügbar.
  • logToProto: Gibt an, ob diese Gruppe Logs im Binärformat erstellt.
  • logToLogcat: Hiermit wird festgelegt, ob diese Gruppe Logs in Logcat sendet.
  • tag: Name der Quelle der protokollierten Nachricht.

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

Unterstützte Argumenttypen

Intern werden in ProtoLog-Formaten Strings mit android.text.TextUtils#formatSimple(String, Object...) formatiert. Die Syntax ist also identisch.

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

Modifikatoren für Breite und Genauigkeit 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 wie android.util.Log:

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

Optimierung bei der Kompilierung aktivieren

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

ProtoLogTool ist eine Codetransformations-Binärdatei, die String-Internierung durchführt und die ProtoLog-Aufrufung aktualisiert. Dieses Binary transformiert jeden ProtoLog-Loggingaufruf, 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 angegebenen Klassen (können importiert, statisch importiert oder als Vollpfad angegeben werden, Platzhalterimporte sind nicht zulässig) und x ist die Logging-Methode.

Die Transformation erfolgt auf Quellebene. Aus dem Formatstring, der Protokollebene und dem Namen der Protokollgruppe wird ein Hash generiert und nach dem ProtoLogGroup-Argument eingefügt. Der tatsächlich generierte Code wird eingefügt und es werden einige neue Zeilenzeichen 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 verwenden, das standardmäßig deaktiviert ist, und es während der lokalen Entwicklung aktivieren, um ein bestimmtes Problem zu beheben. Dieses Muster wird beispielsweise im WindowManager mit den Gruppen WM_DEBUG_WINDOW_TRANSITIONS und WM_DEBUG_WINDOW_TRANSITIONS_MIN verwendet, um verschiedene Arten der Protokollierung von Übergängen zu ermöglichen. Die erste Gruppe ist standardmäßig aktiviert.

Sie können ProtoLog mit Perfetto konfigurieren, wenn Sie eine 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-Internierung sowohl für die Nachricht als auch für alle übergebenen Stringargumente. Um ProtoLog optimal nutzen zu können, sollten wiederholte Werte in den Nachrichten in Variablen isoliert werden.

Betrachten Sie beispielsweise die folgende Anweisung:

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

Bei der Optimierung zur Kompilierungszeit ergibt sich 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");

Dies führt zu den folgenden Meldungen 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 wäre:

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

Der In-Memory-Puffer würde dann 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 Arbeitsspeicherbelegung.

Winscope-Betrachter

Auf dem Tab „ProtoLog-Betrachter“ von Winscope werden ProtoLog-Traces in Tabellenform angezeigt. Sie können Protokolle nach Protokollebene, Tag, Quelldatei (in der sich die ProtoLog-Anweisung befindet) und Nachrichteninhalt filtern. Alle Spalten können gefiltert werden. Wenn Sie auf den Zeitstempel in der ersten Spalte klicken, wird die Zeitachse zum Zeitstempel der Nachricht verschoben. Wenn Sie auf Zur aktuellen Zeit klicken, wird die ProtoLog-Tabelle zum Zeitstempel zurückgescrollt, der in der Zeitachse ausgewählt wurde:

ProtoLog-Betrachter

Abbildung 1: ProtoLog-Betrachter