forked from birkirb/revel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
155 lines (140 loc) · 4.74 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
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
}
// SourceLine structure 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/examples/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)
}
lines, readErr := ReadLines(filename)
if readErr != nil {
utilLog.Error("Unable to read file", "file", filename, "error", readErr)
}
return &Error{
Title: "Runtime Panic",
Path: filename[len(basePath):],
Line: line,
Description: description,
SourceLines: lines,
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 Stack: %s", header, e.Description, e.Stack)
}
// 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)
}
lines := 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) {
// Find first item in SourcePath that isn't in RevelPath.
// If first item is in RevelPath, keep track of position, trim and check again.
partialStack := stack
sourcePath := filepath.ToSlash(SourcePath)
revelPath := filepath.ToSlash(RevelPath)
sumFrame := 0
for {
frame := strings.Index(partialStack, sourcePath)
revelFrame := strings.Index(partialStack, revelPath)
if frame == -1 {
break
} else if frame != revelFrame {
return sumFrame + frame, SourcePath
} else {
// Need to at least trim off the first character so this frame isn't caught again.
partialStack = partialStack[frame+1:]
sumFrame += frame + 1
}
}
for _, module := range Modules {
if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
return frame, module.Path
}
}
return -1, ""
}