Skip to content
This repository was archived by the owner on Jan 25, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions errorcontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package highlight

import (
"bytes"
"io/ioutil"
"sync"

"github.com/pkg/errors"
)

type FrameWithContext struct {
errors.Frame
LinesBefore string
LineContent string
LinesAfter string
}

type sourceReader struct {
mu sync.Mutex
cache map[string][][]byte
}

func newSourceReader() sourceReader {
return sourceReader{
cache: make(map[string][][]byte),
}
}

func (sr *sourceReader) readContextLines(filename string, line, context int) ([][]byte, int) {
// context stands for the number of lines +/- we're returning
sr.mu.Lock()
defer sr.mu.Unlock()

lines, ok := sr.cache[filename]

if !ok {
data, err := ioutil.ReadFile(filename)
if err != nil {
sr.cache[filename] = nil
return nil, 0
}
lines = bytes.Split(data, []byte{'\n'})
sr.cache[filename] = lines
}

return sr.calculateContextLines(lines, line, context)
}

func (sr *sourceReader) calculateContextLines(lines [][]byte, line, context int) ([][]byte, int) {
// Stacktrace lines are 1-indexed, slices are 0-indexed
line--

// contextLine points to a line that caused an issue itself, in relation to
// returned slice.
contextLine := context

if lines == nil || line >= len(lines) || line < 0 {
return nil, 0
}

if context < 0 {
context = 0
contextLine = 0
}

start := line - context
if start < 0 {
contextLine += start
start = 0
}

end := line + context + 1

if end > len(lines) {
end = len(lines)
}

return lines[start:end], contextLine
}

func (sr *sourceReader) addContextLinesToFrame(frame FrameWithContext, lines [][]byte, contextLine int) FrameWithContext {
for i, line := range lines {
switch {
case i < contextLine:
frame.LinesBefore = frame.LinesBefore + string(line)
case i == contextLine:
frame.LineContent = string(line)
default:
frame.LinesAfter = frame.LinesAfter + string(line)
}
}
return frame
}
33 changes: 33 additions & 0 deletions highlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
Expand All @@ -24,6 +25,7 @@ var (
signalChan chan os.Signal
wg sync.WaitGroup
graphqlClientAddress string
sr sourceReader
)

// contextKey represents the keys that highlight may store in the users' context
Expand Down Expand Up @@ -62,6 +64,8 @@ var (

const backendSetupCooldown = 15

const contextLines = 5

var (
lastBackendSetupTimestamp time.Time
)
Expand Down Expand Up @@ -144,6 +148,7 @@ func init() {
errorChan = make(chan BackendErrorObjectInput, 128)
interruptChan = make(chan bool, 1)
signalChan = make(chan os.Signal, 1)
sr = newSourceReader()

signal.Notify(signalChan, syscall.SIGABRT, syscall.SIGTERM, syscall.SIGINT)
SetGraphqlClientAddress("https://pub.highlight.run")
Expand Down Expand Up @@ -254,6 +259,21 @@ func MarkBackendSetup(ctx context.Context) {
}
}

func getFileNameAndLineNumber(s string) (string, int) {
splitSpace := strings.Fields(s)
if len(splitSpace) == 2 {
splitData := strings.Split(splitSpace[1], ":")
if len(splitData) == 2 {
lineNumber, err := strconv.Atoi(splitData[1])
if err != nil {
return "", 0
}
return splitData[0], lineNumber
}
}
return "", 0
}

// ConsumeError adds an error to the queue of errors to be sent to our backend.
// the provided context must have the injected highlight keys from InterceptRequestWithContext.
func ConsumeError(ctx context.Context, errorInput interface{}, tags ...string) {
Expand Down Expand Up @@ -300,6 +320,19 @@ func ConsumeError(ctx context.Context, errorInput interface{}, tags ...string) {
}
var stackFrames []string
for _, frame := range stack {
frame := FrameWithContext{
Frame: frame,
}
initialFrame, err := frame.MarshalText()
if err != nil {
logger.Errorf("[highlight-go] %v", errors.Wrap(err, "error marshaling stack frames"))
return
}
frameFileName, frameLineNumber := getFileNameAndLineNumber(string(initialFrame))
if frameFileName != "" && frameLineNumber != 0 {
lines, contextLine := sr.readContextLines(frameFileName, frameLineNumber, contextLines)
frame = sr.addContextLinesToFrame(frame, lines, contextLine)
}
frameBytes, err := frame.MarshalText()
if err != nil {
logger.Errorf("[highlight-go] %v", errors.Wrap(err, "error marshaling frame text"))
Expand Down