blob: 6f4a9c11f03cd5b934d5c7090dd770192572058a [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"path"
"runtime"
"strings"
)
// StackTrace holds a captured stacktrace in raw form.
type StackTrace struct {
callers []uintptr
}
// StackEntry holds the human understandable form of a StackTrace entry.
type StackEntry struct {
Dir string // The directory name this file is from
File string // The filename for this entry
Line int // The line number for this entry
Package string // The package the function is from
Function string // The function this entry is inside
}
const stackLimit = 50
// StackFilter is invoked during the pc to StackEntry conversion, and can be used to filter/modify the entries.
type StackFilter func(entry *StackEntry) bool
// GetStackFilter gets the active StackFilter for this context.
// If no filter was set, returns InternalPackageFilter.
func GetStackFilter(ctx Context) StackFilter {
if filter, ok := ctx.Value(stackFilterKey).(StackFilter); ok {
return filter
}
return InternalPackageFilter
}
// StackFilter returns a context with the given StackFilter set on it.
func (ctx logContext) StackFilter(f StackFilter) Context {
return ctx.setValue(stackFilterKey, f)
}
// Collect collects the current stack trace.
func (s *StackTrace) Collect() {
var callers [stackLimit]uintptr
count := runtime.Callers(0, callers[:])
s.callers = callers[:count]
}
var internalPackages = map[string]struct{}{
"runtime": {},
"android.googlesource.com/platform/tools/gpu/framework/log": {},
"android.googlesource.com/platform/tools/gpu/framework/log_test": {},
}
// InternalPackageFilter implements a StackFilter that drops lines that come from the logging system itself.
// This is installed by default so that the last entry in the stack trace is the line that entered the logging
// rather than the internal function that captured the stacktrace.
func InternalPackageFilter(entry *StackEntry) bool {
_, filtered := internalPackages[entry.Package]
return filtered
}
func (s StackTrace) Get(calls []StackEntry, filter StackFilter) []StackEntry {
for _, pc := range s.callers {
if len(calls) == cap(calls) {
return calls
}
// See documentation for runtime.Callers for why we use pc-1 in here
f := runtime.FuncForPC(pc - 1)
filename, line := f.FileLine(pc - 1)
entry := StackEntry{Line: line}
entry.Dir, entry.File = path.Split(filename)
name := f.Name()
// name is of the form android.googlesource.com/platform/tools/gpu/framework/log.StacktraceOnError
// we find the last /, then find the next . to split the function name from the package name
i := strings.LastIndex(name, "/")
i += strings.IndexRune(name[i+1:], '.')
entry.Package = name[:i+1]
entry.Function = name[i+2:]
filtered := false
if filter != nil {
filtered = filter(&entry)
}
if !filtered {
calls = append(calls, entry)
}
}
return calls
}
func (s StackTrace) Current(filter StackFilter) (StackEntry, bool) {
calls := [1]StackEntry{}
count := len(s.Get(calls[:0], filter))
return calls[0], count > 0
}
func (s StackTrace) Calls(filter StackFilter) []StackEntry {
calls := make([]StackEntry, 0, len(s.callers))
return s.Get(calls, filter)
}
// StacktraceOnError is a Recorder that collects a stack trace for any log message of Error or higher severity
func StacktraceOnError(ctx Context, r *Record) {
if r.Severity <= ErrorLevel {
r.StackTrace.Collect()
}
}