Skip to content

Add AdbRunner structured logcat streaming API #348

@rmarinho

Description

@rmarinho

Summary

Add structured logcat streaming to Xamarin.Android.Tools.AdbRunner so the MAUI DevTools CLI can surface device logs to agents and IDEs without consumers parsing raw adb logcat output.

Context

adb logcat is the only way to see app logs from an Android device until the DevFlow agent in the app is up and running. Even after that, system-level logs (ART runtime, app crashes, native binder errors) only appear in logcat. The MAUI DevTools CLI and DevFlow skills currently advise raw adb logcat … as a fallback — see maui-labs#197 audit. We want a typed stream so consumers can filter and emit structured JSON.

Proposed API

namespace Xamarin.Android.Tools;

public partial class AdbRunner
{
    /// adb -s <serial> logcat [-v threadtime] [-T 'YYYY-MM-DD HH:mm:ss.SSS' | -d] <filterspec>
    public virtual IAsyncEnumerable<LogcatEntry> LogcatAsync(
        string serial,
        LogcatOptions? options = null,
        CancellationToken cancellationToken = default);

    /// adb -s <serial> logcat -c — clear the device log buffer.
    public virtual Task ClearLogcatAsync(string serial, CancellationToken cancellationToken = default);
}

public record LogcatOptions(
    /// Tag/priority filter pairs. Examples: ("MauiDevFlow","V"), ("DOTNET","I"), ("*","W")
    IReadOnlyList<LogcatFilter>? Filters = null,
    /// Restrict to a process. When set, runs `adb shell pidof <package>` to resolve PID once.
    string? PackageName = null,
    int? Pid = null,
    /// `-T <epoch|date>`: only entries since this point. Implies `-d` is false.
    DateTimeOffset? Since = null,
    /// `-d`: dump existing buffer and exit (no streaming).
    bool DumpAndExit = false,
    /// Buffer to read: main, system, crash, events, all.
    LogcatBuffer Buffers = LogcatBuffer.Main | LogcatBuffer.System | LogcatBuffer.Crash);

public record LogcatFilter(string Tag, LogcatLevel Minimum);
public enum LogcatLevel { Verbose, Debug, Info, Warning, Error, Fatal, Silent }

[Flags]
public enum LogcatBuffer { Main = 1, System = 2, Radio = 4, Events = 8, Crash = 16, All = Main | System | Radio | Events | Crash }

public record LogcatEntry(
    DateTimeOffset Timestamp,
    int Pid,
    int Tid,
    LogcatLevel Level,
    string Tag,
    string Message);

Implementation notes:

  • Use -v threadtime format (well-defined columns) and parse line-by-line. Format spec: MM-DD HH:MM:SS.mmm PID TID L tag: message.
  • The async enumerable yields one LogcatEntry per parsed line. Cancellation kills the underlying adb logcat process.
  • When PackageName is set and PID resolution returns nothing, retry once after a short delay (covers app-not-yet-launched). After two failures, throw.
  • ClearLogcatAsync is separate so streaming can resume from a known point.

Consumer

  • MAUI DevTools CLI (dotnet/maui-labs) — maui android device logcat [--package …] [--tag …] [--since …] [--json] with sensible MAUI defaults (tags MauiDevFlow, DOTNET, mono-rt, ART, level Info on the rest). See maui-labs#197 audit.
  • DevFlow agent debugging — pre-agent diagnostics (the agent itself emits structured logs via the HTTP API once up; this gap covers the pre-agent window).
  • Integration test fixtures that need to assert "log line X appeared within Y seconds" without a manual parser.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions