Permalink
156 lines (140 sloc) 4.72 KB
// 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", 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)
}
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, ""
}