1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
15 // Error description, used as an argument to the error template.
17 SourceType string // The type of source that failed to build.
18 Title, Path, Description string // Description of the error, as presented to the user.
19 Line, Column int // Where the error was encountered.
20 SourceLines []string // The entire source file, split into lines.
21 Stack string // The raw stack trace string from debug.Stack().
22 MetaError string // Error that occurred producing the error page.
23 Link string // A configurable link to wrap the error source in
26 // SourceLine structure to hold the per-source-line details.
27 type SourceLine struct {
33 // NewErrorFromPanic method finds the deepest stack from in user code and
34 // provide a code listing of that, on the line that eventually triggered
35 // the panic. Returns nil if no relevant stack frame can be found.
36 func NewErrorFromPanic(err interface{}) *Error {
38 // Parse the filename and line from the originating line of app code.
39 // /Users/robfig/code/gocode/src/revel/examples/booking/app/controllers/hotels.go:191 (0x44735)
40 stack := string(debug.Stack())
41 frame, basePath := findRelevantStackFrame(stack)
47 stackElement := stack[:strings.Index(stack, "\n")]
48 colonIndex := strings.LastIndex(stackElement, ":")
49 filename := stackElement[:colonIndex]
51 fmt.Sscan(stackElement[colonIndex+1:], &line)
53 // Show an error page.
54 description := "Unspecified error"
56 description = fmt.Sprint(err)
58 lines, readErr := ReadLines(filename)
60 utilLog.Error("Unable to read file", "file", filename, "error", readErr)
63 Title: "Runtime Panic",
64 Path: filename[len(basePath):],
66 Description: description,
72 // Error method constructs a plaintext version of the error, taking
73 // account that fields are optionally set. Returns e.g. Compilation Error
74 // (in views/header.html:51): expected right delim in end; got "}"
75 func (e *Error) Error() string {
80 line = fmt.Sprintf(":%d", e.Line)
82 loc = fmt.Sprintf("(in %s%s)", e.Path, line)
87 header = fmt.Sprintf("%s %s: ", e.Title, loc)
89 header = fmt.Sprintf("%s: ", e.Title)
92 return fmt.Sprintf("%s%s Stack: %s", header, e.Description, e.Stack)
95 // ContextSource method returns a snippet of the source around
96 // where the error occurred.
97 func (e *Error) ContextSource() []SourceLine {
98 if e.SourceLines == nil {
101 start := (e.Line - 1) - 5
105 end := (e.Line - 1) + 5
106 if end > len(e.SourceLines) {
107 end = len(e.SourceLines)
110 lines := make([]SourceLine, end-start)
111 for i, src := range e.SourceLines[start:end] {
112 fileLine := start + i + 1
113 lines[i] = SourceLine{src, fileLine, fileLine == e.Line}
118 // SetLink method prepares a link and assign to Error.Link attribute
119 func (e *Error) SetLink(errorLink string) {
120 errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1)
121 errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1)
123 e.Link = "<a href=" + errorLink + ">" + e.Path + ":" + strconv.Itoa(e.Line) + "</a>"
126 // Return the character index of the first relevant stack frame, or -1 if none were found.
127 // Additionally it returns the base path of the tree in which the identified code resides.
128 func findRelevantStackFrame(stack string) (int, string) {
129 // Find first item in SourcePath that isn't in RevelPath.
130 // If first item is in RevelPath, keep track of position, trim and check again.
131 partialStack := stack
132 sourcePath := filepath.ToSlash(SourcePath)
133 revelPath := filepath.ToSlash(RevelPath)
136 frame := strings.Index(partialStack, sourcePath)
137 revelFrame := strings.Index(partialStack, revelPath)
141 } else if frame != revelFrame {
142 return sumFrame + frame, SourcePath
144 // Need to at least trim off the first character so this frame isn't caught again.
145 partialStack = partialStack[frame+1:]
146 sumFrame += frame + 1
149 for _, module := range Modules {
150 if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 {
151 return frame, module.Path