adb-toolkit/backend_logcat.go

141 lines
3 KiB
Go

package main
import (
"bufio"
"context"
"fmt"
"os/exec"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type LogcatLine struct {
Raw string `json:"raw"`
Level string `json:"level"`
Tag string `json:"tag"`
Message string `json:"message"`
PID string `json:"pid"`
Time string `json:"time"`
}
var (
logcatCancel context.CancelFunc
logcatMu sync.Mutex
)
// StartLogcat begins streaming logcat to the frontend via events.
// filter: optional tag filter e.g. "ActivityManager:I *:S"
// buffer: "main", "radio", "events", "crash", or "all"
func (a *App) StartLogcat(filter string, buffer string) error {
logcatMu.Lock()
defer logcatMu.Unlock()
// Stop any existing logcat
if logcatCancel != nil {
logcatCancel()
logcatCancel = nil
}
adbPath, err := a.getBinaryPath("adb")
if err != nil {
return err
}
args := []string{"logcat", "-v", "threadtime"}
if buffer != "" && buffer != "main" {
if buffer == "all" {
args = append(args, "-b", "all")
} else {
args = append(args, "-b", buffer)
}
}
if filter != "" {
args = append(args, strings.Fields(filter)...)
}
ctx, cancel := context.WithCancel(context.Background())
logcatCancel = cancel
cmd := exec.CommandContext(ctx, adbPath, args...)
setCommandSysProcAttr(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
return fmt.Errorf("failed to get stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
cancel()
return fmt.Errorf("failed to start logcat: %w", err)
}
go func() {
defer cancel()
scanner := bufio.NewScanner(stdout)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Text()
parsed := parseLogcatLine(line)
runtime.EventsEmit(a.ctx, "logcat:line", parsed)
}
runtime.EventsEmit(a.ctx, "logcat:stopped", nil)
cmd.Wait()
}()
return nil
}
// StopLogcat stops the running logcat stream.
func (a *App) StopLogcat() {
logcatMu.Lock()
defer logcatMu.Unlock()
if logcatCancel != nil {
logcatCancel()
logcatCancel = nil
}
}
// ClearLogcat clears all logcat buffers.
func (a *App) ClearLogcat() error {
_, err := a.runCommand("adb", "logcat", "-c")
return err
}
// parseLogcatLine parses a threadtime format logcat line:
// MM-DD HH:MM:SS.mmm PID TID LEVEL TAG: message
func parseLogcatLine(line string) LogcatLine {
result := LogcatLine{Raw: line}
parts := strings.SplitN(line, " ", 7)
if len(parts) < 7 {
result.Message = line
return result
}
result.Time = strings.TrimSpace(parts[0] + " " + parts[1])
result.PID = strings.TrimSpace(parts[2])
// parts[3] = TID
level := strings.TrimSpace(parts[4])
if len(level) > 0 {
result.Level = level
}
tagAndMsg := strings.TrimSpace(parts[5])
if idx := strings.Index(tagAndMsg, ":"); idx >= 0 {
result.Tag = strings.TrimSpace(tagAndMsg[:idx])
if len(parts) > 6 {
result.Message = strings.TrimSpace(parts[6])
}
} else {
result.Tag = tagAndMsg
if len(parts) > 6 {
result.Message = strings.TrimSpace(parts[6])
}
}
return result
}