forked from revel/revel
-
Notifications
You must be signed in to change notification settings - Fork 1
/
errors.go
126 lines (113 loc) · 3.7 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
package revel
import (
"fmt"
"runtime/debug"
"strconv"
"strings"
)
// An 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
}
// Find 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,
}
}
// Construct 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)
}
// 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
}
// 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, BasePath); frame != -1 {
return frame, BasePath
}
for _, module := range Modules {
if frame := strings.Index(stack, module.Path); frame != -1 {
return frame, module.Path
}
}
return -1, ""
}
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>"
}