Il sistema di logging di Android mira a raggiungere l'accessibilità universale e la facilità d'uso, presupponendo che tutti i dati di log possano essere rappresentati come una sequenza di caratteri. Questa ipotesi è in linea con la maggior parte dei casi d'uso, in particolare quando la leggibilità dei log è fondamentale senza strumenti specializzati. Tuttavia, negli ambienti che richiedono prestazioni di logging elevate e dimensioni dei log limitate, il logging basato su testo non è ottimale. Uno di questi scenari è WindowManager, che richiede un sistema di logging solido che gestisca i log di transizione delle finestre in tempo reale con un impatto minimo sul sistema.
ProtoLog è l'alternativa per soddisfare le esigenze di logging di WindowManager e servizi simili. ProtoLog offre i seguenti vantaggi rispetto a logcat:
- Utilizza meno risorse per la registrazione.
- Dal punto di vista di uno sviluppatore, funziona come il framework di logging Android predefinito.
- Consente di attivare o disattivare le istruzioni di log in fase di runtime.
- Può anche scrivere nel logcat.
Per ottimizzare l'utilizzo della memoria, ProtoLog utilizza un meccanismo di internamento delle stringhe. Questo meccanismo calcola e salva un hash compilato del messaggio. Per migliorare le prestazioni, ProtoLog esegue l'interning delle stringhe durante la compilazione per i servizi di sistema. Registra solo l'identificatore e gli argomenti del messaggio in fase di runtime. Quando generi una traccia ProtoLog o ottieni una segnalazione di bug, ProtoLog incorpora automaticamente il dizionario dei messaggi creato in fase di compilazione. In questo modo è possibile decodificare i messaggi da qualsiasi build.
ProtoLog memorizza i messaggi in formato binario (proto) all'interno di una traccia Perfetto.
La decodifica dei messaggi avviene all'interno di trace_processor di Perfetto. Il processo decodifica
i messaggi proto binari, traduce gli identificatori dei messaggi in stringhe utilizzando il
dizionario dei messaggi incorporato e formatta la stringa utilizzando argomenti dinamici.
ProtoLog supporta gli stessi livelli di log di android.utils.Log, ovvero d,
v, i, w, e e wtf.
ProtoLog lato client
Inizialmente, ProtoLog era destinato solo al lato server di WindowManager, operando all'interno di un singolo processo e componente. In seguito, è stato ampliato per includere il codice della shell WindowManager nel processo dell'interfaccia utente di sistema. Tuttavia, l'utilizzo di ProtoLog richiedeva un codice di configurazione boilerplate complesso. Inoltre, la registrazione di Proto è stata limitata ai processi del server di sistema e dell'interfaccia utente di sistema. Ciò rendeva difficile l'incorporazione in altri processi e richiedeva una configurazione separata del buffer di memoria per ciascuno. ProtoLog è ora disponibile per il codice lato client, eliminando la necessità di codice boilerplate aggiuntivo.
A differenza del codice dei servizi di sistema, il codice lato client di solito salta l'interning delle stringhe in fase di compilazione. L'interning delle stringhe viene invece eseguito dinamicamente in un thread in background. Di conseguenza, mentre ProtoLog lato client offre vantaggi di utilizzo della memoria paragonabili a ProtoLog sui servizi di sistema, comporta un overhead delle prestazioni leggermente superiore e non offre il vantaggio di riduzione della memoria della memoria bloccata che offre la sua controparte lato server.
Gruppi ProtoLog
I messaggi ProtoLog sono organizzati in gruppi chiamati ProtoLogGroups, in modo simile a come i messaggi Logcat sono organizzati per TAG. Questi ProtoLogGroups fungono da
cluster di messaggi che puoi attivare o disattivare in fase di runtime. Controllano anche
se i messaggi vengono rimossi durante la compilazione e dove vengono registrati
(proto, logcat o entrambi). Ogni ProtoLogGroup include le seguenti proprietà:
enabled: se impostato sufalse, i messaggi in questo gruppo vengono esclusi durante la compilazione e non sono disponibili in fase di runtime.logToProto: Definisce se questo gruppo registra i log in formato binario.logToLogcat: Definisce se questo gruppo registra i log in logcat.tag: il nome dell'origine del messaggio registrato.
Ogni processo che utilizza ProtoLog deve avere un'istanza ProtoLogGroup configurata.
Tipi di argomenti supportati
ProtoLog formatta internamente le stringhe utilizzando android.text.TextUtils#formatSimple(String, Object...),
quindi la sintassi è la stessa.
ProtoLog supporta i seguenti tipi di argomenti:
%b- booleano%d,%x- tipo integrale (short, integer o long)%f: tipo di virgola mobile (float o double)%s- stringa%%: un carattere percentuale letterale
Sono supportati i modificatori di larghezza e precisione, ad esempio %04d e %10b.
Tuttavia, argument_index e flags non sono supportati.
Utilizzare ProtoLog in un nuovo servizio
Per utilizzare ProtoLog in un nuovo servizio:
- Crea una definizione
ProtoLogGroupper questo servizio. Inizializza la definizione prima del primo utilizzo. Ad esempio, inizializzalo durante la creazione del processo:
ProtoLog.init(ProtoLogGroup.values());Utilizza
ProtoLognello stesso modo in cui utilizziandroid.util.Log:ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
Attivare l'ottimizzazione in fase di compilazione
Per abilitare ProtoLog in fase di compilazione in un processo, devi modificare le regole di compilazione
e richiamare il binario protologtool.
ProtoLogTool è un file binario di trasformazione del codice che esegue l'interning delle stringhe
e aggiorna la chiamata ProtoLog. Questo file binario trasforma ogni chiamata di logging ProtoLog come mostrato in questo esempio:
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 questo esempio, ProtoLog, ProtoLogImpl e ProtoLogGroup sono le
classi fornite come argomenti (possono essere importate, importate staticamente o con il percorso completo,
le importazioni con caratteri jolly non sono consentite) e x è il metodo di logging.
La trasformazione viene eseguita a livello di origine. Un hash viene generato dalla
stringa di formato, dal livello di log e dal nome del gruppo di log e viene inserito dopo l'argomento
ProtoLogGroup. Il codice reale generato è incorporato e viene aggiunto un numero di nuovi
caratteri di fine riga per preservare la numerazione delle righe nel file.
Esempio:
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"],
}
Opzioni della riga di comando
Uno dei principali vantaggi di ProtoLog è che puoi attivarlo o disattivarlo in fase di runtime. Ad esempio, puoi avere un logging più dettagliato in una build, disattivato per
impostazione predefinita e attivarlo durante lo sviluppo locale per eseguire il debug di un problema specifico. Questo pattern viene utilizzato, ad esempio, in WindowManager con i gruppi
WM_DEBUG_WINDOW_TRANSITIONS e WM_DEBUG_WINDOW_TRANSITIONS_MIN che consentono
diversi tipi di logging delle transizioni, con il primo abilitato per impostazione predefinita.
Puoi configurare ProtoLog utilizzando Perfetto quando avvii una traccia. Puoi anche configurare ProtoLog localmente utilizzando la riga di comando adb.
Il comando adb shell cmd protolog_configuration supporta i seguenti
argomenti:
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
Suggerimenti per un utilizzo efficace
ProtoLog utilizza l'interning delle stringhe sia per il messaggio che per gli argomenti stringa trasmessi. Ciò significa che, per ottenere maggiori vantaggi da ProtoLog, i messaggi devono isolare i valori ripetuti in variabili.
Ad esempio, considera la seguente affermazione:
Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);
Quando viene ottimizzato in fase di compilazione, si traduce in questo modo:
ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);
Se ProtoLog viene utilizzato nel codice con gli argomenti 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");
Vengono visualizzati i seguenti messaggi in memoria:
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)
Se, invece, l'istruzione ProtoLog è stata scritta come segue:
Protolog.v(MY_GROUP, "The argument value is %s", argument);
Il buffer in memoria risulterebbe:
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)
Questa sequenza comporta un utilizzo di memoria inferiore del 35%.
Visualizzatore Winscope
La scheda del visualizzatore ProtoLog di Winscope mostra le tracce ProtoLog organizzate in formato tabellare. Puoi filtrare le tracce in base al livello di log, al tag, al file di origine (in cui è presente l'istruzione ProtoLog) e ai contenuti del messaggio. Tutte le colonne sono filtrabili. Se fai clic sul timestamp nella prima colonna, la sequenza temporale viene spostata sul timestamp del messaggio. Inoltre, se fai clic su Vai all'ora corrente, la tabella ProtoLog torna al timestamp selezionato nella sequenza temporale:
Figura 1. Visualizzatore ProtoLog