0
Files
aleesa-jabber-go/vendor/github.com/cockroachdb/errors/withstack/reportable.go
2023-09-17 12:18:09 +03:00

205 lines
6.0 KiB
Go

// Copyright 2019 The Cockroach Authors.
//
// 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 withstack
import (
"errors"
"fmt"
"go/build"
"path/filepath"
"strconv"
"strings"
"github.com/cockroachdb/errors/errbase"
"github.com/getsentry/sentry-go"
pkgErr "github.com/pkg/errors"
)
// ReportableStackTrace aliases the type of the same name in the sentry
// package. This is used by the 'report' error package.
type ReportableStackTrace = sentry.Stacktrace
// GetReportableStackTrace extracts a stack trace embedded in the
// given error in the format suitable for Sentry reporting.
//
// This supports:
// - errors generated by github.com/pkg/errors (either generated
// locally or after transfer through the network),
// - errors generated with WithStack() in this package,
// - any other error that implements a StackTrace() method
// returning a StackTrace from github.com/pkg/errors.
//
// Note: Sentry wants the oldest call frame first, so
// the entries are reversed in the result.
func GetReportableStackTrace(err error) *ReportableStackTrace {
// If we have a stack trace in the style of github.com/pkg/errors
// (either from there or our own withStack), use it.
if st, ok := err.(errbase.StackTraceProvider); ok {
return convertPkgStack(st.StackTrace())
}
// If we have flattened a github.com/pkg/errors-style stack
// trace to a string, it will happen in the error's safe details
// and we need to parse it.
if sd, ok := err.(errbase.SafeDetailer); ok {
details := sd.SafeDetails()
if len(details) > 0 {
switch errbase.GetTypeKey(err) {
case pkgFundamental, pkgWithStackName, ourWithStackName:
return parsePrintedStack(details[0])
}
}
}
// No conversion available - no stack trace.
return nil
}
type frame = sentry.Frame
// convertPkgStack converts a StackTrace from github.com/pkg/errors
// to a Stacktrace in github.com/getsentry/sentry-go.
func convertPkgStack(st errbase.StackTrace) *ReportableStackTrace {
// If there are no frames, the entire stacktrace is nil.
if len(st) == 0 {
return nil
}
// Note: the stack trace logic changed between go 1.11 and 1.12.
// Trying to analyze the frame PCs point-wise will cause
// the output to change between the go versions.
return parsePrintedStack(fmt.Sprintf("%+v", st))
}
// trimPath is a copy of the same function in package sentry-go.
func trimPath(filename string) string {
for _, prefix := range trimPaths {
if trimmed := strings.TrimPrefix(filename, prefix); len(trimmed) < len(filename) {
return trimmed
}
}
return filename
}
var trimPaths []string
// init is a copy of the same function in package sentry-go.
func init() {
// Collect all source directories, and make sure they
// end in a trailing "separator"
for _, prefix := range build.Default.SrcDirs() {
if prefix[len(prefix)-1] != filepath.Separator {
prefix += string(filepath.Separator)
}
trimPaths = append(trimPaths, prefix)
}
}
// functionName is an adapted copy of the same function in package sentry-go.
func functionName(fnName string) (pack string, name string) {
name = fnName
// We get this:
// runtime/debug.*T·ptrmethod
// and want this:
// pack = runtime/debug
// name = *T.ptrmethod
if idx := strings.LastIndex(name, "."); idx != -1 {
pack = name[:idx]
name = name[idx+1:]
}
name = strings.Replace(name, "·", ".", -1)
return
}
// parsePrintedStack reverse-engineers a reportable stack trace from
// the result of printing a github.com/pkg/errors stack trace with format %+v.
func parsePrintedStack(st string) *ReportableStackTrace {
// A printed stack trace looks like a repetition of either:
// "unknown"
// or
// <result of fn.Name()>
// <tab><file>:<linenum>
// It's also likely to contain a heading newline character(s).
var frames []frame
lines := strings.Split(strings.TrimSpace(st), "\n")
for i := 0; i < len(lines); i++ {
nextI, file, line, fnName := parsePrintedStackEntry(lines, i)
i = nextI
// Compose the frame.
frame := frame{
AbsPath: file,
Filename: trimPath(file),
Lineno: line,
InApp: true,
Module: "unknown",
Function: fnName,
}
if fnName != "unknown" {
// Extract the function/module details.
frame.Module, frame.Function = functionName(fnName)
}
frames = append(frames, frame)
}
if frames == nil {
return nil
}
// Sentry wants the frames with the oldest first, so reverse them.
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
frames[i], frames[j] = frames[j], frames[i]
}
return &ReportableStackTrace{Frames: frames}
}
// parsePrintedStackEntry extracts the stack entry information
// in lines at position i. It returns the new value of i if more than
// one line was read.
func parsePrintedStackEntry(
lines []string, i int,
) (newI int, file string, line int, fnName string) {
// The function name is on the first line.
fnName = lines[i]
// The file:line pair may be on the line after that.
if i < len(lines)-1 && strings.HasPrefix(lines[i+1], "\t") {
fileLine := strings.TrimSpace(lines[i+1])
// Separate file path and line number.
lineSep := strings.LastIndexByte(fileLine, ':')
if lineSep == -1 {
file = fileLine
} else {
file = fileLine[:lineSep]
lineStr := fileLine[lineSep+1:]
line, _ = strconv.Atoi(lineStr)
}
i++
}
return i, file, line, fnName
}
var pkgFundamental errbase.TypeKey
var pkgWithStackName errbase.TypeKey
var ourWithStackName errbase.TypeKey
func init() {
err := errors.New("")
pkgFundamental = errbase.GetTypeKey(pkgErr.New(""))
pkgWithStackName = errbase.GetTypeKey(pkgErr.WithStack(err))
ourWithStackName = errbase.GetTypeKey(WithStack(err))
}