Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit db079b42 authored by Simon Chan's avatar Simon Chan
Browse files

feat(log): format log entry like native binary

parent 2621f83c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -175,7 +175,7 @@ export const Grid = withDisplayName('Grid')(({
                if (scrollTop < bodyRef.scrollHeight - bodyRef.clientHeight && bodyRef.scrollTop < scrollTop) {
                    setAutoScroll(false);
                }
            } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 50) {
            } else if (bodyRef.scrollTop + bodyRef.offsetHeight >= bodyRef.scrollHeight - 10) {
                setAutoScroll(true);
            }

+56 −4
Original line number Diff line number Diff line
import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react";
import { Checkbox, ICommandBarItemProps, Stack, StackItem } from "@fluentui/react";
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
import { AndroidLogEntry, AndroidLogPriority, Logcat } from '@yume-chan/android-bin';
import { AndroidLogEntry, AndroidLogPriority, formatAndroidLogEntry, Logcat, LogcatFormat } from '@yume-chan/android-bin';
import { BTree } from '@yume-chan/b-tree';
import { AbortController, ReadableStream, WritableStream } from '@yume-chan/stream-extra';
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import Head from "next/head";
import { FormEvent, useEffect, useState } from 'react';

import { CommandBar, Grid, GridColumn, GridHeaderProps, GridRowProps } from "../components";
import { GlobalState } from "../state";
@@ -55,6 +57,7 @@ const state = makeAutoObservable({
    buffer: [] as LogRow[],
    flushRequested: false,
    list: [] as LogRow[],
    selection: new BTree(6),
    count: 0,
    stream: undefined as ReadableStream<AndroidLogEntry> | undefined,
    stopSignal: undefined as AbortController | undefined,
@@ -98,6 +101,7 @@ const state = makeAutoObservable({
    },
    clear() {
        this.list = [];
        this.selection.clear();
        this.selectedCount = 0;
    },
    get empty() {
@@ -130,7 +134,16 @@ const state = makeAutoObservable({
                disabled: this.selectedCount === 0,
                iconProps: { iconName: Icons.Copy },
                onClick: () => {

                    let text = '';
                    for (const index of this.selection) {
                        text += formatAndroidLogEntry(
                            this.list[index],
                            LogcatFormat.Brief
                        ) + '\n';
                    }
                    // Chrome on Windows can't copy null characters
                    text = text.replace(/\u0000/g, '');
                    navigator.clipboard.writeText(text);
                }
            },
            {
@@ -139,13 +152,52 @@ const state = makeAutoObservable({
                disabled: this.selectedCount === 0,
                iconProps: { iconName: Icons.Copy },
                onClick: () => {

                    let text = '';
                    for (const index of this.selection) {
                        text += this.list[index].message + '\n';
                    }
                    // Chrome on Windows can't copy null characters
                    text = text.replace(/\u0000/g, '');
                    navigator.clipboard.writeText(text);
                }
            }
        ];
    },
    get columns(): Column[] {
        return [
            {
                width: 40,
                title: '',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const [checked, setChecked] = useState(false);
                    useEffect(() => {
                        setChecked(this.selection.has(rowIndex));
                    }, [rowIndex]);

                    const handleChange = useStableCallback((e?: FormEvent<EventTarget>, checked?: boolean) => {
                        if (checked === undefined) {
                            return;
                        }
                        if (checked) {
                            this.selection.add(rowIndex);
                            setChecked(true);
                        } else {
                            this.selection.delete(rowIndex);
                            setChecked(false);
                        }
                        runInAction(() => {
                            // Trigger mobx
                            this.selectedCount = this.selection.size;
                        });
                    });

                    return (
                        <Stack className={className} verticalAlign='center' horizontalAlign='center' {...rest}>
                            <Checkbox checked={checked} onChange={handleChange} />
                        </Stack>
                    );
                }
            },
            {
                width: 200,
                title: 'Time',
+1 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ export function register() {
            WindowConsole: <WindowConsoleRegular style={STYLE} />,

            // Required by @fluentui/react
            Checkmark: <CheckmarkRegular style={STYLE} />,
            StatusCircleCheckmark: <CheckmarkRegular style={STYLE} />,
            ChevronUpSmall: <ChevronUpRegular style={STYLE} />,
            ChevronDownSmall: <ChevronDownRegular style={STYLE} />,
+52 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ export enum AndroidLogPriority {
    Unknown,
    Default,
    Verbose,
    Debug,
    Info,
    Warn,
    Error,
@@ -32,6 +33,41 @@ export enum AndroidLogPriority {
    Silent,
}

// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> = {
    [AndroidLogPriority.Unknown]: '?',
    [AndroidLogPriority.Default]: '?',
    [AndroidLogPriority.Verbose]: 'V',
    [AndroidLogPriority.Debug]: 'D',
    [AndroidLogPriority.Info]: 'I',
    [AndroidLogPriority.Warn]: 'W',
    [AndroidLogPriority.Error]: 'E',
    [AndroidLogPriority.Fatal]: 'F',
    [AndroidLogPriority.Silent]: 'S',
};

export enum LogcatFormat {
    Brief,
    Process,
    Tag,
    Thread,
    Raw,
    Time,
    ThreadTime,
    Long
}

export interface LogcatFormatModifiers {
    usec?: boolean;
    printable?: boolean;
    year?: boolean;
    zone?: boolean;
    epoch?: boolean;
    monotonic?: boolean;
    uid?: boolean;
    descriptive?: boolean;
}

export interface LogcatOptions {
    pid?: number;
    ids?: LogId[];
@@ -65,6 +101,21 @@ export interface AndroidLogEntry extends LoggerEntry {
    message: string;
}

// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
export function formatAndroidLogEntry(
    entry: AndroidLogEntry,
    format: LogcatFormat = LogcatFormat.Brief,
    modifier?: LogcatFormatModifiers
) {
    const uid = modifier?.uid ? `${entry.uid.toString().padStart(5)}:` : '';

    switch (format) {
        // TODO: implement other formats
        default:
            return `${AndroidLogPriorityToCharacter[entry.priority]}/${entry.tag.padEnd(8)}(${uid}${entry.pid.toString().padStart(5)}): ${entry.message}`;
    }
}

function findTagEnd(payload: Uint8Array) {
    for (const separator of [0, ' '.charCodeAt(0), ':'.charCodeAt(0)]) {
        const index = payload.indexOf(separator);
@@ -200,6 +251,6 @@ export class Logcat extends AdbCommandBase {
            return stdout;
        }).pipeThrough(new BufferedTransformStream(stream => {
            return deserializeAndroidLogEntry(stream);
        }))
        }));
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -55,13 +55,13 @@ describe('BTree', () => {

                const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2);
                for (let value of values) {
                    tree.insert(value);
                    tree.add(value);
                    validateTree(tree);
                    expect(tree.has(value)).toBe(true);
                }

                for (let value of values) {
                    tree.remove(value);
                    tree.delete(value);
                    validateTree(tree);
                    expect(tree.has(value)).toBe(false);
                }
@@ -73,14 +73,14 @@ describe('BTree', () => {
                const values = Array.from({ length: LENGTH }, (_, i) => i - LENGTH / 2);
                shuffle(values);
                for (const value of values) {
                    tree.insert(value);
                    tree.add(value);
                    validateTree(tree);
                    expect(tree.has(value)).toBe(true);
                }

                shuffle(values);
                for (const value of values) {
                    tree.remove(value);
                    tree.delete(value);
                    validateTree(tree);
                    expect(tree.has(value)).toBe(false);
                }
Loading