• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pace / bricks / 13717956986

06 Mar 2025 02:30PM UTC coverage: 51.823% (-4.8%) from 56.612%
13717956986

push

github

web-flow
Merge pull request #406 from pace/gomod-update

This updates all dependencies to the latest version, excluding

github.com/bsm/redislock
github.com/dave/jennifer

as newer versions lead to unwanted behavior.

54 of 82 new or added lines in 9 files covered. (65.85%)

453 existing lines in 15 files now uncovered.

4889 of 9434 relevant lines covered (51.82%)

20.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/maintenance/errors/raven/stacktrace.go
1
// Copyright 2011 The Go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
4
// Some code from the runtime/debug package of the Go standard library.
5

6
package raven
7

8
import (
9
        "bytes"
10
        "go/build"
11
        "os"
12
        "path/filepath"
13
        "runtime"
14
        "strings"
15
        "sync"
16

17
        "github.com/pkg/errors"
18
)
19

20
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
21
type Stacktrace struct {
22
        // Required
23
        Frames []*StacktraceFrame `json:"frames"`
24
}
25

26
func (s *Stacktrace) Class() string { return "stacktrace" }
×
27

UNCOV
28
func (s *Stacktrace) Culprit() string {
×
UNCOV
29
        for i := len(s.Frames) - 1; i >= 0; i-- {
×
UNCOV
30
                frame := s.Frames[i]
×
UNCOV
31
                if frame.InApp && frame.Module != "" && frame.Function != "" {
×
32
                        return frame.Module + "." + frame.Function
×
33
                }
×
34
        }
UNCOV
35
        return ""
×
36
}
37

38
type StacktraceFrame struct {
39
        // At least one required
40
        Filename string `json:"filename,omitempty"`
41
        Function string `json:"function,omitempty"`
42
        Module   string `json:"module,omitempty"`
43

44
        // Optional
45
        Lineno       int      `json:"lineno,omitempty"`
46
        Colno        int      `json:"colno,omitempty"`
47
        AbsolutePath string   `json:"abs_path,omitempty"`
48
        ContextLine  string   `json:"context_line,omitempty"`
49
        PreContext   []string `json:"pre_context,omitempty"`
50
        PostContext  []string `json:"post_context,omitempty"`
51
        InApp        bool     `json:"in_app"`
52
}
53

54
// Try to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
UNCOV
55
func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []string) *Stacktrace {
×
UNCOV
56
        stacktracer, errHasStacktrace := err.(interface {
×
UNCOV
57
                StackTrace() errors.StackTrace
×
UNCOV
58
        })
×
UNCOV
59
        if errHasStacktrace {
×
60
                var frames []*StacktraceFrame
×
61
                for _, f := range stacktracer.StackTrace() {
×
62
                        pc := uintptr(f) - 1
×
63
                        fn := runtime.FuncForPC(pc)
×
64
                        var file string
×
65
                        var line int
×
66
                        if fn != nil {
×
67
                                file, line = fn.FileLine(pc)
×
68
                        } else {
×
69
                                file = "unknown"
×
70
                        }
×
71
                        frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
×
72
                        if frame != nil {
×
73
                                frames = append([]*StacktraceFrame{frame}, frames...)
×
74
                        }
×
75
                }
76
                return &Stacktrace{Frames: frames}
×
UNCOV
77
        } else {
×
UNCOV
78
                return NewStacktrace(skip+1, context, appPackagePrefixes)
×
UNCOV
79
        }
×
80
}
81

82
// Intialize and populate a new stacktrace, skipping skip frames.
83
//
84
// context is the number of surrounding lines that should be included for context.
85
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
86
// one line with no surrounding context, and 0 returns no context.
87
//
88
// appPackagePrefixes is a list of prefixes used to check whether a package should
89
// be considered "in app".
UNCOV
90
func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace {
×
UNCOV
91
        var frames []*StacktraceFrame
×
UNCOV
92
        for i := 1 + skip; ; i++ {
×
UNCOV
93
                pc, file, line, ok := runtime.Caller(i)
×
UNCOV
94
                if !ok {
×
UNCOV
95
                        break
×
96
                }
UNCOV
97
                frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
×
UNCOV
98
                if frame != nil {
×
UNCOV
99
                        frames = append(frames, frame)
×
UNCOV
100
                }
×
101
        }
102
        // If there are no frames, the entire stacktrace is nil
UNCOV
103
        if len(frames) == 0 {
×
UNCOV
104
                return nil
×
UNCOV
105
        }
×
106
        // Optimize the path where there's only 1 frame
UNCOV
107
        if len(frames) == 1 {
×
108
                return &Stacktrace{frames}
×
109
        }
×
110
        // Sentry wants the frames with the oldest first, so reverse them
UNCOV
111
        for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
×
UNCOV
112
                frames[i], frames[j] = frames[j], frames[i]
×
UNCOV
113
        }
×
UNCOV
114
        return &Stacktrace{frames}
×
115
}
116

117
// Build a single frame using data returned from runtime.Caller.
118
//
119
// context is the number of surrounding lines that should be included for context.
120
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
121
// one line with no surrounding context, and 0 returns no context.
122
//
123
// appPackagePrefixes is a list of prefixes used to check whether a package should
124
// be considered "in app".
UNCOV
125
func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
×
UNCOV
126
        frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false}
×
UNCOV
127
        frame.Module, frame.Function = functionName(pc)
×
UNCOV
128

×
UNCOV
129
        // `runtime.goexit` is effectively a placeholder that comes from
×
UNCOV
130
        // runtime/asm_amd64.s and is meaningless.
