blob: f9ec510206f45e60062762c2296159a24457f406 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build android
// Package logreaderlib implements the LogFile interface from
// v.io/v23/services/logreader for Android applications where log lines are
// written using the Android logging API.
package logreaderlib
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"os/exec"
"regexp"
"strconv"
"strings"
"v.io/v23/context"
"v.io/v23/glob"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/services/logreader"
"v.io/v23/verror"
)
var errNoSizeForPriority = verror.Register(pkgPath+".errNoSizeForPriority", verror.NoRetry, "{1:}{2:} log entries may be present but cannot determine the size for {:}")
// NewLogFileService returns a logreader.LogFileServer suitable for use on
// Android devices where the underlying data is extracted using the "logcat"
// command. See http://developer.android.com/tools/debugging/debugging-log.html
//
// The first argument to this function is ignored. It is only present to ensure
// that this function has the same signature whether this package is being
// compiled for android or not.
func NewLogFileService(_, suffix string) interface{} {
return logreader.LogFileServer(androidLogFile(suffix))
}
type androidLogFile string
func (l androidLogFile) Size(ctx *context.T, _ rpc.ServerCall) (int64, error) {
switch l.priority() {
case "":
return 0, nil
case "V":
// Verbose means everything, so "logcat -g" is what we want.
default:
// Cannot obtain size without running through the whole log and
// filtering for the appropriate tags. Error out instead of
// working so hard.
return 0, verror.New(errNoSizeForPriority, ctx, l)
}
// Output from the "adb logcat -g" command I ran in February 2016 is
// below. This output format is not part of the SDK and is subject to
// change at any time.
//
// main: ring buffer is 256Kb (255Kb consumed), max entry is 5120b, max payload is 4076b
// system: ring buffer is 256Kb (252Kb consumed), max entry is 5120b, max payload is 4076b
// crash: ring buffer is 256Kb (6Kb consumed), max entry is 5120b, max payload is 4076b
re, err := regexp.Compile("([0-9]+[^ ]+)[bB] consumed")
if err != nil {
return 0, err
}
cmd := logcat("-g")
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
if err := cmd.Start(); err != nil {
return 0, err
}
defer cmd.Wait()
scanner := bufio.NewScanner(out)
var sum int64
for scanner.Scan() {
m := re.FindSubmatch(scanner.Bytes())
if len(m) > 1 && len(m[1]) > 0 {
s := string(m[1])
mult := 1
switch {
case strings.HasSuffix(s, "K"):
s = strings.TrimSuffix(s, "K")
mult = 1 << 10
case strings.HasSuffix(s, "M"):
s = strings.TrimSuffix(s, "M")
mult = 1 << 20
}
n, err := strconv.Atoi(s)
if err != nil {
return sum, verror.New(errOperationFailed, ctx, fmt.Errorf("failed to parse output of 'logcat -g': %q (%v)", s, err))
}
sum += int64(n * mult)
}
}
return sum, scanner.Err()
}
func (l androidLogFile) ReadLog(ctx *context.T, call logreader.LogFileReadLogServerCall, startpos int64, numEntries int32, follow bool) (int64, error) {
if len(l) == 0 {
return 0, verror.NewErrNoExist(ctx)
}
priority := "*:" + l.priority()
var cmd *exec.Cmd
if follow {
cmd = logcat(priority)
} else {
cmd = logcat("-d", priority)
}
// As a cheap form of "auditing", log who is reading the logs!
readerBlessings, _ := security.RemoteBlessingNames(ctx, call.Security())
ctx.Infof("ReadLog:Begin: by %v logcat %v, follow:%v", readerBlessings, priority, follow)
defer ctx.Infof("ReadLog:End: %v logcat %v, follow:%v", readerBlessings, priority, follow)
out, err := cmd.StdoutPipe()
if err != nil {
return 0, err
}
defer out.Close()
if err := cmd.Start(); err != nil {
return 0, err
}
defer cmd.Process.Kill()
if startpos > 0 {
if _, err := io.CopyN(ioutil.Discard, out, startpos); err != nil {
return 0, err
}
}
reader := newFollowReader(ctx, out, startpos, follow)
if numEntries == logreader.AllEntries {
numEntries = int32(math.MaxInt32)
}
for i := int32(0); i < numEntries; i++ {
line, offset, err := reader.readLine()
if err == io.EOF && i > 0 {
return reader.tell(), nil
}
if err == io.EOF {
return reader.tell(), verror.NewErrEndOfFile(ctx)
}
if err != nil {
return reader.tell(), verror.New(errOperationFailed, ctx, err)
}
if err := call.SendStream().Send(logreader.LogEntry{Position: offset, Line: line}); err != nil {
return reader.tell(), err
}
}
return reader.tell(), nil
}
// priority returns the substring used by the android logging system.
// Verbose -> V, Info -> I etc.
func (l androidLogFile) priority() string {
if len(l) == 0 {
return ""
}
return string(l)[0:1]
}
func (l androidLogFile) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
if len(l) > 0 {
return nil
}
// From http://developer.android.com/tools/debugging/debugging-log.html
sender := call.SendStream()
for _, v := range []string{"Verbose", "Debug", "Info", "Warning", "Error", "Fatal"} {
if m.Match(v) {
sender.Send(naming.GlobChildrenReplyName{Value: v})
}
}
return nil
}
func logcat(args ...string) *exec.Cmd { return exec.Command("/system/bin/logcat", args...) }