forked from revel/revel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
130 lines (117 loc) · 3.88 KB
/
errors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package revel
import (
"fmt"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
)
// Error description, used as an argument to the error template.
type Error struct {
SourceType string // The type of source that failed to build.
Title, Path, Description string // Description of the error, as presented to the user.
Line, Column int // Where the error was encountered.
SourceLines []string // The entire source file, split into lines.
Stack string // The raw stack trace string from debug.Stack().
MetaError string // Error that occurred producing the error page.
Link string // A configurable link to wrap the error source in
}
// An object to hold the per-source-line details.
type sourceLine struct {
Source string
Line int
IsError bool
}
// NewErrorFromPanic method finds the deepest stack from in user code and
// provide a code listing of that, on the line that eventually triggered
// the panic. Returns nil if no relevant stack frame can be found.
func NewErrorFromPanic(err interface{}) *Error {
// Parse the filename and line from the originating line of app code.
// /Users/robfig/code/gocode/src/revel/samples/booking/app/controllers/hotels.go:191 (0x44735)
stack := string(debug.Stack())
frame, basePath := findRelevantStackFrame(stack)
if frame == -1 {
return nil
}
stack = stack[frame:]
stackElement := stack[:strings.Index(stack, "\n")]
colonIndex := strings.LastIndex(stackElement, ":")
filename := stackElement[:colonIndex]
var line int
fmt.Sscan(stackElement[colonIndex+1:], &line)
// Show an error page.
description := "Unspecified error"
if err != nil {
description = fmt.Sprint(err)
}
return &Error{
Title: "Runtime Panic",
Path: filename[len(basePath):],
Line: line,
Description: description,
SourceLines: MustReadLines(filename),
Stack: stack,
}
}
// Error method constructs a plaintext version of the error, taking
// account that fields are optionally set. Returns e.g. Compilation Error
// (in views/header.html:51): expected right delim in end; got "}"
func (e *Error) Error() string {
loc := ""
if e.Path != "" {
line := ""
if e.Line != 0 {
line = fmt.Sprintf(":%d", e.Line)
}
loc = fmt.Sprintf("(in %s%s)", e.Path, line)
}
header := loc
if e.Title != "" {
if loc != "" {
header = fmt.Sprintf("%s %s: ", e.Title, loc)
} else {
header = fmt.Sprintf("%s: ", e.Title)
}
}
return fmt.Sprintf("%s%s", header, e.Description)
}
// ContextSource method returns a snippet of the source around
// where the error occurred.
func (e *Error) ContextSource() []sourceLine {
if e.SourceLines == nil {
return nil
}
start := (e.Line - 1) - 5
if start < 0 {
start = 0
}
end := (e.Line - 1) + 5
if end > len(e.SourceLines) {
end = len(e.SourceLines)
}
var lines []sourceLine = make([]sourceLine, end-start)
for i, src := range e.SourceLines[start:end] {
fileLine := start + i + 1
lines[i] = sourceLine{src, fileLine, fileLine == e.Line}
}
return lines
}
// SetLink method prepares a link and assign to Error.Link attribute
func (e *Error) SetLink(errorLink string) {
errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
}
// Return the character index of the first relevant stack frame, or -1 if none were found.
// Additionally it returns the base path of the tree in which the identified code resides.
func findRelevantStackFrame(stack string) (int, string) {
if frame := strings.Index(stack, filepath.ToSlash(BasePath)); frame != -1 {
return frame, BasePath
}
for _, module := range Modules {
if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
return frame, module.Path
}
}
return -1, ""
}