×
UNCOV
131
        if frame.Module == "runtime" && frame.Function == "goexit" {
×
UNCOV
132
                return nil
×
UNCOV
133
        }
×
134

UNCOV
135
        if frame.Module == "main" {
×
136
                frame.InApp = true
×
UNCOV
137
        } else {
×
UNCOV
138
                for _, prefix := range appPackagePrefixes {
×
139
                        if strings.HasPrefix(frame.Module, prefix) && !strings.Contains(frame.Module, "vendor") && !strings.Contains(frame.Module, "third_party") {
×
140
                                frame.InApp = true
×
141
                        }
×
142
                }
143
        }
144

UNCOV
145
        if context > 0 {
×
UNCOV
146
                contextLines, lineIdx := fileContext(file, line, context)
×
UNCOV
147
                if len(contextLines) > 0 {
×
UNCOV
148
                        for i, line := range contextLines {
×
UNCOV
149
                                switch {
×
UNCOV
150
                                case i < lineIdx:
×
UNCOV
151
                                        frame.PreContext = append(frame.PreContext, string(line))
×
UNCOV
152
                                case i == lineIdx:
×
UNCOV
153
                                        frame.ContextLine = string(line)
×
UNCOV
154
                                default:
×
UNCOV
155
                                        frame.PostContext = append(frame.PostContext, string(line))
×
156
                                }
157
                        }
158
                }
159
        } else if context == -1 {
×
160
                contextLine, _ := fileContext(file, line, 0)
×
161
                if len(contextLine) > 0 {
×
162
                        frame.ContextLine = string(contextLine[0])
×
163
                }
×
164
        }
UNCOV
165
        return frame
×
166
}
167

168
// Retrieve the name of the package and function containing the PC.
UNCOV
169
func functionName(pc uintptr) (string, string) {
×
UNCOV
170
        fn := runtime.FuncForPC(pc)
×
UNCOV
171
        if fn == nil {
×
172
                return "", ""
×
173
        }
×
174

UNCOV
175
        return splitFunctionName(fn.Name())
×
176
}
177

UNCOV
178
func splitFunctionName(name string) (string, string) {
×
UNCOV
179
        var pack string
×
UNCOV
180

×
UNCOV
181
        if pos := strings.LastIndex(name, "/"); pos != -1 {
×
UNCOV
182
                pack = name[:pos+1]
×
UNCOV
183
                name = name[pos+1:]
×
UNCOV
184
        }
×
185

UNCOV
186
        if pos := strings.Index(name, "."); pos != -1 {
×
UNCOV
187
                pack += name[:pos]
×
UNCOV
188
                name = name[pos+1:]
×
UNCOV
189
        }
×
190

UNCOV
191
        return pack, name
×
192
}
193

194
var (
195
        fileCacheLock sync.Mutex
196
        fileCache     = make(map[string][][]byte)
197
)
198

UNCOV
199
func fileContext(filename string, line, context int) ([][]byte, int) {
×
UNCOV
200
        fileCacheLock.Lock()
×
UNCOV
201
        defer fileCacheLock.Unlock()
×
UNCOV
202
        lines, ok := fileCache[filename]
×
UNCOV
203
        if !ok {
×
UNCOV
204
                data, err := os.ReadFile(filename)
×
UNCOV
205
                if err != nil {
×
206
                        // cache errors as nil slice: code below handles it correctly
×
207
                        // otherwise when missing the source or running as a different user, we try
×
208
                        // reading the file on each error which is unnecessary
×
209
                        fileCache[filename] = nil
×
210
                        return nil, 0
×
211
                }
×
UNCOV
212
                lines = bytes.Split(data, []byte{'\n'})
×
UNCOV
213
                fileCache[filename] = lines
×
214
        }
215

UNCOV
216
        if lines == nil {
×
217
                // cached error from ReadFile: return no lines
×
218
                return nil, 0
×
219
        }
×
220

UNCOV
221
        line-- // stack trace lines are 1-indexed
×
UNCOV
222
        start := line - context
×
UNCOV
223
        var idx int
×
UNCOV
224
        if start < 0 {
×
225
                start = 0
×
226
                idx = line
×
UNCOV
227
        } else {
×
UNCOV
228
                idx = context
×
UNCOV
229
        }
×
UNCOV
230
        end := line + context + 1
×
UNCOV
231
        if line >= len(lines) {
×
232
                return nil, 0
×
233
        }
×
UNCOV
234
        if end > len(lines) {
×
235
                end = len(lines)
×
236
        }
×
UNCOV
237
        return lines[start:end], idx
×
238
}
239

240
var trimPaths []string
241

242
// Try to trim the GOROOT or GOPATH prefix off of a filename
UNCOV
243
func trimPath(filename string) string {
×
UNCOV
244
        for _, prefix := range trimPaths {
×
UNCOV
245
                if trimmed := strings.TrimPrefix(filename, prefix); len(trimmed) < len(filename) {
×
UNCOV
246
                        return trimmed
×
UNCOV
247
                }
×
248
        }
UNCOV
249
        return filename
×
250
}
251

UNCOV
252
func init() {
×
UNCOV
253
        // Collect all source directories, and make sure they
×
UNCOV
254
        // end in a trailing "separator"
×
UNCOV
255
        for _, prefix := range build.Default.SrcDirs() {
×
UNCOV
256
                if prefix[len(prefix)-1] != filepath.Separator {
×
UNCOV
257
                        prefix += string(filepath.Separator)
×
UNCOV
258
                }
×
UNCOV
259
                trimPaths = append(trimPaths, prefix)
×
260
        }
261
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc